--- title: "Auto Captions" description: "Integrate automatic caption generation into your CE.SDK application using the Auto Caption plugin with pluggable speech-to-text providers." platform: angular url: "https://img.ly/docs/cesdk/angular/-73368c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [AI Integration](https://img.ly/docs/cesdk/angular/user-interface/ai-integration-5aa356/) > [Auto Captions](https://img.ly/docs/cesdk/angular/-73368c/) --- Generate captions automatically from spoken audio in video and audio blocks using CE.SDK's Auto Caption plugin. > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ai-integration-auto-captions-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-user-interface-ai-integration-auto-captions-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-user-interface-ai-integration-auto-captions-browser/) The Auto Caption plugin extracts audio from media blocks, sends it to a speech-to-text provider, and creates timed caption blocks from the transcription. It supports both a built-in UI workflow where users select which blocks to transcribe and a programmatic API for automation. The plugin ships with an ElevenLabs Scribe V2 provider via fal.ai, and you can implement your own provider using the `TranscriptionProvider` interface. For manually creating and editing captions, see [Add Captions](https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/). ```typescript file=@cesdk_web_examples/guides-user-interface-ai-integration-auto-captions-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import type { TranscriptionProvider } from '@imgly/plugin-autocaption-web'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import AutocaptionPlugin from '@imgly/plugin-autocaption-web'; import { ElevenLabsScribeV2 } from '@imgly/plugin-autocaption-web/fal-ai'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins for the video editor await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: [ 'ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload' ] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Register the Auto Caption plugin with the built-in ElevenLabs provider await cesdk.addPlugin( AutocaptionPlugin({ provider: ElevenLabsScribeV2({ // The proxy URL forwards requests to fal.ai with the API key added // server-side, keeping the key out of the browser proxyUrl: 'https://your-server.com/api/fal-proxy' }) }) ); // Create a video scene and add a video clip with spoken audio await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; engine.block.setDuration(page, 30); const videoUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4'; const track = engine.block.create('track'); engine.block.appendChild(page, track); const videoClip = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 30, timeOffset: 0 } }); engine.block.appendChild(track, videoClip); engine.block.fillParent(track); // Example: Implementing a custom transcription provider // Use this pattern to connect any speech-to-text service // eslint-disable-next-line @typescript-eslint/no-unused-vars const _customProvider: TranscriptionProvider = { name: 'My Custom STT', async transcribe(audio: Blob, options?) { // Send the audio blob to your speech-to-text service const formData = new FormData(); formData.append('audio', audio, 'audio.mp4'); if (options?.language) { formData.append('language', options.language); } const response = await fetch('https://your-stt-api.com/transcribe', { method: 'POST', body: formData, signal: options?.abortSignal }); const result = await response.json(); // Return the transcription as a valid SRT string return { srt: result.srt }; } }; // To use the custom provider instead of ElevenLabs: // AutocaptionPlugin({ provider: customProvider }) // Open the caption panel so the Auto Caption button is visible cesdk.ui.openPanel('//ly.img.panel/inspector/caption'); } } export default Example; ``` This guide covers how to use the built-in auto caption UI, install and configure the plugin with the ElevenLabs provider, set up a proxy server for secure API communication, implement a custom transcription provider, and troubleshoot common issues. ## Using the Built-in Auto Caption UI The Auto Caption plugin adds an "Auto Caption" button to the caption panel. Clicking it opens an auto-generate view where you select which media blocks to transcribe. ### Accessing the Auto-Generate View Open the caption panel from the inspector and click the "Auto Caption" button. The auto-generate view lists all audio and video blocks in the scene with checkboxes. Muted blocks and video blocks without audio tracks appear disabled and cannot be selected. ### Selecting Blocks Use the checkboxes to choose which blocks to include in caption generation. The "Select All" button selects every eligible block, and "Deselect All" clears the selection. Only blocks with audible audio content can be selected. ### Generating and Cancelling Captions Click "Generate Captions" to start transcription. The plugin extracts audio from each selected block, sends it to the configured provider, and creates caption blocks from the returned transcription. You can cancel an in-progress generation at any time — the plugin cleans up any partially created caption blocks. After generation completes, the view switches to the caption edit view where you can review, restyle, and adjust the generated captions. ## Installing the Plugin Install `@imgly/plugin-autocaption-web` alongside `@cesdk/cesdk-js`. The plugin version must match the CE.SDK version (unified versioning). ```typescript highlight-install-plugin import AutocaptionPlugin from '@imgly/plugin-autocaption-web'; import { ElevenLabsScribeV2 } from '@imgly/plugin-autocaption-web/fal-ai'; ``` The plugin is imported as a default export, and the fal.ai provider is imported from the `/fal-ai` subpath. The `@fal-ai/client` dependency is bundled into the provider, so you don't need to install it separately. ## Configuring the Built-in Provider We register the Auto Caption plugin with `cesdk.addPlugin()`, passing the ElevenLabs Scribe V2 provider configured with a `proxyUrl`. The proxy URL points to your server endpoint that forwards requests to fal.ai with the API key added server-side. ```typescript highlight-configure-provider // Register the Auto Caption plugin with the built-in ElevenLabs provider await cesdk.addPlugin( AutocaptionPlugin({ provider: ElevenLabsScribeV2({ // The proxy URL forwards requests to fal.ai with the API key added // server-side, keeping the key out of the browser proxyUrl: 'https://your-server.com/api/fal-proxy' }) }) ); ``` The `ElevenLabsScribeV2()` factory accepts a configuration object with `proxyUrl` (required) and optional `headers` for custom authentication headers. You can also pass `debug: true` to the `AutocaptionPlugin()` call to enable console logging of each pipeline step, including audio sizes and provider timings. ## Setting Up a Proxy Server The built-in provider communicates with fal.ai to run the ElevenLabs Scribe V2 model. Calling fal.ai directly from the browser would expose your API key in client-side code, which is insecure. A proxy server sits between the browser and fal.ai. Your browser sends requests to your own server endpoint, which adds the fal.ai API key and forwards the request. The API key never leaves the server. Your proxy endpoint needs to: - Accept POST requests from the browser - Add the `Authorization` header with your fal.ai API key - Forward the request body to fal.ai - Handle CORS so the browser can reach the endpoint For detailed proxy implementation instructions, see [Proxy Server](https://img.ly/docs/cesdk/angular/user-interface/ai-integration/proxy-server-61f901/). ## Implementing a Custom Transcription Provider You can use any speech-to-text service by implementing the `TranscriptionProvider` interface. The interface requires a `name` string and a `transcribe()` method that receives an audio blob and returns SRT text. ```typescript highlight-custom-provider // Example: Implementing a custom transcription provider // Use this pattern to connect any speech-to-text service // eslint-disable-next-line @typescript-eslint/no-unused-vars const _customProvider: TranscriptionProvider = { name: 'My Custom STT', async transcribe(audio: Blob, options?) { // Send the audio blob to your speech-to-text service const formData = new FormData(); formData.append('audio', audio, 'audio.mp4'); if (options?.language) { formData.append('language', options.language); } const response = await fetch('https://your-stt-api.com/transcribe', { method: 'POST', body: formData, signal: options?.abortSignal }); const result = await response.json(); // Return the transcription as a valid SRT string return { srt: result.srt }; } }; // To use the custom provider instead of ElevenLabs: // AutocaptionPlugin({ provider: customProvider }) ``` ### The TranscriptionProvider Interface The `transcribe()` method receives two arguments: - **`audio`**: A `Blob` in audio/mp4 format, produced by `engine.block.exportAudio()`. Send this to your speech-to-text service. - **`options`**: An optional object with: - `language`: A BCP-47 language code (e.g., `"en"`, `"de"`, `"pt"`) for the expected spoken language. - `abortSignal`: An `AbortSignal` to cancel the transcription request. Pass this to `fetch()` or your HTTP client to abort the request. - `maxLineLength`: Maximum characters per SRT line (default 37). - `maxLines`: Maximum lines per SRT cue (default 1). - `debug`: Enable verbose logging to the console (default `false`). The method must return `{ srt: string }` containing a valid SRT-formatted subtitle string. The plugin then passes this SRT to `engine.block.createCaptionsFromURI()` to create the caption blocks. ## Caption Generation Workflow When the user clicks "Generate Captions", the plugin runs the following pipeline for each selected block: 1. **Load media** — Calls `engine.block.forceLoadAVResource()` to ensure the media is ready. 2. **Export audio** — Extracts audio via `engine.block.exportAudio()`, producing an MP4 blob. 3. **Transcribe** — Sends the blob to the `TranscriptionProvider.transcribe()` method. 4. **Create captions** — Converts the SRT string to caption blocks with `engine.block.createCaptionsFromURI()`. 5. **Add to track** — Appends caption blocks to a caption track on the page using `engine.block.appendChild()`. 6. **Style** — Applies the `ly.img.caption.presets.outline` preset via `engine.asset.fetchAsset()` and `engine.asset.applyToBlock()`. 7. **Position** — Centers the first caption with `engine.block.alignHorizontally()` and `engine.block.alignVertically()`. 8. **Undo step** — Wraps the entire operation in a single undo step with `engine.editor.addUndoStep()`. Multiple blocks are processed in parallel using `Promise.all`. Existing caption tracks are removed before new ones are created. ## Handling Errors and Notifications The plugin handles errors per-block, so if one block fails, others continue processing. When transcription fails or no speech is detected, the plugin shows a notification via `cesdk.ui.showNotification()`. - **Total failure**: All blocks failed — shows an error notification. - **Partial failure**: Some blocks succeeded, others failed — shows a warning listing which blocks had issues. - **Cancellation**: When the user cancels, the plugin throws an `AbortError` and cleans up any caption blocks already created during the current generation. ## Troubleshooting - **Plugin not appearing in caption panel**: Verify `cesdk.addPlugin()` is called with the `AutocaptionPlugin()` result and the caption panel is enabled in the editor. - **Proxy errors**: Check that `proxyUrl` is accessible from the browser, handles CORS preflight requests, and correctly forwards the fal.ai API key. - **"No speech detected" for all blocks**: Verify audio blocks are not muted and contain audible speech. Video blocks without audio tracks are intentionally disabled. - **Generation hangs or times out**: Check network connectivity to fal.ai through your proxy. The ElevenLabs model can take 10-30 seconds for longer audio. - **Captions not styled correctly**: Verify the `CaptionPresetsAssetSource` plugin is registered, which provides the `ly.img.caption.presets` asset source. - **Muted blocks cannot be selected**: This is intentional. Muted audio blocks and video blocks without audio tracks produce no audio output for transcription. ## Next Steps - [Add Captions](https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/) — Manually create and edit caption blocks - [Update Caption Presets](https://img.ly/docs/cesdk/angular/create-video/update-caption-presets-e9c385/) — Customize caption styling presets - [Proxy Server](https://img.ly/docs/cesdk/angular/user-interface/ai-integration/proxy-server-61f901/) — Set up secure API communication - [Custom Provider](https://img.ly/docs/cesdk/angular/user-interface/ai-integration/custom-provider-16e851/) — Create custom AI providers - [Integrate AI Features](https://img.ly/docs/cesdk/angular/user-interface/ai-integration/integrate-8e906c/) — Overview of AI integration --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Actions API" description: "Learn how to use the Actions API to register and customize action handlers in CE.SDK" platform: angular url: "https://img.ly/docs/cesdk/angular/actions-6ch24x/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Actions](https://img.ly/docs/cesdk/angular/actions-6ch24x/) --- The Actions API provides a centralized way to manage and customize actions for various user interactions in CE.SDK. > **Note:** The Actions API is available after CE.SDK initialization through > `cesdk.actions`. > **Tip:** CE.SDK also provides a Utils API (`cesdk.utils`) with utility functions for > common operations like exporting, file handling, and UI dialogs. These > utilities can be used directly or within your custom actions. ## API Methods The Actions API provides four methods: - `register(actionId, handler)` - Register an action function for a specific event - `get(actionId)` - Retrieve a registered action function - `run(actionId, ...args)` - Execute a registered action with the provided arguments (throws if not registered) - `list(matcher)` - Lists registered action IDs, optionally filtered by wildcard pattern ## Getting Started Register actions after initializing CE.SDK: ```javascript import CreativeEditorSDK from '@cesdk/cesdk-js'; const cesdk = await CreativeEditorSDK.create(container, { // license: 'YOUR_CESDK_LICENSE_KEY', }); // Register an action cesdk.actions.register('actionType', async (...args) => { // Your custom implementation return result; }); // Execute a registered action await cesdk.actions.run('actionType', arg1, arg2); // Or retrieve an action to call it later const action = cesdk.actions.get('actionType'); // List all registered actions const allActions = cesdk.actions.list(); // List actions matching a pattern const exportActions = cesdk.actions.list({ matcher: 'export*' }); ``` ## Default Actions CE.SDK automatically registers the following default actions: ### Scene Creation - `scene.create` - Creates a new scene with configurable mode, layout and page sizes ### Action Handlers - `saveScene` - Saves the current scene (default: downloads scene file) - `shareScene` - Shares the current scene (no default implementation) - `exportDesign` - Exports design in various formats (default: downloads the exported file) - `importScene` - Imports scene or archive files (default: opens file picker) - `exportScene` - Exports scene or archive (default: downloads the file) - `uploadFile` - Uploads files to asset sources (default: local upload for development) - `onUnsupportedBrowser` - Handles unsupported browsers (no default implementation) - `video.decode.checkSupport` - Checks video decoding/playback support (shows blocking dialog if unsupported) - `video.encode.checkSupport` - Checks video encoding/export support (shows warning dialog if unsupported) ### Zoom Actions - `zoom.toBlock` - Zoom to a specific block with configurable padding - `zoom.toPage` - Zoom to the current page with auto-fit support - `zoom.toSelection` - Zoom to currently selected blocks - `zoom.in` - Zoom in by one step with configurable maximum - `zoom.out` - Zoom out by one step with configurable minimum - `zoom.toLevel` - Set zoom to a specific level ### Scroll Actions - `scroll.toPage` - Scroll the viewport to center on a specific page with optional animation ### Video Timeline Zoom Actions - `timeline.zoom.in` - Zoom in the video timeline by one step - `timeline.zoom.out` - Zoom out the video timeline by one step - `timeline.zoom.fit` - Fit the video timeline to show all content - `timeline.zoom.toLevel` - Set the video timeline zoom to a specific level - `timeline.zoom.reset` - Reset the video timeline zoom to default (1.0) > **Note:** The `shareScene` and `onUnsupportedBrowser` actions do not have default > implementations and must be registered manually. > **Tip:** CE.SDK provides both an Actions API for handling user actions and a Utils API > for utility functions. See the Utils API section below for details on > available utilities. ### Scene Creation #### `scene.create` Creates a new scene with configurable mode, layout and page sizes. Returns the scene block ID. ```javascript // Create a default Design scene await cesdk.actions.run('scene.create'); // Create a Video scene await cesdk.actions.run('scene.create', { mode: 'Video' }); ``` **Options:** | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `mode` | `'Design' \| 'Video'` | `'Design'` | The scene mode. | | `layout` | `SceneLayout` | `'VerticalStack'` | The scene layout. Ignored for Video mode. | | `page` | `PageSpec` | — | A single page specification. Cannot be used together with `pages`. | | `pageCount` | `number` | `1` | Number of pages to create from the single `page` spec. Ignored when `pages` is used. | | `pages` | `PageSpec[]` | — | An array of page specifications, one page per entry. Cannot be used together with `page`. | #### Page Specification Pages can be specified in three ways: **1. Direct dimensions** Specify width, height, and unit directly: ```javascript await cesdk.actions.run('scene.create', { page: { width: 1080, height: 1920, unit: 'Pixel' } }); // With fixed orientation (prevents rotation from swapping dimensions) await cesdk.actions.run('scene.create', { page: { width: 1080, height: 1920, unit: 'Pixel', fixedOrientation: true } }); ``` **2. Asset source reference** Reference a page preset from an asset source by its source and asset IDs: ```javascript // Use an Instagram Story preset await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); ``` **3. Asset object** Pass an asset object directly, for example one returned by `engine.asset.findAssets()`: ```javascript const result = await cesdk.engine.asset.findAssets('ly.img.page.presets', { query: '' }); await cesdk.actions.run('scene.create', { page: result.assets[0] }); ``` #### Multiple Pages Create scenes with multiple pages: ```javascript // Multiple pages with different sizes await cesdk.actions.run('scene.create', { pages: [ { width: 1080, height: 1920, unit: 'Pixel' }, { width: 1920, height: 1080, unit: 'Pixel' } ] }); // Multiple identical pages using pageCount await cesdk.actions.run('scene.create', { page: { width: 1080, height: 1080, unit: 'Pixel' }, pageCount: 3 }); ``` > **Tip:** When no `page` or `pages` option is provided, `scene.create` creates a single > page with the default format from the configured page preset asset sources. > **Note:** The `createDesignScene()` and `createVideoScene()` methods are deprecated. > Use `cesdk.actions.run('scene.create')` and > `cesdk.actions.run('scene.create', { mode: 'Video' })` instead. ### Scene Management Actions #### `saveScene` Handles saving the current scene. Default implementation downloads the scene file. ```javascript // Basic implementation cesdk.actions.register('saveScene', async () => { const scene = await cesdk.engine.scene.saveToString(); console.log('Scene saved:', scene.length, 'characters'); // Production: // await yourAPI.saveScene(scene); cesdk.ui.showNotification('Scene saved successfully'); }); // With loading dialog cesdk.actions.register('saveScene', async () => { const dialogController = cesdk.utils.showLoadingDialog({ title: 'Saving Scene', message: 'Please wait...', progress: 'indeterminate', }); try { const scene = await cesdk.engine.scene.saveToString(); console.log('Scene saved:', scene.length, 'characters'); // Production: // await yourAPI.saveScene(scene); dialogController.showSuccess({ title: 'Saved', message: 'Scene saved successfully', }); } catch (error) { dialogController.showError({ title: 'Save Failed', message: 'Could not save the scene', }); throw error; } }); ``` #### `shareScene` Handles scene sharing. No default implementation. ```javascript // Register share functionality cesdk.actions.register('shareScene', async () => { const scene = await cesdk.engine.scene.saveToString(); const shareUrl = 'https://example.com/shared-scene-placeholder'; console.log('Scene ready to share:', scene.length, 'characters'); // Production: // const shareUrl = await yourAPI.createShareableLink(scene); await navigator.share({ url: shareUrl }); }); ``` #### `importScene` and `exportScene` Handle scene import/export operations with support for both scene files and archives. ```javascript // Import scene or archive cesdk.actions.register('importScene', async ({ format }) => { if (format === 'archive') { console.log('Archive import requested'); // Production: // const archive = await yourAPI.loadArchive(); // await cesdk.engine.scene.loadFromArchiveURL(archive); } else { console.log('Scene import requested'); // Production: // const scene = await yourAPI.loadScene(); // await cesdk.engine.scene.loadFromString(scene); } }); // Export scene or archive cesdk.actions.register('exportScene', async ({ format }) => { if (format === 'archive') { const archive = await cesdk.engine.scene.saveToArchive(); console.log('Archive ready for export:', archive.length, 'bytes'); // Production: // await yourAPI.uploadArchive(archive); } else { const scene = await cesdk.engine.scene.saveToString(); console.log('Scene ready for export:', scene.length, 'characters'); // Production: // await yourAPI.uploadScene(scene); } }); ``` ### Export Operations #### `exportDesign` Handles all export operations (images, PDFs, videos). Default implementation downloads the exported file. ```javascript // Basic implementation cesdk.actions.register('exportDesign', async options => { // Use the utils API to perform the export with loading dialog const { blobs, options: exportOptions } = await cesdk.utils.export(options); console.log('Exported', blobs.length, 'files'); blobs.forEach((blob, i) => console.log(`File ${i + 1}:`, blob.size, 'bytes')); // Production: // await Promise.all(blobs.map(blob => yourCDN.upload(blob))); cesdk.ui.showNotification('Export completed successfully'); }); // Direct engine export with custom loading dialog (bypassing utils) cesdk.actions.register('exportDesign', async options => { const dialogController = cesdk.utils.showLoadingDialog({ title: 'Exporting', message: 'Processing your export...', }); try { const page = cesdk.engine.scene.getCurrentPage(); if (page === null) { throw new Error('No page selected for export'); } let result; if (options?.mimeType?.startsWith('video/')) { // Video export with progress result = await cesdk.engine.block.exportVideo(page, { ...options, onProgress: (rendered, encoded, total) => { dialogController.updateProgress({ value: rendered, max: total, }); }, }); } else { // Static export (image/PDF) result = await cesdk.engine.block.export(page, options); } console.log('File ready for export:', result.size, 'bytes'); // Production: // await yourCDN.upload(result); dialogController.showSuccess({ title: 'Export Complete', message: 'Files uploaded successfully', }); } catch (error) { dialogController.showError({ title: 'Export Failed', message: 'Could not complete the export', }); throw error; } }); ``` ### File Upload Action #### `uploadFile` Handles file uploads to asset sources. Default implementation uses local upload for development. ```javascript // Register production upload handler cesdk.actions.register('uploadFile', async (file, onProgress, context) => { console.log('Uploading file:', file.name, file.size, 'bytes'); onProgress(50); // Simulate progress await new Promise(resolve => setTimeout(resolve, 500)); onProgress(100); // Production: // const asset = await yourStorageService.upload(file, { // onProgress: (percent) => onProgress(percent), // context // }); // Return AssetDefinition return { id: 'local-' + Date.now(), label: { en: file.name }, meta: { uri: URL.createObjectURL(file), thumbUri: URL.createObjectURL(file), kind: 'image', width: 1920, height: 1080, // Production: // uri: asset.url, // thumbUri: asset.thumbnailUrl, // width: asset.width, // height: asset.height }, }; }); ``` You can control which file types users can upload by setting the `upload/supportedMimeTypes` setting: ```javascript // Example 1: Only allow images cesdk.engine.editor.setSettingString( 'upload/supportedMimeTypes', 'image/png,image/jpeg,image/gif,image/svg+xml', ); // Example 2: Allow images and videos cesdk.engine.editor.setSettingString( 'upload/supportedMimeTypes', 'image/png,image/jpeg,image/gif,video/mp4,video/quicktime', ); // Example 3: Allow specific document types cesdk.engine.editor.setSettingString( 'upload/supportedMimeTypes', 'application/pdf,image/png,image/jpeg', ); ``` > **Caution:** The default `uploadFile` implementation uses local upload for development > only. Always register a proper upload handler for production. ### Unsupported Browser Action #### `onUnsupportedBrowser` Handles unsupported browser detection. No default implementation is provided. ```javascript // Register handler for unsupported browsers cesdk.actions.register('onUnsupportedBrowser', () => { // Redirect to a custom compatibility page window.location.href = '/browser-not-supported'; }); ``` ### Video Support Actions CE.SDK provides actions to detect video capabilities at runtime. These actions help you handle browsers and platforms that lack required video codecs, particularly Linux browsers and Firefox. #### `video.decode.checkSupport` Checks if the browser supports video decoding and playback via the WebCodecs API. If unsupported, displays a blocking error dialog that users cannot dismiss. Returns `true` if video decoding is supported, `false` otherwise. ```javascript // Check video decode support before loading video content const isSupported = cesdk.actions.run('video.decode.checkSupport'); if (!isSupported) { // A blocking error dialog is shown automatically // The user cannot proceed with video editing return; } // Safe to proceed with video content await cesdk.engine.scene.loadFromURL(videoSceneUrl); // You can also disable the dialog and handle feedback yourself: const supportedSilently = cesdk.actions.run('video.decode.checkSupport', { dialog: false }); ``` **Options:** | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `dialog` | `boolean \| { show: boolean; backdrop?: 'transparent' \| 'opaque' }` | `true` (backdrop: `'opaque'`) | Controls dialog display. Use `false` to disable, or an object for fine-grained control. | > **Caution:** The `video.decode.checkSupport` action shows a blocking dialog with no dismiss option > when video is not supported. Only call this action when you intend to work > with video content. #### `video.encode.checkSupport` Checks if the browser supports video encoding/export (H.264 video and AAC audio encoding). If unsupported, displays a warning dialog that users can dismiss to continue editing. Returns a `Promise` - `true` if video encoding is supported, `false` otherwise. ```javascript // Check video encode support before attempting export const canExport = await cesdk.actions.run('video.encode.checkSupport'); if (!canExport) { // A warning dialog is shown automatically // User can dismiss and continue editing // Consider offering server-side export as alternative console.log('Video export unavailable - consider server-side rendering'); } // You can also disable the dialog and handle feedback yourself: const canExportSilently = await cesdk.actions.run('video.encode.checkSupport', { dialog: false }); ``` **Options:** | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `dialog` | `boolean \| { show: boolean; backdrop?: 'transparent' \| 'opaque' }` | `true` (backdrop: `'transparent'`) | Controls dialog display. Use `false` to disable, or an object for fine-grained control. | > **Tip:** For platforms that don't support client-side video export (Linux, Firefox), > consider using [CE.SDK Renderer](#broken-link-7f3e9a) > for server-side video rendering. #### Platform Support | Platform | Video Import | Video Export | | -------- | ------------ | ------------ | | Chrome/Edge (Windows, macOS) | ✅ | ✅ | | Safari (macOS) | ✅ | ✅ | | Chrome (Linux) | ❌ | ❌ | | Firefox (all platforms) | ❌ | ❌ | ### Zoom Actions CE.SDK provides built-in zoom actions for controlling the viewport zoom level and focus. These actions are automatically registered and can be customized or called programmatically. #### Available Zoom Actions - `zoom.toBlock` - Zoom to a specific block with configurable padding - `zoom.toPage` - Zoom to the current page (or a specified page) - `zoom.toSelection` - Zoom to the currently selected blocks - `zoom.in` - Zoom in by one step - `zoom.out` - Zoom out by one step - `zoom.toLevel` - Set zoom to a specific level #### `zoom.toBlock` Zooms the viewport to fit a specific block. ```javascript // Zoom to a block with default settings await cesdk.actions.run('zoom.toBlock', blockId); // Zoom with custom padding and animation await cesdk.actions.run('zoom.toBlock', blockId, { padding: 50, // Uniform padding on all sides animate: true, autoFit: false }); // Different padding for each side await cesdk.actions.run('zoom.toBlock', blockId, { padding: { top: 20, bottom: 20, left: 40, right: 40 }, animate: { duration: 0.3, easing: 'EaseInOut' } }); ``` #### `zoom.toPage` Zooms to the current page or a specified page. If no options are provided, defaults to the current page. ```javascript // Zoom to current page with auto-fit await cesdk.actions.run('zoom.toPage', { autoFit: true, animate: false }); // Zoom with custom padding await cesdk.actions.run('zoom.toPage', { padding: { x: 40, y: 80 }, animate: true }); ``` #### `zoom.toSelection` Zooms to fit all currently selected blocks in the viewport. ```javascript // Zoom to selection with animation await cesdk.actions.run('zoom.toSelection', { padding: 40, animate: true }); // Auto-fit to selection await cesdk.actions.run('zoom.toSelection', { autoFit: true, padding: { x: 20, y: 20 } }); ``` #### `zoom.in` and `zoom.out` Step-based zoom controls with configurable limits. ```javascript // Zoom in with default settings await cesdk.actions.run('zoom.in'); // Zoom in with custom maximum await cesdk.actions.run('zoom.in', { maxZoom: 4, // Maximum zoom level animate: true }); // Zoom out with custom minimum await cesdk.actions.run('zoom.out', { minZoom: 0.25, // Minimum zoom level animate: { duration: 0.2, easing: 'EaseOut' } }); ``` #### `zoom.toLevel` Sets the zoom to a specific level. ```javascript // Set zoom to 100% await cesdk.actions.run('zoom.toLevel', 1.0); // Set zoom to 200% with animation await cesdk.actions.run('zoom.toLevel', 2.0, { animate: true, minZoom: 0.125, maxZoom: 32 }); // Fit to width (50% zoom) await cesdk.actions.run('zoom.toLevel', 0.5, { animate: { duration: 0.3, easing: 'EaseInOut' } }); ``` #### Padding Options Padding can be specified in multiple ways: ```javascript // Uniform padding on all sides { padding: 20 } // Different horizontal and vertical padding { padding: { x: 40, y: 20 } } // Individual padding for each side { padding: { top: 10, bottom: 20, left: 30, right: 40 } } ``` #### Animation Options Animation can be a boolean or an object with detailed settings: ```javascript // Simple animation toggle { animate: true } // Uses default duration and easing { animate: false } // No animation // Detailed animation configuration { animate: { duration: 0.3, // Duration in seconds easing: 'EaseInOut', // 'Linear', 'EaseIn', 'EaseOut', or 'EaseInOut' interruptible: true // Whether the animation can be interrupted } } ``` #### Auto-Fit Mode The `autoFit` option enables automatic zoom adjustment when the viewport resizes: ```javascript // Enable auto-fit to maintain proper framing await cesdk.actions.run('zoom.toPage', { autoFit: true, padding: { x: 40, y: 80 } }); ``` When auto-fit is enabled, the zoom level will automatically adjust to keep the target properly framed when the viewport size changes. #### Custom Zoom Action Example You can override the default zoom actions with custom implementations: ```javascript // Custom zoom to page with analytics cesdk.actions.register('zoom.toPage', async (options) => { // Track zoom event console.log('User zoomed to page'); // Get current page const currentPage = cesdk.engine.scene.getCurrentPage(); if (!currentPage) return; // Apply custom zoom logic await cesdk.engine.scene.zoomToBlock(currentPage, { padding: options?.padding ?? { x: 50, y: 100 }, animate: options?.animate ?? true }); // Custom post-zoom behavior cesdk.ui.showNotification('Zoomed to page'); }); ``` ### Video Timeline Zoom Actions The video timeline has its own set of zoom controls for managing the timeline view. These actions are registered when the video timeline component is active and provide instant zoom without animation. #### `timeline.zoom.in` Zooms in the video timeline by one step (multiplies current zoom level by 1.25). ```javascript // Zoom in the timeline await cesdk.actions.run('timeline.zoom.in'); ``` #### `timeline.zoom.out` Zooms out the video timeline by one step (divides current zoom level by 1.25). ```javascript // Zoom out the timeline await cesdk.actions.run('timeline.zoom.out'); ``` #### `timeline.zoom.fit` Automatically adjusts the timeline zoom to fit all content in the visible area. ```javascript // Fit timeline to show all content await cesdk.actions.run('timeline.zoom.fit'); ``` #### `timeline.zoom.toLevel` Sets the timeline zoom to a specific level. ```javascript // Set timeline zoom to 100% await cesdk.actions.run('timeline.zoom.toLevel', 1.0); // Set timeline zoom to 150% await cesdk.actions.run('timeline.zoom.toLevel', 1.5); // Set timeline zoom to 50% await cesdk.actions.run('timeline.zoom.toLevel', 0.5); ``` #### `timeline.zoom.reset` Resets the timeline zoom to the default level (1.0 or 100%). ```javascript // Reset timeline zoom to default await cesdk.actions.run('timeline.zoom.reset'); ``` ### Scroll Actions CE.SDK provides a scroll action for panning the viewport to different pages without changing the zoom level. This is useful for multi-page navigation where you want to maintain the current zoom. #### `scroll.toPage` Scrolls the viewport to center on a specific page without changing the zoom level. ```javascript // Scroll to current page without animation await cesdk.actions.run('scroll.toPage'); // Scroll to current page with smooth animation await cesdk.actions.run('scroll.toPage', { animate: true }); // Scroll to a specific page await cesdk.actions.run('scroll.toPage', { pageId: myPageId, animate: true }); ``` #### Parameters The `scroll.toPage` action accepts an optional options object: - `pageId` (optional): The ID of the page to scroll to. If not provided, scrolls to the current page. - `animate` (optional): Whether to animate the scroll transition. Default is `false`. #### Scroll vs Zoom The key difference between `scroll.toPage` and `zoom.toPage`: - **`scroll.toPage`**: Pans the viewport to center on the page while maintaining the current zoom level - **`zoom.toPage`**: Adjusts the zoom level to fit the page within the viewport with padding Use `scroll.toPage` when you want to navigate between pages in a multi-page document while keeping the same zoom level. Use `zoom.toPage` when you want to frame a page properly within the viewport. ## Utils API CE.SDK provides a Utils API with utility functions for common operations. These utilities are available through `cesdk.utils`: ### Loading Dialogs ```javascript // Create and manage loading dialogs const dialogController = cesdk.utils.showLoadingDialog({ title: 'Processing...', message: 'Please wait', // Can also be an array of strings progress: 0, // Initial progress value or 'indeterminate' cancelLabel: 'Cancel', abortTitle: 'Abort Operation?', abortMessage: 'Are you sure you want to abort?', abortLabel: 'Abort', size: 'large', // 'regular' or 'large' clickOutsideToClose: false, onAbort: () => console.log('User cancelled'), onDone: () => console.log('Dialog closed'), }); // Update progress dialogController.updateProgress({ value: 50, max: 100 }); // Show success or error dialogController.showSuccess({ title: 'Done!', message: 'Operation completed', }); dialogController.showError({ title: 'Error', message: 'Something went wrong' }); // Close dialog dialogController.close(); ``` ### Export Utility The export utility automatically handles both static (images, PDFs) and video exports: ```javascript // Export image or PDF const { blobs, options } = await cesdk.utils.export({ mimeType: 'image/png', pngCompressionLevel: 7, }); // Export video (automatically detected by MIME type) const { blobs, options } = await cesdk.utils.export({ mimeType: 'video/mp4', onProgress: (rendered, encoded, total) => { console.log(`Progress: ${rendered}/${total} frames`); }, }); ``` ### File Operations ```javascript // Load file from user const file = await cesdk.utils.loadFile({ accept: 'image/*', returnType: 'File', // 'dataURL', 'text', 'blob', 'arrayBuffer', or 'File' }); // Download file to user's device await cesdk.utils.downloadFile(blob, 'image/png'); // Local upload (development only) const asset = await cesdk.utils.localUpload(file, context); ``` ### Video Support Detection Check browser video capabilities before working with video content: ```javascript // Check if video decoding/playback is supported if (cesdk.utils.supportsVideoDecode()) { // Safe to load and play video content await cesdk.engine.scene.loadFromURL(videoSceneUrl); } else { // Show fallback UI or message console.log('Video playback not available in this browser'); } // Check if video encoding/export is supported (async) if (await cesdk.utils.supportsVideoEncode()) { // Video export is available const blob = await cesdk.engine.block.exportVideo(page); } else { // Suggest server-side rendering alternative console.log('Video export not available - consider using CE.SDK Renderer'); } ``` These utilities provide the same checks as the `video.decode.checkSupport` and `video.encode.checkSupport` actions, but without showing dialogs. Use them when you want to check support silently and handle the UI yourself. ## Implementation Examples ### Environment-Based Upload Strategy ```javascript // Use local upload in development, CDN in production cesdk.actions.register('uploadFile', async (file, onProgress, context) => { if (process.env.NODE_ENV === 'development') { // Use utils for local upload return await cesdk.utils.localUpload(file, context); } else { console.log('Production upload for:', file.name); onProgress(100); // Production: // const asset = await yourCDNService.upload(file, { // onProgress: onProgress // }); return { id: 'prod-' + Date.now(), label: { en: file.name }, meta: { uri: URL.createObjectURL(file), thumbUri: URL.createObjectURL(file), // Production: // uri: asset.url, // thumbUri: asset.thumbnailUrl }, }; } }); ``` ### Combining Utils with Custom Logic ```javascript // Use utils for heavy lifting, add custom business logic cesdk.actions.register('exportDesign', async options => { console.log('Export started:', { format: options?.mimeType }); // Production: // analytics.track('export_started', { format: options?.mimeType }); // Use utils to handle the export with loading dialog const { blobs, options: exportOptions } = await cesdk.utils.export(options); // Custom post-processing if (exportOptions.mimeType === 'application/pdf') { console.log('PDF ready for watermarking:', blobs[0].size, 'bytes'); // Production: // const watermarkedBlob = await addWatermark(blobs[0]); // await cesdk.utils.downloadFile(watermarkedBlob, 'application/pdf'); await cesdk.utils.downloadFile(blobs[0], 'application/pdf'); } else { // Direct download for other formats await cesdk.utils.downloadFile(blobs[0], exportOptions.mimeType); } console.log('Export completed:', { format: exportOptions.mimeType }); // Production: // analytics.track('export_completed', { format: exportOptions.mimeType }); }); ``` ## Registering Custom Actions with Custom IDs Beyond the predefined action types, you can register actions with custom IDs for your own application-specific needs: ```javascript // Register a custom action cesdk.actions.register('myCustomAction', async data => { console.log('Custom action triggered with:', data); return { success: true, processedData: data }; }); // Execute the custom action using run const result = await cesdk.actions.run('myCustomAction', { someData: 'value' }); // Or retrieve it for conditional execution const customAction = cesdk.actions.get('myCustomAction'); if (customAction) { const result = await customAction({ someData: 'value' }); } ``` ## Discovering Registered Actions Use `list()` to get all registered action IDs or find actions matching a pattern: ```javascript // Get all registered action IDs const registeredActions = cesdk.actions.list(); console.log('Available actions:', registeredActions); // Find actions matching a pattern const exportActions = cesdk.actions.list({ matcher: 'export*' }); console.log('Export actions:', exportActions); ``` ## Using Actions with Navigation Actions The navigation bar actions in CE.SDK automatically use the registered actions: ### Default Navigation Bar Actions The default navigation bar actions map to actions: - Save action → `saveScene` action - Share action → `shareScene` action - Export actions → `exportDesign` action - Import scene/archive → `importScene` action - Export scene/archive → `exportScene` action --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Animation" description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control." platform: angular url: "https://img.ly/docs/cesdk/angular/animation-ce900c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/) - Add motion to designs with support for keyframes, timeline editing, and programmatic animation control. - [Supported Animation Types](https://img.ly/docs/cesdk/angular/animation/types-4e5f41/) - Apply different animation types to design blocks in CE.SDK and configure their properties. - [Create Animations](https://img.ly/docs/cesdk/angular/animation/create-15cf50/) - Build animations manually or with presets to animate objects, text, and scenes within your design. - [Edit Animations](https://img.ly/docs/cesdk/angular/animation/edit-32c12a/) - Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Animations" description: "Build animations manually or with presets to animate objects, text, and scenes within your design." platform: angular url: "https://img.ly/docs/cesdk/angular/animation/create-15cf50/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/angular/animation/create-15cf50/) --- Add motion to design elements by creating entrance, exit, and loop animations using CE.SDK's animation system. ![Create Animations example showing animated blocks with various animation types](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-create-browser/) CE.SDK provides a unified animation system for adding motion to design elements. Animations are created as separate block instances and attached to target blocks using type-specific methods. You can apply entrance animations (how blocks appear), exit animations (how blocks leave), and loop animations (continuous motion while visible). Text blocks support additional properties for word-by-word or character-by-character reveals. ```typescript file=@cesdk_web_examples/guides-animation-create-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Create Animations Guide * * Demonstrates animation features in CE.SDK: * - Creating entrance (In) animations * - Creating exit (Out) animations * - Creating loop animations * - Configuring duration and easing * - Text animations with writing styles * - Managing animation lifecycle */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages[0]; // Set page dimensions and duration const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); engine.block.setDuration(page, 5.0); // Create gradient background if (engine.block.supportsFill(page)) { const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.2, b: 0.8, a: 1.0 }, stop: 0 }, { color: { r: 0.1, g: 0.3, b: 0.6, a: 1.0 }, stop: 0.5 }, { color: { r: 0.2, g: 0.1, b: 0.4, a: 1.0 }, stop: 1 } ]); // Diagonal gradient from top-left to bottom-right engine.block.setFloat( gradientFill, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( gradientFill, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointY', 1); engine.block.setFill(page, gradientFill); } // ===== Title: "Create Animations" with entrance animation ===== const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Create Animations'); engine.block.setTextFontSize(titleBlock, 120); engine.block.setTextColor(titleBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidthMode(titleBlock, 'Auto'); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); engine.block.setDuration(titleBlock, 5.0); // Check if block supports animations before applying if (engine.block.supportsAnimation(titleBlock)) { // Create an entrance animation const slideIn = engine.block.createAnimation('slide'); engine.block.setInAnimation(titleBlock, slideIn); engine.block.setDuration(slideIn, 1.2); engine.block.setFloat( slideIn, 'animation/slide/direction', (3 * Math.PI) / 2 ); engine.block.setEnum(slideIn, 'animationEasing', 'EaseOut'); } // Center title horizontally and position in upper third const titleWidth = engine.block.getFrameWidth(titleBlock); const titleHeight = engine.block.getFrameHeight(titleBlock); engine.block.setPositionX(titleBlock, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleBlock, pageHeight * 0.25); // ===== IMG.LY Logo with pulsating loop animation ===== const logoBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/imgly_logo.jpg', { size: { width: 200, height: 200 } } ); engine.block.appendChild(page, logoBlock); engine.block.setDuration(logoBlock, 5.0); // Contain the image within the block frame engine.block.setEnum(logoBlock, 'contentFill/mode', 'Contain'); // Create a pulsating loop animation const pulsating = engine.block.createAnimation('pulsating_loop'); engine.block.setLoopAnimation(logoBlock, pulsating); engine.block.setDuration(pulsating, 1.5); // Add fade entrance for the logo const logoFadeIn = engine.block.createAnimation('fade'); engine.block.setInAnimation(logoBlock, logoFadeIn); engine.block.setDuration(logoFadeIn, 0.8); engine.block.setEnum(logoFadeIn, 'animationEasing', 'EaseOut'); // Position logo below title, centered const logoWidth = engine.block.getFrameWidth(logoBlock); engine.block.setPositionX(logoBlock, (pageWidth - logoWidth) / 2); engine.block.setPositionY(logoBlock, pageHeight * 0.25 + titleHeight + 40); // ===== Subtitle with text animation ===== const subtitleBlock = engine.block.create('text'); engine.block.replaceText(subtitleBlock, 'Entrance • Exit • Loop'); engine.block.setTextFontSize(subtitleBlock, 48); engine.block.setTextColor(subtitleBlock, { r: 0.9, g: 0.9, b: 1.0, a: 0.9 }); engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidthMode(subtitleBlock, 'Auto'); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); engine.block.setDuration(subtitleBlock, 5.0); // Create text animation with word-by-word reveal const textAnim = engine.block.createAnimation('fade'); engine.block.setInAnimation(subtitleBlock, textAnim); engine.block.setDuration(textAnim, 1.5); // Configure text animation writing style (Line, Word, or Character) engine.block.setEnum(textAnim, 'textAnimationWritingStyle', 'Word'); // Set overlap for cascading effect (0 = sequential, 0-1 = cascading) engine.block.setFloat(textAnim, 'textAnimationOverlap', 0.3); // Position subtitle below logo const subtitleWidth = engine.block.getFrameWidth(subtitleBlock); engine.block.setPositionX(subtitleBlock, (pageWidth - subtitleWidth) / 2); engine.block.setPositionY(subtitleBlock, pageHeight * 0.65); // ===== Bottom right text with exit animation ===== const footerBlock = engine.block.create('text'); engine.block.replaceText(footerBlock, 'Powered by CE.SDK'); engine.block.setTextFontSize(footerBlock, 32); engine.block.setTextColor(footerBlock, { r: 1.0, g: 1.0, b: 1.0, a: 0.7 }); engine.block.setEnum(footerBlock, 'text/horizontalAlignment', 'Right'); engine.block.setWidthMode(footerBlock, 'Auto'); engine.block.setHeightMode(footerBlock, 'Auto'); engine.block.appendChild(page, footerBlock); // Footer appears at start and fades out at the end engine.block.setTimeOffset(footerBlock, 0); engine.block.setDuration(footerBlock, 5.0); // Create exit animation that plays at the end of the block's duration const fadeOut = engine.block.createAnimation('fade'); engine.block.setOutAnimation(footerBlock, fadeOut); engine.block.setDuration(fadeOut, 1.0); engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn'); // Position footer at bottom right with padding const footerWidth = engine.block.getFrameWidth(footerBlock); const footerHeight = engine.block.getFrameHeight(footerBlock); engine.block.setPositionX(footerBlock, pageWidth - footerWidth - 60); engine.block.setPositionY(footerBlock, pageHeight - footerHeight - 40); // ===== Animation Properties Demo ===== // Create slide animation and configure direction for title const titleInAnim = engine.block.getInAnimation(titleBlock); if (titleInAnim !== 0) { // Discover all available properties for this animation const properties = engine.block.findAllProperties(titleInAnim); console.log('Slide animation properties:', properties); } // Example: Retrieve animations to verify they're attached const currentTitleIn = engine.block.getInAnimation(titleBlock); const currentLogoLoop = engine.block.getLoopAnimation(logoBlock); const currentFooterOut = engine.block.getOutAnimation(footerBlock); console.log( 'Animation IDs - Title In:', currentTitleIn, 'Logo Loop:', currentLogoLoop, 'Footer Out:', currentFooterOut ); // Get available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); console.log('Available easing options:', easingOptions); // Set initial playback time to show the scene after animations start engine.block.setPlaybackTime(page, 2.0); } } export default Example; ``` This guide covers how to create and configure animations programmatically, including entrance, exit, loop, and text animations with customizable timing and easing. ## Animation Fundamentals We first verify that a block supports animations before creating and attaching them. The basic pattern involves creating an animation instance with `createAnimation()`, then attaching it using the appropriate setter method. ```typescript highlight-check-support const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Create Animations'); engine.block.setTextFontSize(titleBlock, 120); engine.block.setTextColor(titleBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidthMode(titleBlock, 'Auto'); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); engine.block.setDuration(titleBlock, 5.0); // Check if block supports animations before applying if (engine.block.supportsAnimation(titleBlock)) { // Create an entrance animation const slideIn = engine.block.createAnimation('slide'); engine.block.setInAnimation(titleBlock, slideIn); engine.block.setDuration(slideIn, 1.2); engine.block.setFloat( slideIn, 'animation/slide/direction', (3 * Math.PI) / 2 ); engine.block.setEnum(slideIn, 'animationEasing', 'EaseOut'); } ``` Animation support is available for: - **Graphic blocks** with image or video fills - **Text blocks** with additional writing style options - **Shape blocks** with fills CE.SDK provides several animation presets: - **Entrance animations**: slide, fade, blur, zoom, pop, wipe, pan - **Exit animations**: same types as entrance - **Loop animations**: breathing\_loop, spin\_loop, fade\_loop, pulsating\_loop, jump\_loop, squeeze\_loop, sway\_loop ## Entrance Animations Entrance animations define how blocks appear on screen. We create the animation with `createAnimation()`, attach it with `setInAnimation()`, and configure the duration with `setDuration()`. ```typescript highlight-entrance-animation // Add fade entrance for the logo const logoFadeIn = engine.block.createAnimation('fade'); engine.block.setInAnimation(logoBlock, logoFadeIn); engine.block.setDuration(logoFadeIn, 0.8); engine.block.setEnum(logoFadeIn, 'animationEasing', 'EaseOut'); ``` The `animationEasing` property controls the animation curve. Available options include Linear, EaseIn, EaseOut, and EaseInOut. ## Exit Animations Exit animations define how blocks leave the screen. We attach them with `setOutAnimation()`. CE.SDK manages timing automatically to prevent overlap between entrance and exit animations. ```typescript highlight-exit-animation const footerBlock = engine.block.create('text'); engine.block.replaceText(footerBlock, 'Powered by CE.SDK'); engine.block.setTextFontSize(footerBlock, 32); engine.block.setTextColor(footerBlock, { r: 1.0, g: 1.0, b: 1.0, a: 0.7 }); engine.block.setEnum(footerBlock, 'text/horizontalAlignment', 'Right'); engine.block.setWidthMode(footerBlock, 'Auto'); engine.block.setHeightMode(footerBlock, 'Auto'); engine.block.appendChild(page, footerBlock); // Footer appears at start and fades out at the end engine.block.setTimeOffset(footerBlock, 0); engine.block.setDuration(footerBlock, 5.0); // Create exit animation that plays at the end of the block's duration const fadeOut = engine.block.createAnimation('fade'); engine.block.setOutAnimation(footerBlock, fadeOut); engine.block.setDuration(fadeOut, 1.0); engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn'); ``` When a block has both entrance and exit animations, CE.SDK adjusts their timing based on the block's duration on the timeline. ## Loop Animations Loop animations run continuously while the block is visible. We use animation types ending in `_loop` and attach them with `setLoopAnimation()`. ```typescript highlight-loop-animation const logoBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/imgly_logo.jpg', { size: { width: 200, height: 200 } } ); engine.block.appendChild(page, logoBlock); engine.block.setDuration(logoBlock, 5.0); // Contain the image within the block frame engine.block.setEnum(logoBlock, 'contentFill/mode', 'Contain'); // Create a pulsating loop animation const pulsating = engine.block.createAnimation('pulsating_loop'); engine.block.setLoopAnimation(logoBlock, pulsating); engine.block.setDuration(pulsating, 1.5); ``` Loop animations continue throughout the block's visible duration, creating continuous motion effects like breathing, spinning, or pulsating. ## Animation Properties Each animation type exposes configurable properties. We use `setFloat()` and `setEnum()` to adjust these properties, and `findAllProperties()` to discover available options. ```typescript highlight-animation-properties // Create slide animation and configure direction for title const titleInAnim = engine.block.getInAnimation(titleBlock); if (titleInAnim !== 0) { // Discover all available properties for this animation const properties = engine.block.findAllProperties(titleInAnim); console.log('Slide animation properties:', properties); } ``` Common configurable properties include: - **Direction**: Set in radians for slide animations (0=right, PI/2=bottom, PI=left, 3\*PI/2=top) - **Easing**: Linear, EaseIn, EaseOut, EaseInOut ## Text Animations Text blocks support additional animation properties for granular control over how text appears. The `textAnimationWritingStyle` property controls whether the animation applies to the entire text, line by line, word by word, or character by character. ```typescript highlight-text-animation const subtitleBlock = engine.block.create('text'); engine.block.replaceText(subtitleBlock, 'Entrance • Exit • Loop'); engine.block.setTextFontSize(subtitleBlock, 48); engine.block.setTextColor(subtitleBlock, { r: 0.9, g: 0.9, b: 1.0, a: 0.9 }); engine.block.setEnum(subtitleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidthMode(subtitleBlock, 'Auto'); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); engine.block.setDuration(subtitleBlock, 5.0); // Create text animation with word-by-word reveal const textAnim = engine.block.createAnimation('fade'); engine.block.setInAnimation(subtitleBlock, textAnim); engine.block.setDuration(textAnim, 1.5); // Configure text animation writing style (Line, Word, or Character) engine.block.setEnum(textAnim, 'textAnimationWritingStyle', 'Word'); // Set overlap for cascading effect (0 = sequential, 0-1 = cascading) engine.block.setFloat(textAnim, 'textAnimationOverlap', 0.3); ``` Writing style options: - **Line**: Animate entire lines together - **Word**: Animate word by word - **Character**: Animate character by character The `textAnimationOverlap` property (0 to 1) controls the cascading effect. A value of 0 means sequential animation, while values closer to 1 create more overlap between segments. ## Managing Animation Lifecycle We can retrieve current animations using `getInAnimation()`, `getOutAnimation()`, and `getLoopAnimation()`. A return value of 0 indicates no animation is attached. ```typescript highlight-manage-lifecycle // Example: Retrieve animations to verify they're attached const currentTitleIn = engine.block.getInAnimation(titleBlock); const currentLogoLoop = engine.block.getLoopAnimation(logoBlock); const currentFooterOut = engine.block.getOutAnimation(footerBlock); console.log( 'Animation IDs - Title In:', currentTitleIn, 'Logo Loop:', currentLogoLoop, 'Footer Out:', currentFooterOut ); // Get available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); console.log('Available easing options:', easingOptions); ``` When replacing animations, destroy the old instance with `destroy()` to prevent memory leaks. ## Troubleshooting ### Animation Not Playing Verify the block supports animations with `supportsAnimation()`. Check that the scene is in Video mode, as animations require timeline-based playback. ### Duration Issues Ensure the animation is attached to a block before setting its duration. Duration is set on the animation instance, not the block. ### Memory Leaks When replacing an animation, destroy the old animation instance before creating a new one: ```typescript const current = engine.block.getInAnimation(block); if (current !== 0) { engine.block.destroy(current); } const newAnim = engine.block.createAnimation('fade'); engine.block.setInAnimation(block, newAnim); ``` ### Timing Conflicts If entrance and exit animations seem to overlap incorrectly, CE.SDK automatically adjusts durations to prevent conflicts. Reduce individual animation durations if needed. ## API Reference | Method | Description | | --------------------------------------------- | ------------------------------------------ | | `engine.block.createAnimation(type)` | Create animation instance | | `engine.block.supportsAnimation(block)` | Check if block supports animations | | `engine.block.setInAnimation(block, anim)` | Attach entrance animation | | `engine.block.setOutAnimation(block, anim)` | Attach exit animation | | `engine.block.setLoopAnimation(block, anim)` | Attach loop animation | | `engine.block.getInAnimation(block)` | Get entrance animation (0 if none) | | `engine.block.getOutAnimation(block)` | Get exit animation (0 if none) | | `engine.block.getLoopAnimation(block)` | Get loop animation (0 if none) | | `engine.block.setDuration(anim, seconds)` | Set animation duration | | `engine.block.setEnum(anim, prop, value)` | Set enum property (easing, writing style) | | `engine.block.setFloat(anim, prop, value)` | Set float property (direction, overlap) | | `engine.block.findAllProperties(anim)` | List available properties | | `engine.block.getEnumValues(prop)` | Get enum options | | `engine.block.destroy(anim)` | Destroy animation instance | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/) — Detailed non-text block animations - [Text Animations](https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/) — Text-specific animation control - [Edit Animations](https://img.ly/docs/cesdk/angular/animation/edit-32c12a/) — Modify existing animations - [Animation Overview](https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/) — Animation concepts --- ## Related Pages - [Base Animations](https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/) - Apply movement, scaling, rotation, or opacity changes to elements using timeline-based keyframes. - [Text Animations](https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/) - Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Base Animations" description: "Apply movement, scaling, rotation, or opacity changes to elements using timeline-based keyframes." platform: angular url: "https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/angular/animation/create-15cf50/) > [Base Animations](https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/) --- Add motion to design blocks with entrance, exit, and loop animations using CE.SDK's animation system. ![Base animations demonstrating slide, fade, zoom, and loop effects on image blocks](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-base-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-base-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-create-base-browser/) Base animations in CE.SDK add motion to design blocks through entrance (In), exit (Out), and loop animations. Animations are created as separate objects and attached to blocks, enabling reusable configurations across multiple elements. ```typescript file=@cesdk_web_examples/guides-animation-create-base-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Base Animations Guide * * Demonstrates animation features for design blocks in CE.SDK: * - Creating and applying entrance (In) animations * - Creating and applying exit (Out) animations * - Creating and applying loop animations * - Configuring duration and easing * - Managing animation lifecycle */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Set white background color if (!engine.block.supportsFill(page) || !engine.block.getFill(page)) { const fill = engine.block.createFill('color'); engine.block.setFill(page, fill); } engine.block.setColor(engine.block.getFill(page), 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Calculate grid layout for 6 demonstration blocks const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Helper to create an image block const createImageBlock = async (index: number, imageUrl: string) => { const graphic = engine.block.create('graphic'); const imageFill = engine.block.createFill('image'); engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl); engine.block.setFill(graphic, imageFill); engine.block.setShape(graphic, engine.block.createShape('rect')); engine.block.setWidth(graphic, blockWidth); engine.block.setHeight(graphic, blockHeight); const pos = getPosition(index); engine.block.setPositionX(graphic, pos.x); engine.block.setPositionY(graphic, pos.y); engine.block.appendChild(page, graphic); return graphic; }; // Sample images for demonstration const imageUrls = [ 'https://img.ly/static/ubq_samples/sample_1.jpg', 'https://img.ly/static/ubq_samples/sample_2.jpg', 'https://img.ly/static/ubq_samples/sample_3.jpg', 'https://img.ly/static/ubq_samples/sample_4.jpg', 'https://img.ly/static/ubq_samples/sample_5.jpg', 'https://img.ly/static/ubq_samples/sample_6.jpg' ]; // Block 1: Check animation support and create entrance animation const block1 = await createImageBlock(0, imageUrls[0]); // Check if block supports animations before applying if (engine.block.supportsAnimation(block1)) { // Create an entrance animation const slideAnimation = engine.block.createAnimation('slide'); engine.block.setInAnimation(block1, slideAnimation); engine.block.setDuration(slideAnimation, 1.0); } // Block 2: Entrance animation with easing configuration const block2 = await createImageBlock(1, imageUrls[1]); // Create a fade entrance animation with easing const fadeInAnimation = engine.block.createAnimation('fade'); engine.block.setInAnimation(block2, fadeInAnimation); engine.block.setDuration(fadeInAnimation, 1.0); engine.block.setEnum(fadeInAnimation, 'animationEasing', 'EaseOut'); // Block 3: Exit animation const block3 = await createImageBlock(2, imageUrls[2]); // Create an exit animation const zoomInAnimation = engine.block.createAnimation('zoom'); engine.block.setInAnimation(block3, zoomInAnimation); engine.block.setDuration(zoomInAnimation, 1.0); const fadeOutAnimation = engine.block.createAnimation('fade'); engine.block.setOutAnimation(block3, fadeOutAnimation); engine.block.setDuration(fadeOutAnimation, 1.0); engine.block.setEnum(fadeOutAnimation, 'animationEasing', 'EaseIn'); // Block 4: Loop animation const block4 = await createImageBlock(3, imageUrls[3]); // Create a breathing loop animation const breathingLoop = engine.block.createAnimation('breathing_loop'); engine.block.setLoopAnimation(block4, breathingLoop); engine.block.setDuration(breathingLoop, 1.0); // Block 5: Animation properties and slide direction const block5 = await createImageBlock(4, imageUrls[4]); // Create slide animation and configure direction const slideFromTop = engine.block.createAnimation('slide'); engine.block.setInAnimation(block5, slideFromTop); engine.block.setDuration(slideFromTop, 1.0); // Set slide direction (in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top) engine.block.setFloat( slideFromTop, 'animation/slide/direction', Math.PI / 2 ); engine.block.setEnum(slideFromTop, 'animationEasing', 'EaseInOut'); // Discover all available properties for this animation const properties = engine.block.findAllProperties(slideFromTop); // eslint-disable-next-line no-console console.log('Slide animation properties:', properties); // Block 6: Get animations and replace them const block6 = await createImageBlock(5, imageUrls[5]); // Set initial animations const initialIn = engine.block.createAnimation('pan'); engine.block.setInAnimation(block6, initialIn); engine.block.setDuration(initialIn, 1.0); const spinLoop = engine.block.createAnimation('spin_loop'); engine.block.setLoopAnimation(block6, spinLoop); engine.block.setDuration(spinLoop, 1.0); // Get current animations const currentIn = engine.block.getInAnimation(block6); const currentLoop = engine.block.getLoopAnimation(block6); const currentOut = engine.block.getOutAnimation(block6); // eslint-disable-next-line no-console console.log( 'Animation IDs - In:', currentIn, 'Loop:', currentLoop, 'Out:', currentOut ); // Replace in animation (destroy old one first to avoid memory leaks) if (currentIn !== 0) { engine.block.destroy(currentIn); } const newInAnimation = engine.block.createAnimation('wipe'); engine.block.setInAnimation(block6, newInAnimation); engine.block.setDuration(newInAnimation, 1.0); // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); // eslint-disable-next-line no-console console.log('Available easing options:', easingOptions); // Set initial playback time to 1 second (after entrance animations) engine.block.setPlaybackTime(page, 1.0); } } export default Example; ``` This guide covers creating animations, attaching them to blocks, configuring properties like duration and easing, and managing animation lifecycle. ## Animation Fundamentals Before applying animations to a block, we verify it supports them using `supportsAnimation()`. Once confirmed, we create an animation instance and attach it to the block. ```typescript highlight-supports-animation // Check if block supports animations before applying if (engine.block.supportsAnimation(block1)) { // Create an entrance animation const slideAnimation = engine.block.createAnimation('slide'); engine.block.setInAnimation(block1, slideAnimation); engine.block.setDuration(slideAnimation, 1.0); } ``` We use `createAnimation()` with an animation type like `'slide'`, `'fade'`, or `'zoom'`. The animation is then attached using `setInAnimation()` for entrance animations. Duration is set with `setDuration()` in seconds. CE.SDK provides several animation types: - **Entrance animations**: `slide`, `fade`, `blur`, `grow`, `zoom`, `pop`, `wipe`, `pan`, `baseline`, `spin` - **Loop animations**: `spin_loop`, `fade_loop`, `blur_loop`, `pulsating_loop`, `breathing_loop`, `jump_loop`, `squeeze_loop`, `sway_loop` ## Entrance Animations Entrance animations (In animations) define how a block appears on screen. We create the animation, attach it with `setInAnimation()`, and configure its properties. ```typescript highlight-entrance-animation // Create a fade entrance animation with easing const fadeInAnimation = engine.block.createAnimation('fade'); engine.block.setInAnimation(block2, fadeInAnimation); engine.block.setDuration(fadeInAnimation, 1.0); engine.block.setEnum(fadeInAnimation, 'animationEasing', 'EaseOut'); ``` We use `setEnum()` to configure the easing function. Available easing options include `'Linear'`, `'EaseIn'`, `'EaseOut'`, and `'EaseInOut'`. The `'EaseOut'` easing starts fast and slows down toward the end, creating a natural deceleration effect. ## Exit Animations Exit animations (Out animations) define how a block leaves the screen. We use `setOutAnimation()` to attach them. ```typescript highlight-exit-animation // Create an exit animation const zoomInAnimation = engine.block.createAnimation('zoom'); engine.block.setInAnimation(block3, zoomInAnimation); engine.block.setDuration(zoomInAnimation, 1.0); const fadeOutAnimation = engine.block.createAnimation('fade'); engine.block.setOutAnimation(block3, fadeOutAnimation); engine.block.setDuration(fadeOutAnimation, 1.0); engine.block.setEnum(fadeOutAnimation, 'animationEasing', 'EaseIn'); ``` When using both entrance and exit animations, CE.SDK automatically manages their timing to prevent overlap. Changing the duration of an In animation may adjust the Out animation's duration to maintain valid timing. ## Loop Animations Loop animations run continuously while the block is visible. We use `setLoopAnimation()` to attach them. ```typescript highlight-loop-animation // Create a breathing loop animation const breathingLoop = engine.block.createAnimation('breathing_loop'); engine.block.setLoopAnimation(block4, breathingLoop); engine.block.setDuration(breathingLoop, 1.0); ``` The duration for loop animations defines the length of each cycle. A 2-second breathing loop will complete one full pulse every 2 seconds. ## Animation Properties Each animation type has specific configurable properties. We use `findAllProperties()` to discover available properties for an animation. ```typescript highlight-animation-properties // Create slide animation and configure direction const slideFromTop = engine.block.createAnimation('slide'); engine.block.setInAnimation(block5, slideFromTop); engine.block.setDuration(slideFromTop, 1.0); // Set slide direction (in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top) engine.block.setFloat( slideFromTop, 'animation/slide/direction', Math.PI / 2 ); engine.block.setEnum(slideFromTop, 'animationEasing', 'EaseInOut'); // Discover all available properties for this animation const properties = engine.block.findAllProperties(slideFromTop); // eslint-disable-next-line no-console console.log('Slide animation properties:', properties); ``` For slide animations, the `animation/slide/direction` property controls the entry direction in radians: - `0` — From the right - `Math.PI / 2` — From the bottom - `Math.PI` — From the left - `3 * Math.PI / 2` — From the top ## Managing Animation Lifecycle Animation objects must be properly managed to avoid memory leaks. When replacing an animation, we destroy the old one before setting the new one. We can retrieve current animations using `getInAnimation()`, `getOutAnimation()`, and `getLoopAnimation()`. ```typescript highlight-manage-animations // Set initial animations const initialIn = engine.block.createAnimation('pan'); engine.block.setInAnimation(block6, initialIn); engine.block.setDuration(initialIn, 1.0); const spinLoop = engine.block.createAnimation('spin_loop'); engine.block.setLoopAnimation(block6, spinLoop); engine.block.setDuration(spinLoop, 1.0); // Get current animations const currentIn = engine.block.getInAnimation(block6); const currentLoop = engine.block.getLoopAnimation(block6); const currentOut = engine.block.getOutAnimation(block6); // eslint-disable-next-line no-console console.log( 'Animation IDs - In:', currentIn, 'Loop:', currentLoop, 'Out:', currentOut ); // Replace in animation (destroy old one first to avoid memory leaks) if (currentIn !== 0) { engine.block.destroy(currentIn); } const newInAnimation = engine.block.createAnimation('wipe'); engine.block.setInAnimation(block6, newInAnimation); engine.block.setDuration(newInAnimation, 1.0); ``` A return value of `0` indicates no animation is attached. Destroying a design block also destroys all its attached animations, but detached animations must be destroyed manually. ## Easing Functions We can query available easing options using `getEnumValues()`. ```typescript highlight-easing-options // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); // eslint-disable-next-line no-console console.log('Available easing options:', easingOptions); ``` Easing functions control animation acceleration: | Easing | Description | | ----------- | --------------------------------------------- | | `Linear` | Constant speed throughout | | `EaseIn` | Starts slow, accelerates toward the end | | `EaseOut` | Starts fast, decelerates toward the end | | `EaseInOut` | Starts slow, speeds up, then slows down again | ## API Reference | Method | Description | | ------------------------------- | ------------------------------------------ | | `createAnimation(type)` | Create a new animation instance | | `supportsAnimation(block)` | Check if block supports animations | | `setInAnimation(block, anim)` | Apply entrance animation to block | | `setOutAnimation(block, anim)` | Apply exit animation to block | | `setLoopAnimation(block, anim)` | Apply loop animation to block | | `getInAnimation(block)` | Get entrance animation (returns 0 if none) | | `getOutAnimation(block)` | Get exit animation (returns 0 if none) | | `getLoopAnimation(block)` | Get loop animation (returns 0 if none) | | `setDuration(anim, seconds)` | Set animation duration | | `getDuration(anim)` | Get animation duration | | `setEnum(anim, prop, value)` | Set enum property (easing, etc.) | | `setFloat(anim, prop, value)` | Set float property (direction, etc.) | | `findAllProperties(anim)` | Get all available properties for animation | | `getEnumValues(prop)` | Get available values for enum property | | `destroy(anim)` | Destroy animation instance | ## Next Steps - [Text Animations](https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/) — Animate text with writing styles and character control - [Animation Overview](https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/) — Understand animation concepts and capabilities --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Animations" description: "Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation." platform: angular url: "https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/angular/animation/create-15cf50/) > [Text Animations](https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/) --- Create engaging text animations that reveal content line by line, word by word, or character by character with granular control over timing and overlap. ![Text animations demonstrating different writing styles and overlap configurations](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-text-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-create-text-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-create-text-browser/) Text animations in CE.SDK allow you to animate text blocks with granular control over how the text appears. Unlike standard block animations, text animations support writing styles that determine whether animation applies to the entire text, line by line, word by word, or character by character. ```typescript file=@cesdk_web_examples/guides-animation-create-text-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Text Animations Guide * * Demonstrates text-specific animation features in CE.SDK: * - Creating and applying animations to text blocks * - Text animation writing styles (line, word, character) * - Segment overlap configuration * - Combining with easing and duration properties */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Set white background color for the page // First check if page supports fill, if not or doesn't have one, create one if (!engine.block.supportsFill(page) || !engine.block.getFill(page)) { const fill = engine.block.createFill('color'); engine.block.setFill(page, fill); } engine.block.setColor(engine.block.getFill(page), 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Calculate responsive grid layout for demonstrations const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Create a text block and animation // Animations are created separately and then attached to blocks const text1 = engine.block.create('text'); engine.block.setWidth(text1, blockWidth); engine.block.setHeight(text1, blockHeight); engine.block.appendChild(page, text1); const pos1 = getPosition(0); engine.block.setPositionX(text1, pos1.x); engine.block.setPositionY(text1, pos1.y); engine.block.replaceText(text1, 'Creating\nText\nAnimations'); engine.block.setFloat(text1, 'text/fontSize', 48); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text1, 'text/verticalAlignment', 'Center'); // Create an animation instance with the 'baseline' type const animation1 = engine.block.createAnimation('baseline'); // Apply the animation to the text block's entrance engine.block.setInAnimation(text1, animation1); // Set basic animation properties engine.block.setDuration(animation1, 2.0); // Writing Style: Line-by-line animation // Text animates one line at a time from top to bottom const text2 = engine.block.create('text'); engine.block.setWidth(text2, blockWidth); engine.block.setHeight(text2, blockHeight); engine.block.appendChild(page, text2); const pos2 = getPosition(1); engine.block.setPositionX(text2, pos2.x); engine.block.setPositionY(text2, pos2.y); engine.block.replaceText(text2, 'Line by line\nanimation\nfor text'); engine.block.setFloat(text2, 'text/fontSize', 42); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text2, 'text/verticalAlignment', 'Center'); const animation2 = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text2, animation2); engine.block.setDuration(animation2, 2.0); // Set writing style to 'Line' for line-by-line animation engine.block.setEnum(animation2, 'textAnimationWritingStyle', 'Line'); engine.block.setEnum(animation2, 'animationEasing', 'EaseOut'); // Writing Style: Word-by-word animation // Text animates one word at a time in reading order const text3 = engine.block.create('text'); engine.block.setWidth(text3, blockWidth); engine.block.setHeight(text3, blockHeight); engine.block.appendChild(page, text3); const pos3 = getPosition(2); engine.block.setPositionX(text3, pos3.x); engine.block.setPositionY(text3, pos3.y); engine.block.replaceText(text3, 'Animate word by word for emphasis'); engine.block.setFloat(text3, 'text/fontSize', 42); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text3, 'text/verticalAlignment', 'Center'); const animation3 = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text3, animation3); engine.block.setDuration(animation3, 2.5); // Set writing style to 'Word' for word-by-word animation engine.block.setEnum(animation3, 'textAnimationWritingStyle', 'Word'); engine.block.setEnum(animation3, 'animationEasing', 'EaseOut'); // Writing Style: Character-by-character animation // Text animates one character at a time (typewriter effect) const text4 = engine.block.create('text'); engine.block.setWidth(text4, blockWidth); engine.block.setHeight(text4, blockHeight); engine.block.appendChild(page, text4); const pos4 = getPosition(3); engine.block.setPositionX(text4, pos4.x); engine.block.setPositionY(text4, pos4.y); engine.block.replaceText( text4, 'Character by character for typewriter effect' ); engine.block.setFloat(text4, 'text/fontSize', 38); engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text4, 'text/verticalAlignment', 'Center'); const animation4 = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text4, animation4); engine.block.setDuration(animation4, 3.0); // Set writing style to 'Character' for character-by-character animation engine.block.setEnum(animation4, 'textAnimationWritingStyle', 'Character'); engine.block.setEnum(animation4, 'animationEasing', 'Linear'); // Segment Overlap: Sequential animation (overlap = 0) // Each segment completes before the next begins const text5 = engine.block.create('text'); engine.block.setWidth(text5, blockWidth); engine.block.setHeight(text5, blockHeight); engine.block.appendChild(page, text5); const pos5 = getPosition(4); engine.block.setPositionX(text5, pos5.x); engine.block.setPositionY(text5, pos5.y); engine.block.replaceText(text5, 'Sequential animation with zero overlap'); engine.block.setFloat(text5, 'text/fontSize', 40); engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text5, 'text/verticalAlignment', 'Center'); const animation5 = engine.block.createAnimation('pan'); engine.block.setInAnimation(text5, animation5); engine.block.setDuration(animation5, 2.0); engine.block.setEnum(animation5, 'textAnimationWritingStyle', 'Word'); // Set overlap to 0 for sequential animation engine.block.setFloat(animation5, 'textAnimationOverlap', 0.0); engine.block.setEnum(animation5, 'animationEasing', 'EaseOut'); // Segment Overlap: Cascading animation (overlap = 0.4) // Segments overlap partially for smooth flow const text6 = engine.block.create('text'); engine.block.setWidth(text6, blockWidth); engine.block.setHeight(text6, blockHeight); engine.block.appendChild(page, text6); const pos6 = getPosition(5); engine.block.setPositionX(text6, pos6.x); engine.block.setPositionY(text6, pos6.y); engine.block.replaceText(text6, 'Cascading animation with partial overlap'); engine.block.setFloat(text6, 'text/fontSize', 40); engine.block.setEnum(text6, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(text6, 'text/verticalAlignment', 'Center'); const animation6 = engine.block.createAnimation('pan'); engine.block.setInAnimation(text6, animation6); engine.block.setDuration(animation6, 1.5); engine.block.setEnum(animation6, 'textAnimationWritingStyle', 'Word'); // Set overlap to 0.4 for cascading effect engine.block.setFloat(animation6, 'textAnimationOverlap', 0.4); engine.block.setEnum(animation6, 'animationEasing', 'EaseOut'); // Combining animation properties: Duration and Easing // Duration controls overall timing, easing controls acceleration // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); // eslint-disable-next-line no-console console.log('Available easing options:', easingOptions); // Query available writing style options const writingStyleOptions = engine.block.getEnumValues( 'textAnimationWritingStyle' ); // eslint-disable-next-line no-console console.log('Available writing style options:', writingStyleOptions); // Start playback to see animations engine.block.setPlaying(page, true); engine.block.setLooping(page, true); } } export default Example; ``` This guide covers text-specific animation properties like writing styles and segment overlap, enabling dynamic and engaging text presentations in your designs. ## Text Animation Fundamentals We create animations by first creating an animation instance, then attaching it to a text block. The animation block defines how the text will animate, while the text block contains the content and styling. ```typescript highlight-create-animation // Create an animation instance with the 'baseline' type const animation1 = engine.block.createAnimation('baseline'); // Apply the animation to the text block's entrance engine.block.setInAnimation(text1, animation1); // Set basic animation properties engine.block.setDuration(animation1, 2.0); ``` Animations are created separately using `engine.block.createAnimation()` with an animation type like 'baseline', 'fade', or 'pan'. We then attach the animation to the text block's entrance using `engine.block.setInAnimation()`. The animation duration is set with `engine.block.setDuration()`. ## Writing Style Control Text animations support different granularity levels through the `textAnimationWritingStyle` property. This controls whether the animation applies to the entire text at once, or breaks it into segments (lines, words, or characters). We can query available options using `engine.block.getEnumValues('textAnimationWritingStyle')`. ### Line-by-Line Animation The `Line` writing style animates text one line at a time from top to bottom. Each line appears sequentially, creating a structured reveal effect. ```typescript highlight-writing-style-line const animation2 = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text2, animation2); engine.block.setDuration(animation2, 2.0); // Set writing style to 'Line' for line-by-line animation engine.block.setEnum(animation2, 'textAnimationWritingStyle', 'Line'); engine.block.setEnum(animation2, 'animationEasing', 'EaseOut'); ``` We use `engine.block.setEnum()` to set the writing style to `'Line'`. This is ideal for revealing multi-line text in a clear, organized manner. ### Word-by-Word Animation The `Word` writing style animates text one word at a time in reading order. This creates emphasis and draws attention to individual words. ```typescript highlight-writing-style-word const animation3 = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text3, animation3); engine.block.setDuration(animation3, 2.5); // Set writing style to 'Word' for word-by-word animation engine.block.setEnum(animation3, 'textAnimationWritingStyle', 'Word'); engine.block.setEnum(animation3, 'animationEasing', 'EaseOut'); ``` Setting the writing style to `'Word'` is perfect for creating dynamic, engaging text reveals that emphasize key phrases. ### Character-by-Character Animation The `Character` writing style animates text one character at a time, creating a classic typewriter effect. This is the most granular animation option. ```typescript highlight-writing-style-character const animation4 = engine.block.createAnimation('baseline'); engine.block.setInAnimation(text4, animation4); engine.block.setDuration(animation4, 3.0); // Set writing style to 'Character' for character-by-character animation engine.block.setEnum(animation4, 'textAnimationWritingStyle', 'Character'); engine.block.setEnum(animation4, 'animationEasing', 'Linear'); ``` The `'Character'` writing style is ideal for typewriter effects and when you want maximum control over the animation timing. ## Segment Overlap Configuration The `textAnimationOverlap` property controls timing between animation segments. A value of 0 means segments animate sequentially, while values between 0 and 1 create cascading effects where segments overlap partially. We use `engine.block.setFloat()` to set the overlap value. ### Sequential Animation (Overlap = 0) When overlap is set to 0, each segment completes before the next begins, creating a clear, structured reveal effect. ```typescript highlight-overlap-sequential // Set overlap to 0 for sequential animation engine.block.setFloat(animation5, 'textAnimationOverlap', 0.0); engine.block.setEnum(animation5, 'animationEasing', 'EaseOut'); ``` Sequential animation ensures each text segment fully appears before the next one starts, making it perfect for emphasis and readability. ### Cascading Animation (Overlap = 0.4) When overlap is set to a value between 0 and 1, segments animate in a cascading pattern, creating a smooth, flowing effect as they blend together. ```typescript highlight-overlap-cascading // Set overlap to 0.4 for cascading effect engine.block.setFloat(animation6, 'textAnimationOverlap', 0.4); engine.block.setEnum(animation6, 'animationEasing', 'EaseOut'); ``` Cascading animation with partial overlap creates dynamic, fluid text reveals that feel natural and engaging. ## Combining with Animation Properties Text animations can be enhanced with standard animation properties like duration and easing. Duration controls the overall timing of the animation, while easing controls the acceleration curve. ```typescript highlight-duration-easing // Combining animation properties: Duration and Easing // Duration controls overall timing, easing controls acceleration // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); // eslint-disable-next-line no-console console.log('Available easing options:', easingOptions); // Query available writing style options const writingStyleOptions = engine.block.getEnumValues( 'textAnimationWritingStyle' ); // eslint-disable-next-line no-console console.log('Available writing style options:', writingStyleOptions); ``` We use `engine.block.setEnum()` to set the easing function ('EaseIn', 'EaseOut', 'EaseInOut', 'Linear'). We can query available easing options using `engine.block.getEnumValues('animationEasing')`. Combining writing style, overlap, duration, and easing gives us complete control over how text animates. ## API Reference | Method | Description | | ------------------------------------------------- | -------------------------------------------------- | | `createAnimation(type)` | Create a new animation instance | | `setInAnimation(block, animation)` | Apply animation to block entrance | | `setLoopAnimation(block, animation)` | Apply looping animation to block | | `setOutAnimation(block, animation)` | Apply animation to block exit | | `getInAnimation(block)` | Get the entrance animation of a block | | `getLoopAnimation(block)` | Get the looping animation of a block | | `getOutAnimation(block)` | Get the exit animation of a block | | `setDuration(animation, seconds)` | Set animation duration in seconds | | `getDuration(animation)` | Get animation duration | | `setEnum(animation, property, value)` | Set enum property (writing style, easing) | | `getEnum(animation, property)` | Get enum property value | | `setFloat(animation, property, value)` | Set float property (overlap value) | | `getFloat(animation, property)` | Get float property value | | `getEnumValues(property)` | Get available enum options for a property | | `supportsAnimation(block)` | Check if block supports animations | | `replaceText(block, text)` | Set text content of a text block | | `setPlaying(block, enabled)` | Start or stop playback | | `setLooping(block, enabled)` | Enable or disable looping playback | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit Animations" description: "Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks." platform: angular url: "https://img.ly/docs/cesdk/angular/animation/edit-32c12a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) > [Edit Animations](https://img.ly/docs/cesdk/angular/animation/edit-32c12a/) --- Modify existing animations by reading properties, changing duration and easing, and replacing or removing animations from blocks. ![Edit animations demonstrating property modification, easing changes, and animation replacement on image blocks](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-animation-edit-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-edit-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-edit-browser/) Editing animations in CE.SDK involves retrieving existing animations from blocks and modifying their properties. This guide assumes you've already created and attached animations to blocks as covered in the [Base Animations](https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/) guide. ```typescript file=@cesdk_web_examples/guides-animation-edit-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Edit Animations Guide * * Demonstrates how to edit existing animations in CE.SDK: * - Retrieving animations from blocks * - Reading animation properties (type, duration, easing) * - Modifying animation duration and easing * - Adjusting animation-specific properties * - Replacing and removing animations */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages[0]!; // Set white background color const pageFill = engine.block.getFill(page); if (pageFill) { engine.block.setColor(pageFill, 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); } // Calculate grid layout for 6 demonstration blocks const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Helper to create an image block with an initial animation const createAnimatedBlock = async (index: number, imageUrl: string) => { const graphic = engine.block.create('graphic'); const imageFill = engine.block.createFill('image'); engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl); engine.block.setFill(graphic, imageFill); engine.block.setShape(graphic, engine.block.createShape('rect')); engine.block.setWidth(graphic, blockWidth); engine.block.setHeight(graphic, blockHeight); const pos = getPosition(index); engine.block.setPositionX(graphic, pos.x); engine.block.setPositionY(graphic, pos.y); engine.block.appendChild(page, graphic); // Add an initial slide animation const slideAnim = engine.block.createAnimation('slide'); engine.block.setInAnimation(graphic, slideAnim); engine.block.setDuration(slideAnim, 1.0); return graphic; }; // Sample images for demonstration const imageUrls = [ 'https://img.ly/static/ubq_samples/sample_1.jpg', 'https://img.ly/static/ubq_samples/sample_2.jpg', 'https://img.ly/static/ubq_samples/sample_3.jpg', 'https://img.ly/static/ubq_samples/sample_4.jpg', 'https://img.ly/static/ubq_samples/sample_5.jpg', 'https://img.ly/static/ubq_samples/sample_6.jpg' ]; // Block 1: Retrieve animations and check their existence const block1 = await createAnimatedBlock(0, imageUrls[0]); // Retrieve animations from a block const inAnimation = engine.block.getInAnimation(block1); const outAnimation = engine.block.getOutAnimation(block1); const loopAnimation = engine.block.getLoopAnimation(block1); // Check if animations exist (0 means no animation) console.log('In animation:', inAnimation !== 0 ? 'exists' : 'none'); console.log('Out animation:', outAnimation !== 0 ? 'exists' : 'none'); console.log('Loop animation:', loopAnimation !== 0 ? 'exists' : 'none'); // Get animation type if it exists if (inAnimation !== 0) { const animationType = engine.block.getType(inAnimation); console.log('Animation type:', animationType); } // Block 2: Read animation properties const block2 = await createAnimatedBlock(1, imageUrls[1]); // Read animation properties const animation2 = engine.block.getInAnimation(block2); if (animation2 !== 0) { // Get current duration const duration = engine.block.getDuration(animation2); console.log('Duration:', duration, 'seconds'); // Get current easing const easing = engine.block.getEnum(animation2, 'animationEasing'); console.log('Easing:', easing); // Discover all available properties const allProperties = engine.block.findAllProperties(animation2); console.log('Available properties:', allProperties); } // Block 3: Modify animation duration const block3 = await createAnimatedBlock(2, imageUrls[2]); // Modify animation duration const animation3 = engine.block.getInAnimation(block3); if (animation3 !== 0) { // Change duration to 1.5 seconds engine.block.setDuration(animation3, 1.5); // Verify the change const newDuration = engine.block.getDuration(animation3); console.log('Updated duration:', newDuration, 'seconds'); } // Block 4: Change easing function const block4 = await createAnimatedBlock(3, imageUrls[3]); // Change animation easing const animation4 = engine.block.getInAnimation(block4); if (animation4 !== 0) { // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); console.log('Available easing options:', easingOptions); // Set easing to EaseInOut for smooth acceleration and deceleration engine.block.setEnum(animation4, 'animationEasing', 'EaseInOut'); } // Block 5: Adjust animation-specific properties (slide direction) const block5 = await createAnimatedBlock(4, imageUrls[4]); // Adjust animation-specific properties const animation5 = engine.block.getInAnimation(block5); if (animation5 !== 0) { // Get current direction (for slide animations) const currentDirection = engine.block.getFloat( animation5, 'animation/slide/direction' ); console.log('Current direction (radians):', currentDirection); // Change direction to slide from top (3*PI/2 radians) engine.block.setFloat( animation5, 'animation/slide/direction', (3 * Math.PI) / 2 ); } // Block 6: Replace and remove animations const block6 = await createAnimatedBlock(5, imageUrls[5]); // Replace an existing animation const oldAnimation = engine.block.getInAnimation(block6); if (oldAnimation !== 0) { // Destroy the old animation to prevent memory leaks engine.block.destroy(oldAnimation); } // Create and set a new animation const newAnimation = engine.block.createAnimation('zoom'); engine.block.setInAnimation(block6, newAnimation); engine.block.setDuration(newAnimation, 1.2); engine.block.setEnum(newAnimation, 'animationEasing', 'EaseOut'); // Add a loop animation to demonstrate removal const loopAnim = engine.block.createAnimation('breathing_loop'); engine.block.setLoopAnimation(block6, loopAnim); engine.block.setDuration(loopAnim, 1.0); // Remove the loop animation by destroying it const currentLoop = engine.block.getLoopAnimation(block6); if (currentLoop !== 0) { engine.block.destroy(currentLoop); // Verify removal - should now return 0 const verifyLoop = engine.block.getLoopAnimation(block6); console.log( 'Loop animation after removal:', verifyLoop === 0 ? 'none' : 'exists' ); } } } export default Example; ``` This guide covers retrieving animations, reading and modifying properties, changing easing functions, adjusting animation-specific settings, and replacing or removing animations. ## Retrieving Animations Before modifying an animation, we retrieve it from the block using `getInAnimation()`, `getOutAnimation()`, or `getLoopAnimation()`. A return value of `0` indicates no animation is attached. ```typescript highlight-retrieve-animations // Retrieve animations from a block const inAnimation = engine.block.getInAnimation(block1); const outAnimation = engine.block.getOutAnimation(block1); const loopAnimation = engine.block.getLoopAnimation(block1); // Check if animations exist (0 means no animation) console.log('In animation:', inAnimation !== 0 ? 'exists' : 'none'); console.log('Out animation:', outAnimation !== 0 ? 'exists' : 'none'); console.log('Loop animation:', loopAnimation !== 0 ? 'exists' : 'none'); // Get animation type if it exists if (inAnimation !== 0) { const animationType = engine.block.getType(inAnimation); console.log('Animation type:', animationType); } ``` We use `getType()` to identify the animation type (slide, fade, zoom, etc.). This is useful when you need to apply type-specific modifications. ## Reading Animation Properties We can inspect current animation settings using property getters. `getDuration()` returns the animation length in seconds, while `getEnum()` retrieves values like easing functions. ```typescript highlight-read-properties // Read animation properties const animation2 = engine.block.getInAnimation(block2); if (animation2 !== 0) { // Get current duration const duration = engine.block.getDuration(animation2); console.log('Duration:', duration, 'seconds'); // Get current easing const easing = engine.block.getEnum(animation2, 'animationEasing'); console.log('Easing:', easing); // Discover all available properties const allProperties = engine.block.findAllProperties(animation2); console.log('Available properties:', allProperties); } ``` Use `findAllProperties()` to discover all configurable properties for an animation. Different animation types expose different properties—slide animations have direction, while loop animations may have intensity or scale properties. ## Modifying Animation Duration Change animation timing with `setDuration()`. The duration is specified in seconds. ```typescript highlight-modify-duration // Modify animation duration const animation3 = engine.block.getInAnimation(block3); if (animation3 !== 0) { // Change duration to 1.5 seconds engine.block.setDuration(animation3, 1.5); // Verify the change const newDuration = engine.block.getDuration(animation3); console.log('Updated duration:', newDuration, 'seconds'); } ``` When modifying In or Out animation durations, CE.SDK automatically adjusts the paired animation to prevent overlap. For loop animations, the duration defines the cycle length. ## Changing Easing Functions Easing controls animation acceleration. We use `setEnum()` with the `'animationEasing'` property to change it. ```typescript highlight-change-easing // Change animation easing const animation4 = engine.block.getInAnimation(block4); if (animation4 !== 0) { // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); console.log('Available easing options:', easingOptions); // Set easing to EaseInOut for smooth acceleration and deceleration engine.block.setEnum(animation4, 'animationEasing', 'EaseInOut'); } ``` Use `getEnumValues('animationEasing')` to discover available options: | Easing | Description | | ----------- | ----------------------------------------------- | | `Linear` | Constant speed throughout | | `EaseIn` | Starts slow, accelerates toward the end | | `EaseOut` | Starts fast, decelerates toward the end | | `EaseInOut` | Starts slow, speeds up, then slows down again | ## Adjusting Animation-Specific Properties Each animation type has unique configurable properties. For slide animations, we can change the entry direction. ```typescript highlight-adjust-properties // Adjust animation-specific properties const animation5 = engine.block.getInAnimation(block5); if (animation5 !== 0) { // Get current direction (for slide animations) const currentDirection = engine.block.getFloat( animation5, 'animation/slide/direction' ); console.log('Current direction (radians):', currentDirection); // Change direction to slide from top (3*PI/2 radians) engine.block.setFloat( animation5, 'animation/slide/direction', (3 * Math.PI) / 2 ); } ``` The `animation/slide/direction` property uses radians: - `0` — From the right - `Math.PI / 2` — From the bottom - `Math.PI` — From the left - `3 * Math.PI / 2` — From the top For text animations, you can adjust `textAnimationWritingStyle` (Line, Word, Character) and `textAnimationOverlap` (0 for sequential, 1 for simultaneous). ## Replacing Animations To swap an animation type, destroy the existing animation before setting a new one. This prevents memory leaks from orphaned animation objects. ```typescript highlight-replace-animation // Replace an existing animation const oldAnimation = engine.block.getInAnimation(block6); if (oldAnimation !== 0) { // Destroy the old animation to prevent memory leaks engine.block.destroy(oldAnimation); } // Create and set a new animation const newAnimation = engine.block.createAnimation('zoom'); engine.block.setInAnimation(block6, newAnimation); engine.block.setDuration(newAnimation, 1.2); engine.block.setEnum(newAnimation, 'animationEasing', 'EaseOut'); ``` We first retrieve and destroy the old animation, then create and attach a new one with the desired type and properties. ## Removing Animations Remove an animation by destroying it. After destruction, the getter returns `0`. ```typescript highlight-remove-animation // Add a loop animation to demonstrate removal const loopAnim = engine.block.createAnimation('breathing_loop'); engine.block.setLoopAnimation(block6, loopAnim); engine.block.setDuration(loopAnim, 1.0); // Remove the loop animation by destroying it const currentLoop = engine.block.getLoopAnimation(block6); if (currentLoop !== 0) { engine.block.destroy(currentLoop); // Verify removal - should now return 0 const verifyLoop = engine.block.getLoopAnimation(block6); console.log( 'Loop animation after removal:', verifyLoop === 0 ? 'none' : 'exists' ); } ``` Destroying a design block automatically destroys all its attached animations. However, detached animations must be destroyed manually to free memory. ## API Reference | Method | Description | | ------------------------------------- | -------------------------------------------------- | | `block.getInAnimation(block)` | Get entrance animation (returns 0 if none) | | `block.getOutAnimation(block)` | Get exit animation (returns 0 if none) | | `block.getLoopAnimation(block)` | Get loop animation (returns 0 if none) | | `block.getType(anim)` | Get animation type string | | `block.getDuration(anim)` | Get animation duration in seconds | | `block.setDuration(anim, seconds)` | Set animation duration | | `block.getEnum(anim, prop)` | Get enum property value | | `block.setEnum(anim, prop, value)` | Set enum property value | | `block.getFloat(anim, prop)` | Get float property value | | `block.setFloat(anim, prop, value)` | Set float property value | | `block.findAllProperties(anim)` | Get all available properties | | `block.getEnumValues(prop)` | Get available values for enum property | | `block.destroy(anim)` | Destroy animation and free memory | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/) — Create entrance, exit, and loop animations - [Text Animations](https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/) — Animate text with writing styles and character control - [Animation Overview](https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/) — Understand animation concepts and capabilities --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control." platform: angular url: "https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) > [Overview](https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/) --- Animations in CreativeEditor SDK (CE.SDK) bring your designs to life by adding motion to images, text, and design elements. Whether you're creating a dynamic social media post, a video ad, or an engaging product demo, animations help capture attention and communicate ideas more effectively. With CE.SDK, you can create and edit animations either through the built-in UI timeline or programmatically using the CreativeEngine API. Animated designs can be exported as MP4 videos, allowing you to deliver polished, motion-rich content entirely client-side. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Supported Animation Types" description: "Apply different animation types to design blocks in CE.SDK and configure their properties." platform: angular url: "https://img.ly/docs/cesdk/angular/animation/types-4e5f41/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) > [Supported Animation Types](https://img.ly/docs/cesdk/angular/animation/types-4e5f41/) --- Apply entrance, exit, and loop animations to design blocks using the available animation types in CE.SDK. ![Animation types demonstrating slide, fade, zoom, and loop effects on image blocks](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-animation-types-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-animation-types-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-animation-types-browser/) CE.SDK organizes animations into three categories: entrance (In), exit (Out), and loop. Each category determines when the animation plays during the block's lifecycle. This guide demonstrates different animation types and their configurable properties. ```typescript file=@cesdk_web_examples/guides-animation-types-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Supported Animation Types Guide * * Demonstrates how to use different animation types in CE.SDK: * - Entrance animations (slide, fade, zoom, spin) * - Exit animations with timing and easing * - Loop animations for continuous motion * - Animation property configuration */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video features for animation playback cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const scene = engine.scene.get()!; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Set white background color if (!engine.block.supportsFill(page) || !engine.block.getFill(page)) { const fill = engine.block.createFill('color'); engine.block.setFill(page, fill); } const pageFill = engine.block.getFill(page)!; engine.block.setColor(pageFill, 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Calculate grid layout for 6 demonstration blocks const pageWidth = engine.block.getWidth(page)!; const pageHeight = engine.block.getHeight(page)!; const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Helper to create an image block const createImageBlock = async (index: number, imageUrl: string) => { const graphic = engine.block.create('graphic'); const imageFill = engine.block.createFill('image'); engine.block.setString(imageFill, 'fill/image/imageFileURI', imageUrl); engine.block.setFill(graphic, imageFill); engine.block.setShape(graphic, engine.block.createShape('rect')); engine.block.setWidth(graphic, blockWidth); engine.block.setHeight(graphic, blockHeight); const pos = getPosition(index); engine.block.setPositionX(graphic, pos.x); engine.block.setPositionY(graphic, pos.y); engine.block.appendChild(page, graphic); return graphic; }; // Sample images for demonstration const imageUrls = [ 'https://img.ly/static/ubq_samples/sample_1.jpg', 'https://img.ly/static/ubq_samples/sample_2.jpg', 'https://img.ly/static/ubq_samples/sample_3.jpg', 'https://img.ly/static/ubq_samples/sample_4.jpg', 'https://img.ly/static/ubq_samples/sample_5.jpg', 'https://img.ly/static/ubq_samples/sample_6.jpg' ]; // Block 1: Slide entrance animation with direction const block1 = await createImageBlock(0, imageUrls[0]); // Create a slide animation that enters from the left const slideAnimation = engine.block.createAnimation('slide'); engine.block.setInAnimation(block1, slideAnimation); engine.block.setDuration(slideAnimation, 1.0); // Direction in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top engine.block.setFloat(slideAnimation, 'animation/slide/direction', Math.PI); engine.block.setEnum(slideAnimation, 'animationEasing', 'EaseOut'); // Block 2: Fade animation with easing const block2 = await createImageBlock(1, imageUrls[1]); // Create a fade entrance animation const fadeAnimation = engine.block.createAnimation('fade'); engine.block.setInAnimation(block2, fadeAnimation); engine.block.setDuration(fadeAnimation, 1.0); engine.block.setEnum(fadeAnimation, 'animationEasing', 'EaseInOut'); // Block 3: Zoom animation const block3 = await createImageBlock(2, imageUrls[2]); // Create a zoom animation with fade effect const zoomAnimation = engine.block.createAnimation('zoom'); engine.block.setInAnimation(block3, zoomAnimation); engine.block.setDuration(zoomAnimation, 1.0); engine.block.setBool(zoomAnimation, 'animation/zoom/fade', true); // Block 4: Exit animation const block4 = await createImageBlock(3, imageUrls[3]); // Create entrance and exit animations const wipeIn = engine.block.createAnimation('wipe'); engine.block.setInAnimation(block4, wipeIn); engine.block.setDuration(wipeIn, 1.0); engine.block.setEnum(wipeIn, 'animation/wipe/direction', 'Right'); // Exit animation plays before block disappears const fadeOut = engine.block.createAnimation('fade'); engine.block.setOutAnimation(block4, fadeOut); engine.block.setDuration(fadeOut, 1.0); engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn'); // Block 5: Loop animation const block5 = await createImageBlock(4, imageUrls[4]); // Create a breathing loop animation const breathingLoop = engine.block.createAnimation('breathing_loop'); engine.block.setLoopAnimation(block5, breathingLoop); engine.block.setDuration(breathingLoop, 2.0); // Intensity: 0 = 1.25x max scale, 1 = 2.5x max scale engine.block.setFloat( breathingLoop, 'animation/breathing_loop/intensity', 0.3 ); // Block 6: Combined animations const block6 = await createImageBlock(5, imageUrls[5]); // Apply entrance, exit, and loop animations together const spinIn = engine.block.createAnimation('spin'); engine.block.setInAnimation(block6, spinIn); engine.block.setDuration(spinIn, 1.0); engine.block.setEnum(spinIn, 'animation/spin/direction', 'Clockwise'); engine.block.setFloat(spinIn, 'animation/spin/intensity', 0.5); const blurOut = engine.block.createAnimation('blur'); engine.block.setOutAnimation(block6, blurOut); engine.block.setDuration(blurOut, 1.0); const swayLoop = engine.block.createAnimation('sway_loop'); engine.block.setLoopAnimation(block6, swayLoop); engine.block.setDuration(swayLoop, 1.5); // Discover available properties for any animation const properties = engine.block.findAllProperties(slideAnimation); // eslint-disable-next-line no-console console.log('Slide animation properties:', properties); // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); // eslint-disable-next-line no-console console.log('Available easing options:', easingOptions); // Set initial playback time to see animations engine.block.setPlaybackTime(page, 1.9); } } export default Example; ``` This guide covers applying entrance animations (slide, fade, zoom), exit animations, loop animations, and configuring animation properties like direction, easing, and intensity. ## Entrance Animations Entrance animations define how a block appears. We use `createAnimation()` with the animation type and attach it using `setInAnimation()`. ### Slide Animation The slide animation moves a block in from a specified direction. The `direction` property uses radians where 0 is right, π/2 is bottom, π is left, and 3π/2 is top. ```typescript highlight-entrance-slide // Create a slide animation that enters from the left const slideAnimation = engine.block.createAnimation('slide'); engine.block.setInAnimation(block1, slideAnimation); engine.block.setDuration(slideAnimation, 1.0); // Direction in radians: 0=right, PI/2=bottom, PI=left, 3*PI/2=top engine.block.setFloat(slideAnimation, 'animation/slide/direction', Math.PI); engine.block.setEnum(slideAnimation, 'animationEasing', 'EaseOut'); ``` ### Fade Animation The fade animation transitions opacity from invisible to fully visible. Easing controls the animation curve. ```typescript highlight-entrance-fade // Create a fade entrance animation const fadeAnimation = engine.block.createAnimation('fade'); engine.block.setInAnimation(block2, fadeAnimation); engine.block.setDuration(fadeAnimation, 1.0); engine.block.setEnum(fadeAnimation, 'animationEasing', 'EaseInOut'); ``` ### Zoom Animation The zoom animation scales the block from a smaller size to its final dimensions. The `fade` property adds an opacity transition during scaling. ```typescript highlight-entrance-zoom // Create a zoom animation with fade effect const zoomAnimation = engine.block.createAnimation('zoom'); engine.block.setInAnimation(block3, zoomAnimation); engine.block.setDuration(zoomAnimation, 1.0); engine.block.setBool(zoomAnimation, 'animation/zoom/fade', true); ``` Other entrance animation types include: - `blur` — Transitions from blurred to clear - `wipe` — Reveals with a directional wipe - `pop` — Bouncy scale effect - `spin` — Rotates the block into view - `grow` — Scales up from a point ## Exit Animations Exit animations define how a block leaves the screen. We use `setOutAnimation()` to attach them. CE.SDK prevents overlap between entrance and exit durations automatically. ```typescript highlight-exit-animation // Create entrance and exit animations const wipeIn = engine.block.createAnimation('wipe'); engine.block.setInAnimation(block4, wipeIn); engine.block.setDuration(wipeIn, 1.0); engine.block.setEnum(wipeIn, 'animation/wipe/direction', 'Right'); // Exit animation plays before block disappears const fadeOut = engine.block.createAnimation('fade'); engine.block.setOutAnimation(block4, fadeOut); engine.block.setDuration(fadeOut, 1.0); engine.block.setEnum(fadeOut, 'animationEasing', 'EaseIn'); ``` In this example, a wipe entrance transitions to a fade exit. Mirror entrance effects for visual consistency, or use contrasting effects for emphasis. ## Loop Animations Loop animations run continuously while the block is visible. They can combine with entrance and exit animations. We use `setLoopAnimation()` to attach them. ```typescript highlight-loop-animation // Create a breathing loop animation const breathingLoop = engine.block.createAnimation('breathing_loop'); engine.block.setLoopAnimation(block5, breathingLoop); engine.block.setDuration(breathingLoop, 2.0); // Intensity: 0 = 1.25x max scale, 1 = 2.5x max scale engine.block.setFloat( breathingLoop, 'animation/breathing_loop/intensity', 0.3 ); ``` The duration controls each cycle length. Loop animation types include: - `breathing_loop` — Slow scale pulse - `pulsating_loop` — Rhythmic scale - `spin_loop` — Continuous rotation - `fade_loop` — Opacity cycling - `sway_loop` — Rotational oscillation - `jump_loop` — Jumping motion - `blur_loop` — Blur cycling - `squeeze_loop` — Squeezing effect ## Combined Animations A single block can have entrance, exit, and loop animations running together. The loop animation runs throughout the block's visibility while entrance and exit animations play at the appropriate times. ```typescript highlight-combined-animations // Apply entrance, exit, and loop animations together const spinIn = engine.block.createAnimation('spin'); engine.block.setInAnimation(block6, spinIn); engine.block.setDuration(spinIn, 1.0); engine.block.setEnum(spinIn, 'animation/spin/direction', 'Clockwise'); engine.block.setFloat(spinIn, 'animation/spin/intensity', 0.5); const blurOut = engine.block.createAnimation('blur'); engine.block.setOutAnimation(block6, blurOut); engine.block.setDuration(blurOut, 1.0); const swayLoop = engine.block.createAnimation('sway_loop'); engine.block.setLoopAnimation(block6, swayLoop); engine.block.setDuration(swayLoop, 1.5); ``` ## Configuring Animation Properties Each animation type has specific configurable properties. We use `findAllProperties()` to discover available properties and `getEnumValues()` to query options for enum properties. ```typescript highlight-discover-properties // Discover available properties for any animation const properties = engine.block.findAllProperties(slideAnimation); // eslint-disable-next-line no-console console.log('Slide animation properties:', properties); // Query available easing options const easingOptions = engine.block.getEnumValues('animationEasing'); // eslint-disable-next-line no-console console.log('Available easing options:', easingOptions); ``` Common configurable properties include: - **Direction**: Controls entry/exit direction in radians or enum values - **Easing**: Animation curve (`Linear`, `EaseIn`, `EaseOut`, `EaseInOut`) - **Intensity**: Strength of the effect (varies by animation type) - **Fade**: Whether to include opacity transition ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.createAnimation(type)` | Create animation by type string | | `engine.block.setInAnimation(block, anim)` | Attach entrance animation | | `engine.block.setOutAnimation(block, anim)` | Attach exit animation | | `engine.block.setLoopAnimation(block, anim)` | Attach loop animation | | `engine.block.setDuration(anim, seconds)` | Set animation duration | | `engine.block.setFloat(anim, property, value)` | Set numeric property | | `engine.block.setEnum(anim, property, value)` | Set enum property | | `engine.block.setBool(anim, property, value)` | Set boolean property | | `engine.block.findAllProperties(anim)` | Discover configurable properties | | `engine.block.getEnumValues(property)` | Get available enum values | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/angular/animation/create/base-0fc5c4/) — Create and attach animations to blocks - [Text Animations](https://img.ly/docs/cesdk/angular/animation/create/text-d6f4aa/) — Animate text with writing styles - [Animation Overview](https://img.ly/docs/cesdk/angular/animation/overview-6a2ef2/) — Animation concepts and capabilities --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "API Reference" description: "Find out how to use the API of the CESDK." platform: angular url: "https://img.ly/docs/cesdk/angular/api-reference/overview-8f24e1/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [API Reference](https://img.ly/docs/cesdk/angular/api-reference/overview-8f24e1/) --- For , the following packages are available: - [@cesdk/cesdk-js](`$\{props.platform.slug}/api/cesdk-js/`) - [@cesdk/engine](`$\{props.platform.slug}/api/engine/`) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Automate Workflows" description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale." platform: angular url: "https://img.ly/docs/cesdk/angular/automation-715209/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/automation/overview-34d971/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale. - [Batch Processing](https://img.ly/docs/cesdk/angular/automation/batch-processing-ab2d18/) - Manage batch processing workflows in web apps with CE.SDK - [Auto-Resize](https://img.ly/docs/cesdk/angular/automation/auto-resize-4c2d58/) - Configure blocks to dynamically adjust dimensions using Absolute, Percent, and Auto sizing modes for responsive layouts and content-driven expansion. - [Data Merge](https://img.ly/docs/cesdk/angular/automation/data-merge-ae087c/) - Generate personalized designs from templates by merging external data using text variables and placeholder blocks - [Automate Design Generation](https://img.ly/docs/cesdk/angular/automation/design-generation-98a99e/) - Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Auto-Resize" description: "Configure blocks to dynamically adjust dimensions using Absolute, Percent, and Auto sizing modes for responsive layouts and content-driven expansion." platform: angular url: "https://img.ly/docs/cesdk/angular/automation/auto-resize-4c2d58/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) > [Auto-Resize](https://img.ly/docs/cesdk/angular/automation/auto-resize-4c2d58/) --- Configure blocks to dynamically adjust their dimensions using three sizing modes: Absolute for fixed values, Percent for parent-relative sizing, and Auto for content-driven expansion. ![Auto-Resize example showing text blocks with automatic sizing](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-automation-auto-resize-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-automation-auto-resize-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-automation-auto-resize-browser/) CE.SDK provides three sizing modes for controlling block dimensions. Absolute mode uses fixed pixel values. Percent mode sizes blocks relative to their parent container. Auto mode automatically expands blocks to fit their content. You can set width and height modes independently, allowing flexible combinations like fixed width with auto height for text that wraps. ```typescript file=@cesdk_web_examples/guides-automation-auto-resize-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Auto-Resize Guide * * Demonstrates block sizing modes and responsive layout patterns: * - Setting width and height modes (Absolute, Percent, Auto) * - Reading computed frame dimensions after layout * - Centering text blocks based on computed dimensions * - Creating responsive layouts with percentage-based sizing */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Create a text block with Auto sizing mode // Auto mode makes the block expand to fit its content const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Auto-Resize Demo'); engine.block.setFloat(titleBlock, 'text/fontSize', 64); // Set width and height modes to Auto // The block will automatically size to fit the text content engine.block.setWidthMode(titleBlock, 'Auto'); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); // Read computed frame dimensions after layout // getFrameWidth/getFrameHeight return the actual rendered size const titleWidth = engine.block.getFrameWidth(titleBlock); const titleHeight = engine.block.getFrameHeight(titleBlock); console.log( `Title dimensions: ${titleWidth.toFixed(0)}x${titleHeight.toFixed( 0 )} pixels` ); // Calculate centered position using frame dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const centerX = (pageWidth - titleWidth) / 2; const centerY = (pageHeight - titleHeight) / 2 - 100; // Offset up for layout // Position the title at center engine.block.setPositionX(titleBlock, centerX); engine.block.setPositionY(titleBlock, centerY); // Create a block using Percent mode for responsive sizing // Percent mode sizes the block relative to its parent const backgroundBlock = engine.block.create('graphic'); engine.block.setShape(backgroundBlock, engine.block.createShape('rect')); const fill = engine.block.createFill('color'); engine.block.setColor(fill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 0.3 }); engine.block.setFill(backgroundBlock, fill); // Set to Percent mode - values are normalized (0-1) engine.block.setWidthMode(backgroundBlock, 'Percent'); engine.block.setHeightMode(backgroundBlock, 'Percent'); engine.block.setWidth(backgroundBlock, 0.8); // 80% of parent width engine.block.setHeight(backgroundBlock, 0.3); // 30% of parent height // Center the background block engine.block.setPositionX(backgroundBlock, pageWidth * 0.1); // 10% margin engine.block.setPositionY(backgroundBlock, pageHeight * 0.6); engine.block.appendChild(page, backgroundBlock); // Create a subtitle with Auto mode const subtitleBlock = engine.block.create('text'); engine.block.replaceText( subtitleBlock, 'Text automatically sizes to fit content' ); engine.block.setFloat(subtitleBlock, 'text/fontSize', 32); engine.block.setWidthMode(subtitleBlock, 'Auto'); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); // Read computed dimensions and center const subtitleWidth = engine.block.getFrameWidth(subtitleBlock); const subtitleCenterX = (pageWidth - subtitleWidth) / 2; engine.block.setPositionX(subtitleBlock, subtitleCenterX); engine.block.setPositionY(subtitleBlock, pageHeight * 0.7); // Verify sizing modes const titleWidthMode = engine.block.getWidthMode(titleBlock); const titleHeightMode = engine.block.getHeightMode(titleBlock); const bgWidthMode = engine.block.getWidthMode(backgroundBlock); const bgHeightMode = engine.block.getHeightMode(backgroundBlock); console.log( `Title modes: width=${titleWidthMode}, height=${titleHeightMode}` ); console.log( `Background modes: width=${bgWidthMode}, height=${bgHeightMode}` ); // Select the title block to show the auto-sized result engine.block.select(titleBlock); console.log( 'Auto-resize guide initialized. Try changing text content to see auto-sizing in action.' ); } } export default Example; ``` This guide covers how to set and query sizing modes, read computed frame dimensions after layout, center blocks using frame dimensions, and create responsive layouts with percentage-based sizing. ## Initialize the Editor We start by initializing CE.SDK with a Design scene and setting up the page dimensions for our layout. ```typescript highlight=highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; ``` ## Size Modes CE.SDK supports three sizing modes for block dimensions: - **Absolute**: Fixed dimensions in design units. The default mode where `setWidth()` and `setHeight()` set exact pixel values. - **Percent**: Dimensions relative to parent container. A value of 80 makes the block 80% of its parent's size. - **Auto**: Content-driven sizing. The block expands or contracts to fit its content, primarily useful for text blocks. ## Setting Size Modes Use `setWidthMode()` and `setHeightMode()` to configure how a block calculates its dimensions. Width and height modes can be set independently. ### Auto Mode for Text Auto mode makes text blocks expand to fit their content: ```typescript highlight=highlight-auto-mode // Create a text block with Auto sizing mode // Auto mode makes the block expand to fit its content const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Auto-Resize Demo'); engine.block.setFloat(titleBlock, 'text/fontSize', 64); // Set width and height modes to Auto // The block will automatically size to fit the text content engine.block.setWidthMode(titleBlock, 'Auto'); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); ``` With Auto mode, the block's dimensions are calculated automatically based on the content. This is useful when the text content varies and you want the block to always fit exactly. ### Percent Mode for Responsive Layouts Percent mode sizes blocks relative to their parent: ```typescript highlight=highlight-percent-mode // Create a block using Percent mode for responsive sizing // Percent mode sizes the block relative to its parent const backgroundBlock = engine.block.create('graphic'); engine.block.setShape(backgroundBlock, engine.block.createShape('rect')); const fill = engine.block.createFill('color'); engine.block.setColor(fill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 0.3 }); engine.block.setFill(backgroundBlock, fill); // Set to Percent mode - values are normalized (0-1) engine.block.setWidthMode(backgroundBlock, 'Percent'); engine.block.setHeightMode(backgroundBlock, 'Percent'); engine.block.setWidth(backgroundBlock, 0.8); // 80% of parent width engine.block.setHeight(backgroundBlock, 0.3); // 30% of parent height // Center the background block engine.block.setPositionX(backgroundBlock, pageWidth * 0.1); // 10% margin engine.block.setPositionY(backgroundBlock, pageHeight * 0.6); engine.block.appendChild(page, backgroundBlock); ``` Percent values represent the percentage of the parent container. A width of 80 with Percent mode means 80% of the parent's width. ## Reading Frame Dimensions After layout, use `getFrameWidth()` and `getFrameHeight()` to read the computed dimensions: ```typescript highlight=highlight-read-frame-dimensions // Read computed frame dimensions after layout // getFrameWidth/getFrameHeight return the actual rendered size const titleWidth = engine.block.getFrameWidth(titleBlock); const titleHeight = engine.block.getFrameHeight(titleBlock); console.log( `Title dimensions: ${titleWidth.toFixed(0)}x${titleHeight.toFixed( 0 )} pixels` ); ``` Frame dimensions return the actual rendered size regardless of the sizing mode. This is essential when using Auto mode since you need the computed size for positioning calculations. ## Centering Blocks Combine Auto mode with frame dimensions to center blocks based on their actual size: ```typescript highlight=highlight-center-block // Calculate centered position using frame dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const centerX = (pageWidth - titleWidth) / 2; const centerY = (pageHeight - titleHeight) / 2 - 100; // Offset up for layout // Position the title at center engine.block.setPositionX(titleBlock, centerX); engine.block.setPositionY(titleBlock, centerY); ``` This pattern reads the computed dimensions after Auto sizing and calculates the centered position. ## Additional Auto-Sized Content You can create multiple auto-sized blocks and position them relative to each other: ```typescript highlight=highlight-subtitle-auto // Create a subtitle with Auto mode const subtitleBlock = engine.block.create('text'); engine.block.replaceText( subtitleBlock, 'Text automatically sizes to fit content' ); engine.block.setFloat(subtitleBlock, 'text/fontSize', 32); engine.block.setWidthMode(subtitleBlock, 'Auto'); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); // Read computed dimensions and center const subtitleWidth = engine.block.getFrameWidth(subtitleBlock); const subtitleCenterX = (pageWidth - subtitleWidth) / 2; engine.block.setPositionX(subtitleBlock, subtitleCenterX); engine.block.setPositionY(subtitleBlock, pageHeight * 0.7); ``` ## Verifying Size Modes Query the current size modes to verify your configuration: ```typescript highlight=highlight-check-modes // Verify sizing modes const titleWidthMode = engine.block.getWidthMode(titleBlock); const titleHeightMode = engine.block.getHeightMode(titleBlock); const bgWidthMode = engine.block.getWidthMode(backgroundBlock); const bgHeightMode = engine.block.getHeightMode(backgroundBlock); console.log( `Title modes: width=${titleWidthMode}, height=${titleHeightMode}` ); console.log( `Background modes: width=${bgWidthMode}, height=${bgHeightMode}` ); ``` ## Troubleshooting **Frame dimensions return 0**: Layout may not have updated yet. Read frame dimensions after all content is set and the block is attached to the scene hierarchy. **Percent mode not working**: The block must have a parent container. Percent mode calculates size relative to the parent's dimensions. **Auto mode not resizing**: Auto mode works with content that has intrinsic size, primarily text blocks. Graphics require explicit dimensions. **Unexpected dimensions**: Check which mode is active using `getWidthMode()` and `getHeightMode()`. The mode affects how width and height values are interpreted. ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.getWidth(block)` | Get block width in current mode | | `engine.block.setWidth(block, value)` | Set block width in current mode | | `engine.block.getWidthMode(block)` | Get current width mode: Absolute, Percent, or Auto | | `engine.block.setWidthMode(block, mode)` | Set width mode: Absolute, Percent, or Auto | | `engine.block.getHeight(block)` | Get block height in current mode | | `engine.block.setHeight(block, value)` | Set block height in current mode | | `engine.block.getHeightMode(block)` | Get current height mode: Absolute, Percent, or Auto | | `engine.block.setHeightMode(block, mode)` | Set height mode: Absolute, Percent, or Auto | | `engine.block.getFrameWidth(block)` | Get computed width after layout | | `engine.block.getFrameHeight(block)` | Get computed height after layout | | `engine.block.setPositionX(block, value)` | Set block X position | | `engine.block.setPositionY(block, value)` | Set block Y position | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Batch Processing" description: "Manage batch processing workflows in web apps with CE.SDK" platform: angular url: "https://img.ly/docs/cesdk/angular/automation/batch-processing-ab2d18/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) > [Batch Processing](https://img.ly/docs/cesdk/angular/automation/batch-processing-ab2d18/) --- This guide shows you how to use CE.SDK to create and manage batch processing workflows in the browser. Batch processing automates creative operations at scale, from enabling template population and multi-format exports, to bulk transformations and production pipelines. In the browser, batch processing means automating the same CreativeEngine workflow while the tab stays open. Instead of the user editing/exporting items one by one, your front-end: 1. Loops through a dataset. 2. Produces a series of outputs. This guides helps you understand how the CE.SDK can work in a batch process workflow. ## What You’ll Learn - Two different batch processing approaches: - Sequential - Parallel - How to batch: - Templates population with data. - Exports to different formats (PNG, JPEG, PDF, MP4). - Thumbnails generation. - How to optimize memory usage. ## Batch Processing Strategies You can run batch operations in two ways: - **Sequential:** a single engine loop. - **Parallel:** multiple workers spinning up. The following examples show both approaches when running a batch export in the browser: ```ts // ... downloadBlob logic //Start the engine and download the scene const engine = await CreativeEngine.init({ license: LICENSE_KEY }); for (const record of records) { await engine.scene.loadFromString(record.scene); const blob = await engine.block.export(engine.scene.getPages()[0], 'image/png'); await downloadBlob(blob, `${record.id}.png`); } engine.dispose(); ``` 1. `CreativeEngine.init` spins up a single engine instance for the tab. 2. The loop iterates over the `record` dataset. 3. The Engine loads the scene. 4. The `export` call renders the first page as a PNG blob. 5. The code disposes of the engine to free resources. ```ts const workers = [new Worker('worker.js'), new Worker('worker.js')]; await Promise.all( records.map((record, idx) => workers[idx % workers.length].postMessage({ type: 'PROCESS', record }) ) ); ``` In this code: 1. 2 workers run in separate threads. 2. Each worker receives a different data set. 3. Each worker runs the heavier CreativeEngine work off the main thread. 4. `Promise.all` waits for every worker call to finish before moving on. The following table summarizes the pros and cons of each approach: | Approach | When to use | Pros | Cons | | --- | --- | --- | --- | | **Sequential** | - Default browser workload
- Small batch sizes
- Limited RAM on user devices | - Lower memory footprint
- Simpler code path
- Easy cleanup |- Slower total runtime
- UI can feel locked if not chunked
| | **Parallel** | - Big datasets
Enough resources in user devices
| - Higher throughput
- Can keep UI responsive
| - More memory consumption per tab
Coordination complexity
- Throttling risk | ## How To Batch Template Population For this operation, you generate personalized outputs at scale by combining: - Templates - Structured data ### Set the Data Sources Batch workflows can use a variety of data sources to populate a template, such as: - CSV files with parsing libraries - JSON from REST APIs - Databases (SQL, NoSQL) - Stream data The following examples show how to set three different data sources: ```ts await fetch('path/to/dataset.json').then((r) => r.json()); ``` ```ts await fetch('https://api.example.com/dataset').then((r) => r.json()); ``` ```ts // Define key variables let textVariables = { first_name: '', last_name: '', address: '', city: '', }; ``` ### Update the Template You can automate template population, update media, and show conditional content based on data. Find some examples in existing guides: | Action | EngineAPI function | Related guide | | --- | --- | --- | | Set text variables | `engine.variable.setString(variableId, value)` | [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) | | Update image fills | `engine.block.setString(block, 'fill/image/imageFileURI', url)` | [Insert Images](https://img.ly/docs/cesdk/angular/insert-media/images-63848a/) | | Edit block properties | `engine.block.setFloat(block, key, value)` / `engine.block.setColor(block, key, color)` | [Apply Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) | ### Batch Export the Design The CE.SDK provides a set of format options when exporting the edited designs: | Format | EngineAPI function | Related guide | | --- | --- | --- | | PNG | `engine.block.export(block, 'image/png')` | [PNG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/) | | JPEG | `engine.block.export(block, 'image/jpeg', 0.95)` | [JPEG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-jpeg-6f88e9/) | | PDF | `engine.block.export(block, 'application/pdf')` | [PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) | | MP4 | `engine.block.exportVideo(block, MimeType.Mp4)` | [MP4](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-mp4-c998a8/) | Check all the export options in the [Export section](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/). ### Batch Thumbnail Generation from Static Scenes The export feature allows you to automate thumbnails generation by tweaking the format and the size of the design, for example: ```ts // Example: Real-time thumbnail generation const thumbnailEngine = await CreativeEngine.init({ container: null }); async function generateThumbnail(sceneData) { await thumbnailEngine.scene.loadFromString(sceneData); const page = thumbnailEngine.scene.getPages()[0]; // Generate small preview const thumbnail = await thumbnailEngine.block.export(page, 'image/jpeg', { targetWidth: 200, targetHeight: 200, quality: 0.7, }); return thumbnail; } ``` Read more about thumbnails generation in [the Engine guide](https://img.ly/docs/cesdk/angular/engine-interface-6fb7cf/). The CE.SDK also provides over 20 pre-designed text layouts to apply on thumbnails. Check the [relevant guide](https://img.ly/docs/cesdk/angular/text/text-designs-a1b2c3/) to use them. ### Batch Thumbnail Generation from Video Scenes Extract representative frames from videos efficiently, and automate this action using the dedicated CE.SDK features: | Action | EngineAPI function | Related guide | | --- | --- | --- | | Load video source | `engine.scene.createFromVideo()` | [Create from Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) | | Seek to timestamp | `engine.block.setPlaybackTime()` | [Control Audio and Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) | | Export single frame | `engine.block.export(block, options)` | [To PNG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/)
[Text Designs](https://img.ly/docs/cesdk/angular/text/text-designs-a1b2c3/) | | Generate sequence thumbnails | `engine.block.generateVideoThumbnailSequence()` | [Trim Video Clips](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) | | Size thumbnails consistently | `targetWidth / targetHeight` export options | [To PNG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/) | The following code shows how to **generate thumbnails from a video**: ```ts import CreativeEngine from '@cesdk/engine'; const engine = await CreativeEngine.init({ license: LICENSE_KEY }); await engine.scene.loadFromURL('/assets/video-scene.scene'); const [page] = engine.scene.getPages(); const videoBlock = engine.block .getChildren(page) .find((child) => engine.block.getType(child) === 'video'); if (videoBlock) { const videoFill = engine.block.getFill(videoBlock); await engine.block.setPlaybackTime(videoFill, 4.2); const thumbnail = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 640, targetHeight: 360 }); await downloadBlob(thumbnail, 'scene-thumb.png'); } engine.dispose(); ``` The preceding code: 1. Loads a scene containing a video. 2. Seeks to 4.2 s. 3. Exports the page as a PNG. 4. Saves the thumbnail. ## Optimize Memory Usage Every export produces and accumulates: - Blobs - URLs - Engine state Proper **cleanup** ensures batch processes complete without resource exhaustion. Without proper cleanup, the browser might: - Hits memory ceiling. - Crash. - Slow down. Consider the following actions to **avoid exhausting the client**: | Strategy | Code | | --- | --- | | Revoke blob URLs immediately after use | `URL.revokeObjectURL()` | | Dispose engine instances when finished | `engine.dispose()` | | Chunk large datasets into smaller batches | | | Consider garbage collection timing | | Treat cleanup as part of **each loop** iteration, by either: - Freeing resources **after each item**. - Chunking resources, by loading smaller parts of your datasets at a time. > **Note:** To **handle large batches**, consider the following workflows:- Split into smaller chunks. > - Log progress. > - Monitor status. ## Apply Error Handling Batch runs often work with **large records of data**. Some factors can make the job crash, such as: - A malformed asset - Timeouts When your job encounters one of these errors, you can proactively **avoid the job’s failure** using the following patterns: - Catch errors inside each loop iteration. - Log failing records so you can retry them later. - Decide whether to keep going or stop when an error happens. - Collect a summary of all failures for post-run review. For example, the preceding code to generate thumbnails now handles errors gracefully to avoid crashes: ```ts import CreativeEngine from '@cesdk/engine'; let engine; try { engine = await CreativeEngine.init({ license: LICENSE_KEY }); await engine.scene.loadFromURL('/assets/video-scene.scene'); const [page] = engine.scene.getPages(); if (!page) throw new Error('Scene has no pages.'); const videoBlock = engine.block .getChildren(page) .find((child) => engine.block.getType(child) === 'video'); if (!videoBlock) throw new Error('No video block found.'); const videoFill = engine.block.getFill(videoBlock); if (!videoFill) throw new Error('Video block is missing its fill.'); await engine.block.setPlaybackTime(videoFill, 4.2); const thumbnail = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 640, targetHeight: 360 }); await downloadBlob(thumbnail, 'scene-thumb.png'); } catch (error) { console.error('Failed to generate thumbnail', error); } finally { engine?.dispose(); } ``` ### Use Retry Logic Some errors are temporary due to factors such as: - Network hiccup - Rate limits - Busy CDN To avoid saturating the related service, you can use smart retries after a short delay. If the error persist: 1. Double the delay. 2. Retry 3. Double again the delay exponentially after each retry. This strategy allows you to identify temporary failures that could be resolved later. For **API failures**, consider using circuit breaking patterns that: - Pause the calls on repeated errors. - Test again after a delay. ### Check the Input Data Before Processing Lightweight checks can help you with: - Catching bad inputs early. - Preventing waste of time and compute on batches that’ll fail. Add checks **before**: - Launching the CE.SDK. - Loading scenes. - Exporting large scenes. The following table contains some checks **examples**: | Check | Example | | --- | --- | | Check input data structure | `if (!isValidRecord(record)) throw new Error('Invalid payload');` | | Check file existence and accessibility | `await fs.promises.access(filePath, fs.constants.R_OK);` | | Verify templates load correctly | `await engine.scene.loadFromURL(templateUrl);` | | Use dry-run mode for testing | `if (options.dryRun) return simulate(record);` | For example, the following **data validation function** checks: - The record type - The `id` - The HTTPS template URL - The presence of variants It throws descriptive errors if any of these elements are missing. ```ts function validateRecord(record) { if (typeof record !== 'object' || record === null) { throw new Error('Record must be an object'); } if (typeof record.id !== 'string') { throw new Error('Missing record id'); } if (!record.templateUrl?.startsWith('https://')) { throw new Error('Invalid template URL'); } if (!Array.isArray(record.variants) || record.variants.length === 0) { throw new Error('Record requires at least one variant'); } return true; } ``` ## Batch Process on Production When running on production, enhance browser-based batch processes with architecture and UX decisions that help the user run the workflow, such as: - **User-initiated batches**: keep work tied to explicit user actions; show confirmation dialogs for large jobs. - **Chunked processing**: split datasets into small slices (for example, 20 records) to avoid blocking the main thread. - **Resource caps**: document safe limits (for example, 50–100 exports per session) and enforce them in the UI. - **Persistence**: use `localStorage` or IndexedDB to cache progress so reloads can resume work. ### Monitor the Process Give users visibility inside the tab and send lightweight telemetry upstream. - Render UI elements that show the state, such as: - Progress bars - Per-item status chips - Send `fetch` calls to your backend for: - Error logs - Aggregated stats - When a chunk fails: 1. Show in-app notifications/snackbars. 2. Offer retries. For example, the following code: - Structures logging. - Renders it with timestamps. ```ts function reportBatchMetrics(batchMetrics) { const entry = { timestamp: new Date().toISOString(), ...batchMetrics, }; console.table([entry]); return fetch('/api/logs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entry), }); } ``` ## Troubleshooting | Issue | Cause | Solution | | -------------------------- | ------------------------------------------ | --------------------------------------------------- | | Out of memory errors | Blob URLs not revoked, engine not disposed | Call `URL.revokeObjectURL()` and `engine.dispose()` | | Slow processing speed | Template loaded each iteration | Load template once, modify variables only | | Items fail silently | Missing error handling | Wrap processing in try-catch blocks | | Inconsistent outputs | Shared state between iterations | Reset state or reload template each iteration | | Process hangs indefinitely | Uncaught promise rejection | Use error handling and timeouts | | Performance bottlenecks | Multiple | - Profile batch operations
- Identify slow operations
- Optimize export settings
- Reduce template complexity
| ### Debugging Strategies Effective troubleshooting techniques for batch processing in web apps include: - Retry with small batches. - Console log detailed error information. - Isolate problematic items. ## Next Steps - [Headless Mode](https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/) - Learn headless engine operation basics - [Design Generation](https://img.ly/docs/cesdk/angular/automation/design-generation-98a99e/) - Automate single design generation workflows - [Export Designs](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Deep dive into export options and formats - [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) - Work with dynamic text content in templates - [Source Sets](https://img.ly/docs/cesdk/angular/import-media/source-sets-5679c8/) - Specify assets sources for each block. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Data Merge" description: "Generate personalized designs from templates by merging external data using text variables and placeholder blocks" platform: angular url: "https://img.ly/docs/cesdk/angular/automation/data-merge-ae087c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) > [Data Merge](https://img.ly/docs/cesdk/angular/automation/data-merge-ae087c/) --- Generate personalized designs from a single template by merging external data into CE.SDK templates using text variables and placeholder blocks. ![Data Merge example showing personalized business card design](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-automation-data-merge-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-automation-data-merge-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-automation-data-merge-browser/) Data merge generates multiple personalized designs from a single template by replacing variable content with external data. Use it for certificates, badges, team cards, or any design requiring consistent layout with varying content. ```typescript file=@cesdk_web_examples/guides-automation-data-merge-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Data Merge Guide * * Demonstrates merging external data into templates: * - Setting text variables with engine.variable.setString() * - Finding variables with engine.variable.findAll() * - Finding blocks by name with engine.block.findByName() * - Updating image content in placeholder blocks * - Exporting personalized designs */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 400, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Sample data to merge into the template const sampleData = { name: 'Alex Smith', title: 'Creative Developer', email: 'alex.smith@example.com', photoUrl: 'https://img.ly/static/ubq_samples/sample_1.jpg' }; // Create a profile photo block with a semantic name const photoBlock = engine.block.create('graphic'); engine.block.setShape(photoBlock, engine.block.createShape('rect')); const photoFill = engine.block.createFill('image'); engine.block.setString( photoFill, 'fill/image/imageFileURI', sampleData.photoUrl ); engine.block.setFill(photoBlock, photoFill); engine.block.setWidth(photoBlock, 150); engine.block.setHeight(photoBlock, 150); engine.block.setPositionX(photoBlock, 50); engine.block.setPositionY(photoBlock, 125); engine.block.setName(photoBlock, 'profile-photo'); engine.block.appendChild(page, photoBlock); // Create a text block with variable placeholders const textBlock = engine.block.create('text'); const textContent = `{{name}} {{title}} {{email}}`; engine.block.replaceText(textBlock, textContent); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setFloat(textBlock, 'text/fontSize', 32); engine.block.setPositionX(textBlock, 230); engine.block.setPositionY(textBlock, 140); engine.block.appendChild(page, textBlock); // Set the variable values from data engine.variable.setString('name', sampleData.name); engine.variable.setString('title', sampleData.title); engine.variable.setString('email', sampleData.email); // Discover all variables in the scene const variables = engine.variable.findAll(); console.log('Variables in scene:', variables); // Check if the text block references any variables const hasVariables = engine.block.referencesAnyVariables(textBlock); console.log('Text block has variables:', hasVariables); // Find blocks by their semantic name const [foundPhotoBlock] = engine.block.findByName('profile-photo'); if (foundPhotoBlock) { console.log('Found profile-photo block:', foundPhotoBlock); // Update the image content const fill = engine.block.getFill(foundPhotoBlock); engine.block.setString( fill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); } // Export the personalized design const blob = await engine.block.export(page, { mimeType: 'image/png' }); console.log('Exported PNG blob:', blob.size, 'bytes'); // Create a download link for the exported image const url = URL.createObjectURL(blob); console.log('Download URL created:', url); // Select the text block to show the variable values engine.block.select(textBlock); console.log( 'Data merge guide initialized. Try changing variable values in the console.' ); } } export default Example; ``` This guide covers how to prepare templates with variables, set values from data, and export personalized designs. ## Initialize the Editor We start by initializing CE.SDK with a Design scene and setting up the page dimensions for our template. ```typescript highlight=highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 400, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; ``` ## Prepare Sample Data In a real application, data comes from a CSV file, database, or API. Here we define a sample record with the fields we want to merge into the template. ```typescript highlight=highlight-sample-data // Sample data to merge into the template const sampleData = { name: 'Alex Smith', title: 'Creative Developer', email: 'alex.smith@example.com', photoUrl: 'https://img.ly/static/ubq_samples/sample_1.jpg' }; ``` Each data record contains field names that map to template variables and placeholder blocks. ## Create Template Layout We build the template by creating blocks and assigning semantic names. The profile photo block uses `setName()` so we can find and update it later. ```typescript highlight=highlight-create-template // Create a profile photo block with a semantic name const photoBlock = engine.block.create('graphic'); engine.block.setShape(photoBlock, engine.block.createShape('rect')); const photoFill = engine.block.createFill('image'); engine.block.setString( photoFill, 'fill/image/imageFileURI', sampleData.photoUrl ); engine.block.setFill(photoBlock, photoFill); engine.block.setWidth(photoBlock, 150); engine.block.setHeight(photoBlock, 150); engine.block.setPositionX(photoBlock, 50); engine.block.setPositionY(photoBlock, 125); engine.block.setName(photoBlock, 'profile-photo'); engine.block.appendChild(page, photoBlock); ``` Using semantic names like `profile-photo` makes it easy to locate and modify blocks when processing different data records. ## Add Text with Variables Text variables use double curly brace syntax: `{{variableName}}`. We create a text block with variable placeholders for name, title, and email. ```typescript highlight=highlight-create-text-with-variables // Create a text block with variable placeholders const textBlock = engine.block.create('text'); const textContent = `{{name}} {{title}} {{email}}`; engine.block.replaceText(textBlock, textContent); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setFloat(textBlock, 'text/fontSize', 32); engine.block.setPositionX(textBlock, 230); engine.block.setPositionY(textBlock, 140); engine.block.appendChild(page, textBlock); ``` Variables in text blocks automatically display their values when set through the Variable API. ## Set Variable Values We use `engine.variable.setString()` to define the value for each variable. When a variable is set, all text blocks referencing that variable update automatically. ```typescript highlight=highlight-set-variables // Set the variable values from data engine.variable.setString('name', sampleData.name); engine.variable.setString('title', sampleData.title); engine.variable.setString('email', sampleData.email); ``` Variable values persist throughout the engine session. Setting a variable to a new value updates all references immediately. ## Discover Variables Use `engine.variable.findAll()` to discover which variables exist in the scene. Use `engine.block.referencesAnyVariables()` to check if a specific block contains variable references. ```typescript highlight=highlight-discover-variables // Discover all variables in the scene const variables = engine.variable.findAll(); console.log('Variables in scene:', variables); // Check if the text block references any variables const hasVariables = engine.block.referencesAnyVariables(textBlock); console.log('Text block has variables:', hasVariables); ``` This is useful when loading existing templates to determine which data fields are required. ## Find and Update Placeholder Blocks Use `engine.block.findByName()` to locate blocks by their semantic name. Once found, you can update properties like image content by modifying the fill URI. ```typescript highlight=highlight-find-by-name // Find blocks by their semantic name const [foundPhotoBlock] = engine.block.findByName('profile-photo'); if (foundPhotoBlock) { console.log('Found profile-photo block:', foundPhotoBlock); // Update the image content const fill = engine.block.getFill(foundPhotoBlock); engine.block.setString( fill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); } ``` This pattern works well for updating profile photos, logos, or other image placeholders in templates. ## Export the Design After merging data into the template, export the personalized design using `engine.block.export()`. ```typescript highlight=highlight-export // Export the personalized design const blob = await engine.block.export(page, { mimeType: 'image/png' }); console.log('Exported PNG blob:', blob.size, 'bytes'); // Create a download link for the exported image const url = URL.createObjectURL(blob); console.log('Download URL created:', url); ``` You can export to PNG, JPEG, WebP, or PDF formats. For batch processing, collect blobs in an array or write directly to a file system. ## Troubleshooting ### Variables Not Rendering If variable placeholders show instead of values: - Verify the variable name matches exactly (case-sensitive) - Use `engine.variable.findAll()` to check which variables are defined - Ensure `engine.variable.setString()` was called before rendering ### Block Not Found If `findByName()` returns an empty array: - Check the block name was set with `engine.block.setName()` - Verify the name string matches exactly (case-sensitive) - Ensure the block exists in the current scene ### Image Not Updating If placeholder images don't update: - Get the fill block first with `engine.block.getFill()` - Use the correct property path: `fill/image/imageFileURI` - Verify the image URL is accessible and valid ## API Reference | Method | Description | |--------|-------------| | `engine.variable.setString(name, value)` | Set a text variable's value | | `engine.variable.getString(name)` | Get a text variable's value | | `engine.variable.findAll()` | List all variable names in the scene | | `engine.variable.remove(name)` | Remove a variable | | `engine.block.findByName(name)` | Find blocks by their semantic name | | `engine.block.setName(block, name)` | Set a block's semantic name | | `engine.block.replaceText(block, text)` | Replace text content in a text block | | `engine.block.referencesAnyVariables(block)` | Check if block contains variable references | | `engine.block.getFill(block)` | Get the fill block of a design block | | `engine.block.setString(block, property, value)` | Set a string property value | | `engine.block.export(block, options)` | Export a block to an image format | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Automate Design Generation" description: "Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API." platform: angular url: "https://img.ly/docs/cesdk/angular/automation/design-generation-98a99e/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) > [Design Generation](https://img.ly/docs/cesdk/angular/automation/design-generation-98a99e/) --- Automating design generation simplifies workflows and allows you to create dynamic, personalized designs at scale. By combining design templates with external data or user-provided input, you can quickly generate professional outputs for various use cases, from banner ads to direct mail. With IMG.LY, you can use templates to define dynamic elements such as text, images, or other assets. These elements are populated with real-time data or user inputs during the generation process. This guide will walk you through the process of using the CE.SDK for programmatic design generation. [Launch Web Demo](https://img.ly/showcases/cesdk/headless-design/web) ## Populating a Template A design template is a pre-configured layout that includes placeholders for dynamic elements such as text, images, or other assets. These placeholders define where and how specific content will appear in the final design. During the generation process, the placeholders are replaced with actual data to create a completed output. - **Creating or Editing Templates:** Design templates can be created or edited directly within the CE.SDK using our UI or programmatically. Learn more in the [Create Templates guide](https://img.ly/docs/cesdk/angular/create-templates-3aef79/). - **Dynamic Content Sources:** Templates can be populated with data from various sources, such as: - **JSON files:** Useful for batch operations where data is pre-prepared. - **External APIs:** Ideal for real-time updates and dynamic integrations. - **User Input:** Data provided directly by the user through a UI. For detailed information on using and managing templates, see [Use Templates](https://img.ly/docs/cesdk/angular/use-templates/overview-ae74e1/). Below is a diagram illustrating how data is merged into a template to produce a final design: ![Template data merge process diagram showing how variables and assets flow into the final output](./assets/schema.excalidraw.svg) ## Example Workflow ### 1. Prepare the Template Start by designing a template with text variables. Here's an example postcard template with placeholders for the recipient's details: ![Example postcard template with highlighted variable placeholders for name and address](./assets/scene-example-backside.png) ### 2. Load the Template into the Editor Initialize the CE.SDK and load your prepared template: ```ts example=basic-scene marker=cesdk-init-after // Load a template from your server or a CDN const sceneUrl = 'https://cdn.img.ly/assets/demo/v4/ly.img.template/templates/cesdk_postcard_2.scene'; await engine.scene.loadFromURL(sceneUrl); ``` ### 3. Provide Data to Populate the Template Populate your template with data from your chosen source: ```ts example=basic-scene marker=cesdk-init-after // Option 1: Prepare your data as a javascript object const data = { textVariables: { first_name: 'John', last_name: 'Doe', address: '123 Main St.', city: 'Anytown', }, }; // Option 2: Fetch from an API // const data = await fetch('https://api.example.com/design-data').then(res => res.json()); engine.variable.setString('first_name', data.textVariables.first_name); engine.variable.setString('last_name', data.textVariables.last_name); engine.variable.setString('address', data.textVariables.address); engine.variable.setString('city', data.textVariables.city); ``` ### 4. Export the Final Design Once the template is populated, export the final design in your preferred format: ```ts example=basic-scene marker=cesdk-init-after const output = await engine.block.export(engine.scene.get(), { mimeType: 'application/pdf', }); // Success: 'output' contains your generated design as a PDF Blob // You can now save it or display it in your application ``` Here's what your final output should look like: ![Exported postcard design showing populated name and address fields](./assets/scene-example-backside-export.png) Need help with exports? Check out the [Export Guide](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) for detailed instructions and options. ## Troubleshooting If you encounter issues during the generation process: - Verify that all your variable names exactly match those in your template - Ensure your template is accessible from the provided URL - Check that your data values are in the correct format (strings for text variables) - Monitor the console for detailed error messages from the CE.SDK --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale." platform: angular url: "https://img.ly/docs/cesdk/angular/automation/overview-34d971/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) > [Overview](https://img.ly/docs/cesdk/angular/automation/overview-34d971/) --- ### Output Formats --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Browser Support" description: "Find out which browsers and versions fully support CE.SDK features, including editing and video capabilities." platform: angular url: "https://img.ly/docs/cesdk/angular/browser-support-28c1b0/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/angular/compatibility-fef719/) > [Browser Support](https://img.ly/docs/cesdk/angular/browser-support-28c1b0/) --- The CreativeEditor SDK requires specific APIs to fully function. For video-related features, the required APIs are only supported in certain browsers. As a result, the list of supported browsers is currently limited to the following: | Supported Browser | Graphics Editing | Video Editing | Video Export | | ----------------- | --------------------------------------------- | ----------------- | ----------------- | | Chrome | **114** or newer | **114** or newer | **114** or newer | | Chrome Android | **114** or newer | not supported | not supported | | Chrome iOS | **114** or newer (on iOS/iPadOS 15 or newer) | not supported | not supported | | Edge | **114** or newer | **114** or newer | **114** or newer | | Firefox | **115** or newer | **130** or newer | not supported | | Safari | **15.6** or newer | **26.0** or newer | **26.0** or newer | | Safari iOS | **15.6** or newer (on iOS/iPadOS 15 or newer) | not supported | not supported | **Note:** Firefox supports video editing (decoding) starting with version 130 via the WebCodecs API. However, video export (encoding) is not supported because Firefox does not include the patent-encumbered H.264 and AAC codecs required for video encoding. For video features, CE.SDK automatically shows warning dialogs when unsupported browsers try to use video functionality. You can also detect video support programmatically using the `video.decode.checkSupport` and `video.encode.checkSupport` actions, or the silent `cesdk.utils.supportsVideoDecode()` and `cesdk.utils.supportsVideoEncode()` utilities. See the [Actions API](https://img.ly/docs/cesdk/angular/actions-6ch24x/) for implementation details. While other browsers based on the Chromium project might work fine (Arc, Brave, Opera, Vivaldi etc.) they are not officially supported. ## Host Platform Restrictions All supported browsers rely on the host's platform APIs for different kind of functionality (e.g. video support). Check our [known editor limitations](https://img.ly/docs/cesdk/angular/compatibility-139ef9/) for more details on these. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Colors" description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats." platform: angular url: "https://img.ly/docs/cesdk/angular/colors-a9b79c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/colors/overview-16a177/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats. - [Color Basics](https://img.ly/docs/cesdk/angular/colors/basics-307115/) - Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows. - [For Print](https://img.ly/docs/cesdk/angular/colors/for-print-59bc05/) - Use print-ready color models and settings for professional-quality, production-ready exports. - [For Screen](https://img.ly/docs/cesdk/angular/colors/for-screen-1911f8/) - Documentation for For Screen - [Apply Colors](https://img.ly/docs/cesdk/angular/colors/apply-2211e3/) - Apply solid colors to shapes, backgrounds, and other design elements. - [Create a Color Palette](https://img.ly/docs/cesdk/angular/colors/create-color-palette-7012e0/) - Build reusable color palettes to maintain consistency and streamline user choices. - [Replace Individual Colors](https://img.ly/docs/cesdk/angular/colors/replace-48cd71/) - Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects to swap colors or remove backgrounds. - [Adjust Colors](https://img.ly/docs/cesdk/angular/colors/adjust-590d1e/) - Fine-tune images and design elements by adjusting brightness, contrast, saturation, exposure, and other color properties using CE.SDK's adjustments effect system. - [Color Conversion](https://img.ly/docs/cesdk/angular/colors/conversion-bcd82b/) - Learn how to convert colors between color spaces in CE.SDK. Convert sRGB, CMYK, and spot colors programmatically for screen display or print workflows. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Adjust Colors" description: "Fine-tune images and design elements by adjusting brightness, contrast, saturation, exposure, and other color properties using CE.SDK's adjustments effect system." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/adjust-590d1e/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Adjust Colors](https://img.ly/docs/cesdk/angular/colors/adjust-590d1e/) --- Fine-tune images and design elements using CE.SDK's color adjustments system to control brightness, contrast, saturation, and other visual properties. ![Adjust Colors example showing images with various color adjustments applied](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-adjust-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-adjust-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-adjust-browser/) Color adjustments allow you to modify the visual appearance of images and graphics by changing properties like brightness, contrast, saturation, and color temperature. CE.SDK implements color adjustments as an "adjustments" effect type that you can apply to compatible blocks. ```typescript file=@cesdk_web_examples/guides-colors-adjust-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Adjust Colors Guide * * Demonstrates how to adjust color properties of images and design elements: * - Creating adjustments effects * - Setting brightness, contrast, saturation, and other properties * - Enabling/disabling adjustments * - Reading adjustment values * - Applying different adjustment styles */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); // Enable adjustments in the inspector panel cesdk.feature.enable('ly.img.adjustment'); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Create a sample image to demonstrate color adjustments const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Check if a block supports effects before applying adjustments const imageBlock = await engine.block.addImage(imageUri, { size: { width: 400, height: 300 } }); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); const supportsEffects = engine.block.supportsEffects(imageBlock); console.log('Block supports effects:', supportsEffects); // Create an adjustments effect const adjustmentsEffect = engine.block.createEffect('adjustments'); // Attach the adjustments effect to the image block engine.block.appendEffect(imageBlock, adjustmentsEffect); // Set brightness - positive values lighten, negative values darken engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.4 ); // Set contrast - increases or decreases tonal range engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.35 ); // Set saturation - increases or decreases color intensity engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.5 ); // Set temperature - positive for warmer, negative for cooler tones engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/temperature', 0.25 ); // Read current adjustment values const brightness = engine.block.getFloat( adjustmentsEffect, 'effect/adjustments/brightness' ); console.log('Current brightness:', brightness); // Discover all available adjustment properties const allProperties = engine.block.findAllProperties(adjustmentsEffect); console.log('Available adjustment properties:', allProperties); // Disable adjustments temporarily (effect remains attached) engine.block.setEffectEnabled(adjustmentsEffect, false); console.log( 'Adjustments enabled:', engine.block.isEffectEnabled(adjustmentsEffect) ); // Re-enable adjustments engine.block.setEffectEnabled(adjustmentsEffect, true); // Create a second image to demonstrate a different adjustment style const secondImageBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 150 } }); engine.block.appendChild(page, secondImageBlock); engine.block.setPositionX(secondImageBlock, 50); engine.block.setPositionY(secondImageBlock, 50); // Apply a contrasting style: darker, high contrast, desaturated (moody look) const combinedAdjustments = engine.block.createEffect('adjustments'); engine.block.appendEffect(secondImageBlock, combinedAdjustments); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/brightness', -0.15 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/contrast', 0.4 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/saturation', -0.3 ); // List all effects on the block const effects = engine.block.getEffects(secondImageBlock); console.log('Effects on second image:', effects.length); // Demonstrate removing an effect const tempBlock = await engine.block.addImage(imageUri, { size: { width: 150, height: 100 } }); engine.block.appendChild(page, tempBlock); engine.block.setPositionX(tempBlock, 550); engine.block.setPositionY(tempBlock, 50); const tempEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(tempBlock, tempEffect); engine.block.setFloat(tempEffect, 'effect/adjustments/brightness', 0.5); // Remove the effect by index const tempEffects = engine.block.getEffects(tempBlock); const effectIndex = tempEffects.indexOf(tempEffect); if (effectIndex !== -1) { engine.block.removeEffect(tempBlock, effectIndex); } // Destroy the removed effect to free memory engine.block.destroy(tempEffect); // Add refinement adjustments to demonstrate subtle enhancement properties const refinementEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(tempBlock, refinementEffect); // Sharpness - enhances edge definition engine.block.setFloat( refinementEffect, 'effect/adjustments/sharpness', 0.4 ); // Clarity - increases mid-tone contrast for more detail engine.block.setFloat(refinementEffect, 'effect/adjustments/clarity', 0.35); // Highlights - adjusts bright areas engine.block.setFloat( refinementEffect, 'effect/adjustments/highlights', -0.2 ); // Shadows - adjusts dark areas engine.block.setFloat(refinementEffect, 'effect/adjustments/shadows', 0.3); // Select the main image block to show adjustments panel engine.block.select(imageBlock); console.log( 'Color adjustments guide initialized. Select an image to see the adjustments panel.' ); } } export default Example; ``` This guide covers how to use the built-in adjustments UI panel and how to apply color adjustments programmatically using the block API. ## Using the Built-in Adjustments UI CE.SDK provides a built-in adjustments panel that allows users to modify color properties interactively. Users can access this panel by selecting an image or graphic block in the editor. ### Enable Adjustments Features To give users access to adjustments in the inspector panel, we enable the adjustments feature using CE.SDK's Feature API. ```typescript highlight=highlight-feature-enable // Enable adjustments in the inspector panel cesdk.feature.enable('ly.img.adjustment'); ``` With adjustments enabled, users can: - **Adjust sliders** for brightness, contrast, saturation, exposure, and more - **See real-time preview** of changes as they adjust values - **Reset adjustments** individually or all at once to restore defaults ## Programmatic Color Adjustments For applications that need to apply adjustments programmatically—whether for automation, batch processing, or dynamic user experiences—we use the block API. ### Check Block Compatibility Before applying adjustments, we verify the block supports effects. Not all block types support adjustments—for example, page blocks don't support effects directly, but image and graphic blocks do. ```typescript highlight=highlight-check-support // Check if a block supports effects before applying adjustments const imageBlock = await engine.block.addImage(imageUri, { size: { width: 400, height: 300 } }); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); const supportsEffects = engine.block.supportsEffects(imageBlock); console.log('Block supports effects:', supportsEffects); ``` ### Create and Apply Adjustments Effect Once we've confirmed a block supports effects, we create an adjustments effect and attach it to the block using `appendEffect()`. ```typescript highlight=highlight-create-adjustments // Create an adjustments effect const adjustmentsEffect = engine.block.createEffect('adjustments'); // Attach the adjustments effect to the image block engine.block.appendEffect(imageBlock, adjustmentsEffect); ``` Each block can have one adjustments effect in its effect stack. The adjustments effect provides access to all color adjustment properties through a single effect instance. ### Modify Adjustment Properties We set individual adjustment values using `setFloat()` with the effect block ID and property path. Each property uses the `effect/adjustments/` prefix followed by the property name. ```typescript highlight=highlight-set-properties // Set brightness - positive values lighten, negative values darken engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.4 ); // Set contrast - increases or decreases tonal range engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.35 ); // Set saturation - increases or decreases color intensity engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.5 ); // Set temperature - positive for warmer, negative for cooler tones engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/temperature', 0.25 ); ``` CE.SDK provides the following adjustment properties: | Property | Description | |----------|-------------| | `brightness` | Overall lightness—positive values lighten, negative values darken | | `contrast` | Tonal range—increases or decreases the difference between light and dark | | `saturation` | Color intensity—positive values increase vibrancy, negative values desaturate | | `exposure` | Exposure compensation—simulates camera exposure adjustments | | `gamma` | Gamma curve—adjusts midtone brightness | | `highlights` | Bright area intensity—controls the lightest parts of the image | | `shadows` | Dark area intensity—controls the darkest parts of the image | | `whites` | White point—adjusts the brightest pixels | | `blacks` | Black point—adjusts the darkest pixels | | `temperature` | Warm/cool color cast—positive for warmer, negative for cooler tones | | `sharpness` | Edge sharpness—enhances or softens edges | | `clarity` | Midtone contrast—increases local contrast for more definition | All properties accept float values. Experiment with different values to achieve the desired visual result. ### Read Adjustment Values We can read current adjustment values using `getFloat()` with the same property paths. Use `findAllProperties()` to discover all available properties on an adjustments effect. ```typescript highlight=highlight-read-values // Read current adjustment values const brightness = engine.block.getFloat( adjustmentsEffect, 'effect/adjustments/brightness' ); console.log('Current brightness:', brightness); // Discover all available adjustment properties const allProperties = engine.block.findAllProperties(adjustmentsEffect); console.log('Available adjustment properties:', allProperties); ``` This is useful for building custom UI controls or syncing adjustment values across your application. ### Enable and Disable Adjustments CE.SDK allows you to temporarily toggle adjustments on and off without removing them from the block. This is useful for before/after comparisons. ```typescript highlight=highlight-enable-disable // Disable adjustments temporarily (effect remains attached) engine.block.setEffectEnabled(adjustmentsEffect, false); console.log( 'Adjustments enabled:', engine.block.isEffectEnabled(adjustmentsEffect) ); // Re-enable adjustments engine.block.setEffectEnabled(adjustmentsEffect, true); ``` When you disable an adjustments effect, it remains attached to the block but won't be rendered until you enable it again. This preserves all adjustment values while giving you control over when adjustments are applied. ## Applying Different Adjustment Styles You can apply different adjustment combinations to create distinct visual styles. This example demonstrates a contrasting moody look using negative brightness, high contrast, and desaturation. ```typescript highlight=highlight-combine-effects // Create a second image to demonstrate a different adjustment style const secondImageBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 150 } }); engine.block.appendChild(page, secondImageBlock); engine.block.setPositionX(secondImageBlock, 50); engine.block.setPositionY(secondImageBlock, 50); // Apply a contrasting style: darker, high contrast, desaturated (moody look) const combinedAdjustments = engine.block.createEffect('adjustments'); engine.block.appendEffect(secondImageBlock, combinedAdjustments); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/brightness', -0.15 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/contrast', 0.4 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/saturation', -0.3 ); // List all effects on the block const effects = engine.block.getEffects(secondImageBlock); console.log('Effects on second image:', effects.length); ``` By combining different adjustment properties, you can create warm and vibrant looks, cool and desaturated styles, or high-contrast dramatic effects. ## Refinement Adjustments Beyond basic color corrections, CE.SDK provides refinement adjustments for fine-tuning image detail and tonal balance. ```typescript highlight=highlight-refinement-adjustments // Add refinement adjustments to demonstrate subtle enhancement properties const refinementEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(tempBlock, refinementEffect); // Sharpness - enhances edge definition engine.block.setFloat( refinementEffect, 'effect/adjustments/sharpness', 0.4 ); // Clarity - increases mid-tone contrast for more detail engine.block.setFloat(refinementEffect, 'effect/adjustments/clarity', 0.35); // Highlights - adjusts bright areas engine.block.setFloat( refinementEffect, 'effect/adjustments/highlights', -0.2 ); // Shadows - adjusts dark areas engine.block.setFloat(refinementEffect, 'effect/adjustments/shadows', 0.3); ``` Refinement properties include: - **Sharpness** - Enhances edge definition for crisper details - **Clarity** - Increases mid-tone contrast for more depth and definition - **Highlights** - Controls the intensity of bright areas - **Shadows** - Controls the intensity of dark areas These adjustments are particularly useful for enhancing photos or preparing images for print. ## Managing Adjustments ### Remove Adjustments When you no longer need adjustments, you can remove them from the effect stack and free resources. Always destroy effects that are no longer in use to prevent memory leaks. ```typescript highlight=highlight-remove-adjustments // Demonstrate removing an effect const tempBlock = await engine.block.addImage(imageUri, { size: { width: 150, height: 100 } }); engine.block.appendChild(page, tempBlock); engine.block.setPositionX(tempBlock, 550); engine.block.setPositionY(tempBlock, 50); const tempEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(tempBlock, tempEffect); engine.block.setFloat(tempEffect, 'effect/adjustments/brightness', 0.5); // Remove the effect by index const tempEffects = engine.block.getEffects(tempBlock); const effectIndex = tempEffects.indexOf(tempEffect); if (effectIndex !== -1) { engine.block.removeEffect(tempBlock, effectIndex); } // Destroy the removed effect to free memory engine.block.destroy(tempEffect); ``` The `removeEffect()` method takes an index position. After removal, destroy the effect instance to ensure proper cleanup. ### Reset Adjustments To reset all adjustments to their default values, you can either: - Set each property to `0.0` individually using `setFloat()` - Remove the adjustments effect and create a new one For most cases, setting properties to `0.0` is more efficient than recreating the effect. ## Troubleshooting ### Adjustments Not Visible If adjustments don't appear after applying them: - Verify the block supports effects using `supportsEffects()` - Check that the effect is enabled with `isEffectEnabled()` - Ensure the adjustments effect was appended to the block, not just created - Confirm adjustment values are non-zero ### Unexpected Results If adjustments produce unexpected visual results: - Check the effect stack order—adjustments applied before or after other effects may produce different results - Verify property paths include the `effect/adjustments/` prefix - Use `findAllProperties()` to verify correct property names ### Property Not Found If you encounter property not found errors: - Use `findAllProperties()` to list all available properties - Ensure property paths use the correct `effect/adjustments/` prefix format ## API Reference | Method | Description | |--------|-------------| | `block.supportsEffects(block)` | Check if a block supports effects | | `block.createEffect('adjustments')` | Create an adjustments effect | | `block.appendEffect(block, effect)` | Add effect to the end of the effect stack | | `block.insertEffect(block, effect, index)` | Insert effect at a specific position | | `block.getEffects(block)` | Get all effects applied to a block | | `block.removeEffect(block, index)` | Remove effect at the specified index | | `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `block.isEffectEnabled(effect)` | Check if an effect is enabled | | `block.setFloat(effect, property, value)` | Set a float property value | | `block.getFloat(effect, property)` | Get a float property value | | `block.findAllProperties(effect)` | List all properties of an effect | | `block.destroy(effect)` | Destroy an effect and free resources | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Apply Colors" description: "Apply solid colors to shapes, backgrounds, and other design elements." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/apply-2211e3/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Apply Color](https://img.ly/docs/cesdk/angular/colors/apply-2211e3/) --- Apply solid colors to design elements like shapes, text, and backgrounds using CE.SDK's color system with support for RGB, CMYK, and spot colors. ![Apply Colors example showing a block with fill, stroke, and shadow colors applied](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-apply-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-apply-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-apply-browser/) Colors in CE.SDK are applied to block properties like fill, stroke, and shadow using `engine.block.setColor()`. The engine supports three color spaces: sRGB for screen display, CMYK for print production, and spot colors for specialized printing requirements. ```typescript file=@cesdk_web_examples/guides-colors-apply-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext, RGBAColor } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Apply Colors Guide * * Demonstrates how to apply solid colors to design elements: * - Creating color objects in RGB, CMYK, and spot color spaces * - Applying colors to fill, stroke, and shadow properties * - Defining and managing spot colors * - Converting colors between color spaces */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Create a graphic block to apply colors to const block = engine.block.create('graphic'); engine.block.setShape(block, engine.block.createShape('rect')); engine.block.setFill(block, engine.block.createFill('color')); engine.block.setWidth(block, 200); engine.block.setHeight(block, 150); engine.block.setPositionX(block, 100); engine.block.setPositionY(block, 100); engine.block.appendChild(page, block); // Create RGB color (values 0.0-1.0) const rgbaBlue: RGBAColor = { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }; // Create CMYK color (cyan, magenta, yellow, black, tint) const cmykRed = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }; // Create spot color reference const spotPink = { name: 'Pink-Flamingo', tint: 1.0, externalReference: 'Pantone' }; // Define spot colors with screen preview approximations engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.41, 0.71); engine.editor.setSpotColorCMYK('Corporate-Blue', 1.0, 0.5, 0.0, 0.2); // Apply RGB color to fill const fill = engine.block.getFill(block); engine.block.setColor(fill, 'fill/color/value', rgbaBlue); // Read the current fill color const currentFillColor = engine.block.getColor(fill, 'fill/color/value'); console.log('Current fill color:', currentFillColor); // Enable and apply stroke color engine.block.setStrokeEnabled(block, true); engine.block.setStrokeWidth(block, 4); engine.block.setColor(block, 'stroke/color', cmykRed); // Enable and apply drop shadow color engine.block.setDropShadowEnabled(block, true); engine.block.setDropShadowOffsetX(block, 5); engine.block.setDropShadowOffsetY(block, 5); engine.block.setColor(block, 'dropShadow/color', spotPink); // Convert colors between color spaces const cmykFromRgb = engine.editor.convertColorToColorSpace( rgbaBlue, 'CMYK' ); console.log('CMYK from RGB:', cmykFromRgb); const rgbFromCmyk = engine.editor.convertColorToColorSpace(cmykRed, 'sRGB'); console.log('RGB from CMYK:', rgbFromCmyk); // List all defined spot colors const allSpotColors = engine.editor.findAllSpotColors(); console.log('Defined spot colors:', allSpotColors); // Update a spot color definition engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.6, 0.8); console.log('Updated Pink-Flamingo spot color'); // Remove a spot color definition (falls back to magenta) engine.editor.removeSpotColor('Corporate-Blue'); console.log('Removed Corporate-Blue spot color'); // Select the block to show in the editor engine.block.select(block); console.log('Apply colors guide initialized.'); } } export default Example; ``` This guide covers how to create color objects in different color spaces, apply colors to fill, stroke, and shadow properties, work with spot colors including defining and managing them, and convert colors between color spaces. ## Create Color Objects CE.SDK represents colors as JavaScript objects with properties specific to each color space. We create color objects that match our target output—RGB for screens, CMYK for print, or spot colors for precise color matching. ```typescript highlight=highlight-create-colors // Create RGB color (values 0.0-1.0) const rgbaBlue: RGBAColor = { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }; // Create CMYK color (cyan, magenta, yellow, black, tint) const cmykRed = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }; // Create spot color reference const spotPink = { name: 'Pink-Flamingo', tint: 1.0, externalReference: 'Pantone' }; ``` RGB colors use `{ r, g, b, a }` with values from 0.0 to 1.0 for each channel, where `a` is alpha (opacity). CMYK colors use `{ c, m, y, k, tint }` where tint controls the overall intensity. Spot colors use `{ name, tint, externalReference }` to reference a defined spot color by name. ## Define Spot Colors Before applying a spot color, we must define its screen preview approximation. The engine needs to know how to display the color since spot colors represent inks that can't be directly rendered on screens. ```typescript highlight=highlight-define-spot // Define spot colors with screen preview approximations engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.41, 0.71); engine.editor.setSpotColorCMYK('Corporate-Blue', 1.0, 0.5, 0.0, 0.2); ``` Use `engine.editor.setSpotColorRGB()` to define the RGB approximation with red, green, and blue values from 0.0 to 1.0. Use `engine.editor.setSpotColorCMYK()` for the CMYK approximation with cyan, magenta, yellow, black, and tint values. A spot color can have both RGB and CMYK approximations defined. ## Apply Fill Colors To set a block's fill color, we first get the fill block using `engine.block.getFill()`, then apply the color using `engine.block.setColor()` with the `'fill/color/value'` property. ```typescript highlight=highlight-apply-fill // Apply RGB color to fill const fill = engine.block.getFill(block); engine.block.setColor(fill, 'fill/color/value', rgbaBlue); // Read the current fill color const currentFillColor = engine.block.getColor(fill, 'fill/color/value'); console.log('Current fill color:', currentFillColor); ``` The fill block is a separate entity from the design block. We can read the current color using `engine.block.getColor()` with the same property path. ## Apply Stroke Colors Stroke colors are applied directly to the design block using the `'stroke/color'` property. We enable the stroke first using `engine.block.setStrokeEnabled()`. ```typescript highlight=highlight-apply-stroke // Enable and apply stroke color engine.block.setStrokeEnabled(block, true); engine.block.setStrokeWidth(block, 4); engine.block.setColor(block, 'stroke/color', cmykRed); ``` The stroke renders around the edges of the block with the specified color. Set the stroke width using `engine.block.setStrokeWidth()` to control the line thickness. ## Apply Shadow Colors Drop shadow colors use the `'dropShadow/color'` property on the design block. Enable shadows first using `engine.block.setDropShadowEnabled()`. ```typescript highlight=highlight-apply-shadow // Enable and apply drop shadow color engine.block.setDropShadowEnabled(block, true); engine.block.setDropShadowOffsetX(block, 5); engine.block.setDropShadowOffsetY(block, 5); engine.block.setColor(block, 'dropShadow/color', spotPink); ``` Control the shadow position using `setDropShadowOffsetX()` and `setDropShadowOffsetY()`. Spot colors work with shadows just like RGB or CMYK colors. ## Convert Between Color Spaces Use `engine.editor.convertColorToColorSpace()` to convert any color to a different color space. This is useful when you need to output designs in a specific color format. ```typescript highlight=highlight-convert-color // Convert colors between color spaces const cmykFromRgb = engine.editor.convertColorToColorSpace( rgbaBlue, 'CMYK' ); console.log('CMYK from RGB:', cmykFromRgb); const rgbFromCmyk = engine.editor.convertColorToColorSpace(cmykRed, 'sRGB'); console.log('RGB from CMYK:', rgbFromCmyk); ``` Pass the source color object and target color space (`'sRGB'` or `'CMYK'`). Spot colors convert to their defined approximation in the target space. Note that color conversions are approximations—CMYK has a smaller color gamut than sRGB. ## List Defined Spot Colors Query all spot colors currently defined in the editor using `engine.editor.findAllSpotColors()`. This returns an array of spot color names. ```typescript highlight=highlight-list-spot // List all defined spot colors const allSpotColors = engine.editor.findAllSpotColors(); console.log('Defined spot colors:', allSpotColors); ``` This is useful for building color pickers or validating that required spot colors are defined before export. ## Update Spot Color Definitions Redefine a spot color's approximation by calling `setSpotColorRGB()` or `setSpotColorCMYK()` with the same name. All blocks using that spot color automatically update their rendered appearance. ```typescript highlight=highlight-update-spot // Update a spot color definition engine.editor.setSpotColorRGB('Pink-Flamingo', 1.0, 0.6, 0.8); console.log('Updated Pink-Flamingo spot color'); ``` This allows you to adjust how spot colors appear on screen without modifying every block that uses them. ## Remove Spot Color Definitions Remove a spot color definition using `engine.editor.removeSpotColor()`. Blocks still referencing that color fall back to the default magenta approximation. ```typescript highlight=highlight-remove-spot // Remove a spot color definition (falls back to magenta) engine.editor.removeSpotColor('Corporate-Blue'); console.log('Removed Corporate-Blue spot color'); ``` This is useful when cleaning up unused spot colors or when you need to signal that a spot color is no longer valid. ## Troubleshooting ### Spot Color Appears Magenta The spot color wasn't defined before use. Call `setSpotColorRGB()` or `setSpotColorCMYK()` with the exact spot color name before applying it to blocks. ### Stroke or Shadow Color Not Visible The effect isn't enabled. Call `setStrokeEnabled(block, true)` or `setDropShadowEnabled(block, true)` before setting the color. ### Color Looks Different After Conversion Color space conversions are approximations. CMYK has a smaller gamut than sRGB, so vibrant colors may appear muted after conversion. ### Can't Apply Color to Fill Apply colors to the fill block obtained from `getFill()`, not the parent design block. The fill is a separate entity with its own color property. ## API Reference | Method | Description | |--------|-------------| | `block.setColor(block, property, color)` | Set a color property on a block | | `block.getColor(block, property)` | Get a color property from a block | | `block.getFill(block)` | Get the fill block of a design block | | `block.setStrokeEnabled(block, enabled)` | Enable or disable stroke on a block | | `block.setDropShadowEnabled(block, enabled)` | Enable or disable drop shadow on a block | | `editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with RGB approximation | | `editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with CMYK approximation | | `editor.findAllSpotColors()` | List all defined spot colors | | `editor.removeSpotColor(name)` | Remove a spot color definition | | `editor.convertColorToColorSpace(color, colorSpace)` | Convert a color to a different color space | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Color Basics" description: "Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/basics-307115/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Basics](https://img.ly/docs/cesdk/angular/colors/basics-307115/) --- Understand the three color spaces in CE.SDK and when to use each for screen or print workflows. ![Color Basics example showing three colored blocks representing sRGB, CMYK, and Spot color spaces](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-basics-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-basics-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-basics-browser/) CE.SDK supports three color spaces: **sRGB** for screen display, **CMYK** for print workflows, and **Spot Color** for specialized printing. Each color space serves different output types and has its own object format for the `setColor()` API. ```typescript file=@cesdk_web_examples/guides-colors-basics-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable spot color feature for the UI cesdk.feature.enable('ly.img.spotColor'); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Calculate block sizes for three columns const margin = 40; const spacing = 30; const availableWidth = pageWidth - 2 * margin - 2 * spacing; const blockWidth = availableWidth / 3; const blockHeight = pageHeight - 2 * margin - 80; // Leave space for labels // Define a spot color with RGB approximation for screen preview engine.editor.setSpotColorRGB('MyBrand Red', 0.95, 0.25, 0.21); // Create three blocks to demonstrate each color space // Block 1: sRGB color (for screen display) const srgbBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( srgbBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color'); // Set fill color using RGBAColor object (values 0.0-1.0) engine.block.setColor(srgbFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); engine.block.setFill(srgbBlock, srgbFill); engine.block.setWidth(srgbBlock, blockWidth); engine.block.setHeight(srgbBlock, blockHeight); engine.block.appendChild(page, srgbBlock); // Block 2: CMYK color (for print workflows) const cmykBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( cmykBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color'); // Set fill color using CMYKColor object (values 0.0-1.0, tint controls opacity) engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, m: 0.8, y: 0.95, k: 0.0, tint: 1.0 }); engine.block.setFill(cmykBlock, cmykFill); engine.block.setWidth(cmykBlock, blockWidth); engine.block.setHeight(cmykBlock, blockHeight); engine.block.appendChild(page, cmykBlock); // Block 3: Spot color (for specialized printing) const spotBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( spotBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const spotFill = engine.block.createFill('//ly.img.ubq/fill/color'); // Set fill color using SpotColor object (references the defined spot color) engine.block.setColor(spotFill, 'fill/color/value', { name: 'MyBrand Red', tint: 1.0, externalReference: '' }); engine.block.setFill(spotBlock, spotFill); engine.block.setWidth(spotBlock, blockWidth); engine.block.setHeight(spotBlock, blockHeight); engine.block.appendChild(page, spotBlock); // Add strokes to demonstrate stroke color property engine.block.setStrokeEnabled(srgbBlock, true); engine.block.setStrokeWidth(srgbBlock, 4); engine.block.setColor(srgbBlock, 'stroke/color', { r: 0.1, g: 0.2, b: 0.5, a: 1.0 }); engine.block.setStrokeEnabled(cmykBlock, true); engine.block.setStrokeWidth(cmykBlock, 4); engine.block.setColor(cmykBlock, 'stroke/color', { c: 0.0, m: 0.5, y: 0.6, k: 0.2, tint: 1.0 }); engine.block.setStrokeEnabled(spotBlock, true); engine.block.setStrokeWidth(spotBlock, 4); engine.block.setColor(spotBlock, 'stroke/color', { name: 'MyBrand Red', tint: 0.7, externalReference: '' }); // Create labels for each color space const labelY = margin + blockHeight + 20; const fontSize = 24; const labels = [ { text: 'sRGB', x: margin + blockWidth / 2 }, { text: 'CMYK', x: margin + blockWidth + spacing + blockWidth / 2 }, { text: 'Spot Color', x: margin + 2 * (blockWidth + spacing) + blockWidth / 2 } ]; for (const label of labels) { const textBlock = engine.block.create('//ly.img.ubq/text'); engine.block.replaceText(textBlock, label.text); engine.block.setTextFontSize(textBlock, fontSize); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.appendChild(page, textBlock); // Center the label below each block const textWidth = engine.block.getWidth(textBlock); engine.block.setPositionX(textBlock, label.x - textWidth / 2); engine.block.setPositionY(textBlock, labelY); } // Position all color blocks engine.block.setPositionX(srgbBlock, margin); engine.block.setPositionY(srgbBlock, margin); engine.block.setPositionX(cmykBlock, margin + blockWidth + spacing); engine.block.setPositionY(cmykBlock, margin); engine.block.setPositionX(spotBlock, margin + 2 * (blockWidth + spacing)); engine.block.setPositionY(spotBlock, margin); // Retrieve and log color values to demonstrate getColor() const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value'); const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value'); const spotColor = engine.block.getColor(spotFill, 'fill/color/value'); console.log('sRGB Color:', srgbColor); console.log('CMYK Color:', cmykColor); console.log('Spot Color:', spotColor); console.log('Color Basics example loaded successfully'); } } export default Example; ``` This guide covers how to choose the correct color space, define and apply colors using the unified `setColor()` API, and configure spot colors with screen preview approximations. ## Color Spaces Overview CE.SDK represents colors as objects with different properties depending on the color space. Use `engine.block.setColor()` to apply any color type to supported properties. **Supported color properties:** - `'fill/color/value'` - Fill color of a block - `'stroke/color'` - Stroke/outline color - `'dropShadow/color'` - Drop shadow color - `'backgroundColor/color'` - Background color - `'camera/clearColor'` - Canvas clear color ## sRGB Colors sRGB is the default color space for screen display. Pass an `RGBAColor` object with `r`, `g`, `b`, `a` components, each in the range 0.0 to 1.0. The `a` (alpha) component controls transparency. ```typescript highlight=highlight-srgb-color // Block 1: sRGB color (for screen display) const srgbBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( srgbBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color'); // Set fill color using RGBAColor object (values 0.0-1.0) engine.block.setColor(srgbFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); engine.block.setFill(srgbBlock, srgbFill); engine.block.setWidth(srgbBlock, blockWidth); engine.block.setHeight(srgbBlock, blockHeight); engine.block.appendChild(page, srgbBlock); ``` sRGB colors are ideal for web and digital content where the output is displayed on screens. ## CMYK Colors CMYK is the color space for print workflows. Pass a `CMYKColor` object with `c`, `m`, `y`, `k` components (0.0 to 1.0) plus a `tint` value that controls opacity. ```typescript highlight=highlight-cmyk-color // Block 2: CMYK color (for print workflows) const cmykBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( cmykBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color'); // Set fill color using CMYKColor object (values 0.0-1.0, tint controls opacity) engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, m: 0.8, y: 0.95, k: 0.0, tint: 1.0 }); engine.block.setFill(cmykBlock, cmykFill); engine.block.setWidth(cmykBlock, blockWidth); engine.block.setHeight(cmykBlock, blockHeight); engine.block.appendChild(page, cmykBlock); ``` When rendered on screen, CMYK colors are converted to RGB using standard conversion formulas. The `tint` value (0.0 to 1.0) is rendered as transparency. > **Note:** During PDF export, CMYK colors are currently converted to RGB using the standard conversion. Tint values are retained in the alpha channel. ## Spot Colors Spot colors are named colors used for specialized printing. Before using a spot color, you must define it with an RGB or CMYK approximation for screen preview. ### Defining Spot Colors Use `engine.editor.setSpotColorRGB()` or `engine.editor.setSpotColorCMYK()` to register a spot color with its screen preview approximation. ```typescript highlight=highlight-define-spot-color // Define a spot color with RGB approximation for screen preview engine.editor.setSpotColorRGB('MyBrand Red', 0.95, 0.25, 0.21); ``` ### Applying Spot Colors Reference a defined spot color using a `SpotColor` object with the `name`, `tint`, and `externalReference` properties. ```typescript highlight=highlight-spot-color // Block 3: Spot color (for specialized printing) const spotBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( spotBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const spotFill = engine.block.createFill('//ly.img.ubq/fill/color'); // Set fill color using SpotColor object (references the defined spot color) engine.block.setColor(spotFill, 'fill/color/value', { name: 'MyBrand Red', tint: 1.0, externalReference: '' }); engine.block.setFill(spotBlock, spotFill); engine.block.setWidth(spotBlock, blockWidth); engine.block.setHeight(spotBlock, blockHeight); engine.block.appendChild(page, spotBlock); ``` When rendered on screen, the spot color uses its RGB or CMYK approximation. During PDF export, spot colors are saved as a [Separation Color Space](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.6.pdf#G9.1850648) that preserves print information. > **Note:** If a block references an undefined spot color, CE.SDK displays magenta (RGB: 1, 0, 1) as a fallback. ## Applying Stroke Colors Strokes support all three color spaces. Enable the stroke, set its width, then apply a color using the `'stroke/color'` property. ```typescript highlight=highlight-stroke-color // Add strokes to demonstrate stroke color property engine.block.setStrokeEnabled(srgbBlock, true); engine.block.setStrokeWidth(srgbBlock, 4); engine.block.setColor(srgbBlock, 'stroke/color', { r: 0.1, g: 0.2, b: 0.5, a: 1.0 }); engine.block.setStrokeEnabled(cmykBlock, true); engine.block.setStrokeWidth(cmykBlock, 4); engine.block.setColor(cmykBlock, 'stroke/color', { c: 0.0, m: 0.5, y: 0.6, k: 0.2, tint: 1.0 }); engine.block.setStrokeEnabled(spotBlock, true); engine.block.setStrokeWidth(spotBlock, 4); engine.block.setColor(spotBlock, 'stroke/color', { name: 'MyBrand Red', tint: 0.7, externalReference: '' }); ``` ## Reading Color Values Use `engine.block.getColor()` to retrieve the current color value from a property. The returned object's shape indicates the color space (RGBAColor, CMYKColor, or SpotColor). ```typescript highlight=highlight-get-color // Retrieve and log color values to demonstrate getColor() const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value'); const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value'); const spotColor = engine.block.getColor(spotFill, 'fill/color/value'); console.log('sRGB Color:', srgbColor); console.log('CMYK Color:', cmykColor); console.log('Spot Color:', spotColor); ``` ## Choosing the Right Color Space | Color Space | Use Case | Output | |-------------|----------|--------| | **sRGB** | Web, digital, screen display | PNG, JPEG, WebP | | **CMYK** | Print workflows (converts to RGB) | PDF (converted) | | **Spot Color** | Specialized printing, brand colors | PDF (Separation Color Space) | ## API Reference | Method | Description | |--------|-------------| | `engine.block.setColor(id, property, value)` | Set a color property on a block. Pass an `RGBAColor`, `CMYKColor`, or `SpotColor` object. | | `engine.block.getColor(id, property)` | Get the current color value from a property. Returns an `RGBAColor`, `CMYKColor`, or `SpotColor` object. | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with an RGB approximation for screen preview. Components range from 0.0 to 1.0. | | `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with a CMYK approximation for screen preview. Components range from 0.0 to 1.0. | | Type | Properties | Description | |------|------------|-------------| | `RGBAColor` | `r`, `g`, `b`, `a` (0.0-1.0) | sRGB color for screen display. Alpha controls transparency. | | `CMYKColor` | `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | CMYK color for print. Tint controls opacity. | | `SpotColor` | `name`, `tint`, `externalReference` | Named color for specialized printing. | ## Next Steps - [Apply Colors](https://img.ly/docs/cesdk/angular/colors/apply-2211e3/) - Apply colors to design elements programmatically - [CMYK Colors](https://img.ly/docs/cesdk/angular/colors/for-print/cmyk-8a1334/) - Work with CMYK for print workflows - [Spot Colors](https://img.ly/docs/cesdk/angular/colors/for-print/spot-c3a150/) - Define and manage spot colors for specialized printing --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Color Conversion" description: "Learn how to convert colors between color spaces in CE.SDK. Convert sRGB, CMYK, and spot colors programmatically for screen display or print workflows." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/conversion-bcd82b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Color Conversion](https://img.ly/docs/cesdk/angular/colors/conversion-bcd82b/) --- Convert colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK. ![Color Conversion example showing color blocks with different color spaces](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-conversion-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-conversion-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-conversion-browser/) CE.SDK supports three color spaces: sRGB, CMYK, and SpotColor. When building color interfaces or preparing designs for export, you may need to convert colors between these spaces. The engine handles the mathematical conversion automatically through the `convertColorToColorSpace()` API. ```typescript file=@cesdk_web_examples/guides-colors-conversion-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; // Type guard helpers for identifying color types function isRGBAColor( color: any ): color is { r: number; g: number; b: number; a: number } { return 'r' in color && 'g' in color && 'b' in color && 'a' in color; } function isCMYKColor( color: any ): color is { c: number; m: number; y: number; k: number; tint: number } { return 'c' in color && 'm' in color && 'y' in color && 'k' in color; } function isSpotColor( color: any ): color is { name: string; tint: number; externalReference: string } { return 'name' in color && 'tint' in color && 'externalReference' in color; } class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable spot color feature for the UI cesdk.feature.enable('ly.img.spotColor'); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Calculate block sizes for three columns const margin = 40; const spacing = 30; const availableWidth = pageWidth - 2 * margin - 2 * spacing; const blockWidth = availableWidth / 3; const blockHeight = pageHeight - 2 * margin - 80; // Leave space for labels // Define a spot color with RGB approximation for screen preview engine.editor.setSpotColorRGB('Brand Red', 0.95, 0.25, 0.21); // Create three blocks with different color spaces // Block 1: sRGB color (for screen display) const srgbBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( srgbBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(srgbFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); engine.block.setFill(srgbBlock, srgbFill); engine.block.setWidth(srgbBlock, blockWidth); engine.block.setHeight(srgbBlock, blockHeight); engine.block.appendChild(page, srgbBlock); // Block 2: CMYK color (for print workflows) const cmykBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( cmykBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, m: 0.8, y: 0.95, k: 0.0, tint: 1.0 }); engine.block.setFill(cmykBlock, cmykFill); engine.block.setWidth(cmykBlock, blockWidth); engine.block.setHeight(cmykBlock, blockHeight); engine.block.appendChild(page, cmykBlock); // Block 3: Spot color (for specialized printing) const spotBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( spotBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const spotFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(spotFill, 'fill/color/value', { name: 'Brand Red', tint: 1.0, externalReference: '' }); engine.block.setFill(spotBlock, spotFill); engine.block.setWidth(spotBlock, blockWidth); engine.block.setHeight(spotBlock, blockHeight); engine.block.appendChild(page, spotBlock); // Position all color blocks engine.block.setPositionX(srgbBlock, margin); engine.block.setPositionY(srgbBlock, margin); engine.block.setPositionX(cmykBlock, margin + blockWidth + spacing); engine.block.setPositionY(cmykBlock, margin); engine.block.setPositionX(spotBlock, margin + 2 * (blockWidth + spacing)); engine.block.setPositionY(spotBlock, margin); // Create labels for each color space const labelY = margin + blockHeight + 20; const fontSize = 24; const labels = [ { text: 'sRGB', x: margin + blockWidth / 2 }, { text: 'CMYK', x: margin + blockWidth + spacing + blockWidth / 2 }, { text: 'Spot Color', x: margin + 2 * (blockWidth + spacing) + blockWidth / 2 } ]; for (const label of labels) { const textBlock = engine.block.create('//ly.img.ubq/text'); engine.block.replaceText(textBlock, label.text); engine.block.setTextFontSize(textBlock, fontSize); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.appendChild(page, textBlock); // Center the label below each block const textWidth = engine.block.getWidth(textBlock); engine.block.setPositionX(textBlock, label.x - textWidth / 2); engine.block.setPositionY(textBlock, labelY); } // Convert colors to sRGB for screen display const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value'); const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value'); const spotColor = engine.block.getColor(spotFill, 'fill/color/value'); // Convert CMYK to sRGB const cmykToRgba = engine.editor.convertColorToColorSpace( cmykColor, 'sRGB' ); console.log('CMYK converted to sRGB:', cmykToRgba); // Convert Spot color to sRGB (uses defined RGB approximation) const spotToRgba = engine.editor.convertColorToColorSpace( spotColor, 'sRGB' ); console.log('Spot color converted to sRGB:', spotToRgba); // Convert colors to CMYK for print workflows const srgbToCmyk = engine.editor.convertColorToColorSpace( srgbColor, 'CMYK' ); console.log('sRGB converted to CMYK:', srgbToCmyk); // Convert Spot color to CMYK for print output // First define CMYK approximation for the spot color engine.editor.setSpotColorCMYK('Brand Red', 0.0, 0.85, 0.9, 0.05); const spotToCmyk = engine.editor.convertColorToColorSpace( spotColor, 'CMYK' ); console.log('Spot color converted to CMYK:', spotToCmyk); // Use type guards to identify color space before conversion if (isRGBAColor(srgbColor)) { console.log( 'sRGB color components:', `R: ${srgbColor.r}, G: ${srgbColor.g}, B: ${srgbColor.b}, A: ${srgbColor.a}` ); } if (isCMYKColor(cmykColor)) { console.log( 'CMYK color components:', `C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}, Tint: ${cmykColor.tint}` ); } if (isSpotColor(spotColor)) { console.log('Spot color name:', spotColor.name, 'Tint:', spotColor.tint); } console.log('Color Conversion example loaded successfully'); } } export default Example; ``` This guide covers how to convert colors between sRGB and CMYK, handle spot color conversions, identify color types with type guards, and understand how tint and alpha values are preserved during conversion. ## Supported Color Spaces CE.SDK supports conversion between three color spaces: | Color Space | Format | Use Case | |-------------|--------|----------| | **sRGB** | `RGBAColor` with `r`, `g`, `b`, `a` (0.0-1.0) | Screen display, web output | | **CMYK** | `CMYKColor` with `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | Print workflows | | **SpotColor** | `SpotColor` with `name`, `tint`, `externalReference` | Specialized printing | ## Setting Up Colors Before converting colors, you need colors in different spaces. This example creates blocks with sRGB, CMYK, and spot colors. First, define a spot color with its RGB approximation for screen preview: ```typescript highlight=highlight-define-spot-color // Define a spot color with RGB approximation for screen preview engine.editor.setSpotColorRGB('Brand Red', 0.95, 0.25, 0.21); ``` Create an sRGB color block for screen display: ```typescript highlight=highlight-srgb-color // Block 1: sRGB color (for screen display) const srgbBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( srgbBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const srgbFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(srgbFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); engine.block.setFill(srgbBlock, srgbFill); engine.block.setWidth(srgbBlock, blockWidth); engine.block.setHeight(srgbBlock, blockHeight); engine.block.appendChild(page, srgbBlock); ``` Create a CMYK color block for print workflows: ```typescript highlight=highlight-cmyk-color // Block 2: CMYK color (for print workflows) const cmykBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( cmykBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const cmykFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, m: 0.8, y: 0.95, k: 0.0, tint: 1.0 }); engine.block.setFill(cmykBlock, cmykFill); engine.block.setWidth(cmykBlock, blockWidth); engine.block.setHeight(cmykBlock, blockHeight); engine.block.appendChild(page, cmykBlock); ``` Create a spot color block for specialized printing: ```typescript highlight=highlight-spot-color // Block 3: Spot color (for specialized printing) const spotBlock = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( spotBlock, engine.block.createShape('//ly.img.ubq/shape/rect') ); const spotFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(spotFill, 'fill/color/value', { name: 'Brand Red', tint: 1.0, externalReference: '' }); engine.block.setFill(spotBlock, spotFill); engine.block.setWidth(spotBlock, blockWidth); engine.block.setHeight(spotBlock, blockHeight); engine.block.appendChild(page, spotBlock); ``` ## Converting to sRGB Use `engine.editor.convertColorToColorSpace(color, 'sRGB')` to convert any color to sRGB format. This is useful for displaying color values on screen or when you need RGB components for CSS or other web-based color operations. ```typescript highlight=highlight-convert-to-srgb // Convert colors to sRGB for screen display const srgbColor = engine.block.getColor(srgbFill, 'fill/color/value'); const cmykColor = engine.block.getColor(cmykFill, 'fill/color/value'); const spotColor = engine.block.getColor(spotFill, 'fill/color/value'); // Convert CMYK to sRGB const cmykToRgba = engine.editor.convertColorToColorSpace( cmykColor, 'sRGB' ); console.log('CMYK converted to sRGB:', cmykToRgba); // Convert Spot color to sRGB (uses defined RGB approximation) const spotToRgba = engine.editor.convertColorToColorSpace( spotColor, 'sRGB' ); console.log('Spot color converted to sRGB:', spotToRgba); ``` When converting CMYK or spot colors to sRGB, the engine returns an `RGBAColor` object with `r`, `g`, `b`, `a` properties. The tint value from CMYK or spot colors becomes the alpha value in the returned sRGB color. ## Converting to CMYK Use `engine.editor.convertColorToColorSpace(color, 'CMYK')` to convert any color to CMYK format. This is essential for print workflows where you need to ensure colors are in the correct space before export. ```typescript highlight=highlight-convert-to-cmyk // Convert colors to CMYK for print workflows const srgbToCmyk = engine.editor.convertColorToColorSpace( srgbColor, 'CMYK' ); console.log('sRGB converted to CMYK:', srgbToCmyk); // Convert Spot color to CMYK for print output // First define CMYK approximation for the spot color engine.editor.setSpotColorCMYK('Brand Red', 0.0, 0.85, 0.9, 0.05); const spotToCmyk = engine.editor.convertColorToColorSpace( spotColor, 'CMYK' ); console.log('Spot color converted to CMYK:', spotToCmyk); ``` When converting sRGB colors to CMYK, the alpha value becomes the tint value in the returned CMYK color. For spot colors, define a CMYK approximation with `setSpotColorCMYK()` before converting. > **Note:** Color space conversions may not be perfectly reversible. Some sRGB colors cannot be exactly represented in CMYK due to different color gamuts. ## Identifying Color Types Before converting a color, you may need to identify its current color space. CE.SDK provides type guard functions to check the color type. ```typescript highlight=highlight-type-guards // Use type guards to identify color space before conversion if (isRGBAColor(srgbColor)) { console.log( 'sRGB color components:', `R: ${srgbColor.r}, G: ${srgbColor.g}, B: ${srgbColor.b}, A: ${srgbColor.a}` ); } if (isCMYKColor(cmykColor)) { console.log( 'CMYK color components:', `C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}, Tint: ${cmykColor.tint}` ); } if (isSpotColor(spotColor)) { console.log('Spot color name:', spotColor.name, 'Tint:', spotColor.tint); } ``` Import the type guards from `@cesdk/cesdk-js`: - `isRGBAColor()` - Returns true if the color is an sRGB color - `isCMYKColor()` - Returns true if the color is a CMYK color - `isSpotColor()` - Returns true if the color is a spot color ## Handling Tint and Alpha The tint and alpha values represent transparency in different color spaces: | Source | Target | Transformation | |--------|--------|----------------| | sRGB (alpha) | CMYK | Alpha becomes tint | | CMYK (tint) | sRGB | Tint becomes alpha | | SpotColor (tint) | sRGB | Tint becomes alpha | | SpotColor (tint) | CMYK | Tint is preserved | ## Practical Use Cases ### Building a Color Picker When displaying a color value from a block in a custom color picker, convert to sRGB to show RGB values: ```typescript const fillColor = engine.block.getColor(fillId, 'fill/color/value'); const rgbaColor = engine.editor.convertColorToColorSpace(fillColor, 'sRGB'); // Display: R: ${rgbaColor.r * 255}, G: ${rgbaColor.g * 255}, B: ${rgbaColor.b * 255} ``` ### Export Preparation Before PDF export for print, verify colors are in CMYK format: ```typescript const color = engine.block.getColor(blockId, 'fill/color/value'); if (!isCMYKColor(color)) { const cmykColor = engine.editor.convertColorToColorSpace(color, 'CMYK'); // Log or display the CMYK values console.log(`C: ${cmykColor.c}, M: ${cmykColor.m}, Y: ${cmykColor.y}, K: ${cmykColor.k}`); } ``` ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Spot color converts to unexpected values | Spot color not defined | Call `setSpotColorRGB()` or `setSpotColorCMYK()` before conversion | | Colors look different after conversion | Color gamut differences | Some sRGB colors cannot be exactly represented in CMYK | | Type errors with converted colors | Wrong type assumption | Use type guards (`isRGBAColor`, `isCMYKColor`, `isSpotColor`) before accessing properties | ## API Reference | Method | Description | |--------|-------------| | `engine.editor.convertColorToColorSpace(color, colorSpace)` | Convert a color to the target color space. Returns an `RGBAColor` for 'sRGB' or `CMYKColor` for 'CMYK'. | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color with an RGB approximation. Components range from 0.0 to 1.0. | | `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define a spot color with a CMYK approximation. Components range from 0.0 to 1.0. | | Type Guard | Description | |------------|-------------| | `isRGBAColor(color)` | Returns true if the color is an `RGBAColor` object | | `isCMYKColor(color)` | Returns true if the color is a `CMYKColor` object | | `isSpotColor(color)` | Returns true if the color is a `SpotColor` object | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create a Color Palette" description: "Build reusable color palettes to maintain consistency and streamline user choices." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/create-color-palette-7012e0/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Create a Color Palette](https://img.ly/docs/cesdk/angular/colors/create-color-palette-7012e0/) --- Build custom color palettes that appear in the CE.SDK color picker using sRGB, CMYK, and Spot colors. ![Create a Color Palette example showing custom color library in the color picker](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-create-color-palette-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-create-color-palette-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-create-color-palette-browser/) Color libraries in CE.SDK are implemented as asset sources containing individual colors as assets. Each library has a unique source ID and can include sRGB colors for screen display, CMYK colors for print workflows, and Spot colors for specialized printing applications. You configure which libraries appear in the color picker through the `'ly.img.colors'` asset library entry. ```typescript file=@cesdk_web_examples/guides-colors-create-color-palette-browser/browser.ts reference-only import type { AssetDefinition, EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import packageJson from './package.json'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; // Define color assets for each color space type const colors: AssetDefinition[] = [ { id: 'brand-blue', label: { en: 'Brand Blue' }, tags: { en: ['brand', 'blue', 'primary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.2, g: 0.4, b: 0.8 } } }, { id: 'brand-coral', label: { en: 'Brand Coral' }, tags: { en: ['brand', 'coral', 'secondary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.95, g: 0.45, b: 0.4 } } }, { id: 'print-magenta', label: { en: 'Print Magenta' }, tags: { en: ['print', 'magenta', 'cmyk'] }, payload: { color: { colorSpace: 'CMYK', c: 0, m: 0.9, y: 0.2, k: 0 } } }, { id: 'metallic-gold', label: { en: 'Metallic Gold' }, tags: { en: ['spot', 'metallic', 'gold'] }, payload: { color: { colorSpace: 'SpotColor', name: 'Metallic Gold Ink', externalReference: 'Custom Inks', representation: { colorSpace: 'sRGB', r: 0.85, g: 0.65, b: 0.13 } } } } ]; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a local asset source and add each color const sourceId = 'my-brand-colors'; engine.asset.addLocalSource(sourceId); for (const color of colors) { await engine.asset.addAssetToSource(sourceId, color); } // Set labels for the color library using i18n cesdk.i18n.setTranslations({ en: { 'libraries.my-brand-colors.label': 'Brand Colors' } }); // Configure the color picker to show custom colors first, then defaults cesdk.ui.updateAssetLibraryEntry('ly.img.color.palette', { sourceIds: ['my-brand-colors', 'ly.img.color.palette'] }); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a local asset source and add color assets to it engine.asset.addLocalSource('my-brand-colors'); for (const color of colors) { engine.asset.addAssetToSource('my-brand-colors', color); } // Configure the color picker to show custom colors alongside the defaults cesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: ['my-brand-colors', 'ly.img.color.palette'] }); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); // Set up the page with dimensions const page = engine.block.findByType('page')[0]; // Apply a soft cream background to the page fill // This complements the Brand Blue rectangle const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.98, g: 0.96, b: 0.92, a: 1.0 }); // Create a graphic block with Brand Blue from the custom palette const block = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( block, engine.block.createShape('//ly.img.ubq/shape/rect') ); const fill = engine.block.createFill('//ly.img.ubq/fill/color'); // Use Brand Blue from our custom palette engine.block.setColor(fill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }); engine.block.setFill(block, fill); engine.block.setWidth(block, 200); engine.block.setHeight(block, 200); // Center the block on the page const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); engine.block.setPositionX(block, (pageWidth - 200) / 2); engine.block.setPositionY(block, (pageHeight - 200) / 2); engine.block.appendChild(page, block); // Select the block and open the fill inspector to show the color picker engine.block.select(block); cesdk.ui.openPanel('//ly.img.panel/inspector/fill'); console.log('Create Color Palette example loaded successfully'); } } export default Example; ``` This guide covers how to define colors in different color spaces, create and configure color libraries, set custom labels, and control the display order in the color picker. ## Defining Color Assets Colors are added to libraries as `AssetDefinition` objects. Each color asset has an `id`, optional `label` and `tags` for display and search, and a `payload.color` property containing the color data. The color type determines which color space is used. ### sRGB Colors sRGB colors use the `AssetRGBColor` type with `colorSpace: 'sRGB'` and `r`, `g`, `b` components as floats from 0.0 to 1.0. Use sRGB colors for screen-based designs and web content. ```typescript highlight=highlight-definitions // Define color assets for each color space type const colors: AssetDefinition[] = [ { id: 'brand-blue', label: { en: 'Brand Blue' }, tags: { en: ['brand', 'blue', 'primary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.2, g: 0.4, b: 0.8 } } }, { id: 'brand-coral', label: { en: 'Brand Coral' }, tags: { en: ['brand', 'coral', 'secondary'] }, payload: { color: { colorSpace: 'sRGB', r: 0.95, g: 0.45, b: 0.4 } } }, { id: 'print-magenta', label: { en: 'Print Magenta' }, tags: { en: ['print', 'magenta', 'cmyk'] }, payload: { color: { colorSpace: 'CMYK', c: 0, m: 0.9, y: 0.2, k: 0 } } }, { id: 'metallic-gold', label: { en: 'Metallic Gold' }, tags: { en: ['spot', 'metallic', 'gold'] }, payload: { color: { colorSpace: 'SpotColor', name: 'Metallic Gold Ink', externalReference: 'Custom Inks', representation: { colorSpace: 'sRGB', r: 0.85, g: 0.65, b: 0.13 } } } } ]; ``` The example defines four colors demonstrating different color spaces. The first two colors—"Brand Blue" and "Brand Coral"—use sRGB for screen display. ### CMYK Colors CMYK colors use the `AssetCMYKColor` type with `colorSpace: 'CMYK'` and `c`, `m`, `y`, `k` components as floats from 0.0 to 1.0. Use CMYK colors for print workflows where color accuracy in printing is critical. The "Print Magenta" color in the example demonstrates the CMYK color space with cyan at 0, magenta at 0.9, yellow at 0.2, and black at 0. ### Spot Colors Spot colors use the `AssetSpotColor` type with `colorSpace: 'SpotColor'`, a `name` that identifies the spot color, an `externalReference` indicating the color book or ink system, and a `representation` using sRGB or CMYK for screen preview. The "Metallic Gold" color demonstrates the spot color format, using a custom ink reference with an sRGB representation for on-screen preview. ## Creating a Color Library We create a local asset source using `engine.asset.addLocalSource()` with a unique source ID. Then we add each color asset using `engine.asset.addAssetToSource()`. ```typescript highlight=highlight-add-library // Create a local asset source and add each color const sourceId = 'my-brand-colors'; engine.asset.addLocalSource(sourceId); for (const color of colors) { await engine.asset.addAssetToSource(sourceId, color); } // Set labels for the color library using i18n cesdk.i18n.setTranslations({ en: { 'libraries.my-brand-colors.label': 'Brand Colors' } }); // Configure the color picker to show custom colors first, then defaults cesdk.ui.updateAssetLibraryEntry('ly.img.color.palette', { sourceIds: ['my-brand-colors', 'ly.img.color.palette'] }); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a local asset source and add color assets to it engine.asset.addLocalSource('my-brand-colors'); for (const color of colors) { engine.asset.addAssetToSource('my-brand-colors', color); } ``` The source ID `'my-brand-colors'` identifies this library throughout the application. You can create multiple libraries with different source IDs to organize colors by purpose—for example, separate libraries for brand colors, print colors, and seasonal palettes. ## Configuring Library Labels We set display labels for color libraries using `cesdk.i18n.setTranslations()`. Labels use the pattern `libraries..label` where `` matches the ID used when creating the source. ```typescript highlight=highlight-config-labels // Set labels for the color library using i18n cesdk.i18n.setTranslations({ en: { 'libraries.my-brand-colors.label': 'Brand Colors' } }); ``` The label "Brand Colors" appears as the section header in the color picker. You can provide translations for multiple locales by adding additional language keys to the translations object. ## Configuring the Color Picker We control which libraries appear in the color picker and their display order using `cesdk.ui.updateAssetLibraryEntry()`. The `sourceIds` array determines both visibility and order—libraries appear in the picker in the same order as the array. ```typescript highlight=highlight-config-order // Configure the color picker to show custom colors first, then defaults cesdk.ui.updateAssetLibraryEntry('ly.img.color.palette', { sourceIds: ['my-brand-colors', 'ly.img.color.palette'] }); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a local asset source and add color assets to it engine.asset.addLocalSource('my-brand-colors'); for (const color of colors) { engine.asset.addAssetToSource('my-brand-colors', color); } // Configure the color picker to show custom colors alongside the defaults cesdk.ui.updateAssetLibraryEntry('ly.img.colors', { sourceIds: ['my-brand-colors', 'ly.img.color.palette'] }); ``` The special source ID `'ly.img.color.palette'` represents CE.SDK's built-in default color palette. Include it in the array to show the default colors alongside your custom library. Remove it from the array to hide the default palette entirely. ## Removing Colors You can remove individual colors from a library using `engine.asset.removeAssetFromSource()` with the source ID and the color's asset ID. ```typescript engine.asset.removeAssetFromSource('my-brand-colors', 'brand-blue'); ``` This removes the color from the library immediately. The color picker updates to reflect the change the next time it renders. ## Troubleshooting ### Colors Not Appearing in Picker If your colors don't appear in the color picker: - Verify the source ID is included in the `sourceIds` array passed to `updateAssetLibraryEntry()` - Check that colors were added using `addAssetToSource()` with the correct source ID - Ensure the asset source was created with `addLocalSource()` before adding colors ### Label Not Showing If the library label doesn't appear: - Verify the translation key follows the `libraries..label` pattern exactly - Check that the source ID in the translation key matches the source ID used in `addLocalSource()` - Ensure `setTranslations()` was called before the color picker renders ### Spot Color Appears Incorrect If a spot color displays incorrectly: - Check that the `representation` property contains a valid sRGB or CMYK color for screen preview - Verify the `name` property is defined and not empty - Ensure the `colorSpace` is set to `'SpotColor'` ### Wrong Library Order The order of libraries in the color picker matches the order in the `sourceIds` array. To change the order: - Reorder the source IDs in the array passed to `updateAssetLibraryEntry()` - The first source ID appears at the top of the color picker ## API Reference | Method | Description | |--------|-------------| | `engine.asset.addLocalSource(sourceId)` | Create a local asset source for colors | | `engine.asset.addAssetToSource(sourceId, asset)` | Add a color asset to a source | | `engine.asset.removeAssetFromSource(sourceId, assetId)` | Remove a color asset from a source | | `cesdk.ui.updateAssetLibraryEntry(entryId, config)` | Configure color library display order | | `cesdk.i18n.setTranslations(translations)` | Set labels for color libraries | | Type | Properties | Description | |------|------------|-------------| | `AssetRGBColor` | `colorSpace`, `r`, `g`, `b` | sRGB color for screen display | | `AssetCMYKColor` | `colorSpace`, `c`, `m`, `y`, `k` | CMYK color for print workflows | | `AssetSpotColor` | `colorSpace`, `name`, `externalReference`, `representation` | Named spot color for specialized printing | | `AssetDefinition` | `id`, `label`, `tags`, `payload` | Color asset structure with metadata | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "For Print" description: "Use print-ready color models and settings for professional-quality, production-ready exports." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/for-print-59bc05/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/angular/colors/for-print-59bc05/) --- --- ## Related Pages - [CMYK Colors](https://img.ly/docs/cesdk/angular/colors/for-print/cmyk-8a1334/) - Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control. - [Spot Colors](https://img.ly/docs/cesdk/angular/colors/for-print/spot-c3a150/) - Define, apply, and manage spot colors for professional print workflows with premixed inks. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "CMYK Colors" description: "Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/for-print/cmyk-8a1334/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/angular/colors/for-print-59bc05/) > [CMYK Colors](https://img.ly/docs/cesdk/angular/colors/for-print/cmyk-8a1334/) --- Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control. ![CMYK Colors example showing blocks with cyan, magenta, yellow, and black colors for print](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-for-print-cmyk-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-for-print-cmyk-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-for-print-cmyk-browser/) CMYK (Cyan, Magenta, Yellow, Key/Black) is the standard color model for print production. Unlike RGB which is additive and designed for screens, CMYK uses subtractive color mixing to represent how inks combine on paper. CE.SDK supports CMYK colors natively, allowing you to prepare designs for professional print output while maintaining accurate color representation. ```typescript file=@cesdk_web_examples/guides-colors-for-print-cmyk-browser/browser.ts reference-only import type { CMYKColor, EditorPlugin, EditorPluginContext, RGBAColor } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; // Type guard to check if a color is CMYK // Color can be RGBAColor, CMYKColor, or SpotColor const isCMYKColor = (color: unknown): color is CMYKColor => { return ( typeof color === 'object' && color !== null && 'c' in color && 'm' in color && 'y' in color && 'k' in color ); }; /** * CE.SDK Plugin: CMYK Colors Guide * * This example demonstrates: * - Creating CMYK color values for print workflows * - Applying CMYK colors to fills, strokes, and shadows * - Using the tint property for color intensity control * - Converting between RGB and CMYK color spaces * - Checking color types with type guards */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a design scene using CE.SDK convenience method await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page background to light gray for visibility (using CMYK) const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { c: 0.0, m: 0.0, y: 0.0, k: 0.04, tint: 1.0 }); // Helper function to create a graphic block with a color fill const createColorBlock = ( x: number, y: number, width: number, height: number, shape: 'rect' | 'ellipse' = 'rect' ): { block: number; fill: number } => { const block = engine.block.create('graphic'); const blockShape = engine.block.createShape(shape); engine.block.setShape(block, blockShape); engine.block.setWidth(block, width); engine.block.setHeight(block, height); engine.block.setPositionX(block, x); engine.block.setPositionY(block, y); engine.block.appendChild(page, block); const colorFill = engine.block.createFill('color'); engine.block.setFill(block, colorFill); return { block, fill: colorFill }; }; // Create CMYK color objects for print production // CMYK values range from 0.0 to 1.0 const cmykCyan: CMYKColor = { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }; const cmykMagenta: CMYKColor = { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }; const cmykYellow: CMYKColor = { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }; const cmykBlack: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0 }; // Example 1: Apply CMYK Cyan to a fill const { fill: cyanFill } = createColorBlock(50, 50, 150, 150); engine.block.setColor(cyanFill, 'fill/color/value', cmykCyan); // Example 2: Apply CMYK Magenta to a fill const { fill: magentaFill } = createColorBlock(220, 50, 150, 150); engine.block.setColor(magentaFill, 'fill/color/value', cmykMagenta); // Example 3: Apply CMYK Yellow to a fill const { fill: yellowFill } = createColorBlock(390, 50, 150, 150); engine.block.setColor(yellowFill, 'fill/color/value', cmykYellow); // Example 4: Apply CMYK Black to a fill const { fill: blackFill } = createColorBlock(560, 50, 150, 150); engine.block.setColor(blackFill, 'fill/color/value', cmykBlack); // Example 5: Use tint for partial color intensity // Tint of 0.5 gives 50% color intensity const cmykHalfMagenta: CMYKColor = { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 0.5 }; const { fill: tintedFill } = createColorBlock(50, 220, 150, 150, 'ellipse'); engine.block.setColor(tintedFill, 'fill/color/value', cmykHalfMagenta); // Example 6: Apply CMYK color to stroke const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock( 220, 220, 150, 150 ); // Set fill to white (using CMYK) engine.block.setColor(strokeBlockFill, 'fill/color/value', { c: 0.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }); // Enable stroke and set CMYK color engine.block.setStrokeEnabled(strokeBlock, true); engine.block.setStrokeWidth(strokeBlock, 8); const cmykStrokeColor: CMYKColor = { c: 0.8, m: 0.2, y: 0.0, k: 0.1, tint: 1.0 }; engine.block.setColor(strokeBlock, 'stroke/color', cmykStrokeColor); // Example 7: Apply CMYK color to drop shadow const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock( 390, 220, 150, 150 ); // Set fill to light gray (using CMYK) engine.block.setColor(shadowBlockFill, 'fill/color/value', { c: 0.0, m: 0.0, y: 0.0, k: 0.05, tint: 1.0 }); // Enable drop shadow and set CMYK color engine.block.setDropShadowEnabled(shadowBlock, true); engine.block.setDropShadowOffsetX(shadowBlock, 10); engine.block.setDropShadowOffsetY(shadowBlock, 10); engine.block.setDropShadowBlurRadiusX(shadowBlock, 15); engine.block.setDropShadowBlurRadiusY(shadowBlock, 15); const cmykShadowColor: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 0.6, tint: 0.8 }; engine.block.setColor(shadowBlock, 'dropShadow/color', cmykShadowColor); // Example 8: Read CMYK color from a block const { fill: readFill } = createColorBlock(560, 220, 150, 150, 'ellipse'); const cmykOrange: CMYKColor = { c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0 }; engine.block.setColor(readFill, 'fill/color/value', cmykOrange); // Retrieve and check the color const retrievedColor = engine.block.getColor(readFill, 'fill/color/value'); if (isCMYKColor(retrievedColor)) { // eslint-disable-next-line no-console console.log( `CMYK Color - C: ${retrievedColor.c}, M: ${retrievedColor.m}, Y: ${retrievedColor.y}, K: ${retrievedColor.k}, Tint: ${retrievedColor.tint}` ); } // Example 9: Convert RGB to CMYK const rgbBlue: RGBAColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }; const convertedCmyk = engine.editor.convertColorToColorSpace( rgbBlue, 'CMYK' ); const { fill: convertedFill } = createColorBlock(50, 390, 150, 150); engine.block.setColor(convertedFill, 'fill/color/value', convertedCmyk); // eslint-disable-next-line no-console console.log('RGB to CMYK conversion:', convertedCmyk); // Example 10: Convert CMYK to RGB (for demonstration) const cmykGreen: CMYKColor = { c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0 }; const convertedRgb = engine.editor.convertColorToColorSpace( cmykGreen, 'sRGB' ); // eslint-disable-next-line no-console console.log('CMYK to RGB conversion:', convertedRgb); // Display using original CMYK color const { fill: previewFill } = createColorBlock(220, 390, 150, 150); engine.block.setColor(previewFill, 'fill/color/value', cmykGreen); // Example 11: Use CMYK colors in gradients const gradientBlock = engine.block.create('graphic'); const gradientShape = engine.block.createShape('rect'); engine.block.setShape(gradientBlock, gradientShape); engine.block.setWidth(gradientBlock, 320); engine.block.setHeight(gradientBlock, 150); engine.block.setPositionX(gradientBlock, 390); engine.block.setPositionY(gradientBlock, 390); engine.block.appendChild(page, gradientBlock); const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setFill(gradientBlock, gradientFill); // Set gradient stops with CMYK colors engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0 }, { color: { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0.5 }, { color: { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 } ]); // Zoom to fit all content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` This guide covers how to create CMYK color values, apply them to fills, strokes, and shadows, use the tint property for color intensity control, and convert between color spaces. ## Understanding CMYK Colors ### When to Use CMYK Use CMYK colors when preparing designs for commercial printing or when print service providers require CMYK values. Screen displays convert CMYK to RGB for preview, but exported PDFs retain the original CMYK values for accurate print reproduction. CMYK colors in CE.SDK have five properties: - `c` (Cyan): 0.0 to 1.0 - `m` (Magenta): 0.0 to 1.0 - `y` (Yellow): 0.0 to 1.0 - `k` (Key/Black): 0.0 to 1.0 - `tint`: 0.0 to 1.0 (controls overall color intensity) ## Creating CMYK Colors Create CMYK color objects using the `CMYKColor` interface. All component values range from 0.0 to 1.0: ```typescript highlight-create-cmyk // Create CMYK color objects for print production // CMYK values range from 0.0 to 1.0 const cmykCyan: CMYKColor = { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }; const cmykMagenta: CMYKColor = { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }; const cmykYellow: CMYKColor = { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }; const cmykBlack: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0 }; ``` The tint property acts as a multiplier for the entire color—a tint of 1.0 applies the full color, while 0.5 applies 50% intensity. ## Applying CMYK Colors to Fills Apply CMYK colors to color fills using `engine.block.setColor()`. First create a color fill with `engine.block.createFill('color')`, then set its color value: ```typescript highlight-apply-fill // Example 1: Apply CMYK Cyan to a fill const { fill: cyanFill } = createColorBlock(50, 50, 150, 150); engine.block.setColor(cyanFill, 'fill/color/value', cmykCyan); ``` The same method works for any color type—RGBA, CMYK, or SpotColor. CE.SDK automatically handles the color representation internally. ## Using the Tint Property The tint property provides fine-grained control over color intensity without modifying the base CMYK values. This is useful for creating lighter variations of a color: ```typescript highlight-tint // Example 5: Use tint for partial color intensity // Tint of 0.5 gives 50% color intensity const cmykHalfMagenta: CMYKColor = { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 0.5 }; const { fill: tintedFill } = createColorBlock(50, 220, 150, 150, 'ellipse'); engine.block.setColor(tintedFill, 'fill/color/value', cmykHalfMagenta); ``` A tint of 0.5 creates a 50% lighter version of the color, useful for secondary elements or subtle backgrounds. ## Applying CMYK to Strokes Apply CMYK colors to strokes using the `stroke/color` property path: ```typescript highlight-stroke // Example 6: Apply CMYK color to stroke const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock( 220, 220, 150, 150 ); // Set fill to white (using CMYK) engine.block.setColor(strokeBlockFill, 'fill/color/value', { c: 0.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }); // Enable stroke and set CMYK color engine.block.setStrokeEnabled(strokeBlock, true); engine.block.setStrokeWidth(strokeBlock, 8); const cmykStrokeColor: CMYKColor = { c: 0.8, m: 0.2, y: 0.0, k: 0.1, tint: 1.0 }; engine.block.setColor(strokeBlock, 'stroke/color', cmykStrokeColor); ``` Enable the stroke first with `setStrokeEnabled()`, set the stroke width, then apply the CMYK color. ## Applying CMYK to Drop Shadows Apply CMYK colors to drop shadows using the `dropShadow/color` property path: ```typescript highlight-shadow // Example 7: Apply CMYK color to drop shadow const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock( 390, 220, 150, 150 ); // Set fill to light gray (using CMYK) engine.block.setColor(shadowBlockFill, 'fill/color/value', { c: 0.0, m: 0.0, y: 0.0, k: 0.05, tint: 1.0 }); // Enable drop shadow and set CMYK color engine.block.setDropShadowEnabled(shadowBlock, true); engine.block.setDropShadowOffsetX(shadowBlock, 10); engine.block.setDropShadowOffsetY(shadowBlock, 10); engine.block.setDropShadowBlurRadiusX(shadowBlock, 15); engine.block.setDropShadowBlurRadiusY(shadowBlock, 15); const cmykShadowColor: CMYKColor = { c: 0.0, m: 0.0, y: 0.0, k: 0.6, tint: 0.8 }; engine.block.setColor(shadowBlock, 'dropShadow/color', cmykShadowColor); ``` Enable the drop shadow first, configure its offset and blur radius, then apply the CMYK color. ## Reading CMYK Colors Retrieve color values from blocks using `engine.block.getColor()`. The returned color could be RGBA, CMYK, or SpotColor, so use the `isCMYKColor()` type guard to check: ```typescript highlight-read-color // Example 8: Read CMYK color from a block const { fill: readFill } = createColorBlock(560, 220, 150, 150, 'ellipse'); const cmykOrange: CMYKColor = { c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0 }; engine.block.setColor(readFill, 'fill/color/value', cmykOrange); // Retrieve and check the color const retrievedColor = engine.block.getColor(readFill, 'fill/color/value'); if (isCMYKColor(retrievedColor)) { // eslint-disable-next-line no-console console.log( `CMYK Color - C: ${retrievedColor.c}, M: ${retrievedColor.m}, Y: ${retrievedColor.y}, K: ${retrievedColor.k}, Tint: ${retrievedColor.tint}` ); } ``` The `isCMYKColor()` type guard checks if a color has the CMYK properties (`c`, `m`, `y`, `k`). ## Converting Between Color Spaces Use `engine.editor.convertColorToColorSpace()` to convert colors between 'sRGB' and 'CMYK': ```typescript highlight-convert // Example 9: Convert RGB to CMYK const rgbBlue: RGBAColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }; const convertedCmyk = engine.editor.convertColorToColorSpace( rgbBlue, 'CMYK' ); const { fill: convertedFill } = createColorBlock(50, 390, 150, 150); engine.block.setColor(convertedFill, 'fill/color/value', convertedCmyk); // eslint-disable-next-line no-console console.log('RGB to CMYK conversion:', convertedCmyk); // Example 10: Convert CMYK to RGB (for demonstration) const cmykGreen: CMYKColor = { c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0 }; const convertedRgb = engine.editor.convertColorToColorSpace( cmykGreen, 'sRGB' ); // eslint-disable-next-line no-console console.log('CMYK to RGB conversion:', convertedRgb); // Display using original CMYK color const { fill: previewFill } = createColorBlock(220, 390, 150, 150); engine.block.setColor(previewFill, 'fill/color/value', cmykGreen); ``` Note that color conversions may not be perfectly reversible due to differences in color gamuts between RGB and CMYK. Some RGB colors cannot be accurately represented in CMYK and vice versa. ## Using CMYK in Gradients CMYK colors work in gradient color stops. Create a gradient fill and set stops using `engine.block.setGradientColorStops()`: ```typescript highlight-gradient // Example 11: Use CMYK colors in gradients const gradientBlock = engine.block.create('graphic'); const gradientShape = engine.block.createShape('rect'); engine.block.setShape(gradientBlock, gradientShape); engine.block.setWidth(gradientBlock, 320); engine.block.setHeight(gradientBlock, 150); engine.block.setPositionX(gradientBlock, 390); engine.block.setPositionY(gradientBlock, 390); engine.block.appendChild(page, gradientBlock); const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setFill(gradientBlock, gradientFill); // Set gradient stops with CMYK colors engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0 }, { color: { c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0 }, stop: 0.5 }, { color: { c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 } ]); ``` This creates a gradient transitioning through the primary CMYK colors—cyan, magenta, and yellow. ## Troubleshooting ### Colors Look Different on Screen vs Print Screen displays convert CMYK to RGB for preview. The exported PDF retains original CMYK values. For accurate color preview, use calibrated monitors and proof prints. ### Tint Not Having Expected Effect Ensure the tint value is between 0 and 1. A tint of 0 makes the color fully transparent, while 1 applies full intensity. ### Type Guard Returns False Make sure you're checking a `Color` value returned from `engine.block.getColor()`. The `isCMYKColor()` function only works with color values, not arbitrary objects. ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.setColor()` | Set a color property value | | `engine.block.getColor()` | Get a color property from a block | | `engine.editor.convertColorToColorSpace()` | Convert color to a different color space | | `engine.block.createFill()` | Create a color fill | | `engine.block.setFill()` | Assign a fill to a block | | `engine.block.getFill()` | Get the fill from a block | | `engine.block.setGradientColorStops()` | Set gradient color stops | | `isCMYKColor()` | Check if a color is CMYK | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Spot Colors" description: "Define, apply, and manage spot colors for professional print workflows with premixed inks." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/for-print/spot-c3a150/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/angular/colors/for-print-59bc05/) > [Spot Colors](https://img.ly/docs/cesdk/angular/colors/for-print/spot-c3a150/) --- Define and manage spot colors programmatically for professional print workflows with exact color matching through premixed inks. ![Spot Colors example showing blocks with various spot color fills and tints](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-for-print-spot-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-for-print-spot-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-for-print-spot-browser/) Spot colors are named colors reproduced using premixed inks in print production, providing exact color matching that CMYK process colors cannot guarantee. CE.SDK maintains a registry of spot color definitions at the editor level, where each spot color has a name and screen approximations (RGB and/or CMYK) for display purposes. The actual premixed ink is used during printing based on the color name. ```typescript file=@cesdk_web_examples/guides-colors-for-print-spot-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext, SpotColor } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; // Type guard to check if a color is a SpotColor // Color can be RGBAColor, CMYKColor, or SpotColor const isSpotColor = (color: unknown): color is SpotColor => { return ( typeof color === 'object' && color !== null && 'name' in color && 'tint' in color && 'externalReference' in color ); }; /** * CE.SDK Plugin: Spot Colors Guide * * This example demonstrates: * - Defining spot colors with RGB and CMYK approximations * - Applying spot colors to fills, strokes, and shadows * - Using tints for lighter color variations * - Querying and updating spot color definitions * - Removing spot colors and handling the magenta fallback * - Assigning spot colors to cutout types */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a design scene using CE.SDK convenience method await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page background to light gray for visibility const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); // Helper function to create a graphic block with a color fill const createColorBlock = ( x: number, y: number, width: number, height: number, shape: 'rect' | 'ellipse' = 'rect' ): { block: number; fill: number } => { const block = engine.block.create('graphic'); const blockShape = engine.block.createShape(shape); engine.block.setShape(block, blockShape); engine.block.setWidth(block, width); engine.block.setHeight(block, height); engine.block.setPositionX(block, x); engine.block.setPositionY(block, y); engine.block.appendChild(page, block); const colorFill = engine.block.createFill('color'); engine.block.setFill(block, colorFill); return { block, fill: colorFill }; }; // Define a spot color with RGB approximation // RGB values range from 0.0 to 1.0 engine.editor.setSpotColorRGB('Brand-Primary', 0.8, 0.1, 0.2); // Add CMYK approximation for the same spot color // This provides print-accurate preview in addition to screen display engine.editor.setSpotColorCMYK('Brand-Primary', 0.05, 0.95, 0.85, 0.0); // Define another spot color with both approximations engine.editor.setSpotColorRGB('Brand-Accent', 0.2, 0.4, 0.8); engine.editor.setSpotColorCMYK('Brand-Accent', 0.75, 0.5, 0.0, 0.0); // Apply spot colors to fills using SpotColor objects // The tint property (0.0 to 1.0) controls color intensity // The externalReference field stores metadata like color system origin const brandPrimary: SpotColor = { name: 'Brand-Primary', tint: 1.0, externalReference: '' }; // Create a block and apply the Brand-Primary spot color const { fill: primaryFill } = createColorBlock(50, 50, 150, 150); engine.block.setColor(primaryFill, 'fill/color/value', brandPrimary); // Apply Brand-Accent to another block const brandAccent: SpotColor = { name: 'Brand-Accent', tint: 1.0, externalReference: '' }; const { fill: accentFill } = createColorBlock(220, 50, 150, 150); engine.block.setColor(accentFill, 'fill/color/value', brandAccent); // Use tints for lighter variations without defining new spot colors // Tint of 0.5 gives 50% color intensity const brandPrimaryHalfTint: SpotColor = { name: 'Brand-Primary', tint: 0.5, externalReference: '' }; const { fill: tintedFill } = createColorBlock(390, 50, 150, 150, 'ellipse'); engine.block.setColor(tintedFill, 'fill/color/value', brandPrimaryHalfTint); // Create a gradient of tints const brandAccentLightTint: SpotColor = { name: 'Brand-Accent', tint: 0.3, externalReference: '' }; const { fill: lightTintFill } = createColorBlock(560, 50, 150, 150); engine.block.setColor( lightTintFill, 'fill/color/value', brandAccentLightTint ); // Apply spot colors to strokes and shadows const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock( 50, 220, 150, 150 ); // Set fill to white engine.block.setColor(strokeBlockFill, 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Enable stroke and apply spot color engine.block.setStrokeEnabled(strokeBlock, true); engine.block.setStrokeWidth(strokeBlock, 8); const strokeColor: SpotColor = { name: 'Brand-Primary', tint: 1.0, externalReference: '' }; engine.block.setColor(strokeBlock, 'stroke/color', strokeColor); // Apply spot color to drop shadow const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock( 220, 220, 150, 150 ); engine.block.setColor(shadowBlockFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); engine.block.setDropShadowEnabled(shadowBlock, true); engine.block.setDropShadowOffsetX(shadowBlock, 10); engine.block.setDropShadowOffsetY(shadowBlock, 10); engine.block.setDropShadowBlurRadiusX(shadowBlock, 15); engine.block.setDropShadowBlurRadiusY(shadowBlock, 15); const shadowColor: SpotColor = { name: 'Brand-Accent', tint: 0.8, externalReference: '' }; engine.block.setColor(shadowBlock, 'dropShadow/color', shadowColor); // Query all defined spot colors const spotColors = engine.editor.findAllSpotColors(); // eslint-disable-next-line no-console console.log('Defined spot colors:', spotColors); // Query RGB approximation for a spot color const rgbaApprox = engine.editor.getSpotColorRGBA('Brand-Primary'); // eslint-disable-next-line no-console console.log('Brand-Primary RGB approximation:', rgbaApprox); // Query CMYK approximation for a spot color const cmykApprox = engine.editor.getSpotColorCMYK('Brand-Primary'); // eslint-disable-next-line no-console console.log('Brand-Primary CMYK approximation:', cmykApprox); // Read back the color from a block and check if it's a spot color const retrievedColor = engine.block.getColor( primaryFill, 'fill/color/value' ); if (isSpotColor(retrievedColor)) { // eslint-disable-next-line no-console console.log( `Retrieved SpotColor - Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}` ); } // Update an existing spot color's approximation // This changes how the color appears on screen without affecting the color name engine.editor.setSpotColorRGB('Brand-Accent', 0.3, 0.5, 0.9); // eslint-disable-next-line no-console console.log('Updated Brand-Accent RGB approximation'); // Show the updated color in a new block const { fill: updatedFill } = createColorBlock(390, 220, 150, 150); const updatedAccent: SpotColor = { name: 'Brand-Accent', tint: 1.0, externalReference: '' }; engine.block.setColor(updatedFill, 'fill/color/value', updatedAccent); // Define a temporary spot color engine.editor.setSpotColorRGB('Temporary-Color', 0.5, 0.8, 0.3); // Create a block using the temporary color const { fill: tempFill } = createColorBlock(560, 220, 150, 150); const tempColor: SpotColor = { name: 'Temporary-Color', tint: 1.0, externalReference: '' }; engine.block.setColor(tempFill, 'fill/color/value', tempColor); // Remove the spot color definition // Blocks using this color will display magenta (default fallback) engine.editor.removeSpotColor('Temporary-Color'); // eslint-disable-next-line no-console console.log('Removed Temporary-Color - block now shows magenta fallback'); // Verify the color is no longer defined const remainingSpotColors = engine.editor.findAllSpotColors(); // eslint-disable-next-line no-console console.log('Remaining spot colors:', remainingSpotColors); // Assign spot colors to cutout types for die-cutting operations // First define a spot color for the die line engine.editor.setSpotColorRGB('DieLine', 1.0, 0.0, 1.0); engine.editor.setSpotColorCMYK('DieLine', 0.0, 1.0, 0.0, 0.0); // Associate the spot color with a cutout type // CutoutType can be 'Solid' or 'Dashed' engine.editor.setSpotColorForCutoutType('Solid', 'DieLine'); // Query the assigned spot color const cutoutSpotColor = engine.editor.getSpotColorForCutoutType('Solid'); // eslint-disable-next-line no-console console.log('Cutout type Solid uses spot color:', cutoutSpotColor); // Create a legend block with text const textBlock = engine.block.create('text'); engine.block.replaceText(textBlock, 'Spot Colors Demo'); engine.block.setTextFontSize(textBlock, 36); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setPositionX(textBlock, 50); engine.block.setPositionY(textBlock, 400); engine.block.appendChild(page, textBlock); // Create smaller label texts const labels = [ { text: 'Brand-Primary', x: 50, y: 205 }, { text: 'Brand-Accent', x: 220, y: 205 }, { text: 'Primary 50%', x: 390, y: 205 }, { text: 'Accent 30%', x: 560, y: 205 }, { text: 'Stroke', x: 50, y: 375 }, { text: 'Shadow', x: 220, y: 375 }, { text: 'Updated', x: 390, y: 375 }, { text: 'Removed', x: 560, y: 375 } ]; for (const label of labels) { const labelBlock = engine.block.create('text'); engine.block.replaceText(labelBlock, label.text); engine.block.setTextFontSize(labelBlock, 14); engine.block.setWidthMode(labelBlock, 'Auto'); engine.block.setHeightMode(labelBlock, 'Auto'); engine.block.setPositionX(labelBlock, label.x); engine.block.setPositionY(labelBlock, label.y); engine.block.appendChild(page, labelBlock); } // Zoom to fit all content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` This guide covers how to define spot colors with RGB and CMYK approximations, apply them to design elements with varying tints, query and update definitions, and assign spot colors to cutout types for die-cutting operations. ## Understanding Spot Colors Spot colors differ from CMYK process colors in several important ways: - **Exact color matching** - Premixed inks guarantee consistent color reproduction across print runs - **Brand consistency** - Essential for logos and brand colors (e.g., Pantone colors) - **Specialty effects** - Enable metallic, fluorescent, and other specialty inks - **Color gamut** - Some colors are unreproducible with CMYK process inks In CE.SDK, spot colors have three components: - **Name** - The identifier used in print output (e.g., "Pantone-485-C") - **Approximations** - RGB and/or CMYK values for screen display - **Tint** - A value from 0.0 to 1.0 controlling color intensity ## Define Spot Colors ### RGB Approximation We register spot colors using `engine.editor.setSpotColorRGB()`. This creates a new spot color if the name doesn't exist, or updates the approximation if it does. ```typescript highlight-define-spot-rgb // Define a spot color with RGB approximation // RGB values range from 0.0 to 1.0 engine.editor.setSpotColorRGB('Brand-Primary', 0.8, 0.1, 0.2); ``` RGB approximations display the spot color on screen during editing. The values range from 0.0 to 1.0 for each channel. ### CMYK Approximation We can add a CMYK approximation using `engine.editor.setSpotColorCMYK()`. This provides print-accurate previews alongside the RGB screen display. ```typescript highlight-define-spot-cmyk // Add CMYK approximation for the same spot color // This provides print-accurate preview in addition to screen display engine.editor.setSpotColorCMYK('Brand-Primary', 0.05, 0.95, 0.85, 0.0); // Define another spot color with both approximations engine.editor.setSpotColorRGB('Brand-Accent', 0.2, 0.4, 0.8); engine.editor.setSpotColorCMYK('Brand-Accent', 0.75, 0.5, 0.0, 0.0); ``` For best results, provide both RGB and CMYK approximations for each spot color. RGB displays on screen while CMYK enables accurate print preview. ## Apply Spot Colors to Design Elements We apply spot colors to blocks using `engine.block.setColor()` with a SpotColor object containing `name`, `tint`, and `externalReference`. ```typescript highlight-apply-spot-fill // Apply spot colors to fills using SpotColor objects // The tint property (0.0 to 1.0) controls color intensity // The externalReference field stores metadata like color system origin const brandPrimary: SpotColor = { name: 'Brand-Primary', tint: 1.0, externalReference: '' }; // Create a block and apply the Brand-Primary spot color const { fill: primaryFill } = createColorBlock(50, 50, 150, 150); engine.block.setColor(primaryFill, 'fill/color/value', brandPrimary); ``` The SpotColor object has these properties: - **name** - Must match a defined spot color exactly (case-sensitive) - **tint** - Controls intensity from 0.0 (transparent) to 1.0 (full strength) - **externalReference** - Optional metadata like the color system origin (e.g., "Pantone") > **Note:** The spot color must be defined before applying it to blocks. Undefined spot colors display as magenta—the default fallback color. ### Using Tints Tints create lighter variations without defining separate spot colors for each shade. A tint of 0.5 gives 50% color intensity. ```typescript highlight-tint // Use tints for lighter variations without defining new spot colors // Tint of 0.5 gives 50% color intensity const brandPrimaryHalfTint: SpotColor = { name: 'Brand-Primary', tint: 0.5, externalReference: '' }; const { fill: tintedFill } = createColorBlock(390, 50, 150, 150, 'ellipse'); engine.block.setColor(tintedFill, 'fill/color/value', brandPrimaryHalfTint); // Create a gradient of tints const brandAccentLightTint: SpotColor = { name: 'Brand-Accent', tint: 0.3, externalReference: '' }; const { fill: lightTintFill } = createColorBlock(560, 50, 150, 150); engine.block.setColor( lightTintFill, 'fill/color/value', brandAccentLightTint ); ``` Use tints for: - Color variations in design systems - Lighter backgrounds using brand colors - Gradient-like effects with consistent spot color names ### Strokes and Shadows Spot colors work with any color property, including strokes and drop shadows. ```typescript highlight-stroke-shadow // Apply spot colors to strokes and shadows const { block: strokeBlock, fill: strokeBlockFill } = createColorBlock( 50, 220, 150, 150 ); // Set fill to white engine.block.setColor(strokeBlockFill, 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Enable stroke and apply spot color engine.block.setStrokeEnabled(strokeBlock, true); engine.block.setStrokeWidth(strokeBlock, 8); const strokeColor: SpotColor = { name: 'Brand-Primary', tint: 1.0, externalReference: '' }; engine.block.setColor(strokeBlock, 'stroke/color', strokeColor); // Apply spot color to drop shadow const { block: shadowBlock, fill: shadowBlockFill } = createColorBlock( 220, 220, 150, 150 ); engine.block.setColor(shadowBlockFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); engine.block.setDropShadowEnabled(shadowBlock, true); engine.block.setDropShadowOffsetX(shadowBlock, 10); engine.block.setDropShadowOffsetY(shadowBlock, 10); engine.block.setDropShadowBlurRadiusX(shadowBlock, 15); engine.block.setDropShadowBlurRadiusY(shadowBlock, 15); const shadowColor: SpotColor = { name: 'Brand-Accent', tint: 0.8, externalReference: '' }; engine.block.setColor(shadowBlock, 'dropShadow/color', shadowColor); ``` The `stroke/color` and `dropShadow/color` properties accept the same SpotColor objects as fill colors. ## Query Spot Color Definitions ### List Defined Spot Colors We retrieve all defined spot colors with `engine.editor.findAllSpotColors()`. ```typescript highlight-query-spot // Query all defined spot colors const spotColors = engine.editor.findAllSpotColors(); // eslint-disable-next-line no-console console.log('Defined spot colors:', spotColors); // Query RGB approximation for a spot color const rgbaApprox = engine.editor.getSpotColorRGBA('Brand-Primary'); // eslint-disable-next-line no-console console.log('Brand-Primary RGB approximation:', rgbaApprox); // Query CMYK approximation for a spot color const cmykApprox = engine.editor.getSpotColorCMYK('Brand-Primary'); // eslint-disable-next-line no-console console.log('Brand-Primary CMYK approximation:', cmykApprox); // Read back the color from a block and check if it's a spot color const retrievedColor = engine.block.getColor( primaryFill, 'fill/color/value' ); if (isSpotColor(retrievedColor)) { // eslint-disable-next-line no-console console.log( `Retrieved SpotColor - Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}` ); } ``` This returns an array of spot color names currently registered in the editor. ### Get Color Approximations Query individual color approximations with `engine.editor.getSpotColorRGBA()` or `engine.editor.getSpotColorCMYK()`. Querying an undefined spot color returns magenta values—use this to detect missing definitions. ### Read Colors from Blocks When reading a color back from a block, `engine.block.getColor()` can return an `RGBAColor`, `CMYKColor`, or `SpotColor`. Use a type guard to check if it's a SpotColor: ```typescript const isSpotColor = (color: unknown): color is SpotColor => { return ( typeof color === 'object' && color !== null && 'name' in color && 'tint' in color && 'externalReference' in color ); }; const retrievedColor = engine.block.getColor(fill, 'fill/color/value'); if (isSpotColor(retrievedColor)) { console.log(`Name: ${retrievedColor.name}, Tint: ${retrievedColor.tint}`); } ``` ## Update and Remove Spot Colors ### Update Approximations We update spot colors by calling the set methods again with the same name. This changes how the color appears on screen without affecting the color name in the print output. ```typescript highlight-update-spot // Update an existing spot color's approximation // This changes how the color appears on screen without affecting the color name engine.editor.setSpotColorRGB('Brand-Accent', 0.3, 0.5, 0.9); // eslint-disable-next-line no-console console.log('Updated Brand-Accent RGB approximation'); // Show the updated color in a new block const { fill: updatedFill } = createColorBlock(390, 220, 150, 150); const updatedAccent: SpotColor = { name: 'Brand-Accent', tint: 1.0, externalReference: '' }; engine.block.setColor(updatedFill, 'fill/color/value', updatedAccent); ``` Existing blocks using that spot color automatically reflect the updated approximation. ### Remove Spot Colors We remove spot colors with `engine.editor.removeSpotColor()` when they're no longer needed. ```typescript highlight-remove-spot // Define a temporary spot color engine.editor.setSpotColorRGB('Temporary-Color', 0.5, 0.8, 0.3); // Create a block using the temporary color const { fill: tempFill } = createColorBlock(560, 220, 150, 150); const tempColor: SpotColor = { name: 'Temporary-Color', tint: 1.0, externalReference: '' }; engine.block.setColor(tempFill, 'fill/color/value', tempColor); // Remove the spot color definition // Blocks using this color will display magenta (default fallback) engine.editor.removeSpotColor('Temporary-Color'); // eslint-disable-next-line no-console console.log('Removed Temporary-Color - block now shows magenta fallback'); // Verify the color is no longer defined const remainingSpotColors = engine.editor.findAllSpotColors(); // eslint-disable-next-line no-console console.log('Remaining spot colors:', remainingSpotColors); ``` Removing a spot color doesn't affect blocks already using it—they display magenta until redefined or until you apply a different color. ## Spot Colors for Cutouts CE.SDK supports assigning spot colors to cutout types for die-cutting, embossing, and other print finishing operations. ```typescript highlight-cutout // Assign spot colors to cutout types for die-cutting operations // First define a spot color for the die line engine.editor.setSpotColorRGB('DieLine', 1.0, 0.0, 1.0); engine.editor.setSpotColorCMYK('DieLine', 0.0, 1.0, 0.0, 0.0); // Associate the spot color with a cutout type // CutoutType can be 'Solid' or 'Dashed' engine.editor.setSpotColorForCutoutType('Solid', 'DieLine'); // Query the assigned spot color const cutoutSpotColor = engine.editor.getSpotColorForCutoutType('Solid'); // eslint-disable-next-line no-console console.log('Cutout type Solid uses spot color:', cutoutSpotColor); ``` Use `engine.editor.setSpotColorForCutoutType()` to associate a spot color with a specific cutout type. Available cutout types are `'Solid'` and `'Dashed'`, representing different die-line styles used in print finishing. All cutout blocks of that type automatically use the assigned spot color in the output. Query the assignment with `engine.editor.getSpotColorForCutoutType()`. ## Best Practices **Define early** - Register spot colors at initialization before applying them to blocks. Undefined colors display as magenta, which can confuse users. **Use descriptive names** - Match your print vendor's reference (e.g., "Pantone-485-C") to ensure correct ink matching in production. **Provide both approximations** - RGB for screen display, CMYK for print-accurate previews. This gives designers the best experience across different workflows. **Use tints sparingly** - Prefer tints (0.0-1.0) for lighter variations rather than defining separate spot colors for each shade. This keeps your spot color list manageable. **Validate before export** - Query `findAllSpotColors()` to verify all expected spot colors are defined before exporting for print. ## Troubleshooting ### Spot Color Displays as Magenta The spot color hasn't been defined. Call `setSpotColorRGB()` or `setSpotColorCMYK()` with the color name before applying it to blocks. ### Color Approximation Looks Wrong Update the approximation values using `setSpotColorRGB()` or `setSpotColorCMYK()`. Remember that RGB values are for screen display while CMYK values are for print preview. ### Spot Color Not in Output Verify the spot color name matches exactly (names are case-sensitive). Check that the block is using a SpotColor object, not an RGB or CMYK color value. ### Can't Remove Spot Color Ensure you're using the exact name string. Note that removing a spot color doesn't update existing blocks—they'll show magenta until redefined or replaced with a different color. ## API Reference | Method | Description | | ------------------------------------------------- | ------------------------------------------------ | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define/update spot color with RGB approximation | | `engine.editor.setSpotColorCMYK(name, c, m, y, k)` | Define/update spot color with CMYK approximation | | `engine.editor.findAllSpotColors()` | Get array of all defined spot color names | | `engine.editor.getSpotColorRGBA(name)` | Query RGB approximation for a spot color | | `engine.editor.getSpotColorCMYK(name)` | Query CMYK approximation for a spot color | | `engine.editor.removeSpotColor(name)` | Remove a spot color from the registry | | `engine.editor.setSpotColorForCutoutType(type, color)` | Assign spot color to a cutout type | | `engine.editor.getSpotColorForCutoutType(type)` | Get spot color assigned to a cutout type | | `engine.block.setColor(block, property, color)` | Apply color (including SpotColor) to a property | | `engine.block.getColor(block, property)` | Read color from a block property | ## Next Steps - [Export for Printing](https://img.ly/docs/cesdk/angular/export-save-publish/for-printing-bca896/) - Export designs with spot colors for professional print production - [Apply Colors](https://img.ly/docs/cesdk/angular/colors/apply-2211e3/) - Apply colors to fills, strokes, and shadows - [CMYK Colors](https://img.ly/docs/cesdk/angular/colors/for-print/cmyk-8a1334/) - Work with CMYK process colors --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "For Screen" description: "Documentation for For Screen" platform: angular url: "https://img.ly/docs/cesdk/angular/colors/for-screen-1911f8/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/angular/colors/for-screen-1911f8/) --- --- ## Related Pages - [sRGB Colors](https://img.ly/docs/cesdk/angular/colors/for-screen/srgb-e6f59b/) - Work with sRGB colors for screen-based designs including creating RGBA colors, applying them to design elements, and converting from other color spaces. - [P3 Colors](https://img.ly/docs/cesdk/angular/colors/for-screen/p3-706127/) - Understand the P3 wide color gamut, its platform availability in CE.SDK, and alternatives for Web development. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "P3 Colors" description: "Understand the P3 wide color gamut, its platform availability in CE.SDK, and alternatives for Web development." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/for-screen/p3-706127/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/angular/colors/for-screen-1911f8/) > [P3 Colors](https://img.ly/docs/cesdk/angular/colors/for-screen/p3-706127/) --- Understand the P3 wide color gamut and its availability across CE.SDK platforms. > **Not Available on Web:** P3 colors are not currently supported on Web platforms. Use [sRGB colors](https://img.ly/docs/cesdk/angular/colors/for-screen/srgb-e6f59b/) instead. P3 enables more vibrant reds, greens, and other colors beyond the standard sRGB gamut. This is valuable for displays that support the DCI-P3 color space, including modern Apple devices and high-end monitors. ## What is P3? The DCI-P3 color space was developed for digital cinema and has been widely adopted in consumer displays, particularly by Apple since 2016. P3 covers roughly 25% more visible colors than sRGB, especially in the red, orange, and green-cyan regions. Key differences from sRGB: - **Gamut size**: P3 encompasses a larger color range - **Primary colors**: P3 red and green are more saturated - **Backwards compatibility**: P3 content on sRGB displays is automatically converted P3 colors only appear more vibrant on P3-capable displays. On sRGB displays, colors are converted and may appear less saturated. ## Platform Support **Supported platforms:** - **Android**: `supportsP3()` and `checkP3Support()` APIs - **iOS/Swift**: `supportsP3()` and `checkP3Support()` APIs **Not supported:** - Browser - Server (Node.js) On Web platforms, CE.SDK uses sRGB as the working color space. The Web binding supports sRGB, CMYK, and Spot Colors. ## P3 vs sRGB: When to Use Each | Use Case | Recommended | | --- | --- | | Native mobile apps (Apple devices) | P3 | | Photo/video editing with color accuracy | P3 | | Web applications | sRGB | | Cross-platform consistency | sRGB | ## Next Steps - [sRGB Colors](https://img.ly/docs/cesdk/angular/colors/for-screen/srgb-e6f59b/) — Apply sRGB colors for screen output - [Color Conversion](https://img.ly/docs/cesdk/angular/colors/conversion-bcd82b/) — Convert between color spaces --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "sRGB Colors" description: "Work with sRGB colors for screen-based designs including creating RGBA colors, applying them to design elements, and converting from other color spaces." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/for-screen/srgb-e6f59b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/angular/colors/for-screen-1911f8/) > [sRGB Colors](https://img.ly/docs/cesdk/angular/colors/for-screen/srgb-e6f59b/) --- Apply sRGB colors to design elements for screen-based output using RGBA color values with red, green, blue, and alpha components. ![sRGB Colors example showing colored shapes with fills, strokes, shadows, and transparency](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-for-screen-srgb-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-for-screen-srgb-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-for-screen-srgb-browser/) sRGB is the standard color space for screen displays. CE.SDK represents sRGB colors as RGBA objects where each component (red, green, blue, alpha) uses floating-point values between 0.0 and 1.0. This differs from the traditional 0-255 integer range used in many design tools. ```typescript file=@cesdk_web_examples/guides-colors-for-screen-srgb-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { isRGBAColor } from '@cesdk/engine'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: sRGB Colors Guide * * This example demonstrates: * - Creating sRGB/RGBA colors * - Applying sRGB colors to fills, strokes, and shadows * - Retrieving colors from design elements * - Converting colors to sRGB * - Working with alpha transparency * - Using type guards to identify RGBA colors */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a design scene using CE.SDK await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Create RGBA color objects for sRGB color space // Values are floating-point numbers between 0.0 and 1.0 const blueColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }; const redColor = { r: 0.9, g: 0.2, b: 0.2, a: 1.0 }; const greenColor = { r: 0.2, g: 0.8, b: 0.3, a: 1.0 }; // Create semi-transparent colors using the alpha channel // Alpha of 0.5 means 50% opacity const semiTransparentPurple = { r: 0.6, g: 0.2, b: 0.8, a: 0.5 }; const semiTransparentOrange = { r: 1.0, g: 0.5, b: 0.0, a: 0.7 }; // Create blocks to demonstrate color application const block1 = engine.block.create('graphic'); engine.block.setShape(block1, engine.block.createShape('rect')); engine.block.setWidth(block1, 150); engine.block.setHeight(block1, 150); engine.block.setPositionX(block1, 50); engine.block.setPositionY(block1, 50); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); engine.block.setShape(block2, engine.block.createShape('ellipse')); engine.block.setWidth(block2, 150); engine.block.setHeight(block2, 150); engine.block.setPositionX(block2, 250); engine.block.setPositionY(block2, 50); engine.block.appendChild(page, block2); const block3 = engine.block.create('graphic'); engine.block.setShape(block3, engine.block.createShape('rect')); engine.block.setWidth(block3, 150); engine.block.setHeight(block3, 150); engine.block.setPositionX(block3, 450); engine.block.setPositionY(block3, 50); engine.block.appendChild(page, block3); // Apply sRGB colors to block fills // First create a color fill, then set its color value const fill1 = engine.block.createFill('color'); engine.block.setFill(block1, fill1); engine.block.setColor(fill1, 'fill/color/value', blueColor); const fill2 = engine.block.createFill('color'); engine.block.setFill(block2, fill2); engine.block.setColor(fill2, 'fill/color/value', redColor); const fill3 = engine.block.createFill('color'); engine.block.setFill(block3, fill3); engine.block.setColor(fill3, 'fill/color/value', greenColor); // Create blocks for stroke demonstration const strokeBlock = engine.block.create('graphic'); engine.block.setShape(strokeBlock, engine.block.createShape('rect')); engine.block.setWidth(strokeBlock, 150); engine.block.setHeight(strokeBlock, 150); engine.block.setPositionX(strokeBlock, 50); engine.block.setPositionY(strokeBlock, 250); engine.block.appendChild(page, strokeBlock); const strokeFill = engine.block.createFill('color'); engine.block.setFill(strokeBlock, strokeFill); engine.block.setColor(strokeFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); // Apply sRGB color to stroke engine.block.setStrokeEnabled(strokeBlock, true); engine.block.setStrokeWidth(strokeBlock, 5); engine.block.setColor(strokeBlock, 'stroke/color', { r: 0.1, g: 0.1, b: 0.5, a: 1.0 }); // Create block for drop shadow demonstration const shadowBlock = engine.block.create('graphic'); engine.block.setShape(shadowBlock, engine.block.createShape('rect')); engine.block.setWidth(shadowBlock, 150); engine.block.setHeight(shadowBlock, 150); engine.block.setPositionX(shadowBlock, 250); engine.block.setPositionY(shadowBlock, 250); engine.block.appendChild(page, shadowBlock); const shadowFill = engine.block.createFill('color'); engine.block.setFill(shadowBlock, shadowFill); engine.block.setColor(shadowFill, 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Apply sRGB color to drop shadow engine.block.setDropShadowEnabled(shadowBlock, true); engine.block.setDropShadowBlurRadiusX(shadowBlock, 10); engine.block.setDropShadowBlurRadiusY(shadowBlock, 10); engine.block.setDropShadowOffsetX(shadowBlock, 5); engine.block.setDropShadowOffsetY(shadowBlock, 5); engine.block.setColor(shadowBlock, 'dropShadow/color', { r: 0.0, g: 0.0, b: 0.0, a: 0.4 }); // Create blocks for transparency demonstration const transparentBlock1 = engine.block.create('graphic'); engine.block.setShape(transparentBlock1, engine.block.createShape('rect')); engine.block.setWidth(transparentBlock1, 150); engine.block.setHeight(transparentBlock1, 150); engine.block.setPositionX(transparentBlock1, 450); engine.block.setPositionY(transparentBlock1, 250); engine.block.appendChild(page, transparentBlock1); const transparentFill1 = engine.block.createFill('color'); engine.block.setFill(transparentBlock1, transparentFill1); engine.block.setColor( transparentFill1, 'fill/color/value', semiTransparentPurple ); // Overlapping block to show transparency const transparentBlock2 = engine.block.create('graphic'); engine.block.setShape( transparentBlock2, engine.block.createShape('ellipse') ); engine.block.setWidth(transparentBlock2, 150); engine.block.setHeight(transparentBlock2, 150); engine.block.setPositionX(transparentBlock2, 500); engine.block.setPositionY(transparentBlock2, 300); engine.block.appendChild(page, transparentBlock2); const transparentFill2 = engine.block.createFill('color'); engine.block.setFill(transparentBlock2, transparentFill2); engine.block.setColor( transparentFill2, 'fill/color/value', semiTransparentOrange ); // Retrieve the current color from a design element const currentColor = engine.block.getColor(fill1, 'fill/color/value'); console.log('Current color:', currentColor); // Use type guard to check if color is RGBA (sRGB) if (isRGBAColor(currentColor)) { console.log('Color is sRGB/RGBA'); console.log('Red:', currentColor.r); console.log('Green:', currentColor.g); console.log('Blue:', currentColor.b); console.log('Alpha:', currentColor.a); } // Convert a CMYK color to sRGB const cmykColor = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }; const convertedToSrgb = engine.editor.convertColorToColorSpace( cmykColor, 'sRGB' ); console.log('Converted to sRGB:', convertedToSrgb); // Zoom to fit content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` This guide covers creating RGBA color objects, applying them to fills, strokes, and shadows, retrieving colors from elements, converting colors to sRGB, and working with transparency. ## Using the Built-in Color Picker UI The built-in color picker allows users to select sRGB colors visually. Users can pick colors from a gradient, enter hex values, or adjust RGB sliders. The color picker automatically handles value conversion between hex and floating-point formats. The UI includes: - Color picker gradient and hue slider - Hex value input field - RGB component sliders - Opacity/alpha slider for transparency control ## Creating sRGB Colors Programmatically We first set up a design scene and get a reference to the page. ```typescript highlight=highlight-setup // Create a design scene using CE.SDK await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } ``` We create RGBA color objects by specifying r, g, b, and a properties. All four components are required and use values from 0.0 to 1.0. ```typescript highlight=highlight-create-rgba // Create RGBA color objects for sRGB color space // Values are floating-point numbers between 0.0 and 1.0 const blueColor = { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }; const redColor = { r: 0.9, g: 0.2, b: 0.2, a: 1.0 }; const greenColor = { r: 0.2, g: 0.8, b: 0.3, a: 1.0 }; ``` The alpha channel controls transparency. A value of 1.0 is fully opaque, while 0.0 is fully transparent. ```typescript highlight=highlight-create-transparent // Create semi-transparent colors using the alpha channel // Alpha of 0.5 means 50% opacity const semiTransparentPurple = { r: 0.6, g: 0.2, b: 0.8, a: 0.5 }; const semiTransparentOrange = { r: 1.0, g: 0.5, b: 0.0, a: 0.7 }; ``` ## Applying sRGB Colors to Fills We use `engine.block.setColor()` to apply colors to block properties. For fills, we first create a color fill and then set its color value. ```typescript highlight=highlight-apply-fill // Apply sRGB colors to block fills // First create a color fill, then set its color value const fill1 = engine.block.createFill('color'); engine.block.setFill(block1, fill1); engine.block.setColor(fill1, 'fill/color/value', blueColor); const fill2 = engine.block.createFill('color'); engine.block.setFill(block2, fill2); engine.block.setColor(fill2, 'fill/color/value', redColor); const fill3 = engine.block.createFill('color'); engine.block.setFill(block3, fill3); engine.block.setColor(fill3, 'fill/color/value', greenColor); ``` ## Applying sRGB Colors to Strokes We can apply sRGB colors to strokes using the `'stroke/color'` property path. ```typescript highlight=highlight-apply-stroke // Apply sRGB color to stroke engine.block.setStrokeEnabled(strokeBlock, true); engine.block.setStrokeWidth(strokeBlock, 5); engine.block.setColor(strokeBlock, 'stroke/color', { r: 0.1, g: 0.1, b: 0.5, a: 1.0 }); ``` ## Applying sRGB Colors to Shadows Drop shadows also support sRGB colors. We use the `'dropShadow/color'` property path. A semi-transparent black creates a natural shadow effect. ```typescript highlight=highlight-apply-shadow // Apply sRGB color to drop shadow engine.block.setDropShadowEnabled(shadowBlock, true); engine.block.setDropShadowBlurRadiusX(shadowBlock, 10); engine.block.setDropShadowBlurRadiusY(shadowBlock, 10); engine.block.setDropShadowOffsetX(shadowBlock, 5); engine.block.setDropShadowOffsetY(shadowBlock, 5); engine.block.setColor(shadowBlock, 'dropShadow/color', { r: 0.0, g: 0.0, b: 0.0, a: 0.4 }); ``` ## Retrieving Colors from Elements We use `engine.block.getColor()` to read the current color from a design element. The returned color could be RGBA, CMYK, or a spot color depending on what was set. ```typescript highlight=highlight-get-color // Retrieve the current color from a design element const currentColor = engine.block.getColor(fill1, 'fill/color/value'); console.log('Current color:', currentColor); ``` ## Identifying sRGB Colors We use the `isRGBAColor()` type guard to check if a color is sRGB. This is useful when working with colors that could be from any supported color space. ```typescript highlight=highlight-identify-rgba // Use type guard to check if color is RGBA (sRGB) if (isRGBAColor(currentColor)) { console.log('Color is sRGB/RGBA'); console.log('Red:', currentColor.r); console.log('Green:', currentColor.g); console.log('Blue:', currentColor.b); console.log('Alpha:', currentColor.a); } ``` ## Converting Colors to sRGB We use `engine.editor.convertColorToColorSpace()` to convert CMYK or spot colors to sRGB for screen display. ```typescript highlight=highlight-convert-to-srgb // Convert a CMYK color to sRGB const cmykColor = { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }; const convertedToSrgb = engine.editor.convertColorToColorSpace( cmykColor, 'sRGB' ); console.log('Converted to sRGB:', convertedToSrgb); ``` ## API Reference | Method | Description | | ---------------------------------------- | ---------------------------------------------- | | `createFill('color')` | Create a new color fill object | | `setFill(block, fill)` | Assign fill to a block | | `setColor(block, property, color)` | Set color value (RGBA) | | `getColor(block, property)` | Get current color value | | `setStrokeEnabled(block, enabled)` | Enable or disable stroke rendering | | `setStrokeWidth(block, width)` | Set stroke width | | `setDropShadowEnabled(block, enabled)` | Enable or disable drop shadow | | `convertColorToColorSpace(color, space)` | Convert color to specified color space | | `isRGBAColor(color)` | Type guard to check if color is RGBA | ## Troubleshooting **Colors appear incorrect:** Verify values are in the 0.0-1.0 range, not 0-255. A value of 255 would be clamped to 1.0. **Color not visible:** Check that the alpha value is not 0.0 and that the fill or stroke is enabled on the block. **Type errors:** Ensure all four components (r, g, b, a) are provided for RGBA colors. Omitting alpha will cause validation errors. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/overview-16a177/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Overview](https://img.ly/docs/cesdk/angular/colors/overview-16a177/) --- Colors are a fundamental part of design in the CreativeEditor SDK (CE.SDK). Whether you're designing for digital screens or printed materials, consistent color management ensures your creations look the way you intend. CE.SDK offers flexible tools for working with colors through both the user interface and programmatically, making it easy to manage color workflows at any scale. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Replace Individual Colors" description: "Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects to swap colors or remove backgrounds." platform: angular url: "https://img.ly/docs/cesdk/angular/colors/replace-48cd71/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) > [Replace Individual Colors](https://img.ly/docs/cesdk/angular/colors/replace-48cd71/) --- Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects. ![Replace Colors Hero](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-colors-replace-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-colors-replace-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-colors-replace-browser/) CE.SDK provides two specialized effects for color replacement: the **Recolor** effect swaps pixels matching a source color with a target color, while the **Green Screen** effect removes pixels matching a specified color to create transparency. Both effects use configurable tolerance parameters to control which pixels are affected, enabling use cases from product color variations to background removal. ```typescript file=@cesdk_web_examples/guides-colors-replace-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Replace Colors Guide * * Demonstrates color replacement using Recolor and Green Screen effects: * - Using the built-in effects UI * - Creating and applying Recolor effects * - Creating and applying Green Screen effects * - Configuring effect properties * - Managing multiple effects */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Enable effects in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Calculate responsive grid layout for 6 examples const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Use sample images for demonstrations const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const blockSize = { width: blockWidth, height: blockHeight }; // Create a Recolor effect to swap red colors to blue const block1 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block1); const recolorEffect = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); // Red source color engine.block.setColor(recolorEffect, 'effect/recolor/toColor', { r: 0.0, g: 0.5, b: 1.0, a: 1.0 }); // Blue target color engine.block.appendEffect(block1, recolorEffect); // Select this block to show the effects panel engine.block.setSelected(block1, true); // Configure color matching precision for Recolor effect const block2 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block2); const recolorEffect2 = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', { r: 0.8, g: 0.6, b: 0.4, a: 1.0 }); // Skin tone source engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', { r: 0.3, g: 0.7, b: 0.3, a: 1.0 }); // Green tint // Adjust color match tolerance (0-1, higher = more inclusive) engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3); // Adjust brightness match tolerance engine.block.setFloat( recolorEffect2, 'effect/recolor/brightnessMatch', 0.2 ); // Adjust edge smoothness engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1); engine.block.appendEffect(block2, recolorEffect2); // Create a Green Screen effect to remove green backgrounds const block3 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block3); const greenScreenEffect = engine.block.createEffect('green_screen'); // Specify the color to remove (green) engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.appendEffect(block3, greenScreenEffect); // Fine-tune Green Screen removal parameters const block4 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block4); const greenScreenEffect2 = engine.block.createEffect('green_screen'); engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', { r: 0.2, g: 0.8, b: 0.3, a: 1.0 }); // Specific green shade // Adjust color match tolerance engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/colorMatch', 0.4 ); // Adjust edge smoothness for cleaner removal engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/smoothness', 0.2 ); // Reduce color spill from green background engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5); engine.block.appendEffect(block4, greenScreenEffect2); // Demonstrate managing multiple effects on a block const block5 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block5); // Add multiple effects to the same block const recolor1 = engine.block.createEffect('recolor'); engine.block.setColor(recolor1, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor1, 'effect/recolor/toColor', { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }); engine.block.appendEffect(block5, recolor1); const recolor2 = engine.block.createEffect('recolor'); engine.block.setColor(recolor2, 'effect/recolor/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor2, 'effect/recolor/toColor', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); engine.block.appendEffect(block5, recolor2); // Get all effects on the block const effects = engine.block.getEffects(block5); // eslint-disable-next-line no-console console.log('Number of effects:', effects.length); // 2 // Disable the first effect without removing it engine.block.setEffectEnabled(effects[0], false); // Check if effect is enabled const isEnabled = engine.block.isEffectEnabled(effects[0]); // eslint-disable-next-line no-console console.log('First effect enabled:', isEnabled); // false // Apply consistent color replacement across multiple blocks const block6 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block6); // Find all image blocks in the scene const allBlocks = engine.block.findByType('//ly.img.ubq/graphic'); // Apply a consistent recolor effect to each block allBlocks.forEach((blockId) => { // Skip if block already has effects if (engine.block.getEffects(blockId).length > 0) { return; } const batchRecolor = engine.block.createEffect('recolor'); engine.block.setColor(batchRecolor, 'effect/recolor/fromColor', { r: 0.8, g: 0.7, b: 0.6, a: 1.0 }); engine.block.setColor(batchRecolor, 'effect/recolor/toColor', { r: 0.6, g: 0.7, b: 0.9, a: 1.0 }); engine.block.setFloat(batchRecolor, 'effect/recolor/colorMatch', 0.25); engine.block.appendEffect(blockId, batchRecolor); }); // Position all blocks in a grid layout const blocks = [block1, block2, block3, block4, block5, block6]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Zoom to show all blocks engine.block.setSelected(block1, true); cesdk.engine.scene.zoomToBlock(page); } } export default Example; ``` This guide covers how to enable the built-in effects panel for interactive editing and how to apply and manage color replacement effects programmatically using the Block API. ## Using the Built-in Effects UI The CE.SDK editor provides a visual effects panel where users can add and configure Recolor and Green Screen effects interactively. Enable the effects feature using the Feature API, then users can access effects through the inspector panel. To enable effects in your editor configuration: ```typescript highlight=highlight-enable-effects-panel // Enable effects in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); ``` With effects enabled, users can: - Select an image block and open the effects panel - Add Recolor or Green Screen effects from the available options - Use visual color pickers to select source and target colors - Adjust tolerance sliders for color match, brightness match, and smoothness - See changes applied in real-time on the canvas ## Programmatic Color Replacement For automation workflows or custom implementations, you can create and apply color replacement effects programmatically using the Block API. Effects are created as blocks, configured with properties, and appended to target blocks. ### Creating a Recolor Effect The Recolor effect replaces pixels matching a source color with a target color. Use `engine.block.createEffect('recolor')` to create the effect, then set the `fromColor` and `toColor` properties using `engine.block.setColor()`. ```typescript highlight=highlight-create-recolor-effect // Create a Recolor effect to swap red colors to blue const block1 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block1); const recolorEffect = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); // Red source color engine.block.setColor(recolorEffect, 'effect/recolor/toColor', { r: 0.0, g: 0.5, b: 1.0, a: 1.0 }); // Blue target color engine.block.appendEffect(block1, recolorEffect); // Select this block to show the effects panel engine.block.setSelected(block1, true); ``` The `fromColor` specifies which color to match in the image, and `toColor` defines the replacement color. Colors use RGBA format with values from 0 to 1. ### Configuring Color Matching Precision Fine-tune which pixels are affected by adjusting the tolerance parameters with `engine.block.setFloat()`: ```typescript highlight=highlight-configure-recolor-matching // Configure color matching precision for Recolor effect const block2 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block2); const recolorEffect2 = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', { r: 0.8, g: 0.6, b: 0.4, a: 1.0 }); // Skin tone source engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', { r: 0.3, g: 0.7, b: 0.3, a: 1.0 }); // Green tint // Adjust color match tolerance (0-1, higher = more inclusive) engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3); // Adjust brightness match tolerance engine.block.setFloat( recolorEffect2, 'effect/recolor/brightnessMatch', 0.2 ); // Adjust edge smoothness engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1); engine.block.appendEffect(block2, recolorEffect2); ``` The Recolor effect has three precision parameters: - **colorMatch** (0-1): Controls hue tolerance. Higher values include more color variations around the source color. - **brightnessMatch** (0-1): Controls luminance tolerance. Higher values include pixels with different brightness levels. - **smoothness** (0-1): Controls edge blending. Higher values create softer transitions at the boundaries of affected areas. ### Creating a Green Screen Effect The Green Screen effect removes pixels matching a specified color, making them transparent. This is commonly used for background removal. ```typescript highlight=highlight-create-green-screen-effect // Create a Green Screen effect to remove green backgrounds const block3 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block3); const greenScreenEffect = engine.block.createEffect('green_screen'); // Specify the color to remove (green) engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.appendEffect(block3, greenScreenEffect); ``` Set the `fromColor` property to specify which color to remove. The effect will make matching pixels transparent. ### Configuring Green Screen Parameters Control the precision of color removal using these parameters: ```typescript highlight=highlight-configure-green-screen // Fine-tune Green Screen removal parameters const block4 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block4); const greenScreenEffect2 = engine.block.createEffect('green_screen'); engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', { r: 0.2, g: 0.8, b: 0.3, a: 1.0 }); // Specific green shade // Adjust color match tolerance engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/colorMatch', 0.4 ); // Adjust edge smoothness for cleaner removal engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/smoothness', 0.2 ); // Reduce color spill from green background engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5); engine.block.appendEffect(block4, greenScreenEffect2); ``` The Green Screen effect parameters: - **colorMatch**: Tolerance for matching the background color - **smoothness**: Edge softness for cleaner cutouts around subjects - **spill**: Reduces color bleed from the removed background onto the subject, useful when the background color reflects onto edges ## Managing Multiple Effects A single block can have multiple effects applied. Use the effect management APIs to list, toggle, and remove effects. ```typescript highlight=highlight-manage-effects // Demonstrate managing multiple effects on a block const block5 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block5); // Add multiple effects to the same block const recolor1 = engine.block.createEffect('recolor'); engine.block.setColor(recolor1, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor1, 'effect/recolor/toColor', { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }); engine.block.appendEffect(block5, recolor1); const recolor2 = engine.block.createEffect('recolor'); engine.block.setColor(recolor2, 'effect/recolor/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor2, 'effect/recolor/toColor', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); engine.block.appendEffect(block5, recolor2); // Get all effects on the block const effects = engine.block.getEffects(block5); // eslint-disable-next-line no-console console.log('Number of effects:', effects.length); // 2 // Disable the first effect without removing it engine.block.setEffectEnabled(effects[0], false); // Check if effect is enabled const isEnabled = engine.block.isEffectEnabled(effects[0]); // eslint-disable-next-line no-console console.log('First effect enabled:', isEnabled); // false ``` Key effect management methods: - `engine.block.getEffects(blockId)`: Returns an array of all effect IDs attached to a block - `engine.block.setEffectEnabled(effectId, enabled)`: Toggle an effect on/off without removing it - `engine.block.isEffectEnabled(effectId)`: Check whether an effect is currently active - `engine.block.removeEffect(blockId, index)`: Remove an effect by its index in the effects array Stacking multiple Recolor effects enables complex color transformations, such as replacing multiple colors in a single image or creating variations. ## Troubleshooting **Colors not matching as expected**: Increase the `colorMatch` tolerance for broader selection, or decrease it for more precise matching. Check that your source color closely matches the actual color in the image. **Harsh edges around replaced areas**: Increase the `smoothness` value to create softer transitions at the boundaries of affected pixels. **Color spill on Green Screen subjects**: Increase the `spill` value to reduce the green tint that often appears on edges when removing green backgrounds. **Effect not visible**: Verify that the effect is enabled using `isEffectEnabled()` and that it has been appended to the block using `appendEffect()`. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "System Compatibility" description: "Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities." platform: angular url: "https://img.ly/docs/cesdk/angular/compatibility-139ef9/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/angular/compatibility-fef719/) > [System Compatibility](https://img.ly/docs/cesdk/angular/compatibility-139ef9/) --- ## Recommended Hardware | Platform | Hardware | | ---------------- | ------------------------------------------------------------------------------ | | Desktop | A notebook or desktop released in the last 7 years and at least 4GB of memory. | | Mobile (Apple) | iPhone 8, iPad (6th gen) or newer | | Mobile (Android) | Phones & tablets released in the last 4 years | ## Video Our video feature introduces additional requirements and we generally distinguish playback (decoding) and export (encoding) capabilities. On the web, certain browser features directly depend on the host operating system. For video, this currently introduces the following limitations: - Transparency in H.265 videos is **not supported** on Windows hosts. - **Chrome on Linux** generally doesn't ship with encoder support for H.264 & AAC, which can cause video exports to fail even though decoding of non-free codecs is supported. - **Firefox** supports video editing (decoding) starting with version 130 via the WebCodecs API. However, video export is **not supported** because Firefox does not include the patent-encumbered H.264 and AAC codecs required for encoding. - **Chromium** although technically the base of Chrome doesn't include any codecs for licensing reasons and therefore can't be used for video editing. It does fall back to system-provided media libraries on e.g. macOS, but support is not guaranteed in any way. - **Linux browsers** generally have limited video support due to codec licensing. Video editing may work if the browser can decode H.264/AAC, but video export typically fails because open-source browser builds do not include the required encoders. - Video is **not supported** on mobile browsers on any platform due to technical limitations which result in performance issues. To detect these limitations at runtime, use the `video.decode.checkSupport` and `video.encode.checkSupport` actions, or the `cesdk.utils.supportsVideoDecode()` and `cesdk.utils.supportsVideoEncode()` utilities. ## Export Limitations The export size is limited by the hardware capabilities of the device, e.g., due to the maximum texture size that can be allocated. The maximum possible export size can be queried via API, see [export guide](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/). --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Compatibility & Security" description: "Learn about CE.SDK's compatibility and security features." platform: angular url: "https://img.ly/docs/cesdk/angular/compatibility-fef719/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/angular/compatibility-fef719/) --- CE.SDK provides robust compatibility and security features across platforms. Learn about supported browsers, frameworks, file formats, language support, and how CE.SDK ensures secure operation in your applications. --- ## Related Pages - [Browser Support](https://img.ly/docs/cesdk/angular/browser-support-28c1b0/) - Find out which browsers and versions fully support CE.SDK features, including editing and video capabilities. - [System Compatibility](https://img.ly/docs/cesdk/angular/compatibility-139ef9/) - Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities. - [File Format Support](https://img.ly/docs/cesdk/angular/file-format-support-3c4b2a/) - See which image, video, audio, font, and template formats CE.SDK supports for import and export. - [Security](https://img.ly/docs/cesdk/angular/security-777bfd/) - Learn how CE.SDK keeps your data private with client-side processing, secure licensing, and GDPR-compliant practices. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Concepts" description: "Key concepts and principles of CE.SDK" platform: angular url: "https://img.ly/docs/cesdk/angular/concepts-c9ff51/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) --- Key Concepts and principles of CE.SDK. --- ## Related Pages - [Key Concepts](https://img.ly/docs/cesdk/angular/key-concepts-21a270/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control. - [Key Capabilities](https://img.ly/docs/cesdk/angular/key-capabilities-dbb5b1/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control. - [Architecture](https://img.ly/docs/cesdk/angular/concepts/architecture-6ea9b2/) - Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state. - [Terminology](https://img.ly/docs/cesdk/angular/concepts/terminology-99e82d/) - Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more. - [Editing Workflow](https://img.ly/docs/cesdk/angular/concepts/editing-workflow-032d27/) - Control editing access with Creator, Adopter, Viewer, and Presenter roles using global and block-level scopes for tailored permissions. - [Blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) - Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK. - [Scenes](https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/) - Create, configure, save, and load scenes—the root container for all design elements in CE.SDK. - [Pages](https://img.ly/docs/cesdk/angular/concepts/pages-7b6bae/) - Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering. - [Assets](https://img.ly/docs/cesdk/angular/concepts/assets-a84fdd/) - Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically. - [Editor State](https://img.ly/docs/cesdk/angular/concepts/edit-modes-1f5b6c/) - Control how users interact with content by switching between edit modes like transform, crop, and text. - [Templating](https://img.ly/docs/cesdk/angular/concepts/templating-f94385/) - Understand how templates work in CE.SDK—reusable designs with variables for dynamic text and placeholders for swappable media. - [Events](https://img.ly/docs/cesdk/angular/concepts/events-353f97/) - Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene. - [Buffers](https://img.ly/docs/cesdk/angular/concepts/buffers-9c565b/) - Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API. - [Resources](https://img.ly/docs/cesdk/angular/concepts/resources-a58d71/) - Learn how CE.SDK loads and manages external media files, including preloading for performance, handling transient data, and relocating resources when URLs change. - [Undo and History](https://img.ly/docs/cesdk/angular/concepts/undo-and-history-99479d/) - Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls. - [Design Units](https://img.ly/docs/cesdk/angular/concepts/design-units-cc6597/) - Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK. - [Headless](https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/) - Run headless CE.SDK's Engine inside a browser-based app. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Architecture" description: "Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/architecture-6ea9b2/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Architecture](https://img.ly/docs/cesdk/angular/concepts/architecture-6ea9b2/) --- Understand how CE.SDK is structured around the CreativeEngine and its six interconnected APIs. CE.SDK is built around the **CreativeEngine**—a single-threaded core runtime that manages state, rendering, and coordination between six specialized APIs. Understanding how these pieces connect helps you navigate the SDK effectively. ## The CreativeEngine The *Engine* is the central coordinator. All operations—creating content, manipulating blocks, rendering, and exporting—flow through it. Initialize it once and access everything else through its API namespaces. The *Engine* manages: - **One active scene** containing all design content - **Six API namespaces** for different domains of functionality - **Event dispatching** for reactive state management - **Resource loading** and caching - **Rendering** to a canvas element (browser) or headless export (server) ## Content Hierarchy CE.SDK organizes content in a tree: *Scene* → *Pages* → *Blocks*. - **Scene**: The root container. One scene per engine instance. Operates in either *Design Mode* (static) or *Video Mode* (timeline-based). - **Pages**: Containers within a scene. Artboards in Design Mode, timeline compositions in Video Mode. - **Blocks**: The atomic units—graphics, text, audio, video. Everything visible is a block. The **Scene API** manages this hierarchy. The **Block API** manipulates individual blocks within it. See [Scenes](https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/), [Pages](https://img.ly/docs/cesdk/angular/concepts/pages-7b6bae/), and [Blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) for details. ## The Six APIs The engine exposes six API namespaces. Here's how they interconnect: ### Scene API (`engine.scene`) Creates and manages the content hierarchy. Works with the *Block API* to populate scenes with content and the *Event API* to notify when structure changes. ### Block API (`engine.block`) The most-used API. Creates, modifies, and queries blocks. Every visual element flows through here. Blocks reference *Assets* loaded through the *Asset API* and can contain *Variables* managed by the *Variable API*. ### Asset API (`engine.asset`) Provides content to the *Block API*. Registers asset sources (images, videos, stickers, templates) and handles queries. When you add an image to a block, the *Asset API* resolves it and the *Block API* applies it. ### Variable API (`engine.variable`) Enables data-driven designs. Define variables at the scene level; reference them in text blocks with `{{variableName}}` syntax. When variable values change, affected blocks update automatically—coordinated through the *Event API*. ### Editor API (`engine.editor`) Controls application state: edit modes, undo/redo history, user roles, and permissions. The *Editor API* determines what operations the *Block API* can perform based on current role and scope settings. ### Event API (`engine.event`) The reactive backbone. Subscribe to changes across all other APIs—block modifications, selection changes, history updates. Build UIs that stay synchronized with engine state. ## How They Connect A typical flow shows the interconnection: 1. **Scene API** creates the content structure 2. **Asset API** provides images, templates, or other content 3. **Block API** creates blocks and applies assets to them 4. **Variable API** injects dynamic data into text blocks 5. **Editor API** controls what users can modify 6. **Event API** notifies your UI of every change Each API focuses on one domain but works through the others. The *Engine* coordinates these interactions. ## Scene Modes The scene mode affects which features are available: - **Design Mode**: Static designs—social posts, print materials, graphics. Blocks positioned spatially. No timeline. - **Video Mode**: Time-based content with duration, playback, and animation. Blocks arranged across time. Choose the mode when creating a scene. It determines which *Block API* properties and *Editor API* capabilities are available. See [Scenes](https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/) for details. ## Integration Patterns CE.SDK runs in two contexts: - **Browser**: The engine renders to a canvas element. Append `engine.element` to your DOM. Use with the built-in UI or build your own. - **Headless**: No rendering, just processing. Use for server-side exports, automation, and batch operations. See [Headless Mode](https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/). Both contexts use the same six APIs—only rendering differs. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Assets" description: "Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/assets-a84fdd/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Assets](https://img.ly/docs/cesdk/angular/concepts/assets-a84fdd/) --- Understand the asset system—how external media and resources like images, stickers, or videos are handled in CE.SDK. ![Assets example showing asset source and applied content](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-assets-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-assets-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-assets-browser/) Images, videos, audio, fonts, stickers, and templates—every premade resource you can add to a design is what we call an *Asset*. The editor gets access to these Assets through *Asset Sources*. When you apply an Asset, CE.SDK creates or modifies a Block to display that content. ```typescript file=@cesdk_web_examples/guides-concepts-assets-browser/browser.ts reference-only import type { AssetQueryData, AssetResult, AssetSource, AssetsQueryResult, EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import packageJson from './package.json'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; /** * CE.SDK Plugin: Assets Concepts Guide * * Demonstrates the core concepts of the asset system: * - What assets are and how they differ from blocks * - Creating and registering asset sources * - Querying and applying assets */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const engine = cesdk.engine; // An asset is a content definition with metadata // It describes content that can be added to designs const stickerAsset: AssetResult = { id: 'sticker-smile', label: 'Smile Sticker', tags: ['emoji', 'happy'], groups: ['stickers'], meta: { uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg', thumbUri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg', blockType: '//ly.img.ubq/graphic', fillType: '//ly.img.ubq/fill/image', width: 62, height: 58, mimeType: 'image/svg+xml' } }; // Asset sources provide assets to the editor // Each source has an id and a findAssets() method const customSource: AssetSource = { id: 'my-assets', async findAssets(query: AssetQueryData): Promise { // Return paginated results matching the query return { assets: [stickerAsset], total: 1, currentPage: query.page, nextPage: undefined }; } }; engine.asset.addSource(customSource); // Query assets from a source const results = await engine.asset.findAssets('my-assets', { page: 0, perPage: 10 }); console.log('Found assets:', results.total); // Apply an asset to create a block in the scene if (results.assets.length > 0) { const blockId = await engine.asset.apply('my-assets', results.assets[0]); console.log('Created block:', blockId); // Center the sticker on the page const page = engine.scene.getCurrentPage(); if (page && blockId) { const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // SVG is 62x58, scale to fit nicely const stickerWidth = 62; const stickerHeight = 58; engine.block.setWidth(blockId, stickerWidth); engine.block.setHeight(blockId, stickerHeight); engine.block.setPositionX(blockId, (pageWidth - stickerWidth) / 2); engine.block.setPositionY(blockId, (pageHeight - stickerHeight) / 2); } } // Local sources support dynamic add/remove operations engine.asset.addLocalSource('uploads', ['image/svg+xml', 'image/png']); engine.asset.addAssetToSource('uploads', { id: 'uploaded-1', label: { en: 'Heart Sticker' }, meta: { uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg', thumbUri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg', blockType: '//ly.img.ubq/graphic', fillType: '//ly.img.ubq/fill/image', mimeType: 'image/svg+xml' } }); // Subscribe to asset source lifecycle events const unsubscribe = engine.asset.onAssetSourceUpdated((sourceId) => { console.log('Source updated:', sourceId); }); // Notify that source contents changed engine.asset.assetSourceContentsChanged('uploads'); unsubscribe(); } } export default Example; ``` This guide covers the core concepts of the Asset system. For detailed instructions on inserting specific media types, see the [Images](https://img.ly/docs/cesdk/angular/insert-media/images-63848a/), [Videos](https://img.ly/docs/cesdk/angular/insert-media/videos-a5fa03/), and [Shapes & Stickers](https://img.ly/docs/cesdk/angular/insert-media/shapes-or-stickers-20ac68/) guides. ## Assets vs Blocks **Assets** are content definitions with metadata (URIs, dimensions, labels) that exist outside the scene. **Blocks** are the visual elements in the scene tree that display content. When you apply an asset, CE.SDK creates a block configured according to the asset's properties. Multiple blocks can reference the same asset, and assets can exist without being used in any block. ## The Asset Data Model An asset describes content that can be added to designs. Each asset has an `id` and optional properties: ```typescript highlight-asset-definition // An asset is a content definition with metadata // It describes content that can be added to designs const stickerAsset: AssetResult = { id: 'sticker-smile', label: 'Smile Sticker', tags: ['emoji', 'happy'], groups: ['stickers'], meta: { uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg', thumbUri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg', blockType: '//ly.img.ubq/graphic', fillType: '//ly.img.ubq/fill/image', width: 62, height: 58, mimeType: 'image/svg+xml' } }; ``` Key properties include: - **`id`** — Unique identifier for the asset - **`label`** — Display name (can be localized) - **`tags`** — Searchable keywords - **`groups`** — Categories for filtering - **`meta`** — Content-specific data including `uri`, `thumbUri`, `blockType`, `fillType`, `width`, `height`, and `mimeType` > **Note:** See the [Content JSON Schema](https://img.ly/docs/cesdk/angular/import-media/content-json-schema-a7b3d2/) guide for the complete property reference. ## Asset Sources Asset sources provide assets to the editor. Each source has an `id` and implements a `findAssets()` method that returns paginated results. ```typescript highlight-asset-source // Asset sources provide assets to the editor // Each source has an id and a findAssets() method const customSource: AssetSource = { id: 'my-assets', async findAssets(query: AssetQueryData): Promise { // Return paginated results matching the query return { assets: [stickerAsset], total: 1, currentPage: query.page, nextPage: undefined }; } }; engine.asset.addSource(customSource); ``` The `findAssets()` callback receives query parameters (`page`, `perPage`, `query`, `tags`, `groups`) and returns a result object with `assets`, `total`, `currentPage`, and `nextPage`. Sources can also implement optional methods like `getGroups()`, `getSupportedMimeTypes()`, and `applyAsset()` for custom behavior. ## Querying Assets Search and filter assets from registered sources using `findAssets()`: ```typescript highlight-query-assets // Query assets from a source const results = await engine.asset.findAssets('my-assets', { page: 0, perPage: 10 }); console.log('Found assets:', results.total); ``` Results include pagination info. Loop through pages until `nextPage` is undefined to retrieve all matching assets. ## Applying Assets Use `apply()` to create a new block from an asset: ```typescript highlight-apply-asset // Apply an asset to create a block in the scene if (results.assets.length > 0) { const blockId = await engine.asset.apply('my-assets', results.assets[0]); console.log('Created block:', blockId); // Center the sticker on the page const page = engine.scene.getCurrentPage(); if (page && blockId) { const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // SVG is 62x58, scale to fit nicely const stickerWidth = 62; const stickerHeight = 58; engine.block.setWidth(blockId, stickerWidth); engine.block.setHeight(blockId, stickerHeight); engine.block.setPositionX(blockId, (pageWidth - stickerWidth) / 2); engine.block.setPositionY(blockId, (pageHeight - stickerHeight) / 2); } } ``` The method returns the new block ID, which you can use to position and configure the block. ## Local Asset Sources Local sources store assets in memory and support dynamic add/remove operations. Use these for user uploads or runtime-generated content: ```typescript highlight-local-source // Local sources support dynamic add/remove operations engine.asset.addLocalSource('uploads', ['image/svg+xml', 'image/png']); engine.asset.addAssetToSource('uploads', { id: 'uploaded-1', label: { en: 'Heart Sticker' }, meta: { uri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg', thumbUri: 'https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg', blockType: '//ly.img.ubq/graphic', fillType: '//ly.img.ubq/fill/image', mimeType: 'image/svg+xml' } }); ``` ## Source Events Subscribe to asset source lifecycle events for reactive UIs: ```typescript highlight-source-events // Subscribe to asset source lifecycle events const unsubscribe = engine.asset.onAssetSourceUpdated((sourceId) => { console.log('Source updated:', sourceId); }); // Notify that source contents changed engine.asset.assetSourceContentsChanged('uploads'); unsubscribe(); ``` Call `assetSourceContentsChanged()` after modifying a source to notify subscribers. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Blocks" description: "Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) --- Work with blocks—the fundamental building units for all visual elements in CE.SDK designs. ![Blocks example showing a scene with graphic and text blocks](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-blocks-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-blocks-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-blocks-browser/) Every visual element in CE.SDK—images, text, shapes, and audio—is represented as a block. Blocks are organized in a tree structure within scenes and pages, where parent-child relationships determine rendering order and visibility. Each block has properties you can read and modify, a `Type` that defines its core behavior, and an optional `Kind` for custom categorization. ```typescript file=@cesdk_web_examples/guides-concepts-blocks-browser/browser.ts reference-only import type { EditorPlugin,EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Blocks Guide * * Demonstrates working with blocks in CE.SDK: * - Block types (graphic, text, audio, page, cutout) * - Block hierarchy (parent-child relationships) * - Block lifecycle (create, duplicate, destroy) * - Block properties and reflection * - Selection and visibility * - Block state management * - Serialization (save/load) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the current scene and page const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } // Find the page block - pages contain all design elements const pages = engine.block.findByType('page'); const page = pages[0]; // Query the block type - returns the full type path const pageType = engine.block.getType(page); console.log('Page block type:', pageType); // '//ly.img.ubq/page' // Type is immutable, determined at creation // Kind is a custom label you can set and change engine.block.setKind(page, 'main-canvas'); const pageKind = engine.block.getKind(page); console.log('Page kind:', pageKind); // 'main-canvas' // Find blocks by kind const mainCanvasBlocks = engine.block.findByKind('main-canvas'); console.log('Blocks with kind "main-canvas":', mainCanvasBlocks.length); // Create a graphic block for an image const graphic = engine.block.create('graphic'); // Duplicate creates a copy with a new UUID const graphicCopy = engine.block.duplicate(graphic); // Destroy removes a block - the duplicate is no longer needed engine.block.destroy(graphicCopy); // Check if a block ID is still valid after operations const isOriginalValid = engine.block.isValid(graphic); const isCopyValid = engine.block.isValid(graphicCopy); console.log('Original valid:', isOriginalValid); // true console.log('Copy valid after destroy:', isCopyValid); // false // Create a rect shape to define the graphic's bounds const rectShape = engine.block.createShape('rect'); engine.block.setShape(graphic, rectShape); // Position and size the graphic (centered horizontally on 800px page) engine.block.setPositionX(graphic, 200); engine.block.setPositionY(graphic, 100); engine.block.setWidth(graphic, 400); engine.block.setHeight(graphic, 300); // Create an image fill and attach it to the graphic const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(graphic, imageFill); // Set content fill mode so the image fills the block bounds engine.block.setEnum(graphic, 'contentFill/mode', 'Cover'); // Blocks form a tree: scene > page > elements // Append the graphic to the page to make it visible engine.block.appendChild(page, graphic); // Query parent-child relationships const graphicParent = engine.block.getParent(graphic); console.log('Graphic parent is page:', graphicParent === page); // true const pageChildren = engine.block.getChildren(page); console.log('Page has children:', pageChildren.length); // Create a text block with content const textBlock = engine.block.create('text'); engine.block.appendChild(page, textBlock); // Position the text block (centered horizontally on 800px page) engine.block.setPositionX(textBlock, 200); engine.block.setPositionY(textBlock, 450); engine.block.setWidth(textBlock, 400); engine.block.setHeight(textBlock, 80); // Set text content engine.block.setString( textBlock, 'text/text', 'Blocks are the building units of CE.SDK designs' ); // Set font size to 72pt engine.block.setFloat(textBlock, 'text/fontSize', 72); // Center-align the text engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); // Check the text block type const textType = engine.block.getType(textBlock); console.log('Text block type:', textType); // '//ly.img.ubq/text' // Use reflection to discover available properties const graphicProperties = engine.block.findAllProperties(graphic); console.log('Graphic block has', graphicProperties.length, 'properties'); // Get property type information const opacityType = engine.block.getPropertyType('opacity'); console.log('Opacity property type:', opacityType); // 'Float' // Check if properties are readable/writable const isOpacityReadable = engine.block.isPropertyReadable('opacity'); const isOpacityWritable = engine.block.isPropertyWritable('opacity'); console.log( 'Opacity readable:', isOpacityReadable, 'writable:', isOpacityWritable ); // Use type-specific getters and setters // Float properties engine.block.setFloat(graphic, 'opacity', 0.9); const opacity = engine.block.getFloat(graphic, 'opacity'); console.log('Graphic opacity:', opacity); // Bool properties engine.block.setBool(page, 'page/marginEnabled', false); const marginEnabled = engine.block.getBool(page, 'page/marginEnabled'); console.log('Page margin enabled:', marginEnabled); // Enum properties - get allowed values first const blendModes = engine.block.getEnumValues('blend/mode'); console.log( 'Available blend modes:', blendModes.slice(0, 3).join(', '), '...' ); engine.block.setEnum(graphic, 'blend/mode', 'Multiply'); const blendMode = engine.block.getEnum(graphic, 'blend/mode'); console.log('Graphic blend mode:', blendMode); // Each block has a stable UUID across save/load cycles const graphicUUID = engine.block.getUUID(graphic); console.log('Graphic UUID:', graphicUUID); // Block names are mutable labels for organization engine.block.setName(graphic, 'Hero Image'); engine.block.setName(textBlock, 'Caption'); const graphicName = engine.block.getName(graphic); console.log('Graphic name:', graphicName); // 'Hero Image' // Select a block programmatically engine.block.select(graphic); // Selects graphic, deselects others // Check selection state const isGraphicSelected = engine.block.isSelected(graphic); console.log('Graphic is selected:', isGraphicSelected); // true // Add to selection without deselecting others engine.block.setSelected(textBlock, true); // Get all selected blocks const selectedBlocks = engine.block.findAllSelected(); console.log('Selected blocks count:', selectedBlocks.length); // 2 // Subscribe to selection changes const unsubscribeSelection = engine.block.onSelectionChanged(() => { const selected = engine.block.findAllSelected(); console.log( 'Selection changed, now selected:', selected.length, 'blocks' ); }); // Control block visibility engine.block.setVisible(graphic, true); const isVisible = engine.block.isVisible(graphic); console.log('Graphic is visible:', isVisible); // Control export inclusion engine.block.setIncludedInExport(graphic, true); const inExport = engine.block.isIncludedInExport(graphic); console.log('Graphic included in export:', inExport); // Control clipping behavior engine.block.setClipped(graphic, false); const isClipped = engine.block.isClipped(graphic); console.log('Graphic is clipped:', isClipped); // Query block state - indicates loading status const graphicState = engine.block.getState(graphic); console.log('Graphic state:', graphicState.type); // 'Ready', 'Pending', or 'Error' // Subscribe to state changes (useful for loading indicators) const unsubscribeState = engine.block.onStateChanged( [graphic], (changedBlocks) => { for (const blockId of changedBlocks) { const state = engine.block.getState(blockId); console.log(`Block ${blockId} state changed to:`, state.type); if (state.type === 'Pending' && state.progress !== undefined) { console.log( 'Loading progress:', Math.round(state.progress * 100) + '%' ); } } } ); // Save blocks to a string for persistence // Include 'bundle' scheme to allow serialization of blocks with bundled fonts const savedString = await engine.block.saveToString( [graphic, textBlock], ['buffer', 'http', 'https', 'bundle'] ); console.log('Blocks saved to string, length:', savedString.length); // Alternatively, blocks can also be saved with their assets to an archive // const savedBlocksArchive = await engine.block.saveToArchive([ // graphic, // textBlock // ]); // Load blocks from string (creates new blocks, not attached to scene) const loadedBlocks = await engine.block.loadFromString(savedString); console.log('Loaded blocks from string:', loadedBlocks.length); // Alternatively, blocks can also be loaded from an archive // const loadedBlocks = await engine.block.loadFromArchiveURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1_blocks.zip' // ); // console.log('Loaded blocks from archive URL:', loadedBlocks.length); // Alternatively, blocks can be loaded from an extracted zip file created with block.saveToArchive // const loadedBlocks = await engine.block.loadFromURL( // 'https://cdn.img.ly/assets/v6/ly.img.text.components/box/blocks.blocks' // ); // console.log('Loaded blocks from URL:', loadedBlocks.length); // Loaded blocks must be parented to appear in the scene // For demo purposes, we won't add them to avoid duplicates for (const block of loadedBlocks) { engine.block.destroy(block); } // Clean up subscriptions when done // In a real application, you'd keep these active as needed unsubscribeSelection(); unsubscribeState(); console.log('Blocks guide initialized successfully.'); console.log('Created graphic block with image fill and text block.'); console.log( 'Demonstrated: types, hierarchy, properties, selection, state, and serialization.' ); } } export default Example; ``` This guide covers block types and their uses, how to create and manage blocks programmatically, how to work with block properties using the reflection system, and how to handle selection, visibility, and state changes. ## Block Types CE.SDK provides several block types, each designed for specific content: - **graphic** (`//ly.img.ubq/graphic`): Visual blocks for images, shapes, and graphics - **text** (`//ly.img.ubq/text`): Text content with typography controls - **audio** (`//ly.img.ubq/audio`): Audio content for video scenes - **page** (`//ly.img.ubq/page`): Container blocks representing canvases or artboards - **cutout** (`//ly.img.ubq/cutout`): Blocks for masking operations We query a block's type using `getType()` and find blocks of a specific type with `findByType()`: ```typescript highlight-block-types // Get the current scene and page const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } // Find the page block - pages contain all design elements const pages = engine.block.findByType('page'); const page = pages[0]; // Query the block type - returns the full type path const pageType = engine.block.getType(page); console.log('Page block type:', pageType); // '//ly.img.ubq/page' ``` Block types are immutable—once created, a block's type cannot change. This distinguishes type from kind. ## Type vs Kind Type and kind serve different purposes. The **type** is determined at creation and defines core behavior. The **kind** is a custom string label you assign for application-specific categorization. ```typescript highlight-type-vs-kind // Type is immutable, determined at creation // Kind is a custom label you can set and change engine.block.setKind(page, 'main-canvas'); const pageKind = engine.block.getKind(page); console.log('Page kind:', pageKind); // 'main-canvas' // Find blocks by kind const mainCanvasBlocks = engine.block.findByKind('main-canvas'); console.log('Blocks with kind "main-canvas":', mainCanvasBlocks.length); ``` Use kind to tag blocks for your application's logic. You can set it with `setKind()`, query it with `getKind()`, and find blocks by kind with `findByKind()`. ## Block Hierarchy Blocks form a tree structure where scenes contain pages, and pages contain design elements. ```typescript highlight-block-hierarchy // Blocks form a tree: scene > page > elements // Append the graphic to the page to make it visible engine.block.appendChild(page, graphic); // Query parent-child relationships const graphicParent = engine.block.getParent(graphic); console.log('Graphic parent is page:', graphicParent === page); // true const pageChildren = engine.block.getChildren(page); console.log('Page has children:', pageChildren.length); ``` Only blocks that are direct or indirect children of a page block are rendered. A scene without any page children won't display content in the editor. Use `appendChild()` to add blocks to parents, `getParent()` to query a block's parent, and `getChildren()` to get a block's children. ## Block Lifecycle Create new blocks with `create()`, duplicate existing blocks with `duplicate()`, and remove blocks with `destroy()`. After destroying a block, `isValid()` returns `false` for that block ID. ```typescript highlight-block-lifecycle // Create a graphic block for an image const graphic = engine.block.create('graphic'); // Duplicate creates a copy with a new UUID const graphicCopy = engine.block.duplicate(graphic); // Destroy removes a block - the duplicate is no longer needed engine.block.destroy(graphicCopy); // Check if a block ID is still valid after operations const isOriginalValid = engine.block.isValid(graphic); const isCopyValid = engine.block.isValid(graphicCopy); console.log('Original valid:', isOriginalValid); // true console.log('Copy valid after destroy:', isCopyValid); // false ``` When duplicating a block, all children are included, and the duplicate receives a new UUID. ## Working with Fills Graphic blocks display content through fills. We create a fill, attach it to a block, and configure its source. ```typescript highlight-fill // Create a rect shape to define the graphic's bounds const rectShape = engine.block.createShape('rect'); engine.block.setShape(graphic, rectShape); // Position and size the graphic (centered horizontally on 800px page) engine.block.setPositionX(graphic, 200); engine.block.setPositionY(graphic, 100); engine.block.setWidth(graphic, 400); engine.block.setHeight(graphic, 300); // Create an image fill and attach it to the graphic const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(graphic, imageFill); // Set content fill mode so the image fills the block bounds engine.block.setEnum(graphic, 'contentFill/mode', 'Cover'); ``` CE.SDK supports several fill types including image, video, color, and gradient fills. See the [Fills guide](https://img.ly/docs/cesdk/angular/filters-and-effects/gradients-0ff079/) for details on available fill types. ## Creating Text Blocks Text blocks display formatted text content. We create a text block, position it, and set its content. ```typescript highlight-text-block // Create a text block with content const textBlock = engine.block.create('text'); engine.block.appendChild(page, textBlock); // Position the text block (centered horizontally on 800px page) engine.block.setPositionX(textBlock, 200); engine.block.setPositionY(textBlock, 450); engine.block.setWidth(textBlock, 400); engine.block.setHeight(textBlock, 80); // Set text content engine.block.setString( textBlock, 'text/text', 'Blocks are the building units of CE.SDK designs' ); // Set font size to 72pt engine.block.setFloat(textBlock, 'text/fontSize', 72); // Center-align the text engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); // Check the text block type const textType = engine.block.getType(textBlock); console.log('Text block type:', textType); // '//ly.img.ubq/text' ``` Text blocks support extensive typography controls covered in the [Text guides](https://img.ly/docs/cesdk/angular/text-8a993a/). ## Block Properties The reflection system lets you discover and manipulate any block property dynamically. Use `findAllProperties()` to get all available properties for a block—they're prefixed by category like `shape/star/points` or `text/fontSize`. ```typescript highlight-block-properties // Use reflection to discover available properties const graphicProperties = engine.block.findAllProperties(graphic); console.log('Graphic block has', graphicProperties.length, 'properties'); // Get property type information const opacityType = engine.block.getPropertyType('opacity'); console.log('Opacity property type:', opacityType); // 'Float' // Check if properties are readable/writable const isOpacityReadable = engine.block.isPropertyReadable('opacity'); const isOpacityWritable = engine.block.isPropertyWritable('opacity'); console.log( 'Opacity readable:', isOpacityReadable, 'writable:', isOpacityWritable ); ``` Query property types with `getPropertyType()`. Returns include `Bool`, `Int`, `Float`, `Double`, `String`, `Color`, `Enum`, or `Struct`. For enum properties, use `getEnumValues()` to get allowed values. ### Property Accessors Use type-specific getters and setters matching the property type: ```typescript highlight-property-accessors // Use type-specific getters and setters // Float properties engine.block.setFloat(graphic, 'opacity', 0.9); const opacity = engine.block.getFloat(graphic, 'opacity'); console.log('Graphic opacity:', opacity); // Bool properties engine.block.setBool(page, 'page/marginEnabled', false); const marginEnabled = engine.block.getBool(page, 'page/marginEnabled'); console.log('Page margin enabled:', marginEnabled); // Enum properties - get allowed values first const blendModes = engine.block.getEnumValues('blend/mode'); console.log( 'Available blend modes:', blendModes.slice(0, 3).join(', '), '...' ); engine.block.setEnum(graphic, 'blend/mode', 'Multiply'); const blendMode = engine.block.getEnum(graphic, 'blend/mode'); console.log('Graphic blend mode:', blendMode); ``` Using the wrong accessor type for a property will cause an error. Always check `getPropertyType()` if you're unsure which accessor to use. ## UUID, Names, and Identity Each block has a UUID that remains stable across save and load operations. Block names are mutable labels for organization. ```typescript highlight-uuid-identity // Each block has a stable UUID across save/load cycles const graphicUUID = engine.block.getUUID(graphic); console.log('Graphic UUID:', graphicUUID); // Block names are mutable labels for organization engine.block.setName(graphic, 'Hero Image'); engine.block.setName(textBlock, 'Caption'); const graphicName = engine.block.getName(graphic); console.log('Graphic name:', graphicName); // 'Hero Image' ``` Use `getUUID()` when you need a persistent identifier for a block. Names are useful for user-facing labels and can be changed freely with `setName()`. ## Selection Control which blocks are selected programmatically. Use `select()` to select a single block (deselecting others) or `setSelected()` to modify selection without affecting other blocks. ```typescript highlight-selection // Select a block programmatically engine.block.select(graphic); // Selects graphic, deselects others // Check selection state const isGraphicSelected = engine.block.isSelected(graphic); console.log('Graphic is selected:', isGraphicSelected); // true // Add to selection without deselecting others engine.block.setSelected(textBlock, true); // Get all selected blocks const selectedBlocks = engine.block.findAllSelected(); console.log('Selected blocks count:', selectedBlocks.length); // 2 // Subscribe to selection changes const unsubscribeSelection = engine.block.onSelectionChanged(() => { const selected = engine.block.findAllSelected(); console.log( 'Selection changed, now selected:', selected.length, 'blocks' ); }); ``` Subscribe to selection changes with `onSelectionChanged()` to update your UI when the selection state changes. ## Visibility Control whether blocks appear on the canvas and are included in exports. ```typescript highlight-visibility // Control block visibility engine.block.setVisible(graphic, true); const isVisible = engine.block.isVisible(graphic); console.log('Graphic is visible:', isVisible); // Control export inclusion engine.block.setIncludedInExport(graphic, true); const inExport = engine.block.isIncludedInExport(graphic); console.log('Graphic included in export:', inExport); ``` A block with `isVisible()` returning true may still not appear if it hasn't been added to a parent, the parent is hidden, or another block obscures it. ### Clipping Clipping determines whether a block's content is constrained to its parent's bounds. When `setClipped(block, true)` is set, any portion of the block extending beyond its parent's boundaries is hidden. When clipping is disabled, the block renders fully even if it overflows its parent container. ```typescript highlight-clipping // Control clipping behavior engine.block.setClipped(graphic, false); const isClipped = engine.block.isClipped(graphic); console.log('Graphic is clipped:', isClipped); ``` ## Block State Blocks track loading progress and error conditions through a state system with three possible states: - **Ready**: Normal state, no pending operations - **Pending**: Operation in progress with optional progress value (0-1) - **Error**: Operation failed (`ImageDecoding`, `VideoDecoding`, `FileFetch`, `AudioDecoding`, `Unknown`) ```typescript highlight-block-state // Query block state - indicates loading status const graphicState = engine.block.getState(graphic); console.log('Graphic state:', graphicState.type); // 'Ready', 'Pending', or 'Error' // Subscribe to state changes (useful for loading indicators) const unsubscribeState = engine.block.onStateChanged( [graphic], (changedBlocks) => { for (const blockId of changedBlocks) { const state = engine.block.getState(blockId); console.log(`Block ${blockId} state changed to:`, state.type); if (state.type === 'Pending' && state.progress !== undefined) { console.log( 'Loading progress:', Math.round(state.progress * 100) + '%' ); } } } ); ``` Subscribe to state changes with `onStateChanged()` to show loading indicators or handle errors in your UI. ## Serialization Save blocks to strings for persistence and restore them later. ```typescript highlight-serialization // Save blocks to a string for persistence // Include 'bundle' scheme to allow serialization of blocks with bundled fonts const savedString = await engine.block.saveToString( [graphic, textBlock], ['buffer', 'http', 'https', 'bundle'] ); console.log('Blocks saved to string, length:', savedString.length); // Alternatively, blocks can also be saved with their assets to an archive // const savedBlocksArchive = await engine.block.saveToArchive([ // graphic, // textBlock // ]); // Load blocks from string (creates new blocks, not attached to scene) const loadedBlocks = await engine.block.loadFromString(savedString); console.log('Loaded blocks from string:', loadedBlocks.length); // Alternatively, blocks can also be loaded from an archive // const loadedBlocks = await engine.block.loadFromArchiveURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1_blocks.zip' // ); // console.log('Loaded blocks from archive URL:', loadedBlocks.length); // Alternatively, blocks can be loaded from an extracted zip file created with block.saveToArchive // const loadedBlocks = await engine.block.loadFromURL( // 'https://cdn.img.ly/assets/v6/ly.img.text.components/box/blocks.blocks' // ); // console.log('Loaded blocks from URL:', loadedBlocks.length); // Loaded blocks must be parented to appear in the scene // For demo purposes, we won't add them to avoid duplicates for (const block of loadedBlocks) { engine.block.destroy(block); } ``` Use `saveToString()` for lightweight serialization or `saveToArchive()` to include all referenced assets. Blocks can be loaded with `loadFromString()`, `loadFromArchiveURL()`, or `loadFromURL()`. For `loadFromArchiveURL()`, the URL should point to the zipped archive file previously saved with `saveToArchive()`, whereas for `loadFromURL()`, it should point to a blocks file within an unzipped archive directory. Loaded blocks are not automatically attached to the scene—you must parent them with `appendChild()` to make them visible. ## Troubleshooting **Block not visible**: Ensure the block is a child of a page that's a child of the scene. **Property setter fails**: Verify the property type matches the setter method used. Use `getPropertyType()` to check. **Block ID invalid after destroy**: Use `isValid()` before operations on potentially destroyed blocks. **State stuck in Pending**: Check network connectivity for remote resources or use state change events to monitor progress. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Buffers" description: "Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/buffers-9c565b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Buffers](https://img.ly/docs/cesdk/angular/concepts/buffers-9c565b/) --- Store and manage temporary binary data directly in memory using CE.SDK's buffer API for dynamically generated content. ![Buffers example showing audio waveform generated from buffer data](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-buffers-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-buffers-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-buffers-browser/) Buffers are in-memory containers for binary data referenced via `buffer://` URIs. Unlike external files that require network or file I/O, buffers exist only during the current session and are not serialized when saving scenes. This makes them ideal for procedural audio, real-time image data, or streaming content that doesn't need to persist beyond the current editing session. ```typescript file=@cesdk_web_examples/guides-concepts-buffers-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; // Helper function to create a WAV file from audio samples function createWavFile( samples: Float32Array, sampleRate: number, numChannels: number ): Uint8Array { const bytesPerSample = 2; // 16-bit audio const blockAlign = numChannels * bytesPerSample; const byteRate = sampleRate * blockAlign; const dataSize = samples.length * bytesPerSample; const fileSize = 44 + dataSize; // WAV header is 44 bytes const buffer = new ArrayBuffer(fileSize); const view = new DataView(buffer); // Write WAV header // "RIFF" chunk descriptor writeString(view, 0, 'RIFF'); view.setUint32(4, fileSize - 8, true); // File size minus RIFF header writeString(view, 8, 'WAVE'); // "fmt " sub-chunk writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); // Subchunk1Size (16 for PCM) view.setUint16(20, 1, true); // AudioFormat (1 = PCM) view.setUint16(22, numChannels, true); // NumChannels view.setUint32(24, sampleRate, true); // SampleRate view.setUint32(28, byteRate, true); // ByteRate view.setUint16(32, blockAlign, true); // BlockAlign view.setUint16(34, bytesPerSample * 8, true); // BitsPerSample // "data" sub-chunk writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); // Subchunk2Size // Write audio samples as 16-bit PCM let offset = 44; for (let i = 0; i < samples.length; i++) { // Convert float (-1 to 1) to 16-bit integer const sample = Math.max(-1, Math.min(1, samples[i])); const intSample = sample < 0 ? sample * 0x8000 : sample * 0x7fff; view.setInt16(offset, intSample, true); offset += 2; } return new Uint8Array(buffer); } function writeString(view: DataView, offset: number, str: string): void { for (let i = 0; i < str.length; i++) { view.setUint8(offset + i, str.charCodeAt(i)); } } class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; // Get the page (first container in video scenes) const pages = engine.block.findByType('page'); const page = pages[0]; // Add a centered text block to explain the example const textBlock = engine.block.create('text'); engine.block.setString( textBlock, 'text/text', 'The audio track in this scene lives in a buffer.' ); engine.block.setFloat(textBlock, 'text/fontSize', 108); engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); engine.block.setHeightMode(textBlock, 'Auto'); // Set text color to white engine.block.setColor(textBlock, 'fill/solid/color', { r: 1, g: 1, b: 1, a: 1 }); // Get page dimensions and position with 10% horizontal margin const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const horizontalMargin = pageWidth * 0.1; const textWidth = pageWidth - horizontalMargin * 2; engine.block.setWidth(textBlock, textWidth); engine.block.setPositionX(textBlock, horizontalMargin); // Append to page first so layout can be computed engine.block.appendChild(page, textBlock); // Force layout computation and get the actual frame height const textHeight = engine.block.getFrameHeight(textBlock); engine.block.setPositionY(textBlock, (pageHeight - textHeight) / 2); // Set duration to match the scene engine.block.setDuration(textBlock, 2); // Create a buffer and get its URI const bufferUri = engine.editor.createBuffer(); console.log('Buffer URI:', bufferUri); // Generate sine wave audio samples const sampleRate = 44100; const duration = 2; // 2 seconds const frequency = 440; // A4 note const numChannels = 2; // Stereo // Create Float32Array for audio samples (interleaved stereo) const numSamples = sampleRate * duration * numChannels; const samples = new Float32Array(numSamples); // Generate a 440 Hz sine wave for (let i = 0; i < numSamples; i += numChannels) { const sampleIndex = i / numChannels; const time = sampleIndex / sampleRate; const value = Math.sin(2 * Math.PI * frequency * time) * 0.5; // 50% amplitude // Write to both left and right channels samples[i] = value; // Left channel samples[i + 1] = value; // Right channel } // Convert samples to WAV format and write to buffer const wavData = createWavFile(samples, sampleRate, numChannels); engine.editor.setBufferData(bufferUri, 0, wavData); // Verify the buffer length const bufferLength = engine.editor.getBufferLength(bufferUri); console.log('Buffer length:', bufferLength, 'bytes'); // Create an audio block const audioBlock = engine.block.create('audio'); // Assign the buffer URI to the audio block engine.block.setString(audioBlock, 'audio/fileURI', bufferUri); // Set audio duration to match the generated samples engine.block.setDuration(audioBlock, duration); // Append the audio block to the page engine.block.appendChild(page, audioBlock); // Demonstrate reading buffer data back const readData = engine.editor.getBufferData(bufferUri, 0, 100); console.log('First 100 bytes of buffer data:', readData); // Demonstrate resizing a buffer with a separate demo buffer const demoBuffer = engine.editor.createBuffer(); engine.editor.setBufferData( demoBuffer, 0, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) ); const demoLength = engine.editor.getBufferLength(demoBuffer); console.log('Demo buffer length before resize:', demoLength); engine.editor.setBufferLength(demoBuffer, demoLength / 2); console.log( 'Demo buffer length after resize:', engine.editor.getBufferLength(demoBuffer) ); engine.editor.destroyBuffer(demoBuffer); // Find all transient resources (including our buffer) const transientResources = engine.editor.findAllTransientResources(); console.log('Transient resources in scene:'); for (const resource of transientResources) { console.log(` URL: ${resource.URL}, Size: ${resource.size} bytes`); } // Demonstrate persisting buffer data using a Blob URL // In production, you would upload to CDN/cloud storage instead const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength); const blob = new Blob([new Uint8Array(bufferData)], { type: 'audio/wav' }); const persistentUrl = URL.createObjectURL(blob); // Update all references from buffer:// to the new URL engine.editor.relocateResource(bufferUri, persistentUrl); console.log('Buffer relocated to:', persistentUrl); console.log('Buffers example loaded successfully'); console.log( 'Note: Audio playback requires user interaction in most browsers' ); } } export default Example; ``` This guide covers how to create and manage buffers, write and read binary data, assign buffers to block properties like audio sources, and handle transient resources when saving scenes. ## Setting Up a Video Scene Since this example uses audio blocks, we first create a video scene. Audio blocks require a timeline-based scene context. ```typescript highlight-create-video-scene await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); ``` ## Creating and Managing Buffers We use `engine.editor.createBuffer()` to allocate a new buffer and receive its URI. This URI follows the `buffer://` scheme and uniquely identifies the buffer within the engine instance. ```typescript highlight-create-buffer // Create a buffer and get its URI const bufferUri = engine.editor.createBuffer(); console.log('Buffer URI:', bufferUri); ``` Buffers persist in memory until you explicitly destroy them with `engine.editor.destroyBuffer()` or the engine instance is disposed. For large buffers or long editing sessions, you should destroy buffers when they're no longer needed to free memory. ## Writing Data to Buffers To populate a buffer with binary data, we use `engine.editor.setBufferData()`. This method takes the buffer URI, an offset in bytes, and a `Uint8Array` containing the data to write. In this example, we generate a 440 Hz sine wave as stereo PCM audio samples. We create a `Float32Array` for the sample values that will be converted to a valid audio format. ```typescript highlight-generate-samples // Generate sine wave audio samples const sampleRate = 44100; const duration = 2; // 2 seconds const frequency = 440; // A4 note const numChannels = 2; // Stereo // Create Float32Array for audio samples (interleaved stereo) const numSamples = sampleRate * duration * numChannels; const samples = new Float32Array(numSamples); // Generate a 440 Hz sine wave for (let i = 0; i < numSamples; i += numChannels) { const sampleIndex = i / numChannels; const time = sampleIndex / sampleRate; const value = Math.sin(2 * Math.PI * frequency * time) * 0.5; // 50% amplitude // Write to both left and right channels samples[i] = value; // Left channel samples[i + 1] = value; // Right channel } ``` When using buffers for audio, the data must be in a recognized audio format like WAV. We convert the raw samples to a WAV file by adding the appropriate headers, then write the complete file to the buffer. ```typescript highlight-write-buffer // Convert samples to WAV format and write to buffer const wavData = createWavFile(samples, sampleRate, numChannels); engine.editor.setBufferData(bufferUri, 0, wavData); // Verify the buffer length const bufferLength = engine.editor.getBufferLength(bufferUri); console.log('Buffer length:', bufferLength, 'bytes'); ``` ## Reading Data from Buffers To read data back from a buffer, we use `engine.editor.getBufferData()` with the buffer URI, a starting offset, and the number of bytes to read. We first query the buffer length with `engine.editor.getBufferLength()` to determine how much data is available. ```typescript highlight-read-buffer // Demonstrate reading buffer data back const readData = engine.editor.getBufferData(bufferUri, 0, 100); console.log('First 100 bytes of buffer data:', readData); ``` This returns a `Uint8Array` that you can convert back to other typed arrays as needed. Partial reads are supported—you can read any range within the buffer bounds. ## Resizing Buffers You can change a buffer's size at any time with `engine.editor.setBufferLength()`. Increasing the size allocates additional space, while decreasing it truncates the data. Here we demonstrate resizing with a separate demo buffer to avoid truncating our audio data. ```typescript highlight-resize-buffer // Demonstrate resizing a buffer with a separate demo buffer const demoBuffer = engine.editor.createBuffer(); engine.editor.setBufferData( demoBuffer, 0, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]) ); const demoLength = engine.editor.getBufferLength(demoBuffer); console.log('Demo buffer length before resize:', demoLength); engine.editor.setBufferLength(demoBuffer, demoLength / 2); console.log( 'Demo buffer length after resize:', engine.editor.getBufferLength(demoBuffer) ); engine.editor.destroyBuffer(demoBuffer); ``` Keep in mind that truncating a buffer permanently discards data beyond the new length. Always query the current length first if you need to preserve the original size, or create a copy before resizing. ## Assigning Buffers to Blocks Buffer URIs work like any other resource URI in CE.SDK. We assign them to block properties using `engine.block.setString()`. For audio blocks, we set the `audio/fileURI` property. ```typescript highlight-create-audio-block // Create an audio block const audioBlock = engine.block.create('audio'); // Assign the buffer URI to the audio block engine.block.setString(audioBlock, 'audio/fileURI', bufferUri); // Set audio duration to match the generated samples engine.block.setDuration(audioBlock, duration); // Append the audio block to the page engine.block.appendChild(page, audioBlock); ``` The same approach works for other resource properties: - **Audio blocks**: `audio/fileURI` - **Image fills**: `fill/image/imageFileURI` - **Video fills**: `fill/video/fileURI` Any property that accepts a URI can reference a buffer. ## Transient Resources and Scene Serialization Buffers are transient resources—the URI gets serialized when you save a scene, but the actual binary data does not persist. This means a saved scene will contain references to `buffer://` URIs that won't resolve when the scene is loaded again. We use `engine.editor.findAllTransientResources()` to discover all transient resources in the current scene, including buffers. Each resource includes its URL and size in bytes. ```typescript highlight-find-transient // Find all transient resources (including our buffer) const transientResources = engine.editor.findAllTransientResources(); console.log('Transient resources in scene:'); for (const resource of transientResources) { console.log(` URL: ${resource.URL}, Size: ${resource.size} bytes`); } ``` > **Note:** **Limitations**Buffers are intended for temporary data only.* Buffer data is not part of scene serialization > * Changes to buffers can't be undone using the history system Note that `engine.scene.saveToString()` does NOT include `buffer://` in its default allowed resource schemes, while `engine.block.saveToString()` does include it. You may need to configure the allowed schemes depending on your serialization needs. ## Persisting Buffer Data To permanently save buffer content, you must extract the data, upload it to persistent storage, then update the block references to point to the new URL. This example demonstrates the pattern using a Blob URL—in production, you would upload to a CDN or cloud storage instead. ```typescript highlight-persist-buffer // Demonstrate persisting buffer data using a Blob URL // In production, you would upload to CDN/cloud storage instead const bufferData = engine.editor.getBufferData(bufferUri, 0, bufferLength); const blob = new Blob([new Uint8Array(bufferData)], { type: 'audio/wav' }); const persistentUrl = URL.createObjectURL(blob); // Update all references from buffer:// to the new URL engine.editor.relocateResource(bufferUri, persistentUrl); console.log('Buffer relocated to:', persistentUrl); ``` We read the buffer data, create a persistent URL from it, then use `engine.editor.relocateResource()` to update all references to the old buffer URI throughout the scene. After relocation, you can save the scene and the new persistent URLs will be serialized. ## Troubleshooting **Buffer data not appearing in exported scene** Buffers are transient and don't persist with scene saves. Use `findAllTransientResources()` to identify buffers, then relocate them to persistent storage before exporting. **Memory usage growing unexpectedly** Call `engine.editor.destroyBuffer()` when buffers are no longer needed. Unlike external resources that can be garbage collected, buffers remain in memory until explicitly destroyed. **Data corruption when writing** Ensure the offset plus data length doesn't exceed the intended buffer bounds. Resize the buffer first with `setBufferLength()` if you need more space. **Buffer URI not recognized by block** Verify the buffer was created in the same engine instance. Buffer URIs are not portable between different engine instances or sessions. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Design Units" description: "Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/design-units-cc6597/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Design Units](https://img.ly/docs/cesdk/angular/concepts/design-units-cc6597/) --- Control measurement systems for precise physical dimensions—create print-ready documents with millimeter or inch units and configurable DPI for export quality. ![Design Units example showing an A4 document configured with millimeter units](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-design-units-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-design-units-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-design-units-browser/) Design units determine the coordinate system for all layout values in CE.SDK—positions, sizes, and margins. The engine supports three unit types: **Pixel** for screen-based designs, **Millimeter** for metric print dimensions, and **Inch** for imperial print formats. ```typescript file=@cesdk_web_examples/guides-concepts-design-units-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Design Units Guide * * Demonstrates working with design units in CE.SDK: * - Understanding unit types (Pixel, Millimeter, Inch) * - Getting and setting the design unit * - Configuring DPI for print output * - Setting up print-ready dimensions */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 210, height: 297, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the current scene const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } // Get the current design unit const currentUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Current design unit:', currentUnit); // 'Pixel' by default // Set design unit to Millimeter for print workflow engine.scene.setDesignUnit('Millimeter'); // Verify the change const newUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Design unit changed to:', newUnit); // 'Millimeter' // Set DPI to 300 for print-quality exports // Higher DPI produces higher resolution output engine.block.setFloat(scene, 'scene/dpi', 300); // Verify the DPI setting const dpi = engine.block.getFloat(scene, 'scene/dpi'); // eslint-disable-next-line no-console console.log('DPI set to:', dpi); // 300 // Get the page and set A4 dimensions (210 x 297 mm) const page = engine.block.findByType('page')[0]; // Verify dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // eslint-disable-next-line no-console console.log(`Page dimensions: ${pageWidth}mm x ${pageHeight}mm`); // Create a text block with millimeter dimensions const textBlock = engine.block.create('text'); engine.block.appendChild(page, textBlock); // Position text at 20mm from left, 30mm from top engine.block.setPositionX(textBlock, 20); engine.block.setPositionY(textBlock, 30); // Set text block size to 170mm x 50mm engine.block.setWidth(textBlock, 170); engine.block.setHeight(textBlock, 50); // Add content to the text block engine.block.setString( textBlock, 'text/text', 'This A4 document uses millimeter units with 300 DPI for print-ready output.' ); // Demonstrate unit comparison // At 300 DPI: 1 inch = 300 pixels, 1 mm = ~11.81 pixels // eslint-disable-next-line no-console console.log('Unit comparison at 300 DPI:'); // eslint-disable-next-line no-console console.log( '- A4 width (210mm) will export as', 210 * (300 / 25.4), 'pixels' ); // eslint-disable-next-line no-console console.log( '- A4 height (297mm) will export as', 297 * (300 / 25.4), 'pixels' ); // eslint-disable-next-line no-console console.log( 'Design units guide initialized. Scene configured for A4 print output.' ); } } export default Example; ``` This guide covers how to get and set design units, configure DPI for export quality, and set up scenes for specific physical dimensions like A4 paper. ## Understanding Design Units ### Supported Unit Types CE.SDK supports three design unit types, each suited for different output scenarios: - **Pixel** — Default unit, ideal for screen-based designs, web graphics, and video content. One unit equals one pixel in the design coordinate space. - **Millimeter** — For print designs targeting metric dimensions (A4, A5, business cards). One unit equals one millimeter at the scene's DPI setting. - **Inch** — For print designs targeting imperial dimensions (letter, legal, US business cards). One unit equals one inch at the scene's DPI setting. ### Design Unit and DPI Relationship DPI (dots per inch) determines how physical units convert to pixels during export. At 300 DPI, a 1-inch block exports as 300 pixels wide. Higher DPI values produce higher-resolution exports suitable for professional printing. For pixel-based scenes, DPI primarily affects font size conversions since font sizes are always specified in points. ## Getting the Current Design Unit Use `engine.scene.getDesignUnit()` to retrieve the current scene's design unit. This returns one of three values: `'Pixel'`, `'Millimeter'`, or `'Inch'`. ```typescript highlight-get-design-unit // Get the current scene const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } // Get the current design unit const currentUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Current design unit:', currentUnit); // 'Pixel' by default ``` ## Setting the Design Unit Use `engine.scene.setDesignUnit()` to change the measurement system. When you change the design unit, CE.SDK automatically converts existing layout values to maintain visual appearance. ```typescript highlight-set-design-unit // Set design unit to Millimeter for print workflow engine.scene.setDesignUnit('Millimeter'); // Verify the change const newUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Design unit changed to:', newUnit); // 'Millimeter' ``` ## Configuring DPI Access DPI through the scene's `scene/dpi` property. For print workflows, 300 DPI is the standard for high-quality output. ```typescript highlight-configure-dpi // Set DPI to 300 for print-quality exports // Higher DPI produces higher resolution output engine.block.setFloat(scene, 'scene/dpi', 300); // Verify the DPI setting const dpi = engine.block.getFloat(scene, 'scene/dpi'); // eslint-disable-next-line no-console console.log('DPI set to:', dpi); // 300 ``` DPI affects different aspects depending on the design unit: - **Physical units (mm, in)**: DPI determines the pixel resolution of exported files - **Pixel units**: DPI only affects the conversion of font sizes from points to pixels ## Setting Up Print-Ready Designs For print workflows, combine `setDesignUnit()` with appropriate DPI and page dimensions. Here's how to set up an A4 document ready for print export: ```typescript highlight-set-page-dimensions // Get the page and set A4 dimensions (210 x 297 mm) const page = engine.block.findByType('page')[0]; // Verify dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // eslint-disable-next-line no-console console.log(`Page dimensions: ${pageWidth}mm x ${pageHeight}mm`); ``` ## Font Sizes and Design Units Font sizes are always specified in points (`pt`), regardless of the scene's design unit. The DPI setting affects how points convert to pixels for rendering. ```typescript highlight-create-text-block // Create a text block with millimeter dimensions const textBlock = engine.block.create('text'); engine.block.appendChild(page, textBlock); // Position text at 20mm from left, 30mm from top engine.block.setPositionX(textBlock, 20); engine.block.setPositionY(textBlock, 30); // Set text block size to 170mm x 50mm engine.block.setWidth(textBlock, 170); engine.block.setHeight(textBlock, 50); // Add content to the text block engine.block.setString( textBlock, 'text/text', 'This A4 document uses millimeter units with 300 DPI for print-ready output.' ); ``` When DPI changes, text blocks automatically adjust their rendered size to maintain visual consistency. ## Understanding Export Resolution The relationship between design units and export resolution is important for print workflows: ```typescript highlight-compare-units // Demonstrate unit comparison // At 300 DPI: 1 inch = 300 pixels, 1 mm = ~11.81 pixels // eslint-disable-next-line no-console console.log('Unit comparison at 300 DPI:'); // eslint-disable-next-line no-console console.log( '- A4 width (210mm) will export as', 210 * (300 / 25.4), 'pixels' ); // eslint-disable-next-line no-console console.log( '- A4 height (297mm) will export as', 297 * (300 / 25.4), 'pixels' ); ``` At 300 DPI: - An A4 page (210 × 297 mm) exports as 2480 × 3508 pixels - A letter page (8.5 × 11 in) exports as 2550 × 3300 pixels ## Troubleshooting ### Exported Dimensions Don't Match Expected Size Verify that DPI is set correctly for physical units. At 300 DPI, 1 inch becomes 300 pixels. Check that your design unit matches your target output format. ### Text Appears Wrong Size After Unit Change Font sizes in points auto-adjust based on DPI. If text looks incorrect, verify the DPI setting matches your workflow requirements. ### Blocks Shift Position After Changing Units CE.SDK preserves visual appearance during unit conversion. If positions seem unexpected, check the original coordinate values—the numeric values change but visual positions should remain stable. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Editor State" description: "Control how users interact with content by switching between edit modes like transform, crop, and text." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/edit-modes-1f5b6c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Editor State](https://img.ly/docs/cesdk/angular/concepts/edit-modes-1f5b6c/) --- Editor state determines how users interact with content on the canvas by controlling which editing mode is active and tracking cursor behavior. This guide covers edit modes, state change subscriptions, cursor state, and interaction detection. ![CE.SDK Editor State Hero Image](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-editor-state-browser/) Edit modes define what type of content users can currently modify. Each mode enables different interaction behaviors—Transform mode for moving and resizing, Crop mode for adjusting content within frames, Text mode for inline text editing, and so on. The engine maintains the current edit mode as part of its state and notifies subscribers when it changes. ```typescript file=@cesdk_web_examples/guides-concepts-editor-state-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Editor State Guide * * Demonstrates working with editor state in CE.SDK: * - Understanding edit modes and switching between them * - Subscribing to state changes * - Reading cursor type and rotation * - Tracking text cursor position * - Detecting active interactions */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Add an image block to demonstrate Crop mode const imageBlock = engine.block.create('graphic'); engine.block.appendChild(page, imageBlock); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); engine.block.setWidth(imageBlock, 350); engine.block.setHeight(imageBlock, 250); engine.block.setPositionX(imageBlock, 50); engine.block.setPositionY(imageBlock, 175); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); // Add a text block to demonstrate Text mode const textBlock = engine.block.create('text'); engine.block.appendChild(page, textBlock); engine.block.replaceText(textBlock, 'Edit this text'); engine.block.setTextFontSize(textBlock, 48); engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 }); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setPositionX(textBlock, 450); engine.block.setPositionY(textBlock, 275); // Subscribe to state changes to track mode transitions // The returned function can be called to unsubscribe when no longer needed const unsubscribeFromStateChanges = engine.editor.onStateChanged(() => { const currentMode = engine.editor.getEditMode(); console.log('Edit mode changed to:', currentMode); // Also log cursor state when state changes const cursorType = engine.editor.getCursorType(); console.log('Current cursor type:', cursorType); }); console.log('State change subscription active'); // Example: Unsubscribe after a delay (in a real app, call when component unmounts) setTimeout(() => { unsubscribeFromStateChanges(); console.log('Unsubscribed from state changes'); }, 10000); // Get the current edit mode (default is Transform) const initialMode = engine.editor.getEditMode(); console.log('Initial edit mode:', initialMode); // Select the image block and switch to Crop mode engine.block.select(imageBlock); engine.editor.setEditMode('Crop'); console.log('Switched to Crop mode on image block'); // After a moment, switch to Transform mode engine.editor.setEditMode('Transform'); console.log('Switched back to Transform mode'); // Create a custom edit mode that inherits from Crop behavior engine.editor.setEditMode('MyCustomCropMode', 'Crop'); console.log( 'Created custom mode based on Crop:', engine.editor.getEditMode() ); // Switch back to Transform for the demo engine.editor.setEditMode('Transform'); // Get the cursor type to display the appropriate mouse cursor const cursorType = engine.editor.getCursorType(); console.log('Cursor type:', cursorType); // Returns: 'Arrow', 'Move', 'MoveNotPermitted', 'Resize', 'Rotate', or 'Text' // Get cursor rotation for directional cursors like resize handles const cursorRotation = engine.editor.getCursorRotation(); console.log('Cursor rotation (radians):', cursorRotation); // Apply to cursor element: transform: rotate(${cursorRotation}rad) // Select the text block and switch to Text mode to get cursor position engine.block.select(textBlock); engine.editor.setEditMode('Text'); // Get text cursor position in screen space const textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX(); const textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY(); console.log('Text cursor position:', { x: textCursorX, y: textCursorY }); // Use these coordinates to position a floating toolbar near the text cursor // Check if a user interaction is currently in progress const isInteracting = engine.editor.unstable_isInteractionHappening(); console.log('Is interaction happening:', isInteracting); // Use this to defer expensive operations during drag/resize operations if (!isInteracting) { console.log('Safe to perform heavy updates'); } // Switch back to Transform mode and select the image for the hero screenshot engine.editor.setEditMode('Transform'); engine.block.select(imageBlock); // Zoom to fit the page engine.scene.enableZoomAutoFit(page, 'Both'); console.log('Editor State guide initialized successfully.'); console.log('Try clicking on blocks to see edit modes change.'); console.log('Double-click on the text block to enter Text mode.'); console.log('Select the image and use the crop handle to enter Crop mode.'); } } export default Example; ``` This guide covers: - Understanding the five built-in edit modes (Transform, Crop, Text, Trim, Playback) - Switching edit modes programmatically - Creating custom edit modes that inherit from built-in modes - Subscribing to state changes for UI synchronization - Reading cursor type and rotation for custom cursors - Tracking text cursor position for overlays - Detecting active user interactions ## Edit Modes CE.SDK supports five built-in edit modes, each designed for a specific type of interaction with canvas content. ### Transform Mode Transform is the default mode that allows users to move, resize, and rotate blocks on the canvas. When a block is selected in Transform mode, control handles appear for manipulation. ```typescript highlight=highlight-get-edit-mode // Get the current edit mode (default is Transform) const initialMode = engine.editor.getEditMode(); console.log('Initial edit mode:', initialMode); ``` Query the current mode using `engine.editor.getEditMode()`. The initial mode is always `'Transform'`. ### Switching Edit Modes Use `engine.editor.setEditMode()` to change the current editing mode. The mode determines what interactions are available on selected blocks. ```typescript highlight=highlight-set-edit-mode // Select the image block and switch to Crop mode engine.block.select(imageBlock); engine.editor.setEditMode('Crop'); console.log('Switched to Crop mode on image block'); // After a moment, switch to Transform mode engine.editor.setEditMode('Transform'); console.log('Switched back to Transform mode'); ``` Available modes include: - **Transform**: Move, resize, and rotate blocks (default) - **Crop**: Adjust media content within block frames - **Text**: Edit text content inline - **Trim**: Adjust clip start and end points (video scenes) - **Playback**: Play video or audio content (limited interactions) ### Custom Edit Modes You can create custom modes that inherit behavior from a built-in base mode. Pass an optional second parameter to `setEditMode()` specifying the base mode. ```typescript highlight=highlight-custom-edit-mode // Create a custom edit mode that inherits from Crop behavior engine.editor.setEditMode('MyCustomCropMode', 'Crop'); console.log( 'Created custom mode based on Crop:', engine.editor.getEditMode() ); // Switch back to Transform for the demo engine.editor.setEditMode('Transform'); ``` Custom modes are useful when you need to track application-specific states while maintaining standard editing behavior. For example, you might use a custom mode to indicate that a specific tool is active in your UI while still allowing Transform interactions. ## Subscribing to State Changes The engine notifies subscribers whenever the editor state changes, including mode switches and cursor updates. ### Using onStateChanged Subscribe to state changes using `engine.editor.onStateChanged()`. The callback fires at the end of each engine update where state changed. The subscription returns an unsubscribe function for cleanup. ```typescript highlight=highlight-on-state-changed // Subscribe to state changes to track mode transitions // The returned function can be called to unsubscribe when no longer needed const unsubscribeFromStateChanges = engine.editor.onStateChanged(() => { const currentMode = engine.editor.getEditMode(); console.log('Edit mode changed to:', currentMode); // Also log cursor state when state changes const cursorType = engine.editor.getCursorType(); console.log('Current cursor type:', cursorType); }); console.log('State change subscription active'); // Example: Unsubscribe after a delay (in a real app, call when component unmounts) setTimeout(() => { unsubscribeFromStateChanges(); console.log('Unsubscribed from state changes'); }, 10000); ``` Common use cases include: - Updating toolbar UI to reflect the current mode - Showing mode-specific panels or controls - Disabling certain actions during Playback mode - Logging state transitions for analytics Always call the unsubscribe function when your component unmounts or when you no longer need updates. This prevents memory leaks and unnecessary callback invocations. ## Cursor State The engine tracks what cursor type should be displayed based on the current context and hovered element. Use this information to display the appropriate mouse cursor in your custom UI. ### Reading Cursor Type Use `engine.editor.getCursorType()` to get the cursor type to display. ```typescript highlight=highlight-cursor-type // Get the cursor type to display the appropriate mouse cursor const cursorType = engine.editor.getCursorType(); console.log('Cursor type:', cursorType); // Returns: 'Arrow', 'Move', 'MoveNotPermitted', 'Resize', 'Rotate', or 'Text' ``` The method returns one of these values: - **Arrow**: Default pointer cursor - **Move**: Indicates the element can be moved - **MoveNotPermitted**: Element cannot be moved in the current context - **Resize**: Resize handle is hovered - **Rotate**: Rotation handle is hovered - **Text**: Text editing cursor ### Reading Cursor Rotation For directional cursors like resize handles, use `engine.editor.getCursorRotation()` to get the rotation angle in radians. ```typescript highlight=highlight-cursor-rotation // Get cursor rotation for directional cursors like resize handles const cursorRotation = engine.editor.getCursorRotation(); console.log('Cursor rotation (radians):', cursorRotation); // Apply to cursor element: transform: rotate(${cursorRotation}rad) ``` Apply this rotation to your cursor image for correct visual feedback. For example, when hovering over a corner resize handle at 45 degrees, the rotation value reflects that angle so your cursor points in the correct direction. ## Text Cursor Position When in Text edit mode, you can track the text cursor (caret) position for rendering custom overlays or toolbars near the insertion point. ### Screen Space Coordinates Use `engine.editor.getTextCursorPositionInScreenSpaceX()` and `engine.editor.getTextCursorPositionInScreenSpaceY()` to get the cursor position in screen pixels. ```typescript highlight=highlight-text-cursor-position // Select the text block and switch to Text mode to get cursor position engine.block.select(textBlock); engine.editor.setEditMode('Text'); // Get text cursor position in screen space const textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX(); const textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY(); console.log('Text cursor position:', { x: textCursorX, y: textCursorY }); // Use these coordinates to position a floating toolbar near the text cursor ``` These values update as the user moves through text. Use them to position floating toolbars, formatting menus, or other UI elements relative to where the user is editing. ## Detecting Active Interactions Determine whether the user is currently in the middle of an interaction like dragging or resizing. ### Using unstable\_isInteractionHappening Call `engine.editor.unstable_isInteractionHappening()` to check if a user interaction is in progress. ```typescript highlight=highlight-interaction-happening // Check if a user interaction is currently in progress const isInteracting = engine.editor.unstable_isInteractionHappening(); console.log('Is interaction happening:', isInteracting); // Use this to defer expensive operations during drag/resize operations if (!isInteracting) { console.log('Safe to perform heavy updates'); } ``` This is useful for: - Deferring expensive operations until after the interaction completes - Showing different UI states during drag operations - Optimizing performance by batching updates Note that this API is marked unstable and may change in future releases. ## Troubleshooting ### Mode Doesn't Change Visually Ensure a block is selected that supports the target mode. For example, switching to Crop mode requires an image or video block to be selected. Switching to Text mode requires a text block. ### State Change Callback Not Firing Verify the subscription is active before the operation that changes state. If you subscribe after the state change occurs, you won't receive the initial notification. ### Cursor Type Always Arrow Check that the mouse is over an interactive element and the element supports the current edit mode. The cursor type only changes when hovering over actionable areas like handles or selectable content. ### Text Cursor Position is 0,0 Confirm the editor is in Text mode with an active text selection. The text cursor position is only valid when actively editing text content. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Editing Workflow" description: "Control editing access with Creator, Adopter, Viewer, and Presenter roles using global and block-level scopes for tailored permissions." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/editing-workflow-032d27/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Editing Workflow](https://img.ly/docs/cesdk/angular/concepts/editing-workflow-032d27/) --- CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements. ![Editing workflow with role-based permissions in CE.SDK](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-editing-workflow-browser/) CE.SDK uses a two-tier permission system: **roles** define user types with preset permissions, while **scopes** control specific capabilities. This enables workflows where templates can be prepared by designers and safely customized by end-users. ```typescript file=@cesdk_web_examples/guides-concepts-editing-workflow-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * Demonstrates CE.SDK's role-based permission system with scopes. */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Roles define user types: 'Creator', 'Adopter', 'Viewer', 'Presenter' const role = engine.editor.getRole(); console.log('Current role:', role); // 'Creator' // Configure scopes when role changes (role change resets to defaults) engine.editor.onRoleChanged(() => { // Set global scopes to 'Defer' so block-level scopes take effect engine.editor.setGlobalScope('editor/select', 'Defer'); engine.editor.setGlobalScope('layer/move', 'Defer'); engine.editor.setGlobalScope('text/edit', 'Defer'); engine.editor.setGlobalScope('lifecycle/destroy', 'Defer'); }); // Get page dimensions for centering const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a locked text block (brand element) const lockedText = engine.block.create('text'); engine.block.replaceText(lockedText, 'Locked Text'); engine.block.setTextFontSize(lockedText, 40); engine.block.setEnum(lockedText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(lockedText, pageWidth); engine.block.setHeightMode(lockedText, 'Auto'); engine.block.setPositionX(lockedText, 0); engine.block.setPositionY(lockedText, pageHeight / 2 - 50); engine.block.appendChild(page, lockedText); // Lock the block - Adopters cannot select, edit, or move it engine.block.setScopeEnabled(lockedText, 'editor/select', false); engine.block.setScopeEnabled(lockedText, 'text/edit', false); engine.block.setScopeEnabled(lockedText, 'layer/move', false); engine.block.setScopeEnabled(lockedText, 'lifecycle/destroy', false); // Create an editable text block (user content) const editableText = engine.block.create('text'); engine.block.replaceText(editableText, 'Editable Text'); engine.block.setTextFontSize(editableText, 40); engine.block.setEnum(editableText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(editableText, pageWidth); engine.block.setHeightMode(editableText, 'Auto'); engine.block.setPositionX(editableText, 0); engine.block.setPositionY(editableText, pageHeight / 2 + 10); engine.block.appendChild(page, editableText); // Center both texts vertically as a group const lockedHeight = engine.block.getFrameHeight(lockedText); const editableHeight = engine.block.getFrameHeight(editableText); const gap = 20; const totalHeight = lockedHeight + gap + editableHeight; const topMargin = (pageHeight - totalHeight) / 2; engine.block.setPositionY(lockedText, topMargin); engine.block.setPositionY(editableText, topMargin + lockedHeight + gap); // Editable block - enable selection and editing engine.block.setScopeEnabled(editableText, 'editor/select', true); engine.block.setScopeEnabled(editableText, 'text/edit', true); engine.block.setScopeEnabled(editableText, 'layer/move', true); // Check resolved permissions (role + global + block scopes) const canEditLocked = engine.block.isAllowedByScope( lockedText, 'text/edit' ); const canEditEditable = engine.block.isAllowedByScope( editableText, 'text/edit' ); // As Creator: both return true (Creators bypass restrictions) console.log( 'Can edit locked:', canEditLocked, 'Can edit editable:', canEditEditable ); // Switch to Adopter to apply restrictions engine.editor.setRole('Adopter'); // Select the editable block to show it's interactive engine.block.select(editableText); } } export default Example; ``` This guide covers: - The four user roles and their purposes - How scopes control editing capabilities - The permission resolution hierarchy - Common template workflow patterns ## Roles Roles define user types with different default permissions: | Role | Purpose | Default Access | |------|---------|----------------| | **Creator** | Designers building templates | Full access to all operations | | **Adopter** | End-users customizing templates | Limited by block-level scopes | | **Viewer** | Preview-only users | Read-only access | | **Presenter** | Slideshow/video presenters | Read-only with playback controls | Creators set the block-level scopes that constrain what Adopters can do. This separation enables brand consistency while allowing personalization. ```typescript highlight-roles // Roles define user types: 'Creator', 'Adopter', 'Viewer', 'Presenter' const role = engine.editor.getRole(); console.log('Current role:', role); // 'Creator' ``` ## Scopes Scopes define specific capabilities organized into categories: - **Text**: Editing content and character formatting - **Fill/Stroke**: Changing colors and shapes - **Layer**: Moving, resizing, rotating, cropping - **Appearance**: Filters, effects, shadows, animations - **Lifecycle**: Deleting and duplicating elements - **Editor**: Adding new elements and selecting ## Global vs Block-Level Scopes **Global scopes** apply editor-wide and determine whether block-level settings are checked: - `'Allow'` — Always permit the operation - `'Deny'` — Always block the operation - `'Defer'` — Check block-level scope settings **Block-level scopes** control permissions on individual blocks. These settings only take effect when the corresponding global scope is set to `'Defer'`. ```typescript highlight-global-scopes // Configure scopes when role changes (role change resets to defaults) engine.editor.onRoleChanged(() => { // Set global scopes to 'Defer' so block-level scopes take effect engine.editor.setGlobalScope('editor/select', 'Defer'); engine.editor.setGlobalScope('layer/move', 'Defer'); engine.editor.setGlobalScope('text/edit', 'Defer'); engine.editor.setGlobalScope('lifecycle/destroy', 'Defer'); }); ``` To lock a specific block, disable its scopes: ```typescript highlight-block-scopes // Lock the block - Adopters cannot select, edit, or move it engine.block.setScopeEnabled(lockedText, 'editor/select', false); engine.block.setScopeEnabled(lockedText, 'text/edit', false); engine.block.setScopeEnabled(lockedText, 'layer/move', false); engine.block.setScopeEnabled(lockedText, 'lifecycle/destroy', false); ``` ## Permission Resolution Permissions resolve in this order: 1. **Role defaults** — Each role has preset global scope values 2. **Global scope** — If `'Allow'` or `'Deny'`, this is the final answer 3. **Block-level scope** — If global is `'Defer'`, check the block's settings Use `isAllowedByScope()` to check the final computed permission for any block and scope combination: ```typescript highlight-check-permissions // Check resolved permissions (role + global + block scopes) const canEditLocked = engine.block.isAllowedByScope( lockedText, 'text/edit' ); const canEditEditable = engine.block.isAllowedByScope( editableText, 'text/edit' ); // As Creator: both return true (Creators bypass restrictions) console.log( 'Can edit locked:', canEditLocked, 'Can edit editable:', canEditEditable ); ``` ## Template Workflow Pattern A typical template workflow: 1. **Designer (Creator)** creates the template layout 2. **Designer** locks brand elements using block scopes 3. **Designer** keeps personalization fields editable 4. **End-user (Adopter)** opens the template 5. **End-user** edits only permitted elements 6. **End-user** exports the personalized result This pattern ensures brand consistency while enabling personalization. ## Implementation Guides For detailed implementation, see these guides: [Lock Design Elements](https://img.ly/docs/cesdk/angular/create-templates/lock-131489/) — Step-by-step instructions for locking specific elements in templates [Set Editing Constraints](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) — Configure which properties users can modify --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Events" description: "Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/events-353f97/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Events](https://img.ly/docs/cesdk/angular/concepts/events-353f97/) --- Monitor and react to block changes in real time by subscribing to creation, update, and destruction events in your CE.SDK scene. ![Events example showing a scene with event subscriptions](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-events-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-events-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-events-browser/) Events enable real-time monitoring of block changes in CE.SDK. When blocks are created, modified, or destroyed, the engine delivers these changes through callback subscriptions at the end of each update cycle. This push-based notification system eliminates the need for polling and enables efficient reactive architectures. ```typescript file=@cesdk_web_examples/guides-concepts-events-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Events Guide * * Demonstrates working with block lifecycle events in CE.SDK: * - Subscribing to all block events * - Filtering events to specific blocks * - Processing Created, Updated, and Destroyed event types * - Event batching and deduplication behavior * - Safe handling of destroyed blocks * - Proper unsubscription for cleanup */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Subscribe to events from all blocks in the scene // Pass an empty array to receive events from every block const unsubscribeAll = engine.event.subscribe([], (events) => { for (const event of events) { console.log( `[All Blocks] ${event.type} event for block ${event.block}` ); } }); // Get the current page to add blocks to const pages = engine.block.findByType('page'); const page = pages[0]; // Create a graphic block - this triggers a Created event const graphic = engine.block.create('graphic'); // Set up the graphic with a shape and fill const rectShape = engine.block.createShape('rect'); engine.block.setShape(graphic, rectShape); // Position and size the graphic engine.block.setPositionX(graphic, 200); engine.block.setPositionY(graphic, 150); engine.block.setWidth(graphic, 400); engine.block.setHeight(graphic, 300); // Add an image fill const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(graphic, imageFill); engine.block.setEnum(graphic, 'contentFill/mode', 'Cover'); // Append to page to make it visible engine.block.appendChild(page, graphic); console.log('Created graphic block:', graphic); // Subscribe to events for specific blocks only // This is more efficient when you only care about certain blocks const unsubscribeSpecific = engine.event.subscribe([graphic], (events) => { for (const event of events) { console.log( `[Specific Block] ${event.type} event for block ${event.block}` ); } }); // Modify the block - this triggers Updated events // Due to deduplication, multiple rapid changes result in one Updated event engine.block.setRotation(graphic, 0.1); // Rotate slightly engine.block.setFloat(graphic, 'opacity', 0.9); // Adjust opacity console.log('Modified graphic block - rotation and opacity changed'); // Process events by checking the type property const unsubscribeProcess = engine.event.subscribe([], (events) => { for (const event of events) { switch (event.type) { case 'Created': { // Block was just created - safe to use Block API const blockType = engine.block.getType(event.block); console.log(`Block created with type: ${blockType}`); break; } case 'Updated': { // Block property changed - safe to use Block API console.log(`Block ${event.block} was updated`); break; } case 'Destroyed': { // Block was destroyed - must check validity before using Block API const isValid = engine.block.isValid(event.block); console.log( `Block ${event.block} destroyed, still valid: ${isValid}` ); break; } } } }); // When handling Destroyed events, always check block validity // The block ID is no longer valid after destruction const unsubscribeDestroyed = engine.event.subscribe([], (events) => { for (const event of events) { if (event.type === 'Destroyed') { // IMPORTANT: Check validity before any Block API calls if (engine.block.isValid(event.block)) { // Block is still valid (this shouldn't happen for Destroyed events) console.log('Block is unexpectedly still valid'); } else { // Block is invalid - expected for Destroyed events // Clean up any references to this block ID console.log( `Block ${event.block} has been destroyed and is invalid` ); } } } }); // Create a second block to demonstrate destruction const textBlock = engine.block.create('text'); engine.block.appendChild(page, textBlock); engine.block.setPositionX(textBlock, 200); engine.block.setPositionY(textBlock, 500); engine.block.setWidth(textBlock, 400); engine.block.setHeight(textBlock, 50); engine.block.setString(textBlock, 'text/text', 'Events Demo'); engine.block.setFloat(textBlock, 'text/fontSize', 48); console.log('Created text block:', textBlock); // Destroy the text block - this triggers a Destroyed event engine.block.destroy(textBlock); console.log('Destroyed text block'); // After destruction, the block ID is no longer valid const isTextBlockValid = engine.block.isValid(textBlock); console.log('Text block still valid after destroy:', isTextBlockValid); // false // Clean up subscriptions when no longer needed // This prevents memory leaks and reduces engine overhead unsubscribeAll(); unsubscribeSpecific(); unsubscribeProcess(); unsubscribeDestroyed(); console.log('Unsubscribed from all event listeners'); // Re-subscribe with a single listener for the demo UI engine.event.subscribe([], (events) => { for (const event of events) { console.log(`Event: ${event.type} - Block: ${event.block}`); } }); console.log('Events guide initialized successfully.'); console.log( 'Demonstrated: subscribing, event types, processing, and cleanup.' ); } } export default Example; ``` This guide covers subscribing to block lifecycle events, processing the three event types (`Created`, `Updated`, `Destroyed`), filtering events to specific blocks, understanding batching and deduplication behavior, and properly cleaning up subscriptions. ## Event Types CE.SDK provides three event types that capture the block lifecycle: - **`Created`**: Fires when a new block is added to the scene - **`Updated`**: Fires when any property of a block changes - **`Destroyed`**: Fires when a block is removed from the scene Each event contains a `block` property with the block ID and a `type` property indicating which event occurred. ## Subscribing to All Blocks Use `engine.event.subscribe()` to register a callback that receives batched events. Pass an empty array to receive events from all blocks in the scene: ```typescript highlight-subscribe-all // Subscribe to events from all blocks in the scene // Pass an empty array to receive events from every block const unsubscribeAll = engine.event.subscribe([], (events) => { for (const event of events) { console.log( `[All Blocks] ${event.type} event for block ${event.block}` ); } }); ``` The callback receives an array of events at the end of each engine update cycle. The function returns an unsubscribe function you should store for cleanup. ## Subscribing to Specific Blocks For better performance when you only care about certain blocks, pass an array of block IDs to filter events: ```typescript highlight-subscribe-specific // Subscribe to events for specific blocks only // This is more efficient when you only care about certain blocks const unsubscribeSpecific = engine.event.subscribe([graphic], (events) => { for (const event of events) { console.log( `[Specific Block] ${event.type} event for block ${event.block}` ); } }); ``` This reduces overhead since the engine only needs to prepare events for the blocks you're tracking. ## Creating Blocks and Handling `Created` Events When you create a block, the engine fires a `Created` event. You can safely use Block API methods on the block ID since the block is valid: ```typescript highlight-event-created // Create a graphic block - this triggers a Created event const graphic = engine.block.create('graphic'); // Set up the graphic with a shape and fill const rectShape = engine.block.createShape('rect'); engine.block.setShape(graphic, rectShape); // Position and size the graphic engine.block.setPositionX(graphic, 200); engine.block.setPositionY(graphic, 150); engine.block.setWidth(graphic, 400); engine.block.setHeight(graphic, 300); // Add an image fill const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(graphic, imageFill); engine.block.setEnum(graphic, 'contentFill/mode', 'Cover'); // Append to page to make it visible engine.block.appendChild(page, graphic); console.log('Created graphic block:', graphic); ``` Use `Created` events to initialize tracking, update UI state, or set up additional subscriptions for the new block. ## Updating Blocks and Handling `Updated` Events Modifying any property of a block triggers an `Updated` event. Due to deduplication, you receive at most one `Updated` event per block per engine update cycle, regardless of how many properties changed: ```typescript highlight-event-updated // Modify the block - this triggers Updated events // Due to deduplication, multiple rapid changes result in one Updated event engine.block.setRotation(graphic, 0.1); // Rotate slightly engine.block.setFloat(graphic, 'opacity', 0.9); // Adjust opacity console.log('Modified graphic block - rotation and opacity changed'); ``` Multiple rapid changes to the same block result in a single `Updated` event, making event handling efficient even during complex operations. ## Processing Events by Type Handle each event type appropriately by checking the `type` property. For `Created` and `Updated` events, you can safely use Block API methods. For `Destroyed` events, the block ID is no longer valid: ```typescript highlight-process-events // Process events by checking the type property const unsubscribeProcess = engine.event.subscribe([], (events) => { for (const event of events) { switch (event.type) { case 'Created': { // Block was just created - safe to use Block API const blockType = engine.block.getType(event.block); console.log(`Block created with type: ${blockType}`); break; } case 'Updated': { // Block property changed - safe to use Block API console.log(`Block ${event.block} was updated`); break; } case 'Destroyed': { // Block was destroyed - must check validity before using Block API const isValid = engine.block.isValid(event.block); console.log( `Block ${event.block} destroyed, still valid: ${isValid}` ); break; } } } }); ``` ## Handling `Destroyed` Events Safely When a block is destroyed, the block ID becomes invalid. Calling Block API methods on a destroyed block throws an exception. Always check validity with `engine.block.isValid()` before operations: ```typescript highlight-destroyed-safety // When handling Destroyed events, always check block validity // The block ID is no longer valid after destruction const unsubscribeDestroyed = engine.event.subscribe([], (events) => { for (const event of events) { if (event.type === 'Destroyed') { // IMPORTANT: Check validity before any Block API calls if (engine.block.isValid(event.block)) { // Block is still valid (this shouldn't happen for Destroyed events) console.log('Block is unexpectedly still valid'); } else { // Block is invalid - expected for Destroyed events // Clean up any references to this block ID console.log( `Block ${event.block} has been destroyed and is invalid` ); } } } }); ``` After verifying the block is invalid, you can safely clean up any local references. The destroy operation itself triggers the `Destroyed` event: ```typescript highlight-event-destroyed // Destroy the text block - this triggers a Destroyed event engine.block.destroy(textBlock); console.log('Destroyed text block'); // After destruction, the block ID is no longer valid const isTextBlockValid = engine.block.isValid(textBlock); console.log('Text block still valid after destroy:', isTextBlockValid); // false ``` Use `isValid()` to clean up any references to destroyed blocks in your application state. ## Unsubscribing from Events The `subscribe()` method returns an unsubscribe function. Call it when you no longer need events to prevent memory leaks and reduce engine overhead: ```typescript highlight-unsubscribe // Clean up subscriptions when no longer needed // This prevents memory leaks and reduces engine overhead unsubscribeAll(); unsubscribeSpecific(); unsubscribeProcess(); unsubscribeDestroyed(); console.log('Unsubscribed from all event listeners'); ``` Always unsubscribe when your component unmounts, the editor closes, or you no longer need to track changes. Keeping unnecessary subscriptions active forces the engine to prepare event lists for each subscriber at every update. ## Event Batching and Deduplication Events are collected during an engine update and delivered together at the end. The engine deduplicates events, so you receive at most one `Updated` event per block per update cycle. Event order in the callback does not reflect the actual order of changes within the update. This batching behavior means: - Multiple property changes to a single block result in one `Updated` event - You cannot determine which specific property changed from the event alone - If you need to track specific property changes, compare against cached values ## Use Cases Events support various reactive patterns in CE.SDK applications: - **Syncing external state**: Keep state management systems (Redux, MobX, Zustand) synchronized with scene changes - **Building reactive UIs**: Update UI components when blocks change without polling - **Tracking changes for undo/redo**: Monitor all block changes for custom history implementations - **Validating scene constraints**: React to block creation or property changes to enforce design rules ## Troubleshooting **Events not firing**: Ensure you haven't unsubscribed prematurely. Verify the blocks you're filtering to still exist. **Exception on `Destroyed` event**: Never call Block API methods on a destroyed block without first checking `engine.block.isValid()`. **Missing events**: Events are deduplicated—multiple rapid changes to the same property result in one `Updated` event. **Memory leaks**: Store the unsubscribe function and call it during cleanup. Forgetting to unsubscribe keeps listeners active. **Event order confusion**: Don't rely on event array order within a single callback—it doesn't reflect chronological order of changes. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Headless" description: "Run headless CE.SDK's Engine inside a browser-based app." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Headless Mode](https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/) --- CreativeEditor SDK (CE.SDK) **Headless Mode** allows your web app to control the Engine API without loading the built-in user interface. Instead of starting the full editor with panels and toolbars, your browser code talks directly to the Engine. That keeps the experience within your own design system—ideal for automation running on the client. ## Common Use Cases - **Automating workflows:** Merge data into templates or generate variations with no user input. - **Automated exports:** Render custom images or videos in the browser and hand them off to storage or your backend. - **Custom interfaces:** Embed the Engine beneath your own UI components. - **Background actions:** Run batch jobs or event-driven edits without overlaying the default UI. ### When to Use Headless Mode ## How Headless Mode Works The standard editor wires UI events (button clicks, drag interactions) to the Engine under the hood. In headless mode, you call the same Engine APIs yourself. ### Features Available in Headless Mode In headless mode, you keep access to the CE.SDK features while staying entirely within your app’s DOM lifecycle, such as: - Scene management - Asset manipulation - Rendering and export - Templates and dynamic content ### Requirements to Run the CreativeEngine in the Client CE.SDK’s Engine needs a **WebGL-capable environment**. When you ship a headless build in the browser: - Point the `baseURL` in your config at a publicly reachable asset directory (CDN or self-hosted) so the browser can load fonts and other Engine files—even when you install the npm package. - Only start the Engine **after the DOM is available** (for example, inside `useEffect`, `mounted`, or `DOMContentLoaded`). - **Dispose** of the Engine during tear down with `engine.dispose()` to release WebGL resources. - Ensure iframes or workers that boot the Engine expose `document` and a WebGL context. ## Set Up Headless Mode in a Web App 1. Install the Engine package: ```bash npm install @cesdk/engine ``` ```` 2. Configure your license and asset location. The global CDN works for prototypes; production apps commonly self-host the asset bundle for faster loads. ```ts const config = { license: '', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/$UBQ_VERSION$/assets' }; ```` 3. Start the Engine from client-side code and reuse the instance across interactions. > **Tip:** The CreativeEngine doesn’t attach a canvas automatically. If you want a live preview, append `engine.element` onto your own layout. ## Quick Start: Initialize the Engine Headlessly When you import `@cesdk/engine`, you get the same runtime that powers the full editor. Using the CreativeEngine instead of the CreativeEditor allows you to expose the Engine interface while skipping all UI bootstrapping that comes with the editor. ```js import CreativeEngine from '@cesdk/engine'; const config = { license: '', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/$UBQ_VERSION$/assets' }; let engine; export async function getEngine() { if (!engine) { engine = await CreativeEngine.init(config); } return engine; } ``` Once started, you can trigger Engine operations inside your app logic from: - Buttons - Keyboard shortcuts - Background tasks ## Example: Build and Export a Scene on a Single Click The workflow below wires the Engine to a button, so the user automatically downloads an edited PNG when clicking on a button: 1. Copy paste the following code (adjust to your stack as needed): ```js // Grab the button element const downloadButton = document.querySelector('#download-headless'); // Register an async click handler downloadButton?.addEventListener('click', async () => { // Wait for the Engine const engine = await getEngine(); // Build a fresh scene const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); // Attach the page to the scene engine.block.appendChild(scene, page); // Add an image layer const imageBlock = engine.block.create('graphic'); engine.block.setShape(imageBlock, engine.block.createShape('rect')); const imageFill = engine.block.createFill('image'); // Load an image fill from the CDN engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); // Set position and dimensions engine.block.setPosition(imageBlock, 100, 100); engine.block.setWidth(imageBlock, 300); engine.block.setHeight(imageBlock, 300); // Append the image to the page engine.block.appendChild(page, imageBlock); // Add a text layer const textBlock = engine.block.create('text'); // Set the text content engine.block.setString(textBlock, 'text/text', 'Hello from Headless Mode!'); // Set position and dimensions engine.block.setPosition(textBlock, 100, 450); engine.block.setWidth(textBlock, 600); // Append the text to the page engine.block.appendChild(page, textBlock); // Export the page as a PNG const exportResult = await engine.block.export(page, 'image/png'); // Normalize the result to a blob const blob = exportResult instanceof Blob ? exportResult : new Blob([exportResult], { type: 'image/png' }); triggerDownload(blob, 'headless-output.png'); }); function triggerDownload(blob, filename) { // Build a temporary object URL const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); // Start the download on click link.click(); // Revoke the link and URL to release resources document.body.removeChild(link); URL.revokeObjectURL(url); } ``` 2. Add a clickable element with the matching ID somewhere in your page’s markup. For example: ```js ``` If you’re in a component-based setup (React, Vue, Svelte, etc.), include the same button inside the component’s render/template: ```js return ( ); ``` Once that element is in the DOM, the snippet’s querySelector('#download-headless') finds it and wires up the click handler that runs the export.
See full module ```js import CreativeEngine from '@cesdk/engine'; const config = { license: '', baseURL: 'https://cdn.img.ly/packages/imgly/cesdk-engine/$UBQ_VERSION$/assets' }; let engine; export async function getEngine() { if (!engine) { engine = await CreativeEngine.init(config); } return engine; } const downloadButton = document.querySelector('#download-headless'); downloadButton?.addEventListener('click', async () => { const engine = await getEngine(); const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); const imageBlock = engine.block.create('graphic'); engine.block.setShape(imageBlock, engine.block.createShape('rect')); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setPosition(imageBlock, 100, 100); engine.block.setWidth(imageBlock, 300); engine.block.setHeight(imageBlock, 300); engine.block.appendChild(page, imageBlock); const textBlock = engine.block.create('text'); engine.block.setString(textBlock, 'text/text', 'Hello from Headless Mode!'); engine.block.setPosition(textBlock, 100, 450); engine.block.setWidth(textBlock, 600); engine.block.appendChild(page, textBlock); const exportResult = await engine.block.export(page, 'image/png'); const blob = exportResult instanceof Blob ? exportResult : new Blob([exportResult], { type: 'image/png' }); triggerDownload(blob, 'headless-output.png'); }); function triggerDownload(blob, filename) { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } ```
After you add the script to your app and run the dev server: 1. Click the `#download-headless` button in your interface. 2. The Engine assembles the scene in memory and exports it as a PNG. 3. The PNG downloads locally (or you can upload it to your backend). ## Go Further Need a hybrid approach? [Engine interface guides](https://img.ly/docs/cesdk/angular/engine-interface-6fb7cf/) show how to combine headless logic with the standard editor UI. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Pages" description: "Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/pages-7b6bae/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Pages](https://img.ly/docs/cesdk/angular/concepts/pages-7b6bae/) --- Pages define the format of your designs—every graphic block, text element, and media file lives inside a page. This guide covers how pages fit into the scene hierarchy, their properties like margins and title templates, and how to configure page dimensions for different layout modes. ![CE.SDK Pages Hero Image](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-pages-browser/) Pages provide the canvas and frame for your designs. Whether you're building a multi-page document, a social media carousel, or a video composition, understanding how pages work will help you with structuring your content correctly. ```typescript file=@cesdk_web_examples/guides-concepts-pages-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Pages Guide * * Demonstrates working with pages in CE.SDK: * - Understanding the scene hierarchy (Scene → Pages → Blocks) * - Creating and managing multiple pages * - Setting page dimensions at the scene level * - Configuring page properties (margins, title templates, fills) * - Navigating between pages */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a scene with VerticalStack layout for multi-page designs engine.scene.create('VerticalStack'); // Get the stack container to configure spacing const [stack] = engine.block.findByType('stack'); engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); // Get the scene to set page dimensions const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } // Set page dimensions at the scene level (all pages share these dimensions) engine.block.setFloat(scene, 'scene/pageDimensions/width', 800); engine.block.setFloat(scene, 'scene/pageDimensions/height', 600); // Create the first page and set its dimensions const firstPage = engine.block.create('page'); engine.block.setWidth(firstPage, 800); engine.block.setHeight(firstPage, 600); engine.block.appendChild(stack, firstPage); // Create the second page with the same dimensions const secondPage = engine.block.create('page'); engine.block.setWidth(secondPage, 800); engine.block.setHeight(secondPage, 600); engine.block.appendChild(stack, secondPage); // Add an image block to the first page const imageBlock = engine.block.create('graphic'); engine.block.appendChild(firstPage, imageBlock); // Create a rect shape for the graphic block const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); // Configure size and position after appending to the page engine.block.setWidth(imageBlock, 400); engine.block.setHeight(imageBlock, 300); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); // Create and configure the image fill const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); // Add a text block to the second page const textBlock = engine.block.create('text'); engine.block.appendChild(secondPage, textBlock); // Configure text properties after appending to the page engine.block.replaceText(textBlock, 'Page 2'); engine.block.setTextFontSize(textBlock, 48); engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 }); engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); // Center the text on the page const textWidth = engine.block.getFrameWidth(textBlock); const textHeight = engine.block.getFrameHeight(textBlock); engine.block.setPositionX(textBlock, (800 - textWidth) / 2); engine.block.setPositionY(textBlock, (600 - textHeight) / 2); // Configure page properties on the first page // Enable and set margins for print bleed engine.block.setBool(firstPage, 'page/marginEnabled', true); engine.block.setFloat(firstPage, 'page/margin/top', 10); engine.block.setFloat(firstPage, 'page/margin/bottom', 10); engine.block.setFloat(firstPage, 'page/margin/left', 10); engine.block.setFloat(firstPage, 'page/margin/right', 10); // Set a custom title template for the first page engine.block.setString(firstPage, 'page/titleTemplate', 'Cover'); // Set a custom title template for the second page engine.block.setString(secondPage, 'page/titleTemplate', 'Content'); // Set a background fill on the second page const colorFill = engine.block.createFill('color'); engine.block.setColor(colorFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 1.0, a: 1.0 }); engine.block.setFill(secondPage, colorFill); // Demonstrate finding pages const allPages = engine.scene.getPages(); console.log('All pages:', allPages); console.log('Number of pages:', allPages.length); // Get the current page (nearest to viewport center or containing selection) const currentPage = engine.scene.getCurrentPage(); console.log('Current page:', currentPage); // Alternative: Find pages using block API const pagesByType = engine.block.findByType('page'); console.log('Pages found by type:', pagesByType); // Check the scene mode (Design vs Video) const sceneMode = engine.scene.getMode(); console.log('Scene mode:', sceneMode); // Select the first page and zoom to fit engine.block.select(firstPage); engine.scene.enableZoomAutoFit(firstPage, 'Both'); console.log('Pages guide initialized with a 2-page design.'); } } export default Example; ``` This guide covers: - Understanding the scene hierarchy: Scene → Pages → Blocks - Creating and managing multiple pages - Setting page dimensions at the scene level - Configuring page properties like margins and title templates - Navigating between pages programmatically ## Pages in the Scene Hierarchy In CE.SDK, content follows a strict hierarchy: a **scene** contains **pages**, and pages contain **content blocks**. Only blocks attached to a page are rendered on the canvas. ```typescript highlight=highlight-create-scene // Create a scene with VerticalStack layout for multi-page designs engine.scene.create('VerticalStack'); // Get the stack container to configure spacing const [stack] = engine.block.findByType('stack'); engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); ``` When you create a scene with a layout mode like `VerticalStack`, pages are automatically arranged according to that mode. Create pages using `engine.block.create('page')`, set their dimensions with `setWidth()` and `setHeight()`, then attach them to the scene (or its stack container) with `engine.block.appendChild()`. ```typescript highlight=highlight-create-pages // Create the first page and set its dimensions const firstPage = engine.block.create('page'); engine.block.setWidth(firstPage, 800); engine.block.setHeight(firstPage, 600); engine.block.appendChild(stack, firstPage); // Create the second page with the same dimensions const secondPage = engine.block.create('page'); engine.block.setWidth(secondPage, 800); engine.block.setHeight(secondPage, 600); engine.block.appendChild(stack, secondPage); ``` Content blocks must be added as children of a page to render. For graphic blocks, set both a shape and a fill for content to display. Append blocks to the page before configuring their properties. ```typescript highlight=highlight-add-content // Add an image block to the first page const imageBlock = engine.block.create('graphic'); engine.block.appendChild(firstPage, imageBlock); // Create a rect shape for the graphic block const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); // Configure size and position after appending to the page engine.block.setWidth(imageBlock, 400); engine.block.setHeight(imageBlock, 300); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); // Create and configure the image fill const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); // Add a text block to the second page const textBlock = engine.block.create('text'); engine.block.appendChild(secondPage, textBlock); // Configure text properties after appending to the page engine.block.replaceText(textBlock, 'Page 2'); engine.block.setTextFontSize(textBlock, 48); engine.block.setTextColor(textBlock, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 }); engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); // Center the text on the page const textWidth = engine.block.getFrameWidth(textBlock); const textHeight = engine.block.getFrameHeight(textBlock); engine.block.setPositionX(textBlock, (800 - textWidth) / 2); engine.block.setPositionY(textBlock, (600 - textHeight) / 2); ``` ## Page Dimensions and Consistency The CE.SDK engine supports pages with different dimensions. When using stacked layout modes (VerticalStack, HorizontalStack), the Editor UI expects all pages to share the same size. However, with the `Free` layout mode, you can set different dimensions for each page in the UI. ```typescript highlight=highlight-set-dimensions // Get the scene to set page dimensions const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } // Set page dimensions at the scene level (all pages share these dimensions) engine.block.setFloat(scene, 'scene/pageDimensions/width', 800); engine.block.setFloat(scene, 'scene/pageDimensions/height', 600); ``` You can set default page dimensions at the scene level using `engine.block.setFloat()` with `scene/pageDimensions/width` and `scene/pageDimensions/height`. The `scene/aspectRatioLock` property controls whether changing one dimension automatically adjusts the other. Individual pages can also have their dimensions set directly with `setWidth()` and `setHeight()`. ## Finding and Navigating Pages CE.SDK provides several methods to locate and navigate between pages in your scene. ```typescript highlight=highlight-find-pages // Demonstrate finding pages const allPages = engine.scene.getPages(); console.log('All pages:', allPages); console.log('Number of pages:', allPages.length); // Get the current page (nearest to viewport center or containing selection) const currentPage = engine.scene.getCurrentPage(); console.log('Current page:', currentPage); // Alternative: Find pages using block API const pagesByType = engine.block.findByType('page'); console.log('Pages found by type:', pagesByType); ``` Use these methods based on your needs: - `engine.scene.getPages()` returns all pages in sorted order - `engine.scene.getCurrentPage()` returns the page containing the current selection, or the page nearest to the viewport center - `engine.block.findByType('page')` finds all page blocks in the scene - `engine.scene.findNearestToViewPortCenterByType('page')` returns pages sorted by their distance from the viewport center ## Page Properties Each page has its own properties that control its appearance and behavior. These are set on the page block itself, not on the scene. ### Margins Page margins define bleed areas useful for print designs. Enable margins and configure each side individually: ```typescript highlight=highlight-page-properties // Configure page properties on the first page // Enable and set margins for print bleed engine.block.setBool(firstPage, 'page/marginEnabled', true); engine.block.setFloat(firstPage, 'page/margin/top', 10); engine.block.setFloat(firstPage, 'page/margin/bottom', 10); engine.block.setFloat(firstPage, 'page/margin/left', 10); engine.block.setFloat(firstPage, 'page/margin/right', 10); // Set a custom title template for the first page engine.block.setString(firstPage, 'page/titleTemplate', 'Cover'); // Set a custom title template for the second page engine.block.setString(secondPage, 'page/titleTemplate', 'Content'); ``` Set `page/marginEnabled` to `true` to enable margins, then use `page/margin/top`, `page/margin/bottom`, `page/margin/left`, and `page/margin/right` to configure each side. ### Title Template The `page/titleTemplate` property defines the display label shown for each page. It supports template variables like `{{ubq.page_index}}` for dynamic numbering. The default value is `"Page {{ubq.page_index}}"`. You can customize this to show labels like "Slide 1", "Cover", or any custom text. ### Fill and Background Pages support fills for background colors or images using the standard fill system. ```typescript highlight=highlight-page-background // Set a background fill on the second page const colorFill = engine.block.createFill('color'); engine.block.setColor(colorFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 1.0, a: 1.0 }); engine.block.setFill(secondPage, colorFill); ``` Create a fill using `engine.block.createFill('color')` or `engine.block.createFill('image')`, configure its properties, then apply it to the page with `engine.block.setFill(page, fill)`. ## Page Layout Modes The scene's layout mode controls how multiple pages are arranged. Set this using `engine.block.setEnum()` on the scene with the `scene/layout` property: - **VerticalStack** (default): Pages stack vertically, one below the other - **HorizontalStack**: Pages arrange horizontally, side by side - **DepthStack**: Pages overlay each other, typically used in video mode - **Free**: Pages can be positioned freely without automatic arrangement ## Pages in Design Mode vs. Video Mode Page behavior varies depending on the scene mode. Query the current mode with `engine.scene.getMode()`. ```typescript highlight=highlight-scene-mode // Check the scene mode (Design vs Video) const sceneMode = engine.scene.getMode(); console.log('Scene mode:', sceneMode); ``` ### Design Mode In Design mode, pages act like artboards. Each page is a separate canvas ideal for multi-page documents, social media posts, or print layouts. Pages exist side by side and don't have time-based properties. ### Video Mode In Video mode, pages represent timeline compositions that transition sequentially during playback. Each page has playback properties: - `playback/duration` controls how long the page appears (in seconds) - `playback/time` tracks the current playback position ## Troubleshooting ### Content Not Visible If content blocks aren't appearing, check these common causes: - Verify the block is attached to a page with `engine.block.appendChild(page, block)` - For graphic blocks, ensure both a shape and fill are set - Append blocks to the page before setting their size and position ### Dimension Inconsistencies If pages appear at unexpected sizes when using stacked layouts, ensure all pages have consistent dimensions. With `Free` layout mode, pages can have different sizes. Set dimensions on individual pages using `setWidth()` and `setHeight()`. ### Page Not Found If `engine.scene.getPages()` returns an empty array, ensure a scene is loaded first. In headless mode, you must create both the scene and pages manually before querying them. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Resources" description: "Learn how CE.SDK loads and manages external media files, including preloading for performance, handling transient data, and relocating resources when URLs change." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/resources-a58d71/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Resources](https://img.ly/docs/cesdk/angular/concepts/resources-a58d71/) --- Manage external media files—images, videos, audio, and fonts—that blocks reference via URIs in CE.SDK. ![Resources example showing a scene with image and video blocks](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-resources-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-resources-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-resources-browser/) Resources are external media files that blocks reference through URI properties like `fill/image/imageFileURI` or `fill/video/fileURI`. CE.SDK loads resources automatically when needed, but you can preload them for better performance. When working with temporary data like buffers or blobs, you need to persist them before saving. If resource URLs change (such as during CDN migration), you can update the mappings without modifying scene data. ```typescript file=@cesdk_web_examples/guides-concepts-resources-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Resources Guide * * Demonstrates resource management in CE.SDK: * - On-demand resource loading * - Preloading resources with forceLoadResources() * - Preloading audio/video with forceLoadAVResource() * - Finding transient resources * - Persisting transient resources during save * - Relocating resources when URLs change * - Finding all media URIs in a scene * - Detecting MIME types */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Initialize CE.SDK with Video mode (required for video resources) cesdk.feature.enable('ly.img.video'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; // Get the current scene and page const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } const pages = engine.block.findByType('page'); const page = pages[0]; // Layout configuration: two blocks with equal margins const margin = 30; const gap = 20; const blockWidth = 300; const blockHeight = 200; // Set page dimensions to hug the blocks const pageWidth = margin + blockWidth + gap + blockWidth + margin; const pageHeight = margin + blockHeight + margin; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Create a graphic block with an image fill // Resources are loaded on-demand when the engine renders the block const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); engine.block.setPositionX(imageBlock, margin); engine.block.setPositionY(imageBlock, margin); engine.block.setWidth(imageBlock, blockWidth); engine.block.setHeight(imageBlock, blockHeight); // Create an image fill - the image loads when the block is rendered const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_4.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setEnum(imageBlock, 'contentFill/mode', 'Cover'); engine.block.appendChild(page, imageBlock); console.log('Created image block - resource loads on-demand when rendered'); // Preload all resources in the scene before rendering // This ensures resources are cached and ready for display console.log('Preloading all resources in the scene...'); await engine.block.forceLoadResources([scene]); console.log('All resources preloaded successfully'); // Preload specific blocks only (useful for optimizing load order) await engine.block.forceLoadResources([imageBlock]); console.log('Image block resources preloaded'); // Create a second graphic block for video const videoBlock = engine.block.create('graphic'); const videoShape = engine.block.createShape('rect'); engine.block.setShape(videoBlock, videoShape); engine.block.setPositionX(videoBlock, margin + blockWidth + gap); engine.block.setPositionY(videoBlock, margin); engine.block.setWidth(videoBlock, blockWidth); engine.block.setHeight(videoBlock, blockHeight); // Create a video fill const videoFill = engine.block.createFill('video'); engine.block.setString( videoFill, 'fill/video/fileURI', 'https://img.ly/static/ubq_video_samples/bbb.mp4' ); engine.block.setFill(videoBlock, videoFill); engine.block.setEnum(videoBlock, 'contentFill/mode', 'Cover'); engine.block.appendChild(page, videoBlock); // Preload video resource to query its properties console.log('Preloading video resource...'); await engine.block.forceLoadAVResource(videoFill); console.log('Video resource preloaded'); // Now we can query video properties const videoDuration = engine.block.getAVResourceTotalDuration(videoFill); const videoWidth = engine.block.getVideoWidth(videoFill); const videoHeight = engine.block.getVideoHeight(videoFill); console.log( `Video properties - Duration: ${videoDuration}s, Size: ${videoWidth}x${videoHeight}` ); // Find all transient resources that need persistence before export // Transient resources include buffers and blobs that won't survive serialization const transientResources = engine.editor.findAllTransientResources(); console.log(`Found ${transientResources.length} transient resources`); for (const resource of transientResources) { console.log( `Transient: URL=${resource.URL}, Size=${resource.size} bytes` ); } // Get all media URIs referenced in the scene // Useful for pre-fetching or validating resource availability const mediaURIs = engine.editor.findAllMediaURIs(); console.log(`Scene contains ${mediaURIs.length} media URIs:`); for (const uri of mediaURIs) { console.log(` - ${uri}`); } // Detect the MIME type of a resource // This downloads the resource if not already cached const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg'; const mimeType = await engine.editor.getMimeType(imageUri); console.log(`MIME type of ${imageUri}: ${mimeType}`); // Relocate a resource when its URL changes // This updates the internal cache mapping without modifying scene data const oldUrl = 'https://example.com/old-location/image.jpg'; const newUrl = 'https://cdn.example.com/new-location/image.jpg'; // In a real scenario, you would relocate after uploading to a new location: // engine.editor.relocateResource(oldUrl, newUrl); console.log(`Resource relocation example: ${oldUrl} -> ${newUrl}`); console.log('Use relocateResource() after uploading to a CDN'); // When saving, use onDisallowedResourceScheme to handle transient resources // This callback is called for each resource with a disallowed scheme (like buffer: or blob:) const sceneString = await engine.block.saveToString( [scene], ['http', 'https'], // Only allow http and https URLs async (url: string) => { // In a real app, upload the resource and return the permanent URL // const response = await uploadToCDN(url); // return response.permanentUrl; // For this example, we'll just log the URL console.log(`Would upload transient resource: ${url}`); // Return the original URL since we're not actually uploading return url; } ); console.log(`Scene saved to string (${sceneString.length} characters)`); // Set playback time to show video content in the scene engine.block.setPlaybackTime(page, 2); console.log('Resources guide initialized successfully.'); console.log( 'Demonstrated: on-demand loading, preloading, transient resources, and relocation.' ); } } export default Example; ``` This guide covers on-demand and preloaded resource loading, identifying and persisting transient resources, relocating resources when URLs change, and discovering all media URIs in a scene. ## On-Demand Loading The engine fetches resources automatically when rendering blocks or preparing exports. This approach requires no extra code but may delay the initial render while resources download. ```typescript highlight-on-demand-loading // Get the current scene and page const scene = engine.scene.get(); if (scene === null) { throw new Error('No scene available'); } const pages = engine.block.findByType('page'); const page = pages[0]; // Layout configuration: two blocks with equal margins const margin = 30; const gap = 20; const blockWidth = 300; const blockHeight = 200; // Set page dimensions to hug the blocks const pageWidth = margin + blockWidth + gap + blockWidth + margin; const pageHeight = margin + blockHeight + margin; engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Create a graphic block with an image fill // Resources are loaded on-demand when the engine renders the block const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); engine.block.setPositionX(imageBlock, margin); engine.block.setPositionY(imageBlock, margin); engine.block.setWidth(imageBlock, blockWidth); engine.block.setHeight(imageBlock, blockHeight); // Create an image fill - the image loads when the block is rendered const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_4.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setEnum(imageBlock, 'contentFill/mode', 'Cover'); engine.block.appendChild(page, imageBlock); console.log('Created image block - resource loads on-demand when rendered'); ``` When you create a block with an image fill, the image doesn't load immediately. The engine fetches it when the block first renders on the canvas. ## Preloading Resources Load resources before they're needed with `forceLoadResources()`. Pass block IDs to load resources for those blocks and their children. Preloading eliminates render delays and is useful when you want the scene fully ready before displaying it. ```typescript highlight-preload-resources // Preload all resources in the scene before rendering // This ensures resources are cached and ready for display console.log('Preloading all resources in the scene...'); await engine.block.forceLoadResources([scene]); console.log('All resources preloaded successfully'); // Preload specific blocks only (useful for optimizing load order) await engine.block.forceLoadResources([imageBlock]); console.log('Image block resources preloaded'); ``` Pass the scene to preload all resources in the entire design, or pass specific blocks to load only what you need. ## Preloading Audio and Video Audio and video resources require `forceLoadAVResource()` for full metadata access. The engine needs to download and parse media files before you can query properties like duration or dimensions. ```typescript highlight-preload-av // Create a second graphic block for video const videoBlock = engine.block.create('graphic'); const videoShape = engine.block.createShape('rect'); engine.block.setShape(videoBlock, videoShape); engine.block.setPositionX(videoBlock, margin + blockWidth + gap); engine.block.setPositionY(videoBlock, margin); engine.block.setWidth(videoBlock, blockWidth); engine.block.setHeight(videoBlock, blockHeight); // Create a video fill const videoFill = engine.block.createFill('video'); engine.block.setString( videoFill, 'fill/video/fileURI', 'https://img.ly/static/ubq_video_samples/bbb.mp4' ); engine.block.setFill(videoBlock, videoFill); engine.block.setEnum(videoBlock, 'contentFill/mode', 'Cover'); engine.block.appendChild(page, videoBlock); // Preload video resource to query its properties console.log('Preloading video resource...'); await engine.block.forceLoadAVResource(videoFill); console.log('Video resource preloaded'); // Now we can query video properties const videoDuration = engine.block.getAVResourceTotalDuration(videoFill); const videoWidth = engine.block.getVideoWidth(videoFill); const videoHeight = engine.block.getVideoHeight(videoFill); console.log( `Video properties - Duration: ${videoDuration}s, Size: ${videoWidth}x${videoHeight}` ); ``` Without preloading, properties like `getAVResourceTotalDuration()` or `getVideoWidth()` may return zero or incomplete values. ## Finding Transient Resources Transient resources are temporary data stored in buffers or blobs that won't survive scene serialization. Use `findAllTransientResources()` to discover them before saving. ```typescript highlight-find-transient // Find all transient resources that need persistence before export // Transient resources include buffers and blobs that won't survive serialization const transientResources = engine.editor.findAllTransientResources(); console.log(`Found ${transientResources.length} transient resources`); for (const resource of transientResources) { console.log( `Transient: URL=${resource.URL}, Size=${resource.size} bytes` ); } ``` Each entry includes the resource URL and its size in bytes. Common transient resources include images from clipboard paste operations, camera captures, or programmatically generated content. ## Finding Media URIs Get all media file URIs referenced in a scene with `findAllMediaURIs()`. This returns a deduplicated list of URIs from image fills, video fills, audio blocks, and other media sources. ```typescript highlight-find-media-uris // Get all media URIs referenced in the scene // Useful for pre-fetching or validating resource availability const mediaURIs = engine.editor.findAllMediaURIs(); console.log(`Scene contains ${mediaURIs.length} media URIs:`); for (const uri of mediaURIs) { console.log(` - ${uri}`); } ``` Use this for pre-fetching resources, validating availability, or building a manifest of all assets in a design. ## Detecting MIME Types Determine a resource's content type with `getMimeType()`. The engine downloads the resource if it's not already cached. ```typescript highlight-detect-mime-type // Detect the MIME type of a resource // This downloads the resource if not already cached const imageUri = 'https://img.ly/static/ubq_samples/sample_4.jpg'; const mimeType = await engine.editor.getMimeType(imageUri); console.log(`MIME type of ${imageUri}: ${mimeType}`); ``` Common return values include `image/jpeg`, `image/png`, `video/mp4`, and `audio/mpeg`. This is useful when you need to verify resource types or make format-dependent decisions. ## Relocating Resources Update URL mappings when resources move with `relocateResource()`. This modifies the internal cache without changing scene data. ```typescript highlight-relocate-resource // Relocate a resource when its URL changes // This updates the internal cache mapping without modifying scene data const oldUrl = 'https://example.com/old-location/image.jpg'; const newUrl = 'https://cdn.example.com/new-location/image.jpg'; // In a real scenario, you would relocate after uploading to a new location: // engine.editor.relocateResource(oldUrl, newUrl); console.log(`Resource relocation example: ${oldUrl} -> ${newUrl}`); console.log('Use relocateResource() after uploading to a CDN'); ``` Use relocation after uploading resources to a CDN or when migrating assets between storage locations. The scene continues to reference the original URL, but the engine fetches from the new location. ## Persisting Transient Resources Handle transient resources during save with the `onDisallowedResourceScheme` callback in `saveToString()`. The callback receives each resource URL with a disallowed scheme (like `buffer:` or `blob:`) and returns the permanent URL after uploading. ```typescript highlight-persist-transient // When saving, use onDisallowedResourceScheme to handle transient resources // This callback is called for each resource with a disallowed scheme (like buffer: or blob:) const sceneString = await engine.block.saveToString( [scene], ['http', 'https'], // Only allow http and https URLs async (url: string) => { // In a real app, upload the resource and return the permanent URL // const response = await uploadToCDN(url); // return response.permanentUrl; // For this example, we'll just log the URL console.log(`Would upload transient resource: ${url}`); // Return the original URL since we're not actually uploading return url; } ); console.log(`Scene saved to string (${sceneString.length} characters)`); ``` This pattern lets you intercept temporary resources, upload them to permanent storage, and save the scene with stable URLs that will work when reloaded. ## Troubleshooting **Slow initial render**: Preload resources with `forceLoadResources()` before displaying the scene. **Export fails with missing resources**: Check `findAllTransientResources()` and persist any temporary resources before export. **Video duration returns 0**: Ensure the video resource is loaded with `forceLoadAVResource()` before querying properties. **Resources not found after reload**: Transient resources (buffers, blobs) are not serialized—relocate them to persistent URLs before saving. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Scenes" description: "Create, configure, save, and load scenes—the root container for all design elements in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Scenes](https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/) --- Scenes are the root container for all designs in CE.SDK. They hold pages, blocks, and the camera that controls what you see in the canvas—and the engine manages only one active scene at a time. ![Scenes example showing a two-page design with different shapes on each page](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-scenes-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-scenes-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-scenes-browser/) Every design you create starts with a scene. Scenes contain pages, and pages contain the visible design elements—text, images, shapes, and other blocks. Understanding how scenes work is essential for building, saving, and restoring user designs. ```typescript file=@cesdk_web_examples/guides-concepts-scenes-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Scenes Guide * * Demonstrates the complete scene lifecycle in CE.SDK: * - Creating scenes with different layouts * - Managing pages within scenes * - Configuring scene properties * - Saving and loading scenes * - Camera control and zoom */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a new design scene with VerticalStack layout // The layout controls how pages are arranged in the canvas engine.scene.create('VerticalStack'); // Get the stack container and add spacing between pages const stack = engine.block.findByType('stack')[0]; engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); // Create the first page const page1 = engine.block.create('page'); engine.block.setWidth(page1, 800); engine.block.setHeight(page1, 600); engine.block.appendChild(stack, page1); // Create a second page const page2 = engine.block.create('page'); engine.block.setWidth(page2, 800); engine.block.setHeight(page2, 600); engine.block.appendChild(stack, page2); // Add a shape to the first page const graphic1 = engine.block.create('graphic'); engine.block.setShape(graphic1, engine.block.createShape('rect')); const fill1 = engine.block.createFill('color'); engine.block.setColor(fill1, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1 }); engine.block.setFill(graphic1, fill1); engine.block.setWidth(graphic1, 400); engine.block.setHeight(graphic1, 300); engine.block.setPositionX(graphic1, 200); engine.block.setPositionY(graphic1, 150); engine.block.appendChild(page1, graphic1); // Add a different shape to the second page const graphic2 = engine.block.create('graphic'); engine.block.setShape(graphic2, engine.block.createShape('ellipse')); const fill2 = engine.block.createFill('color'); engine.block.setColor(fill2, 'fill/color/value', { r: 0.9, g: 0.3, b: 0.2, a: 1 }); engine.block.setFill(graphic2, fill2); engine.block.setWidth(graphic2, 350); engine.block.setHeight(graphic2, 350); engine.block.setPositionX(graphic2, 225); engine.block.setPositionY(graphic2, 125); engine.block.appendChild(page2, graphic2); // Query scene properties const currentUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Scene design unit:', currentUnit); // Get the scene layout const layout = engine.scene.getLayout(); // eslint-disable-next-line no-console console.log('Scene layout:', layout); // Check scene mode (Design or Video) const mode = engine.scene.getMode(); // eslint-disable-next-line no-console console.log('Scene mode:', mode); // Access pages within the scene const pages = engine.scene.getPages(); // eslint-disable-next-line no-console console.log('Number of pages:', pages.length); // Get the current page (nearest to viewport center) const currentPage = engine.scene.getCurrentPage(); // eslint-disable-next-line no-console console.log('Current page ID:', currentPage); // Zoom to show all pages in the scene const scene = engine.scene.get(); if (scene) { await engine.scene.zoomToBlock(scene, { padding: 50 }); } // Get the current zoom level const zoomLevel = engine.scene.getZoomLevel(); // eslint-disable-next-line no-console console.log('Current zoom level:', zoomLevel); // Save the scene to a string for persistence const sceneString = await engine.scene.saveToString(); // eslint-disable-next-line no-console console.log('Scene saved successfully. String length:', sceneString.length); // Demonstrate loading the scene from the saved string // This replaces the current scene with the saved version await engine.scene.loadFromString(sceneString); // eslint-disable-next-line no-console console.log('Scene loaded from saved string'); // Zoom to show all loaded pages const loadedScene = engine.scene.get(); if (loadedScene) { await engine.scene.zoomToBlock(loadedScene, { padding: 50 }); } // eslint-disable-next-line no-console console.log('Scenes guide initialized successfully.'); } } export default Example; ``` This guide covers how to create scenes from scratch, manage pages within scenes, configure scene properties, save and load designs, and control the camera's zoom and position. ## Scene Hierarchy Scenes form the root of CE.SDK's design structure. The hierarchy works as follows: - **Scene** — The root container holding all design content - **Pages** — Direct children of scenes, arranged according to the scene's layout - **Blocks** — Design elements (text, images, shapes) that belong to pages Only blocks attached to pages within the active scene are rendered in the canvas. Use `engine.scene.get()` to retrieve the current scene and `engine.scene.getPages()` to access its pages. ## Creating Scenes ### Creating an Empty Scene Use `engine.scene.create()` to create a new design scene with a configurable page layout. The layout parameter controls how pages are arranged in the canvas. ```typescript highlight-create-scene // Create a new design scene with VerticalStack layout // The layout controls how pages are arranged in the canvas engine.scene.create('VerticalStack'); // Get the stack container and add spacing between pages const stack = engine.block.findByType('stack')[0]; engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); ``` Available layouts include: - `VerticalStack` — Pages stacked vertically - `HorizontalStack` — Pages arranged horizontally - `DepthStack` — Pages layered on top of each other - `Free` — Manual positioning ### Creating for Video Editing For video projects, use `engine.scene.createVideo()` which configures the scene for timeline-based editing: ```typescript const videoScene = engine.scene.createVideo({ page: { size: { width: 1920, height: 1080 } } }); ``` ### Adding Pages After creating a scene, add pages using `engine.block.create('page')`. Configure the page dimensions and append it to the scene's stack container. ```typescript highlight-create-page // Create the first page const page1 = engine.block.create('page'); engine.block.setWidth(page1, 800); engine.block.setHeight(page1, 600); engine.block.appendChild(stack, page1); // Create a second page const page2 = engine.block.create('page'); engine.block.setWidth(page2, 800); engine.block.setHeight(page2, 600); engine.block.appendChild(stack, page2); ``` ### Adding Blocks With pages in place, add design elements like shapes, text, or images. Create a graphic block, configure its shape and fill, then append it to a page. ```typescript highlight-create-block // Add a shape to the first page const graphic1 = engine.block.create('graphic'); engine.block.setShape(graphic1, engine.block.createShape('rect')); const fill1 = engine.block.createFill('color'); engine.block.setColor(fill1, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1 }); engine.block.setFill(graphic1, fill1); engine.block.setWidth(graphic1, 400); engine.block.setHeight(graphic1, 300); engine.block.setPositionX(graphic1, 200); engine.block.setPositionY(graphic1, 150); engine.block.appendChild(page1, graphic1); // Add a different shape to the second page const graphic2 = engine.block.create('graphic'); engine.block.setShape(graphic2, engine.block.createShape('ellipse')); const fill2 = engine.block.createFill('color'); engine.block.setColor(fill2, 'fill/color/value', { r: 0.9, g: 0.3, b: 0.2, a: 1 }); engine.block.setFill(graphic2, fill2); engine.block.setWidth(graphic2, 350); engine.block.setHeight(graphic2, 350); engine.block.setPositionX(graphic2, 225); engine.block.setPositionY(graphic2, 125); engine.block.appendChild(page2, graphic2); ``` ## Scene Properties ### Design Units Query or configure how measurements are interpreted using `engine.scene.getDesignUnit()` and `engine.scene.setDesignUnit()`. This is useful for print workflows where precise physical dimensions matter. ```typescript highlight-scene-properties // Query scene properties const currentUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Scene design unit:', currentUnit); // Get the scene layout const layout = engine.scene.getLayout(); // eslint-disable-next-line no-console console.log('Scene layout:', layout); // Check scene mode (Design or Video) const mode = engine.scene.getMode(); // eslint-disable-next-line no-console console.log('Scene mode:', mode); ``` Supported units are `'Pixel'`, `'Millimeter'`, and `'Inch'`. For more details, see the [Design Units](https://img.ly/docs/cesdk/angular/concepts/design-units-cc6597/) guide. ### Scene Mode Scenes operate in either Design mode or Video mode, determined at creation time. Use `engine.scene.getMode()` to check which mode is active: - **Design** — For static designs like posters, social media graphics, and print materials - **Video** — For timeline-based editing with animations and video clips ### Scene Layout Control how pages are arranged using `engine.scene.getLayout()` and `engine.scene.setLayout()`. The layout affects how users navigate between pages in multi-page designs. ## Page Navigation Access pages within your scene using these methods: ```typescript highlight-page-navigation // Access pages within the scene const pages = engine.scene.getPages(); // eslint-disable-next-line no-console console.log('Number of pages:', pages.length); // Get the current page (nearest to viewport center) const currentPage = engine.scene.getCurrentPage(); // eslint-disable-next-line no-console console.log('Current page ID:', currentPage); ``` The `getCurrentPage()` method returns the page nearest to the viewport center—useful for determining which page the user is currently viewing. ## Camera and Zoom ### Zoom to Block Use `engine.scene.zoomToBlock()` to frame a specific block in the viewport with padding. Pass the scene block to show all pages: ```typescript highlight-camera-zoom // Zoom to show all pages in the scene const scene = engine.scene.get(); if (scene) { await engine.scene.zoomToBlock(scene, { padding: 50 }); } // Get the current zoom level const zoomLevel = engine.scene.getZoomLevel(); // eslint-disable-next-line no-console console.log('Current zoom level:', zoomLevel); ``` ### Zoom Level Get and set the zoom level directly with `engine.scene.getZoomLevel()` and `engine.scene.setZoomLevel()`. A zoom level of 1.0 means one design unit equals one screen pixel. ### Auto-Fit Zoom For continuous auto-framing, use `engine.scene.enableZoomAutoFit()` to automatically keep a block centered as the viewport resizes. ## Saving Scenes ### Saving to String Use `engine.scene.saveToString()` to serialize the current scene. This captures the complete scene structure—pages, blocks, and their properties—as a string you can store. ```typescript highlight-save-scene // Save the scene to a string for persistence const sceneString = await engine.scene.saveToString(); // eslint-disable-next-line no-console console.log('Scene saved successfully. String length:', sceneString.length); ``` The serialized string references external assets by URL rather than embedding them. For complete portability including assets, use `engine.scene.saveToArchive()`. ## Loading Scenes ### Loading from String Use `engine.scene.loadFromString()` to restore a scene from a saved string: ```typescript highlight-load-scene // Demonstrate loading the scene from the saved string // This replaces the current scene with the saved version await engine.scene.loadFromString(sceneString); // eslint-disable-next-line no-console console.log('Scene loaded from saved string'); // Zoom to show all loaded pages const loadedScene = engine.scene.get(); if (loadedScene) { await engine.scene.zoomToBlock(loadedScene, { padding: 50 }); } ``` Loading a new scene replaces any existing scene. The engine only holds one active scene at a time. ### Loading from URL Use `engine.scene.loadFromURL()` to load a scene directly from a remote location: ```typescript await engine.scene.loadFromURL('https://example.com/design.scene'); ``` ## Troubleshooting ### Blocks Not Visible Ensure blocks are attached to pages, and pages are attached to the scene. Orphaned blocks that aren't part of the scene hierarchy won't render. ### Scene Not Loading Check that the scene URL or string is valid. If assets fail to load, consider using the `waitForResources` option to ensure everything loads before rendering. ### Zoom Not Working Verify the scene has a valid camera. Some UI configurations may override programmatic zoom controls. ## Scene Type Represents the scene and its global properties. This section describes the properties available for the **Scene Type** (`//ly.img.ubq/scene`) block type. | Property | Type | Default | Description | | ------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `alwaysOnBottom` | `Bool` | `false` | If true, this element's global sorting order is automatically adjusted to be lower than all other siblings. | | `alwaysOnTop` | `Bool` | `false` | If true, this element's global sorting order is automatically adjusted to be higher than all other siblings. | | `blend/mode` | `Enum` | `"Normal"` | The blend mode to use when compositing the block., Possible values: `"PassThrough"`, `"Normal"`, `"Darken"`, `"Multiply"`, `"ColorBurn"`, `"LinearBurn"`, `"DarkenColor"`, `"Lighten"`, `"Screen"`, `"ColorDodge"`, `"LinearDodge"`, `"LightenColor"`, `"Overlay"`, `"SoftLight"`, `"HardLight"`, `"VividLight"`, `"LinearLight"`, `"PinLight"`, `"HardMix"`, `"Difference"`, `"Exclusion"`, `"Subtract"`, `"Divide"`, `"Hue"`, `"Saturation"`, `"Color"`, `"Luminosity"` | | `clipped` | `Bool` | `false` | This component is used to identify elements whose contents and children should be clipped to their bounds. | | `contentFill/mode` | `Enum` | `"Cover"` | Defines how content should be resized to fit its container (e.g., Crop, Cover, Contain)., Possible values: `"Crop"`, `"Cover"`, `"Contain"` | | `flip/horizontal` | `Bool` | `"-"` | Whether the block is flipped horizontally. | | `flip/vertical` | `Bool` | `"-"` | Whether the block is flipped vertically. | | `globalBoundingBox/height` | `Float` | `"-"` | The height of the block's axis-aligned bounding box in world coordinates., *(read-only)* | | `globalBoundingBox/width` | `Float` | `"-"` | The width of the block's axis-aligned bounding box in world coordinates., *(read-only)* | | `globalBoundingBox/x` | `Float` | `"-"` | The x-coordinate of the block's axis-aligned bounding box in world coordinates., *(read-only)* | | `globalBoundingBox/y` | `Float` | `"-"` | The y-coordinate of the block's axis-aligned bounding box in world coordinates., *(read-only)* | | `height` | `Float` | `0` | The height of the block's frame. | | `height/mode` | `Enum` | `"Auto"` | A mode describing how the height dimension may be interpreted (Absolute, Percent, Auto)., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` | | `highlightEnabled` | `Bool` | `true` | Show highlighting when selected or hovered | | `lastFrame/height` | `Float` | `"-"` | The height of the block's frame from the previous layout pass., *(read-only)* | | `lastFrame/width` | `Float` | `"-"` | The width of the block's frame from the previous layout pass., *(read-only)* | | `lastFrame/x` | `Float` | `"-"` | The x-coordinate of the block's frame from the previous layout pass., *(read-only)* | | `lastFrame/y` | `Float` | `"-"` | The y-coordinate of the block's frame from the previous layout pass., *(read-only)* | | `placeholder/enabled` | `Bool` | `false` | Whether the placeholder behavior is enabled or not. | | `playback/playing` | `Bool` | `false` | A tag that can be set on elements for their playback time to be progressed. | | `playback/soloPlaybackEnabled` | `Bool` | `false` | A tag for blocks where playback should progress while the scene is paused. | | `playback/time` | `Double` | `0` | The current playback time of the block contents in seconds. | | `position/x` | `Float` | `0` | The x-coordinate of the block's origin. | | `position/x/mode` | `Enum` | `"Absolute"` | A mode describing how the x-position may be interpreted., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` | | `position/y` | `Float` | `0` | The y-coordinate of the block's origin. | | `position/y/mode` | `Enum` | `"Absolute"` | A mode describing how the y-position may be interpreted., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` | | `rotation` | `Float` | `0` | The rotation of the block in radians. | | `scene/aspectRatioLock` | `Bool` | `true` | Whether the ratio of the pageDimensions' width and height should remain constant when changing either dimension. | | `scene/designUnit` | `Enum` | `"Pixel"` | The unit type in which the page values (size, distances, etc.) are defined., Possible values: `"Pixel"`, `"Millimeter"`, `"Inch"` | | `scene/dpi` | `Float` | `300` | The DPI value to use when exporting and when converting between pixels and inches or millimeter units. | | `scene/layout` | `Enum` | `"Free"` | A value describing how the scene's children are laid out., Possible values: `"Free"`, `"VerticalStack"`, `"HorizontalStack"`, `"DepthStack"` | | `scene/mode` | `Enum` | `"Video"` | The mode of this scene and all elements inside of it., *(read-only)*, Possible values: `"Design"`, `"Video"` | | `scene/pageDimensions/height` | `Float` | `1` | The height of all pages in this scene. | | `scene/pageDimensions/width` | `Float` | `1` | The width of all pages in this scene. | | `scene/pageFormatId` | `String` | `""` | The identifier of the page format configuration that was most recently selected for the pages in this scene. | | `scene/pixelScaleFactor` | `Float` | `1` | A scale factor that is applied to the final export resolution if the design unit is Pixel. | | `selected` | `Bool` | `false` | Indicates if the block is currently selected. | | `transformLocked` | `Bool` | `false` | DesignBlocks with this tag can't be transformed (moved, rotated, scaled, cropped, or flipped). | | `visible` | `Bool` | `true` | If the block is visible in the editor. | | `width` | `Float` | `0` | The width of the block's frame. | | `width/mode` | `Enum` | `"Auto"` | A mode describing how the width dimension may be interpreted (Absolute, Percent, Auto)., Possible values: `"Absolute"`, `"Percent"`, `"Auto"` | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Templating" description: "Understand how templates work in CE.SDK—reusable designs with variables for dynamic text and placeholders for swappable media." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/templating-f94385/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Templating](https://img.ly/docs/cesdk/angular/concepts/templating-f94385/) --- Templates transform static designs into dynamic, data-driven content. They combine reusable layouts with variable text and placeholder media, enabling personalization at scale. ![Templating example showing a personalized postcard design](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-templating-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-templating-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-templating-browser/) A template is a regular CE.SDK scene that contains **variable tokens** in text and **placeholder blocks** for media. When you load a template, you can populate the variables with data and swap placeholder content—producing personalized designs without modifying the underlying layout. ```typescript file=@cesdk_web_examples/guides-concepts-templating-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Templating Concepts * * Demonstrates the core template concepts in CE.SDK: * - Loading a template from URL * - Discovering and setting variables * - Discovering placeholders */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Load a postcard template from URL // Templates are scenes containing variable tokens and placeholder blocks const templateUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'; await engine.scene.loadFromURL(templateUrl); // Zoom to show the full page in the viewport const page = engine.scene.getCurrentPage(); if (page) { await engine.scene.zoomToBlock(page, { padding: 40 }); } // Discover what variables this template expects // Variables are named slots that can be populated with data const variableNames = engine.variable.findAll(); // eslint-disable-next-line no-console console.log('Template variables:', variableNames); // Set variable values to personalize the template // These values replace {{variableName}} tokens in text blocks engine.variable.setString('Name', 'Jane'); engine.variable.setString('Greeting', 'Wish you were here!'); // eslint-disable-next-line no-console console.log('Variables set successfully.'); // Discover placeholder blocks in the template // Placeholders mark content slots for user or automation replacement const placeholders = engine.block.findAllPlaceholders(); // eslint-disable-next-line no-console console.log('Template placeholders:', placeholders.length); // eslint-disable-next-line no-console console.log('Templating guide completed successfully.'); } } export default Example; ``` This guide explains the core concepts. For implementation details, see the guides linked in each section. ## What Makes a Template Any CE.SDK scene can become a template by adding dynamic elements: | Element | Purpose | Example | |---------|---------|---------| | **Variables** | Dynamic text replacement | `Hello, {{firstName}}!` | | **Placeholders** | Swappable media slots | Profile photo, product image | | **Editing Constraints** | Protected design elements | Locked logo, fixed layout | Templates separate **design** (created once by designers) from **content** (populated at runtime with data). This enables workflows like batch generation, form-based customization, and user personalization. ## Variables Variables enable dynamic text without modifying the design structure. Text blocks contain `{{variableName}}` tokens that CE.SDK resolves at render time. ```typescript highlight-set-variables // Set variable values to personalize the template // These values replace {{variableName}} tokens in text blocks engine.variable.setString('Name', 'Jane'); engine.variable.setString('Greeting', 'Wish you were here!'); // eslint-disable-next-line no-console console.log('Variables set successfully.'); ``` **How variables work:** - Define variables with `engine.variable.setString('name', 'value')` - Reference them in text: `Welcome, {{name}}!` - CE.SDK automatically updates all text blocks using that variable - Tokens are case-sensitive; unmatched tokens render as literal text Variables are scene-scoped and persist when you save the template. Use `engine.variable.findAll()` to discover what variables a template expects. [Learn more about text variables →](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) ## Placeholders Placeholders mark blocks as content slots that users or automation can replace. When you enable placeholder behavior on an image block, it displays an overlay pattern and replacement button in the editor. **How placeholders work:** - Enable with `engine.block.setPlaceholderEnabled(block, true)` - Add visual UI with `engine.block.setPlaceholderBehaviorEnabled(fill, true)` - Users in Adopter mode can select and replace placeholder content - Other design elements remain locked Use `engine.block.findAllPlaceholders()` to discover all placeholder blocks in a loaded template. [Learn more about placeholders →](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/) ## Template Workflows Templates support several common workflows: ### Form-Based Customization Load a template, present a form for variable values, and let users customize text while the design stays consistent. The editor UI handles placeholder replacement through drag-and-drop. ### Batch Generation Load a template programmatically, iterate through data records, set variables for each record, and export personalized designs. This powers use cases like certificates, badges, and personalized marketing. ### Design Systems Create template libraries where designers maintain approved layouts and end users customize within defined boundaries using variables and placeholders. ## Loading and Applying Templates CE.SDK provides two approaches for working with templates: **Load a template** with `engine.scene.loadFromURL()` to replace the current scene entirely, including page dimensions: ```typescript highlight-load-template // Load a postcard template from URL // Templates are scenes containing variable tokens and placeholder blocks const templateUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'; await engine.scene.loadFromURL(templateUrl); // Zoom to show the full page in the viewport const page = engine.scene.getCurrentPage(); if (page) { await engine.scene.zoomToBlock(page, { padding: 40 }); } ``` **Apply a template** with `engine.scene.applyTemplateFromURL()` to merge template content into an existing scene while preserving current page dimensions. [Learn more about importing templates →](https://img.ly/docs/cesdk/angular/create-templates/import-e50084/) ## Creating Templates Build templates by adding variable tokens to text blocks and configuring placeholder behavior on media blocks. Save with `engine.scene.saveToString()` or `engine.scene.saveToArchive()`. [Learn more about creating templates →](https://img.ly/docs/cesdk/angular/create-templates/from-scratch-663cda/) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Terminology" description: "Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/terminology-99e82d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Terminology](https://img.ly/docs/cesdk/angular/concepts/terminology-99e82d/) --- A reference guide to the core terms and concepts used throughout CE.SDK documentation. CE.SDK uses consistent terminology across all platforms. Understanding what we call things helps you navigate the API, read documentation efficiently, and communicate effectively with other developers working on CE.SDK integration. ## Core Architecture ### Engine All operations—creating scenes, manipulating blocks, rendering, and exporting—go through the *Engine*. Initialize it once and use it throughout your application's lifecycle. ### Scene The root container for all design content. A *Scene* contains *Pages*, which contain *Blocks*. Only one *Scene* can be active per *Engine* instance. You can create a *Scene* programmatically or load one from a file. *Scenes* operate in one of two modes: - **Design Mode**: Static designs like social posts, print materials, and graphics - **Video Mode**: Timeline-based content with duration, playback, and animation See [Scenes](https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/) for details. ### Page *Pages* are containers within a *Scene* that hold content *Blocks* (see below) and define working area dimensions. In *Design Mode*, pages are individual artboards. In *Video Mode*, pages are timeline compositions where *Blocks* are arranged across time. See [Pages](https://img.ly/docs/cesdk/angular/concepts/pages-7b6bae/) for details. ### Block The fundamental building unit in CE.SDK. Everything visible in a design is a *Block*—images, text, shapes, graphics, audio, video—and even *Pages* themselves. *Blocks* form a parent-child hierarchy. Each *Block* has two identifiers: - **DesignBlockId**: A numeric handle (integer) used in API calls - **UUID**: A stable string identifier that persists across save and load operations See [Blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) for details. ## Block Anatomy Modify a *Block's* appearance and behavior by attaching *Fills*, *Shapes*, and *Effects*. Most of these modifiers must be created separately and then attached to a *Block*. ### Fill *Fills* cover the surface of a *Block's* shape: - **Color Fill**: Solid color - **Gradient Fill**: Linear, radial, or conical gradients - **Image Fill**: Image content - **Video Fill**: Video content See the [Color Fills](https://img.ly/docs/cesdk/angular/fills/color-7129cd/), [Gradient Fills](https://img.ly/docs/cesdk/angular/filters-and-effects/gradients-0ff079/), [Image Fills](https://img.ly/docs/cesdk/angular/fills/image-e9cb5c/), and [Video Fills](https://img.ly/docs/cesdk/angular/fills/video-ec7f9f/) guides. ### Shape *Shapes* define a *Block's* outline and dimensions, determining the silhouette and how the *Fill* is clipped. *Shape* types include: - **Rect**: Rectangles and squares - **Ellipse**: Circles and ovals - **Polygon**: Multi-sided shapes - **Star**: Star shapes with configurable points - **Line**: Straight lines - **Vector Path**: Custom vector shapes Like *Fills*, *Shapes* are created separately and attached to *Blocks*. See [Shapes](https://img.ly/docs/cesdk/angular/shapes-9f1b2c/) for details. ### Effect *Effects* are non-destructive visual modifications applied to a *Block*. Multiple *Effects* can be stacked. *Effect* categories include: - **Adjustments**: Brightness, contrast, saturation, and other image corrections - **Filters**: LUT-based color grading, duotone - **Stylization**: Pixelize, posterize, half-tone, dot pattern, linocut, outliner - **Distortion**: Liquid, mirror, shifter, cross-cut, extrude blur - **Focus**: Tilt-shift, vignette - **Color**: Recolor, green screen (chroma key) - **Other**: Glow, TV glitch The order determines how multiple effects attached to a single block interact. See [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) for details. ### Blur A modifier that reduces sharpness. *Blur* types include: - **Uniform Blur**: Even blur across the entire block - **Radial Blur**: Circular blur from a center point - **Mirrored Blur**: Blur with reflection > **Note:** **Blur has a dedicated API because it composites differently than other effects.** While most effects like brightness or saturation operate only on a block's own pixels, blur needs to sample pixels from the surrounding area to calculate the blurred result. This means blur interacts with the scene's layering and transparency in ways other effects don't—when you blur a partially transparent block, the engine must handle how that blur blends with whatever content sits behind it. See [Blur](https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/) for details. ### Drop Shadow A built-in block property (not an *Effect*) that renders a shadow beneath blocks. *Drop Shadow* has dedicated API methods for enabling, color, offset, and blur radius. > **Warning:** Unlike effects, drop shadow is configured directly on the block rather than created and attached separately. ## Block Handling These terms describe how *Blocks* are categorized and identified. ### Type The built-in *Type* defines a *Block's* core behavior and available properties. *Type* is immutable—you choose it when creating the *Block*. - `//ly.img.ubq/graphic` — Visual block for images, shapes, and graphics - `//ly.img.ubq/text` — Text content - `//ly.img.ubq/audio` — Audio content - `//ly.img.ubq/page` — Page container - `//ly.img.ubq/scene` — Root scene container - `//ly.img.ubq/track` — Video timeline track - `//ly.img.ubq/stack` — Stack container for layering - `//ly.img.ubq/group` — Group container for organizing blocks - `//ly.img.ubq/camera` — Camera for scene viewing - `//ly.img.ubq/cutout` — Cutout/mask block - `//ly.img.ubq/caption` — Caption/subtitle block - `//ly.img.ubq/captionTrack` — Track for captions The *Type* determines which properties and capabilities a *Block* has. ### Kind A custom string label you assign to categorize *Blocks* for your application. Unlike *Type*, *Kind* is mutable and application-defined. Changing the *Kind* has no effect on appearance or behavior at the engine level. You can query and search for *Blocks* by *Kind*. Common uses: - Categorizing template elements ("logo", "headline", "background") - Filtering blocks for custom UI - Automation workflows that process blocks by purpose ### Property A configurable attribute of a *Block*. *Properties* have types (`Bool`, `Int`, `Float`, `String`, `Color`, `Enum`) and paths like `text/fontSize` or `fill/image/imageFileURI`. Access *Properties* using type-specific getter and setter methods. Each *Block* type exposes different properties, which you can discover programmatically. See [Blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) for details. ## Assets and Resources ### Asset Think of *Assets* as media items that you can provide to your users: images, videos, audio files, fonts, stickers, or templates—anything that can be added to a design. *Assets* have metadata including: - **ID**: Unique identifier within an asset source - **Label**: Display name - **Meta**: Custom metadata (URI, dimensions, format) - **Thumbnail URI**: Preview image URL *Assets* are provided by *Asset Sources* and added through the UI or programmatically. ### Asset Source A provider of *Assets*. *Asset Sources* can be built-in (like the default sticker library) or custom. *Asset Sources* implement a query interface returning paginated results with search and filtering. - **Local Asset Source**: Assets defined in JSON, loaded at initialization - **Remote Asset Source**: Custom implementation fetching from external APIs Register *Asset Sources* with the *Engine* to make *Assets* available throughout your application. ### Resource Loaded data from an *Asset* URI. When you reference an image or video URL in a *Block*, the *Engine* fetches and caches the *Resource*. *Resources* include binary data and metadata for rendering. See [Resources](https://img.ly/docs/cesdk/angular/concepts/resources-a58d71/) for details. ### Buffer A resizable container for arbitrary binary data. *Buffers* are useful for dynamically generated content that doesn't come from a URL, such as synthesized audio or programmatically created images. Create a *Buffer*, write data to it, and reference it by URI in *Block* properties. *Buffer* data is not serialized with scenes and changes cannot be undone. See [Buffers](https://img.ly/docs/cesdk/angular/concepts/buffers-9c565b/) for details. ## Templating and Automation These terms describe dynamic content and reusable designs. ### Template A reusable design with predefined structure and styling. *Templates* typically contain *Placeholders* and *Variables* that users customize while maintaining overall layout and branding. *Templates* are scenes saved in a format that can be loaded and modified. ### Placeholder A *Block* marked for content replacement. When a *Block's* placeholder property is enabled, it signals that the *Block* expects user-provided content—an image drop zone or editable text field. *Placeholders* indicate which parts of a design should be customized versus fixed. See [Placeholders](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/) for details. ### Variable A named value referenced in text blocks using `{{variableName}}` syntax. *Variables* enable data-driven design generation by populating templates with dynamic content. Define *Variables* at the scene level and reference them in text blocks. When a *Variable* value changes, all referencing text blocks update automatically. See [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) for details. ## Permissions and Scopes These terms relate to controlling what operations are allowed. ### Scope A permission setting controlling whether specific operations are allowed on a *Block*. *Scopes* enable fine-grained control over what users can modify—essential for template workflows where some elements should be editable and others locked. Common scopes: - `layer/move` — Allow or prevent moving - `layer/resize` — Allow or prevent resizing - `layer/rotate` — Allow or prevent rotation - `layer/visibility` — Allow or prevent hiding - `lifecycle/destroy` — Allow or prevent deletion - `editor/select` — Allow or prevent selection Enable or disable *Scopes* per *Block* to create controlled editing experiences. See [Lock Design Elements](https://img.ly/docs/cesdk/angular/create-templates/lock-131489/) for details. ### Role A preset collection of *Scope* settings. CE.SDK defines two built-in *Roles*: - **Creator**: Full access to all operations, for template authors - **Adopter**: Restricted access for end-users customizing templates *Roles* provide a convenient way to apply consistent permission sets. ## Layout and Units These terms relate to positioning and measurement. ### Design Unit The measurement unit for dimensions in a *Scene*. The choice affects how positions, sizes, and exports are interpreted. Options: - **Pixel**: Screen pixels, default for digital designs - **Millimeter**: Metric measurement for print - **Inch**: Imperial measurement for print Set the design unit at the scene level—all dimension values are interpreted in that unit. See [Design Units](https://img.ly/docs/cesdk/angular/concepts/design-units-cc6597/) for details. ### DPI (Dots Per Inch) Resolution setting affecting export quality and unit conversion. Higher DPI produces larger exports with more detail. The default is 300 DPI, suitable for print-quality output. DPI matters when working with physical units (millimeters, inches) as it determines how measurements translate to pixel dimensions during export. ## Operating Modes These terms describe how CE.SDK runs. ### Scene Mode The operational mode of a *Scene* determining available features: - **Design Mode**: Static designs. No timeline, no playback. Content arranged spatially on pages. - **Video Mode**: Time-based content. Includes timeline, playback controls, duration properties, and animations. Choose the mode when creating a scene—it affects which properties and operations are available. See [Scenes](https://img.ly/docs/cesdk/angular/concepts/scenes-e8596d/) for details. ### Headless Mode Running CE.SDK without the built-in UI. Used for: - Server-side rendering and export - Automation pipelines - Custom UI implementations - Batch processing In *Headless Mode*, you work directly with *Engine* APIs without the visual editor. See [Headless Mode](https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/) for setup. ## Events and State These terms relate to monitoring changes. ### Event / Subscription A callback mechanism for reacting to changes in the *Engine*. Subscribe to events and receive notifications when state changes. Common events: - Selection changes - Block state changes - History (undo/redo) changes Subscriptions return an unsubscribe function to call when you no longer need notifications. See [Events](https://img.ly/docs/cesdk/angular/concepts/events-353f97/) for details. ### Block State The current status of a *Block* indicating readiness or issues: - **Ready**: Normal state, no pending operations - **Pending**: Operation in progress, with optional progress value (0-1) - **Error**: Operation failed, with error type (`ImageDecoding`, `VideoDecoding`, `FileFetch`, etc.) *Block State* reflects the combined status of the *Block* and its attached *Fill*, *Shape*, and *Effects*. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Undo and History" description: "Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls." platform: angular url: "https://img.ly/docs/cesdk/angular/concepts/undo-and-history-99479d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/angular/concepts-c9ff51/) > [Undo and History](https://img.ly/docs/cesdk/angular/concepts/undo-and-history-99479d/) --- Implement undo/redo functionality and manage multiple history stacks to track editing operations. ![Undo and History example showing the CE.SDK editor with undo/redo controls](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-undo-and-history-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-concepts-undo-and-history-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-concepts-undo-and-history-browser/) CE.SDK automatically tracks editing operations, enabling users to undo and redo changes. The engine creates undo steps for most operations automatically. You can also create multiple independent history stacks to isolate different editing contexts, such as separate histories for a main canvas and an overlay editing panel. ```typescript file=@cesdk_web_examples/guides-concepts-undo-and-history-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; // Subscribe to history updates to track state changes const unsubscribe = engine.editor.onHistoryUpdated(() => { const canUndo = engine.editor.canUndo(); const canRedo = engine.editor.canRedo(); console.log('History updated:', { canUndo, canRedo }); }); // Create a triangle shape and add an undo step to record it in history const block = engine.block.create('graphic'); engine.block.setPositionX(block, 140); engine.block.setPositionY(block, 95); engine.block.setWidth(block, 265); engine.block.setHeight(block, 265); const triangleShape = engine.block.createShape('polygon'); engine.block.setInt(triangleShape, 'shape/polygon/sides', 3); engine.block.setShape(block, triangleShape); const triangleFill = engine.block.createFill('color'); engine.block.setColor(triangleFill, 'fill/color/value', { r: 0.2, g: 0.5, b: 0.9, a: 1 }); engine.block.setFill(block, triangleFill); engine.block.appendChild(page, block); // Commit the block creation to history so it can be undone engine.editor.addUndoStep(); // Log current state - canUndo should now be true console.log('Block created. canUndo:', engine.editor.canUndo()); // Undo the block creation if (engine.editor.canUndo()) { engine.editor.undo(); console.log( 'After undo - canUndo:', engine.editor.canUndo(), 'canRedo:', engine.editor.canRedo() ); } // Redo to restore the block if (engine.editor.canRedo()) { engine.editor.redo(); console.log( 'After redo - canUndo:', engine.editor.canUndo(), 'canRedo:', engine.editor.canRedo() ); } // Create a second history stack for isolated operations const secondaryHistory = engine.editor.createHistory(); const primaryHistory = engine.editor.getActiveHistory(); console.log( 'Created secondary history. Primary:', primaryHistory, 'Secondary:', secondaryHistory ); // Switch to the secondary history engine.editor.setActiveHistory(secondaryHistory); console.log( 'Switched to secondary history. Active:', engine.editor.getActiveHistory() ); // Operations in secondary history are isolated from the primary history const secondBlock = engine.block.create('graphic'); engine.block.setPositionX(secondBlock, 440); engine.block.setPositionY(secondBlock, 95); engine.block.setWidth(secondBlock, 220); engine.block.setHeight(secondBlock, 220); const circleShape = engine.block.createShape('ellipse'); engine.block.setShape(secondBlock, circleShape); const circleFill = engine.block.createFill('color'); engine.block.setColor(circleFill, 'fill/color/value', { r: 0.9, g: 0.3, b: 0.3, a: 1 }); engine.block.setFill(secondBlock, circleFill); engine.block.appendChild(page, secondBlock); // Commit changes to the secondary history engine.editor.addUndoStep(); console.log( 'Block added in secondary history. canUndo:', engine.editor.canUndo() ); // Switch back to primary history engine.editor.setActiveHistory(primaryHistory); console.log( 'Switched back to primary history. canUndo:', engine.editor.canUndo() ); // Clean up the secondary history when no longer needed engine.editor.destroyHistory(secondaryHistory); console.log('Secondary history destroyed'); // Manually add an undo step after custom operations engine.block.setPositionX(block, 190); engine.editor.addUndoStep(); console.log('Manual undo step added. canUndo:', engine.editor.canUndo()); // Remove the most recent undo step if needed if (engine.editor.canUndo()) { engine.editor.removeUndoStep(); console.log('Most recent undo step removed'); } // Reset block position to its original location engine.block.setPositionX(block, 140); // Add instruction text at the end (after all demo operations) const instructionText = engine.block.create('text'); engine.block.setPositionX(instructionText, 50); engine.block.setPositionY(instructionText, 430); engine.block.setWidth(instructionText, 700); engine.block.setHeight(instructionText, 120); engine.block.replaceText( instructionText, 'Open the browser console to see logs of the undo and redo operations in this example.' ); engine.block.setFloat(instructionText, 'text/fontSize', 90); engine.block.setEnum(instructionText, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, instructionText); // Clean up subscription when done (in a real app, call this on cleanup) // unsubscribe(); void unsubscribe; } } export default Example; ``` This guide covers how to use the built-in undo/redo UI controls, perform undo and redo operations programmatically, subscribe to history changes, manually manage undo steps, and work with multiple history stacks. ## Setup We start by initializing the CE.SDK editor and creating a design scene. The engine automatically creates a history stack when the editor is initialized. ```typescript highlight=highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; ``` ## Using the Built-in Undo/Redo UI The CE.SDK editor includes undo and redo buttons in the navigation bar. When users make changes, the undo button becomes active. After undoing, the redo button allows restoring changes. The UI automatically reflects the current history state, disabling buttons when no operations are available. ## Automatic Undo Step Creation Most editing operations automatically create undo steps. When we add a shape to the scene, the engine records this operation in the history stack. ```typescript highlight=highlight-create-block // Create a triangle shape and add an undo step to record it in history const block = engine.block.create('graphic'); engine.block.setPositionX(block, 140); engine.block.setPositionY(block, 95); engine.block.setWidth(block, 265); engine.block.setHeight(block, 265); const triangleShape = engine.block.createShape('polygon'); engine.block.setInt(triangleShape, 'shape/polygon/sides', 3); engine.block.setShape(block, triangleShape); const triangleFill = engine.block.createFill('color'); engine.block.setColor(triangleFill, 'fill/color/value', { r: 0.2, g: 0.5, b: 0.9, a: 1 }); engine.block.setFill(block, triangleFill); engine.block.appendChild(page, block); // Commit the block creation to history so it can be undone engine.editor.addUndoStep(); ``` After creating the shape, `canUndo()` returns `true` since the operation has been recorded as an undoable step. ## Performing Undo and Redo Operations We use `engine.editor.undo()` and `engine.editor.redo()` to programmatically revert or restore changes. Before calling these methods, check availability with `engine.editor.canUndo()` and `engine.editor.canRedo()` to prevent errors. ```typescript highlight=highlight-undo // Undo the block creation if (engine.editor.canUndo()) { engine.editor.undo(); console.log( 'After undo - canUndo:', engine.editor.canUndo(), 'canRedo:', engine.editor.canRedo() ); } ``` The undo operation reverts the most recent change. After undoing, `canRedo()` returns `true` since there's now a step available to restore. ```typescript highlight=highlight-redo // Redo to restore the block if (engine.editor.canRedo()) { engine.editor.redo(); console.log( 'After redo - canUndo:', engine.editor.canUndo(), 'canRedo:', engine.editor.canRedo() ); } ``` The redo operation restores the most recently undone change. After redoing, `canRedo()` returns `false` (unless there are more undo steps to restore). ## Subscribing to History Changes We use `engine.editor.onHistoryUpdated()` to receive notifications when the history state changes. The callback fires after any undo, redo, or new operation. This enables synchronizing custom UI elements with the current history state. ```typescript highlight=highlight-subscribe-history // Subscribe to history updates to track state changes const unsubscribe = engine.editor.onHistoryUpdated(() => { const canUndo = engine.editor.canUndo(); const canRedo = engine.editor.canRedo(); console.log('History updated:', { canUndo, canRedo }); }); ``` The subscription returns an unsubscribe function. Call it when you no longer need notifications, such as when unmounting a component. ## Managing Undo Steps Manually Most editing operations automatically create undo steps. However, some custom operations may require manual checkpoint creation using `engine.editor.addUndoStep()`. This is useful when you make multiple related changes that should be undone as a single unit. ```typescript highlight=highlight-manual-undo-step // Manually add an undo step after custom operations engine.block.setPositionX(block, 190); engine.editor.addUndoStep(); console.log('Manual undo step added. canUndo:', engine.editor.canUndo()); // Remove the most recent undo step if needed if (engine.editor.canUndo()) { engine.editor.removeUndoStep(); console.log('Most recent undo step removed'); } // Reset block position to its original location engine.block.setPositionX(block, 140); ``` We use `engine.editor.removeUndoStep()` to remove the most recent undo step. Always check `canUndo()` before calling this method to ensure an undo step is available. This can be useful when you need to discard changes without affecting the redo stack. ## Working with Multiple History Stacks CE.SDK supports multiple independent history stacks for isolated editing contexts. This is useful when you need separate undo/redo histories for different parts of your application, such as a main canvas and an overlay editor. ### Creating and Switching History Stacks We create a new history stack using `engine.editor.createHistory()`. Use `engine.editor.setActiveHistory()` to switch between stacks. Only the active history responds to undo/redo operations. ```typescript highlight=highlight-multiple-histories // Create a second history stack for isolated operations const secondaryHistory = engine.editor.createHistory(); const primaryHistory = engine.editor.getActiveHistory(); console.log( 'Created secondary history. Primary:', primaryHistory, 'Secondary:', secondaryHistory ); // Switch to the secondary history engine.editor.setActiveHistory(secondaryHistory); console.log( 'Switched to secondary history. Active:', engine.editor.getActiveHistory() ); // Operations in secondary history are isolated from the primary history const secondBlock = engine.block.create('graphic'); engine.block.setPositionX(secondBlock, 440); engine.block.setPositionY(secondBlock, 95); engine.block.setWidth(secondBlock, 220); engine.block.setHeight(secondBlock, 220); const circleShape = engine.block.createShape('ellipse'); engine.block.setShape(secondBlock, circleShape); const circleFill = engine.block.createFill('color'); engine.block.setColor(circleFill, 'fill/color/value', { r: 0.9, g: 0.3, b: 0.3, a: 1 }); engine.block.setFill(secondBlock, circleFill); engine.block.appendChild(page, secondBlock); // Commit changes to the secondary history engine.editor.addUndoStep(); console.log( 'Block added in secondary history. canUndo:', engine.editor.canUndo() ); // Switch back to primary history engine.editor.setActiveHistory(primaryHistory); console.log( 'Switched back to primary history. canUndo:', engine.editor.canUndo() ); ``` Operations performed while a history is active only affect that history. When you switch back to the primary history, its undo/redo state remains unchanged by operations performed in the secondary history. ### Cleaning Up History Stacks We destroy unused history stacks with `engine.editor.destroyHistory()` to free resources. Always clean up secondary histories when they're no longer needed. ```typescript highlight=highlight-destroy-history // Clean up the secondary history when no longer needed engine.editor.destroyHistory(secondaryHistory); console.log('Secondary history destroyed'); ``` ## Troubleshooting Common issues when working with undo/redo functionality: - **Undo step not recorded**: Ensure changes occur after the history subscription is active. The engine only tracks operations that happen while the history is being monitored. - **Redo not available**: Performing any new action after undo clears the redo stack. This is standard behavior to prevent branching history states. - **Wrong history active**: Always verify the correct history is set with `getActiveHistory()` before performing undo/redo operations when using multiple stacks. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Configuration" description: "Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements." platform: angular url: "https://img.ly/docs/cesdk/angular/configuration-2c1c3d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Configuration](https://img.ly/docs/cesdk/angular/configuration-2c1c3d/) --- Set up CE.SDK with license keys, asset base URLs, user IDs, and runtime configuration options to match your application requirements. ![Configuration example showing CE.SDK editor with theme toggle in navigation bar](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-configuration-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-configuration-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-configuration-browser/) `CreativeEditorSDK.create()` initializes the full CE.SDK editor with UI components. The configuration object controls license validation, asset loading, user tracking, and UI behavior. ```typescript file=@cesdk_web_examples/guides-configuration-browser/index.ts reference-only import CreativeEditorSDK from '@cesdk/cesdk-js'; import Example from './browser'; const config = { // License key removes watermarks from exports // Get a free trial at https://img.ly/forms/free-trial // license: 'YOUR_CESDK_LICENSE_KEY', // User ID for accurate MAU tracking across devices userId: 'guides-user', // Custom logger for debugging and monitoring logger: (message: string, level?: string) => { console.log(`[CE.SDK ${level ?? 'Info'}] ${message}`); }, // Enable developer mode for diagnostics devMode: false, // Accessibility settings a11y: { headingsHierarchyStart: 1 as const }, // Location of core engine assets (WASM, data files) // Default: IMG.LY CDN. For production, host assets yourself. // baseURL: 'https://your-cdn.com/cesdk-assets/', // Use local assets when developing with local packages ...(import.meta.env.CESDK_USE_LOCAL && { baseURL: import.meta.env.VITE_CESDK_ASSETS_BASE_URL }) }; CreativeEditorSDK.create('#cesdk_container', config) .then(async (cesdk: CreativeEditorSDK) => { // Expose cesdk for debugging and hero screenshot generation (window as any).cesdk = cesdk; // Load the example plugin await cesdk.addPlugin(new Example()); }) .catch((error: Error) => { // eslint-disable-next-line no-console console.error('Failed to initialize CE.SDK:', error); }); ``` ```typescript file=@cesdk_web_examples/guides-configuration-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a Scene engine.scene.create('VerticalStack', { page: { size: { width: 800, height: 600 } } }); const pages = engine.block.findByType('page'); const page = pages[0]; // ======================================== // Setup: Gradient Background with Title // ======================================== // Create gradient background const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.15, g: 0.1, b: 0.35, a: 1.0 }, stop: 0 }, { color: { r: 0.4, g: 0.2, b: 0.5, a: 1.0 }, stop: 0.5 }, { color: { r: 0.6, g: 0.3, b: 0.4, a: 1.0 }, stop: 1 } ]); engine.block.setFill(page, gradientFill); // Add centered title text const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const titleText = engine.block.create('text'); engine.block.replaceText(titleText, 'Configure your Editor'); engine.block.setFloat(titleText, 'text/fontSize', 12); engine.block.setTextColor(titleText, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setWidthMode(titleText, 'Auto'); engine.block.setHeightMode(titleText, 'Auto'); engine.block.appendChild(page, titleText); // Add IMG.LY subtext const subtitleText = engine.block.create('text'); engine.block.replaceText(subtitleText, 'Powered by IMG.LY'); engine.block.setFloat(subtitleText, 'text/fontSize', 6); engine.block.setTextColor(subtitleText, { r: 0.9, g: 0.9, b: 0.9, a: 0.8 }); engine.block.setWidthMode(subtitleText, 'Auto'); engine.block.setHeightMode(subtitleText, 'Auto'); engine.block.appendChild(page, subtitleText); // Center both texts const titleWidth = engine.block.getFrameWidth(titleText); const titleHeight = engine.block.getFrameHeight(titleText); const subtitleWidth = engine.block.getFrameWidth(subtitleText); const subtitleHeight = engine.block.getFrameHeight(subtitleText); const spacing = 12; const totalHeight = titleHeight + spacing + subtitleHeight; const startY = (pageHeight - totalHeight) / 2; engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleText, startY); engine.block.setPositionX(subtitleText, (pageWidth - subtitleWidth) / 2); engine.block.setPositionY(subtitleText, startY + titleHeight + spacing); // ======================================== // Runtime Configuration: Theme // ======================================== cesdk.ui.setTheme('light'); const currentTheme = cesdk.ui.getTheme(); console.log('Current theme:', currentTheme); // ======================================== // Runtime Configuration: Scale // ======================================== cesdk.ui.setScale('modern'); const currentScale = cesdk.ui.getScale(); console.log('Current scale:', currentScale); // ======================================== // Runtime Configuration: Actions // ======================================== cesdk.actions.register('customSave', async () => { const sceneBlob = await engine.scene.saveToArchive(); await cesdk.utils.downloadFile(sceneBlob, 'application/zip'); }); // ======================================== // Built-in Actions // ======================================== // Add built-in export and import actions to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.exportImage.navigationBar', 'ly.img.exportPDF.navigationBar', 'ly.img.exportScene.navigationBar', 'ly.img.exportArchive.navigationBar', 'ly.img.importScene.navigationBar', 'ly.img.importArchive.navigationBar' ] }); // ======================================== // Engine Settings // ======================================== engine.editor.setSetting('doubleClickToCropEnabled', true); engine.editor.setSetting('highlightColor', { r: 0, g: 0.5, b: 1, a: 1 }); const cropEnabled = engine.editor.getSetting('doubleClickToCropEnabled'); console.log('Double-click crop enabled:', cropEnabled); // ======================================== // Internationalization: Locale // ======================================== cesdk.i18n.setLocale('en'); const currentLocale = cesdk.i18n.getLocale(); console.log('Current locale:', currentLocale); // ======================================== // Internationalization: Translations // ======================================== cesdk.i18n.setTranslations({ en: { 'common.back': 'Go Back', 'common.apply': 'Apply Changes' } }); // Enable Auto-Fit Zoom engine.scene.zoomToBlock(page); engine.scene.enableZoomAutoFit(page, 'Horizontal', 40, 40); } } export default Example; ``` ## Required Configuration The `license` property is the only required configuration. All other properties have sensible defaults. | Property | Type | Purpose | |----------|------|---------| | `license` | `string` | License key to remove export watermarks | The license key validates your CE.SDK subscription and removes watermarks from exports. Get a free trial license at [https://img.ly/forms/free-trial](https://img.ly/forms/free-trial). ## Optional Configuration These properties customize engine behavior and are all optional. ### Engine Properties | Property | Type | Purpose | |----------|------|---------| | `baseURL` | `string` | Location of core engine assets (WASM, data files) | | `userId` | `string` | User identifier for MAU tracking | | `logger` | `function` | Custom logging function | | `role` | `'Creator'` | `'Adopter'` | `'Viewer'` | `'Presenter'` | User role for feature access | | `featureFlags` | `object` | Experimental feature toggles | ### Editor Properties | Property | Type | Purpose | |----------|------|---------| | `devMode` | `boolean` | Enable developer diagnostics | | `a11y` | `object` | Accessibility settings | | `ui` | `object` | User interface customization | ## Configuration Properties ### License Key The license key validates your CE.SDK subscription and removes watermarks from exports. Without a valid license, exports include a watermark. ```typescript highlight=highlight-license // License key removes watermarks from exports // Get a free trial at https://img.ly/forms/free-trial // license: 'YOUR_CESDK_LICENSE_KEY', ``` ### User ID Provide a unique user identifier for accurate Monthly Active User (MAU) tracking. This helps count users correctly when the same person accesses your application from multiple devices. ```typescript highlight=highlight-userId // User ID for accurate MAU tracking across devices userId: 'guides-user', ``` ### Custom Logger Replace the default console logging with a custom logger function. The logger receives a message string and an optional log level (`'Info'`, `'Warning'`, or `'Error'`). ```typescript highlight=highlight-logger // Custom logger for debugging and monitoring logger: (message: string, level?: string) => { console.log(`[CE.SDK ${level ?? 'Info'}] ${message}`); }, ``` ### Developer Mode Enable developer mode to get additional diagnostics and debugging information in the console. ```typescript highlight=highlight-devMode // Enable developer mode for diagnostics devMode: false, ``` ### Accessibility Settings Configure accessibility options like heading hierarchy for screen readers. The `headingsHierarchyStart` property sets which heading level (1-6) the editor should start from. ```typescript highlight=highlight-a11y // Accessibility settings a11y: { headingsHierarchyStart: 1 as const }, ``` ### Asset Base URL The `baseURL` property specifies the location of core engine assets, including WASM files, data files, and JavaScript workers. By default, these load from the IMG.LY CDN. For production deployments, host these assets yourself by copying the `assets` folder from `node_modules/@cesdk/engine/assets` to your server. Content assets like stickers and filters are loaded separately via asset source plugins (imported from `@cesdk/cesdk-js/plugins`), each of which accepts its own `baseURL` option defaulting to `https://cdn.img.ly/assets/v4`. ```typescript highlight=highlight-baseURL // Location of core engine assets (WASM, data files) // Default: IMG.LY CDN. For production, host assets yourself. // baseURL: 'https://your-cdn.com/cesdk-assets/', ``` ### Initialization Pass the configuration object to `CreativeEditorSDK.create()` along with a container element selector. ```typescript highlight=highlight-create CreativeEditorSDK.create('#cesdk_container', config) .then(async (cesdk: CreativeEditorSDK) => { ``` ## Runtime Configuration After initialization, use dedicated APIs to modify settings dynamically. ### Internationalization #### Locale Change the UI language using `cesdk.i18n.setLocale()`. ```typescript highlight=highlight-locale cesdk.i18n.setLocale('en'); const currentLocale = cesdk.i18n.getLocale(); console.log('Current locale:', currentLocale); ``` #### Translations Add or override UI text strings using `cesdk.i18n.setTranslations()`. ```typescript highlight=highlight-translations cesdk.i18n.setTranslations({ en: { 'common.back': 'Go Back', 'common.apply': 'Apply Changes' } }); ``` > **Note:** For complete localization including custom translations and RTL support, see [Localization](https://img.ly/docs/cesdk/angular/user-interface/localization-508e20/). ### Theme Set the UI theme using `cesdk.ui.setTheme()`. Options: `'light'`, `'dark'`, or `'system'`. ```typescript highlight=highlight-theme cesdk.ui.setTheme('light'); const currentTheme = cesdk.ui.getTheme(); console.log('Current theme:', currentTheme); ``` > **Note:** For advanced theming including custom CSS variables and color schemes, see [Theming](https://img.ly/docs/cesdk/angular/user-interface/appearance/theming-4b0938/). ### Actions Register custom actions for user interactions like save and export. ```typescript highlight=highlight-actions cesdk.actions.register('customSave', async () => { const sceneBlob = await engine.scene.saveToArchive(); await cesdk.utils.downloadFile(sceneBlob, 'application/zip'); }); ``` ### Built-in Actions CE.SDK provides built-in actions for common operations like saving, exporting, and importing. Add them to the navigation bar using `insertOrderComponent()`: ```typescript highlight=highlight-builtin-actions // Add built-in export and import actions to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.exportImage.navigationBar', 'ly.img.exportPDF.navigationBar', 'ly.img.exportScene.navigationBar', 'ly.img.exportArchive.navigationBar', 'ly.img.importScene.navigationBar', 'ly.img.importArchive.navigationBar' ] }); ``` **Available built-in actions:** | Action ID | Purpose | |-----------|---------| | `ly.img.saveScene.navigationBar` | Save scene to cloud | | `ly.img.exportImage.navigationBar` | Export as image (PNG/JPEG) | | `ly.img.exportPDF.navigationBar` | Export as PDF | | `ly.img.exportScene.navigationBar` | Export scene file | | `ly.img.exportArchive.navigationBar` | Export as archive (ZIP) | | `ly.img.importScene.navigationBar` | Import scene file | | `ly.img.importArchive.navigationBar` | Import archive (ZIP) | > **Note:** For detailed navigation bar customization including adding buttons and rearranging elements, see [Navigation Bar](https://img.ly/docs/cesdk/angular/user-interface/customization/navigation-bar-4e5d39/). > **Note:** For a complete guide on registering and managing actions, see [Actions](https://img.ly/docs/cesdk/angular/actions-6ch24x/). ### Scale Adjust UI scale for different device types. Options: `'normal'`, `'large'`, or `'modern'`. ```typescript highlight=highlight-scale cesdk.ui.setScale('modern'); const currentScale = cesdk.ui.getScale(); console.log('Current scale:', currentScale); ``` > **Note:** For advanced scale configuration including responsive callbacks, see [Theming](https://img.ly/docs/cesdk/angular/user-interface/appearance/theming-4b0938/). ### Engine Settings Configure engine behavior using `engine.editor.setSetting()`. ```typescript highlight=highlight-settings engine.editor.setSetting('doubleClickToCropEnabled', true); engine.editor.setSetting('highlightColor', { r: 0, g: 0.5, b: 1, a: 1 }); const cropEnabled = engine.editor.getSetting('doubleClickToCropEnabled'); console.log('Double-click crop enabled:', cropEnabled); ``` > **Note:** For a complete reference of available engine settings, see [Engine Interface](https://img.ly/docs/cesdk/angular/engine-interface-6fb7cf/). ## API Reference | Method | Purpose | |--------|---------| | `CreativeEditorSDK.create()` | Initialize editor | | `cesdk.ui.setTheme()` | Set UI theme | | `cesdk.i18n.setLocale()` | Set UI locale | | `cesdk.i18n.setTranslations()` | Add translations | | `cesdk.ui.setScale()` | Set UI scale | | `cesdk.actions.register()` | Register custom actions | | `cesdk.ui.insertOrderComponent()` | Add built-in actions to navigation bar | | `cesdk.utils.downloadFile()` | Download blob as file | | `engine.editor.setSetting()` | Set engine setting | ## Next Steps - [Headless Mode](https://img.ly/docs/cesdk/angular/concepts/headless-mode/browser-24ab98/) - Use CE.SDK without the UI --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Conversion" description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools." platform: angular url: "https://img.ly/docs/cesdk/angular/conversion-c3fbb3/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/angular/conversion-c3fbb3/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/conversion/overview-44dc58/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools. - [To Base64](https://img.ly/docs/cesdk/angular/conversion/to-base64-39ff25/) - Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs. - [To PNG](https://img.ly/docs/cesdk/angular/conversion/to-png-f1660c/) - Export designs and images to PNG format with compression settings and target dimensions using CE.SDK. - [To PDF](https://img.ly/docs/cesdk/angular/conversion/to-pdf-eb937f/) - Convert your design or document into a high-quality, print-ready PDF format. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools." platform: angular url: "https://img.ly/docs/cesdk/angular/conversion/overview-44dc58/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/angular/conversion-c3fbb3/) > [Overview](https://img.ly/docs/cesdk/angular/conversion/overview-44dc58/) --- ## Supported Input and Output Formats CE.SDK accepts a range of input formats when working with designs, including: When it comes to exporting or converting designs, the SDK supports the following output formats: Each format serves different use cases, giving you the flexibility to adapt designs for your application’s needs. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To Base64" description: "Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs." platform: angular url: "https://img.ly/docs/cesdk/angular/conversion/to-base64-39ff25/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/angular/conversion-c3fbb3/) > [To Base64](https://img.ly/docs/cesdk/angular/conversion/to-base64-39ff25/) --- Convert CE.SDK exports to Base64-encoded strings for embedding in HTML, storing in databases, or transmitting via JSON APIs. ![To Base64](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-base64-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-base64-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-conversion-to-base64-browser/) Base64 encoding transforms binary image data into ASCII text, enabling you to embed images directly in HTML, store them in text-only databases, or transmit them through JSON APIs without binary handling. ```typescript file=@cesdk_web_examples/guides-conversion-to-base64-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) throw new Error('CE.SDK instance is required'); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage()!; await engine.scene.zoomToBlock(page); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', onClick: async () => { const currentPage = engine.scene.getCurrentPage()!; const blob = await engine.block.export(currentPage, { mimeType: 'image/png' }); const base64 = await this.blobToBase64(blob); await cesdk.utils.downloadFile(blob, 'image/png'); cesdk.ui.showNotification({ message: `Base64: ${(base64.length / 1024).toFixed(0)} KB`, type: 'success' }); }, key: 'export-base64', label: 'To Base64', icon: '@imgly/Save' } ] }); cesdk.actions.register('exportDesign', async () => { const currentPage = engine.scene.getCurrentPage()!; const blob = await engine.block.export(currentPage, { mimeType: 'image/png' }); const base64 = await this.blobToBase64(blob); await cesdk.utils.downloadFile(blob, 'image/png'); }); } private blobToBase64(blob: Blob): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.onerror = () => reject(reader.error); reader.readAsDataURL(blob); }); } } export default Example; ``` ## Export a Block to Base64 Use `engine.block.export()` to export a design block as a Blob, then convert it to a Base64 data URI. ```typescript const currentPage = engine.scene.getCurrentPage()!; const blob = await engine.block.export(currentPage, { mimeType: 'image/png' }); const base64 = await blobToBase64(blob); ``` The export returns a Blob containing the rendered image. You then convert this Blob to a Base64 data URI using the browser's `FileReader` API. The resulting string includes the MIME type prefix (`data:image/png;base64,...`), making it ready for immediate use as an image source. ## Convert Blob to Base64 Convert the exported Blob into a Base64 data URI using the browser's `FileReader` API. ```typescript highlight=highlight-convert-base64 private blobToBase64(blob: Blob): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result as string); reader.onerror = () => reject(reader.error); reader.readAsDataURL(blob); }); } ``` The `readAsDataURL()` method returns a complete data URI including the MIME type prefix (`data:image/png;base64,...`). This wrapper converts the callback-based FileReader into a Promise for cleaner async/await usage. ## Customize the Built-in Export Action Override the default `exportDesign` action to integrate Base64 conversion into CE.SDK's built-in export flow. ```typescript highlight=highlight-custom-action cesdk.actions.register('exportDesign', async () => { const currentPage = engine.scene.getCurrentPage()!; const blob = await engine.block.export(currentPage, { mimeType: 'image/png' }); const base64 = await this.blobToBase64(blob); await cesdk.utils.downloadFile(blob, 'image/png'); }); ``` When registered, this action replaces the default export behavior. Any UI component or keyboard shortcut that triggers `exportDesign` will use your custom handler instead. ## Download the Export Use `cesdk.utils.downloadFile()` to save the exported Blob to the user's device. The method accepts a Blob and MIME type, triggering a browser download with the appropriate file extension. ## When to Use Base64 Base64 encoding works well for: - Embedding images directly in HTML or CSS without additional HTTP requests - Storing images in text-only databases like Redis or localStorage - Transmitting images through JSON APIs that don't support binary data - Generating data URIs for email templates > **Note:** Base64 increases file size by approximately 33%. For images larger than 100KB, consider binary storage or direct URL references instead. ## Troubleshooting **Base64 string too long** — Use JPEG or WebP formats with lower quality settings. Reduce dimensions with `targetWidth` and `targetHeight` export options. **Image not displaying** — Verify the data URI includes the correct MIME type prefix. Check that the string wasn't truncated during storage or transmission. **Performance issues** — FileReader operations are asynchronous but encoding large images can still block the UI. Consider Web Workers for images over 1MB. ## API Reference | Method | Description | |--------|-------------| | `engine.block.export(block, options)` | Export a block to a Blob with format options (`mimeType`, `jpegQuality`, `webpQuality`, `targetWidth`, `targetHeight`) | | `engine.scene.getCurrentPage()` | Get the currently active page block | | `FileReader.readAsDataURL(blob)` | Convert Blob to Base64 data URI (Browser API) | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a Blob as a file | | `cesdk.actions.register(name, handler)` | Register or override an action | | `cesdk.ui.showNotification(options)` | Display a notification to the user | ## Next Steps - [Export Options](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration - [Export to PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) — Generate PDFs for print and document workflows - [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) — Export specific regions or individual elements - [Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To PDF" description: "Convert your design or document into a high-quality, print-ready PDF format." platform: angular url: "https://img.ly/docs/cesdk/angular/conversion/to-pdf-eb937f/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/angular/conversion-c3fbb3/) > [To PDF](https://img.ly/docs/cesdk/angular/conversion/to-pdf-eb937f/) --- The CE.SDK allows you to convert JPEG, PNG, WebP, BMP and SVG images into PDFs directly in the browser—no server-side processing required. You can perform this conversion programmatically or through the user interface. ![To PDF](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-pdf-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-pdf-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-conversion-to-pdf-browser/) The CE.SDK supports converting single or multiple images to PDF while allowing transformations such as cropping, rotating, and adding text before exporting. You can also customize PDF output settings, including resolution, compatibility and underlayer. ```typescript file=@cesdk_web_examples/guides-conversion-to-pdf-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: To PDF Guide * * This example demonstrates: * - Exporting designs as PDF documents * - Configuring PDF output settings (DPI, compatibility, underlayer) * - Adding a custom PDF export button to the navigation bar */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) throw new Error('CE.SDK instance is required'); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Load a template scene await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage()!; await engine.scene.zoomToBlock(page); // Add PDF export buttons to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-pdf', label: 'PDF', icon: '@imgly/Download', onClick: async () => { // Export scene as PDF (includes all pages) const scene = engine.scene.get()!; const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); // Download using CE.SDK utils await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF exported successfully', type: 'success' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-high-compat', label: 'High Compat', icon: '@imgly/Download', onClick: async () => { const scene = engine.scene.get()!; // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at scene DPI const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'High compatibility PDF exported', type: 'success' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-underlayer', label: 'Underlayer', icon: '@imgly/Download', onClick: async () => { const scene = engine.scene.get()!; // Define the underlayer spot color before export // RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); // Export with underlayer for special media printing const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks underlayer to prevent visible edges underlayerOffset: -2.0 }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF with underlayer exported', type: 'success' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-dpi', label: 'Custom DPI', icon: '@imgly/Download', onClick: async () => { const scene = engine.scene.get()!; // Adjust the scene DPI for print-ready output // Higher DPI = better quality but larger file size engine.block.setFloat(scene, 'scene/dpi', 150); const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF exported at 150 DPI', type: 'success' }); } } ] }); } } export default Example; ``` This guide covers exporting designs as PDF documents, configuring output settings like DPI and compatibility, adding underlayers for specialty printing, and integrating PDF export into the user interface. ## Convert to PDF Programmatically You can use the CE.SDK to load an image, apply basic edits, and export it as a PDF programmatically. The following examples demonstrate how to convert a single image and how to merge multiple images into a single PDF. ### Convert a Single Image to PDF The example below loads an image, applies transformations, and exports it as a PDF. ```ts // Prepare an image URL const imageURL = 'https://img.ly/static/ubq_samples/sample_4.jpg'; // Create a new scene by loading the image immediately await cesdk.createFromImage(imageURL); // Find the automatically added graphic block with an image fill const block = engine.block.findByType('graphic')[0]; // Apply crop with a scale ratio of 2.0 engine.block.setCropScaleRatio(block, 2.0); // Export as PDF Blob const page = engine.scene.getCurrentPage()!; const blob = await engine.block.export(page, { mimeType: 'application/pdf' }); // You can now save it or display it in your application ``` ### Combine Multiple Images into a Single PDF The example below demonstrates how to merge multiple images into a single PDF document. ```ts // Prepare image URLs const images = [ 'https://img.ly/static/ubq_samples/sample_1.jpg', 'https://img.ly/static/ubq_samples/sample_2.jpg', 'https://img.ly/static/ubq_samples/sample_3.jpg', ]; // Create an empty scene with a 'VerticalStack' layout const scene = engine.scene.create('VerticalStack'); const [stack] = engine.block.findByType('stack'); // Load all images as pages for (const image of images) { // Append the new page to the stack const page = engine.block.create('page'); engine.block.appendChild(stack, page); // Set the image as the fill of the page const imageFill = engine.block.createFill('image'); engine.block.setString(imageFill, 'fill/image/imageFileURI', image); engine.block.setFill(page, imageFill); } // Export all images as a single PDF blob const blob = await engine.block.export(scene, { mimeType: 'application/pdf' }); // You can now save it or display it in your application ``` ## Export a Page as PDF Use `engine.block.export()` to export a design block as a PDF. The method accepts a block ID and export options including the MIME type. ```typescript highlight=highlight-export-pdf // Export scene as PDF (includes all pages) const scene = engine.scene.get()!; const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); ``` Export returns a Blob containing the PDF data. You can export a single page for a single-page PDF, or export the entire scene to include all pages in a multi-page PDF document. ## Download the PDF Use `cesdk.utils.downloadFile()` to save the exported PDF to the user's device. ```typescript highlight=highlight-download // Download using CE.SDK utils await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); ``` The utility handles creating a download link and triggering the browser's save dialog with the appropriate file extension. ## PDF Conversion via the User Interface The CE.SDK allows you to enable PDF conversion directly from the user interface. You can customize the UI to include a "Convert to PDF" button, allowing users to trigger conversion to PDF after they [upload images](https://img.ly/docs/cesdk/angular/insert-media/images-63848a/) and perform any edits or adjustments. ### Add a PDF Export Button Integrate PDF export into the CE.SDK interface by adding a custom button to the navigation bar. ```typescript highlight=highlight-export-button // Add PDF export buttons to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-pdf', label: 'PDF', icon: '@imgly/Download', onClick: async () => { // Export scene as PDF (includes all pages) const scene = engine.scene.get()!; const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); // Download using CE.SDK utils await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF exported successfully', type: 'success' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-high-compat', label: 'High Compat', icon: '@imgly/Download', onClick: async () => { const scene = engine.scene.get()!; // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at scene DPI const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'High compatibility PDF exported', type: 'success' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-underlayer', label: 'Underlayer', icon: '@imgly/Download', onClick: async () => { const scene = engine.scene.get()!; // Define the underlayer spot color before export // RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); // Export with underlayer for special media printing const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks underlayer to prevent visible edges underlayerOffset: -2.0 }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF with underlayer exported', type: 'success' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-dpi', label: 'Custom DPI', icon: '@imgly/Download', onClick: async () => { const scene = engine.scene.get()!; // Adjust the scene DPI for print-ready output // Higher DPI = better quality but larger file size engine.block.setFloat(scene, 'scene/dpi', 150); const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); cesdk.ui.showNotification({ message: 'PDF exported at 150 DPI', type: 'success' }); } } ] }); ``` The button triggers the export workflow when clicked, providing users with a convenient way to download their designs as PDF documents. ### Alternative: Register a Custom Component You can also use `ui.registerComponent` to create a more customized button with full control over styling and behavior. ```ts // Register a custom button component cesdk.ui.registerComponent( 'convert.nav', ({ builder: { Button }, engine }) => { Button('convert-to-pdf', { label: 'Convert To PDF', icon: '@imgly/Download', color: 'accent', onClick: async () => { // Export the current scene as a PDF blob const scene = engine.scene.get()!; const blob = await engine.block.export(scene, { mimeType: 'application/pdf', }); // Trigger download of the PDF blob const element = document.createElement('a'); element.setAttribute('href', window.URL.createObjectURL(blob)); element.setAttribute('download', 'converted.pdf'); element.style.display = 'none'; element.click(); element.remove(); }, }); }, ); // Add the custom button at the end of the navigation bar cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ ...cesdk.ui.getComponentOrder({ in: 'ly.img.navigation.bar' }), 'convert.nav', ]); ``` For more details on customizing the UI, see the [User Interface Configuration Guide](https://img.ly/docs/cesdk/angular/user-interface/customization-72b2f8/). ## Configure PDF Output Settings The SDK provides various options for customizing PDF exports. You can control resolution, compatibility, and underlayer settings. ### Available PDF Output Settings - **Resolution:** Adjust the DPI (dots per inch) to create print-ready PDFs with the desired level of detail. - **Page Size:** Define custom dimensions in pixels for the output PDF. If specified, the block will scale to fully cover the target size while maintaining its aspect ratio. - **Compatibility:** Enable this setting to improve compatibility with various PDF viewers. When enabled, images and effects are rasterized based on the scene's DPI instead of being embedded as vector elements. - **Underlayer:** Add an underlayer beneath the image content to optimize printing on non-white or specialty media (e.g., fabric, glass). The ink type is defined in `ExportOptions` using a spot color. You can also apply a positive or negative offset, in design units, to adjust the underlayer's scale. ### Adjust DPI for Print Quality Control the output resolution by setting the scene's DPI property before export. ```typescript highlight=highlight-dpi // Adjust the scene DPI for print-ready output // Higher DPI = better quality but larger file size engine.block.setFloat(scene, 'scene/dpi', 150); ``` Higher DPI values produce better quality output but result in larger file sizes. The default is 300 DPI, which is suitable for most print applications. ### Enable High Compatibility Mode Enable high compatibility mode for consistent rendering across different PDF viewers. ```typescript highlight=highlight-high-compatibility // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at scene DPI const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); ``` When enabled, complex elements like gradients with transparency are rasterized at the scene's DPI setting instead of being embedded as native PDF objects. This ensures consistent appearance in viewers like Safari and macOS Preview but increases file size. ### PDF Performance Optimization The `exportPdfWithHighCompatibility` flag significantly impacts PDF export performance, especially for high-DPI content: **When `true` (default - safer but slower):** - Rasterizes images and gradients at the scene's DPI setting - Maximum compatibility with all PDF viewers including Safari and macOS Preview - Slower performance (4-10x slower for high-DPI content) - Larger file sizes **When `false` (faster but needs testing):** - Embeds images and gradients directly as native PDF objects - 6-15x faster export performance for high-DPI content - Smaller file sizes (typically 30-40% reduction) - May have rendering issues in Safari/macOS Preview with gradients that use transparency ```typescript const scene = engine.scene.get()!; // For maximum performance (test with your print workflow first) engine.block.setFloat(scene, 'scene/dpi', 150); // Reduce from default 300 const blob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: false, // Much faster }); ``` **Before using `exportPdfWithHighCompatibility: false` in production:** - Test generated PDFs with your actual print vendor/equipment - Verify rendering in Safari and macOS Preview if end-users will view PDFs in those applications - Check that gradients with transparency render correctly - Confirm your content renders properly in Adobe Acrobat and Chrome (these typically work fine) **Safe to use `false` when:** - PDFs go directly to professional printing (not viewed in Safari/Preview) - Content is primarily photos and solid colors (minimal gradients with transparency) - Performance is critical for batch processing workflows **Keep `true` when:** - Users view PDFs in Safari or macOS Preview - Maximum compatibility is required - Content has complex gradients with transparency - You cannot test with your print workflow before production ### Add an Underlayer for Specialty Printing Add an underlayer for printing on non-white or transparent media like fabric or glass. ```typescript highlight=highlight-spot-color // Define the underlayer spot color before export // RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); ``` First define the spot color that will be used for the underlayer. The RGB values provide a preview representation in the editor. ```typescript highlight=highlight-underlayer // Export with underlayer for special media printing const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks underlayer to prevent visible edges underlayerOffset: -2.0 }); ``` The underlayer creates a solid background behind your design content. The negative offset shrinks the underlayer slightly to prevent visible edges around the printed output. ### Customizing PDF Output You can configure all PDF settings together when exporting. ```ts const scene = engine.scene.get()!; // Adjust the DPI to 72 engine.block.setFloat(scene, 'scene/dpi', 72); // Set spot color to be used as underlayer engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); const blob = await engine.block.export(scene, { mimeType: 'application/pdf', // Set target width and height in pixels targetWidth: 800, targetHeight: 600, // Increase compatibility with different PDF viewers exportPdfWithHighCompatibility: true, // Add an underlayer beneath the image content exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', underlayerOffset: -2.0, }); ``` ## Troubleshooting **PDF file size too large** — Reduce the scene DPI or disable high compatibility mode. Use JPEG compression for embedded images where quality loss is acceptable. **Gradients look different in some viewers** — Enable `exportPdfWithHighCompatibility` to rasterize gradients at the scene's DPI setting for consistent appearance across all PDF viewers. **Underlayer not visible in print** — Verify the spot color name matches your print vendor's configuration exactly. Check that the PDF wasn't flattened during post-processing. ## API Reference | Method | Description | |--------|-------------| | `engine.block.export(block, options)` | Export a block to a Blob with format options (`mimeType`, `exportPdfWithHighCompatibility`, `exportPdfWithUnderlayer`, `underlayerSpotColorName`, `underlayerOffset`, `targetWidth`, `targetHeight`) | | `engine.block.setFloat(block, property, value)` | Set a float property on a block (use `scene/dpi` to control PDF resolution) | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color for underlayer printing | | `engine.scene.get()` | Get the current scene block ID | | `engine.scene.getCurrentPage()` | Get the currently active page block | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a Blob as a file | ## Next Steps - [Export Options](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration - [Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints - [Export for Printing](https://img.ly/docs/cesdk/angular/export-save-publish/for-printing-bca896/) — Learn more about print-specific export settings --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To PNG" description: "Export designs and images to PNG format with compression settings and target dimensions using CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/conversion/to-png-f1660c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/angular/conversion-c3fbb3/) > [To PNG](https://img.ly/docs/cesdk/angular/conversion/to-png-f1660c/) --- Export designs to PNG format with lossless quality and optional transparency support. ![Export to PNG example showing CE.SDK with PNG export buttons](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-png-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-conversion-to-png-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-conversion-to-png-browser/) PNG is a lossless image format that preserves image quality and supports transparency. It's ideal for designs requiring pixel-perfect fidelity, logos, graphics with transparent backgrounds, and any content where quality cannot be compromised. ```typescript file=@cesdk_web_examples/guides-conversion-to-png-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (!page) throw new Error('No page found'); await engine.scene.zoomToBlock(page, { padding: 40 }); // Export programmatically using the engine API const exportProgrammatically = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png' }); await cesdk.utils.downloadFile(blob, 'image/png'); }; // Export with compression level (0-9) // Higher values produce smaller files but take longer const exportWithCompression = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9 }); await cesdk.utils.downloadFile(blob, 'image/png'); }; // Export with target dimensions // The block scales to fill the target while maintaining aspect ratio const exportWithDimensions = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1920, targetHeight: 1080 }); await cesdk.utils.downloadFile(blob, 'image/png'); }; // Trigger the built-in export action const triggerExportAction = async () => { await cesdk.actions.run('exportDesign', { mimeType: 'image/png' }); }; // Override the default export action to customize behavior cesdk.actions.register('exportDesign', async (options) => { // Use the utils API to export with a loading dialog const { blobs, options: exportOptions } = await cesdk.utils.export(options); // Custom logic: log the export details console.log( `Exported ${blobs.length} file(s) as ${exportOptions.mimeType}` ); // Download the exported file await cesdk.utils.downloadFile(blobs[0], exportOptions.mimeType); }); // Add export dropdown to navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-png', label: 'Export PNG', icon: '@imgly/Save', onClick: exportProgrammatically }, { id: 'ly.img.action.navigationBar', key: 'export-png-action', label: 'Export PNG (action)', icon: '@imgly/Save', onClick: triggerExportAction }, { id: 'ly.img.action.navigationBar', key: 'export-png-compressed', label: 'Export PNG (compressed)', icon: '@imgly/Save', onClick: exportWithCompression }, { id: 'ly.img.action.navigationBar', key: 'export-png-hd', label: 'Export PNG (HD)', icon: '@imgly/Save', onClick: exportWithDimensions } ] }); } } export default Example; ``` This guide covers how to export designs to PNG, configure export options, and integrate with the built-in export action. ## Export to PNG Use `engine.block.export()` to export a design block to PNG. The method returns a Blob containing the image data. ```typescript highlight=highlight-export-programmatic // Export programmatically using the engine API const exportProgrammatically = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png' }); await cesdk.utils.downloadFile(blob, 'image/png'); }; ``` ## Compression Level Control the file size versus export speed tradeoff using `pngCompressionLevel`. Valid values are 0-9, where higher values produce smaller files but take longer to export. Since PNG is lossless, image quality remains unchanged. ```typescript highlight=highlight-options-compression // Export with compression level (0-9) // Higher values produce smaller files but take longer const exportWithCompression = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9 }); await cesdk.utils.downloadFile(blob, 'image/png'); }; ``` The default compression level is 5, providing a good balance between file size and export speed. ## Target Dimensions Resize the output by setting `targetWidth` and `targetHeight`. The block scales to fill the target dimensions while maintaining its aspect ratio. ```typescript highlight=highlight-options-dimensions // Export with target dimensions // The block scales to fill the target while maintaining aspect ratio const exportWithDimensions = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1920, targetHeight: 1080 }); await cesdk.utils.downloadFile(blob, 'image/png'); }; ``` ## Trigger the Export Action The built-in `exportDesign` action triggers the default export workflow with a loading dialog and automatically downloads the file. ```typescript highlight=highlight-export-action // Trigger the built-in export action const triggerExportAction = async () => { await cesdk.actions.run('exportDesign', { mimeType: 'image/png' }); }; ``` ## Override the Export Action Register a custom handler for the `exportDesign` action to customize behavior. This allows you to add custom logic such as uploading to a server or processing the exported file. ```typescript highlight=highlight-override-action // Override the default export action to customize behavior cesdk.actions.register('exportDesign', async (options) => { // Use the utils API to export with a loading dialog const { blobs, options: exportOptions } = await cesdk.utils.export(options); // Custom logic: log the export details console.log( `Exported ${blobs.length} file(s) as ${exportOptions.mimeType}` ); // Download the exported file await cesdk.utils.downloadFile(blobs[0], exportOptions.mimeType); }); ``` The `cesdk.utils.export()` method handles the export with a loading dialog, while `cesdk.utils.downloadFile()` triggers the browser download. ## API Reference | API | Description | | --- | --- | | `engine.block.export(block, options)` | Exports a block to a Blob with the specified options | | `cesdk.actions.run('exportDesign', options)` | Triggers the default export workflow | | `cesdk.actions.register('exportDesign', handler)` | Overrides the default export action | | `cesdk.utils.export(options)` | Exports with a loading dialog, returns `{ blobs, options }` | | `cesdk.utils.downloadFile(blob, mimeType)` | Downloads a Blob as a file | ## Next Steps - [Conversion Overview](https://img.ly/docs/cesdk/angular/conversion/overview-44dc58/) - Learn about other export formats - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Understand the full export workflow - [To PDF](https://img.ly/docs/cesdk/angular/conversion/to-pdf-eb937f/) - Export designs to PDF format --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Audio" description: "Create audio in videos" platform: angular url: "https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) --- The **CreativeEditor (CE.SDK)** provides different **audio features** you can leverage in web-based apps. This section covers how to **add audio blocks**, **extract** audio from videos, control **playback**, generate **waveforms**, and manage **multi-track audio**. ## Use Cases Use the CE.SDK audio features when you need to create: - Background music - Voice-overs - Sound effects - Podcasts ## How Audio Works in the CE.SDK The CE.SDK represents audio as audio blocks. Each block has: - Source (file or extracted track) - Playback properties (time, speed, volume, mute) - Timeline properties (offset, duration, trim length) - Optional waveform thumbnails for UI visualization ### What Are the Timeline Properties Each audio block has properties that determine when and how much of the sound plays: - **Offset:** the delay before an audio block begins playing inside the scene timeline. - **Trim length**: cuts the audio to keep only a specific part of it. - **Duration**: defines how long the audio plays. ### What Are Waveforms Waveforms are **visual representations** of the audio signal over time. They show the amplitude (volume level) of the sound at each moment, using peaks and valleys. The CE.SDK can generate sampled waveform data that you can render in your UI. This is especially helpful for editing tools. ### When to Create VS. Extract Audio The CE.SDK allows you to either create a blank audio block or extract the audio from a video. - **Create an empty audio block** when you want to add external or standalone audio that doesn’t come from a video. - **Extract the audio** when it comes from a video block already in your scene. ## CE.SDK Audio Features Overview The table below summarizes the main audio-related capabilities in CE.SDK.\ Each feature is related to an example further down the page. | **Category** | **Action** | **API Name** | **Notes** | |--------------------------|-----------------------------|---------------------------------------|-----------| | **Create Audio Blocks** | Create empty audio block | `create` | Creates an audio block with no source. | | | Extract audio from video | `createAudioFromVideo` | Requires a video block ID and track index. | | **Playback Control** | Set playback position | `setPlaybackTime` | Time in seconds. | | | Volume | `setVolume` | Range `0.0–1.0`. | | | Mute | `setMuted` | Boolean. | | | Playback speed | `setPlaybackSpeed` | Range `0.25–3.0`. | | **Timeline Management** | Offset | `setTimeOffset` | To move the playback starting point in the scene timeline. | | | Duration | `setDuration` | Total length (seconds). | | | Trim length | `setTrimLength` | Cuts content to a defined length. | | **Replace Audio Source** | Reload edited scene | `scene.loadFromString` | Used when replacing audio at runtime. | | **Waveforms** | Generate thumbnails | `generateAudioThumbnailSequence` | Produces waveform sample data for UI. | | **Export Audio** | Export WAV | `exportAudio` | MIME type: `audio/wav`. | | | Export MP4 | `exportAudio` | MIME type: `audio/mp4`. | ## Examples Find in the following list of examples different API calls listed in the preceding table. ### Create Audio To create an empty audio block, use: ```ts const blockId = engine.block.create('audio'); ``` Use `engine.block.createAudioFromVideo(blockId, trackIndex: number);`. This example: 1. Extracts the first audio track (1) from a video. 2. Appends it to the page for further manipulation. > **Note:** `videoBlockId` and `pageId` must refer to existing blocks. ```ts const audioBlockId = engine.block.createAudioFromVideo(videoBlockId, 0); // Attach to the page so it’s part of the scene engine.block.appendChild(pageId, audioBlockId); ``` This example: 1. Creates an audio block. 2. Attaches the audio block to the page. 3. Sets the source for the audio from a remote URL. > **Note:** `pageId` must refer to an existing page. ```ts // Create an audio block const audioBlockId = engine.block.create('audio'); await engine.block.appendChild(pageId, audioBlockId); engine.block.setString( audioBlockId, 'audio/fileURI', 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a' ); ``` For details on loading sources, check [the dedicated guide](https://img.ly/docs/cesdk/angular/import-media/from-remote-source/unsplash-8f31f0/). This example exports the audio block in **mp4** format: > **Note:** `blockId` must refer to an existing audio block. ```ts engine.block.exportAudio(blockId, { mymeType: 'audio/mp4' } ); ``` This example exports the audio in **wav** format: ```ts const audioData = await engine.block.exportAudio(blockId, { mimeType: 'audio/wav' } ); ``` ### Control Audio Playback Use `engine.block.setPlaybackTime(blockId, time: number)`. This example sets the current playback at 3 seconds: ```ts engine.block.setPlaybackTime(blockId, 3) ``` Use `engine.block.setVolume(blockId, volume: number);`. This example sets the volume at 1 (max value): ```ts engine.block.setVolume(blockId, 1); ``` Use `engine.block.setMuted(blockId, muted: boolean);`. This example mutes the audio of the block: ```ts engine.block.setMuted(blockId, true); ``` Use `engine.block.setPlaybackSpeed(blockId, speed: number);`. This example multiplies the speed by 0.25: ```ts engine.block.setPlaybackSpeed(blockId, 0.25); ``` ### Manage Audio Timeline If the audio has an offset of: - 0 s → It plays immediately when the scene starts. - 2 s → The CE.SDK waits 2 seconds before playing it. - 10 s → The audio only starts at the 10-second mark. Use `engine.block.setTimeOffset(blockId, offset: number)`. This example starts the audio at 2 s on the timeline: ```ts engine.block.setTimeOffset(blockId, 2); ``` Use `engine.block.setDuration(blockId, duration: number)`. This example sets the audio duration for 300 seconds: ```ts engine.block.setDuration(blockId, 300) ``` Use `engine.block.setTrimLength(blockId, length: number);`. This example creates a new trim that: 1. Starts at the second 2 of the audio content. 2. Plays for 10 seconds. ```ts engine.block.setTrimOffset(blockId, 2) engine.block.setTrimLength(blockId, 10); ``` Use: ```ts engine.block.generateAudioThumbnailSequence( blockId, samplesPerChunk: number, timeBegin: number, timeEnd: number, numberOfSamples: number, numberOfChannels: number) ``` This example generates 1 audio sample that: - Produces 3 chunks of this sample. - Start at second 8. - End at second 18. - Is stereo audio (1 for mono, 2 for stereo) > **Note:** `audioBlockId` must refer to an existing block. ```ts const audioThumbnail = engine.block.generateAudioThumbnailSequence( audioBlockId, 3, // samplesPerChunk 8, // timeBegin 18, // timeEnd 1, // numberOfSamples 2, // numberOfChannels // Return the result (chunkIndex, result) => { if (result instanceof Error) { console.error('Thumbnail chunk failed', result); audioThumbnail(); return; } console.log(`Chunk ${chunkIndex}`, result); } ); ``` Once generated, integrate the waveform into your UI. ## Next Steps For each feature’s detailed instructions and options: - Explore the [CE.SDK API options](https://img.ly/docs/cesdk/angular/api-reference/overview-8f24e1/). - Check the dedicated guides in the audio section. --- ## Related Pages - [Add Sound Effects](https://img.ly/docs/cesdk/angular/create-audio/audio/add-sound-effects-9e984e/) - Learn how to use buffers with arbitrary data to generate sound effects programmatically - [Add Music](https://img.ly/docs/cesdk/angular/create-audio/audio/add-music-5b182c/) - Add background music and audio tracks to video projects using CE.SDK's audio block system. - [Adjust Audio Volume](https://img.ly/docs/cesdk/angular/create-video/audio/adjust-volume-7ecc4a/) - Learn how to adjust audio volume in CE.SDK to control playback levels, mute audio, and balance multiple audio sources in video projects. - [Adjust Audio Playback Speed](https://img.ly/docs/cesdk/angular/create-video/audio/adjust-speed-908d57/) - Learn how to adjust audio playback speed in CE.SDK to create slow-motion, time-stretched, and fast-forward audio effects. - [Loop Audio](https://img.ly/docs/cesdk/angular/create-audio/audio/loop-937be7/) - Create seamless repeating audio playback for background music and sound effects using CE.SDK's audio looping system. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Music" description: "Add background music and audio tracks to video projects using CE.SDK's audio block system." platform: angular url: "https://img.ly/docs/cesdk/angular/create-audio/audio/add-music-5b182c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) > [Add Music](https://img.ly/docs/cesdk/angular/create-audio/audio/add-music-5b182c/) --- Add background music and audio tracks to video projects using CE.SDK's audio block system for rich multimedia experiences. ![Add Music example showing audio tracks in the timeline](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-add-music-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-add-music-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-add-music-browser/) Audio blocks are standalone timeline elements that play alongside video content, independent of video fills. You can add music from the built-in asset library or from custom URLs, position tracks on the timeline, configure volume levels, and layer multiple audio tracks for complex soundscapes. ```typescript file=@cesdk_web_examples/guides-create-audio-add-music-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Add Music Guide * * Demonstrates adding background music to video projects: * - Creating audio blocks programmatically * - Setting audio source URIs * - Configuring timeline position and duration * - Adjusting audio volume * - Querying audio assets from the library * - Managing audio blocks */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found in scene'); } // Set page duration for timeline engine.block.setDuration(page, 30); // Enable audio and timeline features for the UI cesdk.feature.enable('ly.img.video.timeline'); cesdk.feature.enable('ly.img.video.audio'); cesdk.feature.enable('ly.img.video.controls.playback'); // Create an audio block for background music const audioBlock = engine.block.create('audio'); // Set the audio source file const audioUri = 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a'; engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Append audio to the page (makes it part of the timeline) engine.block.appendChild(page, audioBlock); // Wait for audio to load to get duration await engine.block.forceLoadAVResource(audioBlock); // Get the total duration of the audio file const totalDuration = engine.block.getAVResourceTotalDuration(audioBlock); console.log('Audio total duration:', totalDuration, 'seconds'); // Set when the audio starts on the timeline (0 = beginning) engine.block.setTimeOffset(audioBlock, 0); // Set how long the audio plays (use full duration or page duration) const playbackDuration = Math.min(totalDuration, 30); engine.block.setDuration(audioBlock, playbackDuration); // Set the audio volume (0.0 = mute, 1.0 = full volume) engine.block.setVolume(audioBlock, 0.8); // Get current volume const currentVolume = engine.block.getVolume(audioBlock); console.log('Audio volume:', currentVolume); // Query available audio tracks from the asset library const audioAssets = await engine.asset.findAssets('ly.img.audio', { page: 0, perPage: 10 }); console.log('Available audio assets:', audioAssets.assets.length); // Log metadata for each audio asset audioAssets.assets.forEach((asset) => { console.log('Audio asset:', { id: asset.id, label: asset.label, duration: asset.meta?.duration, uri: asset.meta?.uri }); }); // Find all audio blocks in the scene const allAudioBlocks = engine.block.findByType('audio'); console.log('Total audio blocks:', allAudioBlocks.length); // Get information about each audio block allAudioBlocks.forEach((block, index) => { const uri = engine.block.getString(block, 'audio/fileURI'); const timeOffset = engine.block.getTimeOffset(block); const duration = engine.block.getDuration(block); const volume = engine.block.getVolume(block); console.log(`Audio block ${index + 1}:`, { uri: uri.split('/').pop(), // Just filename timeOffset: `${timeOffset}s`, duration: `${duration}s`, volume: `${(volume * 100).toFixed(0)}%` }); }); // Example: Remove the second audio block if it exists if (allAudioBlocks.length > 1) { const blockToRemove = allAudioBlocks[1]; // Destroy the block to remove it and free resources engine.block.destroy(blockToRemove); console.log('Removed second audio block'); } console.log( 'Add Music guide initialized. Open the timeline to see audio tracks.' ); } } export default Example; ``` This guide covers how to add music using the built-in audio UI and how to create and configure audio blocks programmatically using the Block API. ## Using the Built-in Audio UI ### Enable Audio Features Audio blocks require Video mode with timeline support. Enable the audio and timeline features to give users access to audio capabilities through the UI. ```typescript highlight-enable-audio-features // Enable audio and timeline features for the UI cesdk.feature.enable('ly.img.video.timeline'); cesdk.feature.enable('ly.img.video.audio'); cesdk.feature.enable('ly.img.video.controls.playback'); ``` These features control: - `ly.img.video.timeline` - Shows the timeline for positioning audio tracks - `ly.img.video.audio` - Enables the audio library in the dock - `ly.img.video.controls.playback` - Adds playback controls for previewing audio ### User Workflow With audio features enabled, users can add music through the interface: 1. **Open the dock** - Access the asset library panel 2. **Select audio category** - Browse available music tracks 3. **Preview tracks** - Listen to audio before adding 4. **Drag to timeline** - Add audio to the project 5. **Position on timeline** - Adjust when audio starts and ends 6. **Adjust volume** - Use the inspector to set volume levels ## Programmatic Audio Creation ### Create Audio Block We create audio blocks using `engine.block.create('audio')` and set the source file using the `audio/fileURI` property. The audio block must be appended to a page to become part of the timeline. ```typescript highlight-create-audio-block // Create an audio block for background music const audioBlock = engine.block.create('audio'); // Set the audio source file const audioUri = 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a'; engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Append audio to the page (makes it part of the timeline) engine.block.appendChild(page, audioBlock); ``` Audio blocks support common formats including M4A, MP3, and WAV. The source URI can point to any accessible URL or local file. ### Configure Timeline Position Audio blocks have timeline properties that control when and how long they play. We use `setTimeOffset()` to set the start time and `setDuration()` to control playback length. ```typescript highlight-configure-timeline // Wait for audio to load to get duration await engine.block.forceLoadAVResource(audioBlock); // Get the total duration of the audio file const totalDuration = engine.block.getAVResourceTotalDuration(audioBlock); console.log('Audio total duration:', totalDuration, 'seconds'); // Set when the audio starts on the timeline (0 = beginning) engine.block.setTimeOffset(audioBlock, 0); // Set how long the audio plays (use full duration or page duration) const playbackDuration = Math.min(totalDuration, 30); engine.block.setDuration(audioBlock, playbackDuration); ``` The `forceLoadAVResource()` method ensures the audio file is loaded before we access its duration. This is important when you need to know the total length of the audio file for timeline calculations. ### Configure Volume Volume is set using `setVolume()` with values from 0.0 (mute) to 1.0 (full volume). This volume level is applied during export and affects the final rendered output. ```typescript highlight-configure-volume // Set the audio volume (0.0 = mute, 1.0 = full volume) engine.block.setVolume(audioBlock, 0.8); // Get current volume const currentVolume = engine.block.getVolume(audioBlock); console.log('Audio volume:', currentVolume); ``` ## Working with Audio Assets ### Query Audio Library CE.SDK provides a demo audio library that you can query using the Asset API. This allows you to build custom audio selection interfaces or programmatically add tracks based on metadata. ```typescript highlight-query-audio-assets // Query available audio tracks from the asset library const audioAssets = await engine.asset.findAssets('ly.img.audio', { page: 0, perPage: 10 }); console.log('Available audio assets:', audioAssets.assets.length); // Log metadata for each audio asset audioAssets.assets.forEach((asset) => { console.log('Audio asset:', { id: asset.id, label: asset.label, duration: asset.meta?.duration, uri: asset.meta?.uri }); }); ``` Each asset includes metadata such as duration, file URI, and thumbnail URL, which you can use to display track information or make programmatic selections. ## Managing Audio Blocks ### List Audio Blocks Use `findByType('audio')` to retrieve all audio blocks in the scene. This is useful for building audio management interfaces or batch operations. ```typescript highlight-list-audio-blocks // Find all audio blocks in the scene const allAudioBlocks = engine.block.findByType('audio'); console.log('Total audio blocks:', allAudioBlocks.length); // Get information about each audio block allAudioBlocks.forEach((block, index) => { const uri = engine.block.getString(block, 'audio/fileURI'); const timeOffset = engine.block.getTimeOffset(block); const duration = engine.block.getDuration(block); const volume = engine.block.getVolume(block); console.log(`Audio block ${index + 1}:`, { uri: uri.split('/').pop(), // Just filename timeOffset: `${timeOffset}s`, duration: `${duration}s`, volume: `${(volume * 100).toFixed(0)}%` }); }); ``` ### Remove Audio To remove an audio block, call `destroy()` which removes it from the scene and frees its resources. ```typescript highlight-remove-audio // Example: Remove the second audio block if it exists if (allAudioBlocks.length > 1) { const blockToRemove = allAudioBlocks[1]; // Destroy the block to remove it and free resources engine.block.destroy(blockToRemove); console.log('Removed second audio block'); } ``` Always destroy blocks that are no longer needed to prevent memory leaks, especially when working with multiple audio files. ## API Reference | Method | Description | | -------------------------------------------------- | ----------------------------------- | | `feature.enable('ly.img.video.timeline')` | Show timeline for audio positioning | | `feature.enable('ly.img.video.audio')` | Enable audio library in dock | | `feature.enable('ly.img.video.controls.playback')` | Add playback controls | | `block.create('audio')` | Create a new audio block | | `block.setString(id, 'audio/fileURI', uri)` | Set the audio source file | | `block.setTimeOffset(id, seconds)` | Set when audio starts on timeline | | `block.setDuration(id, seconds)` | Set audio playback duration | | `block.setVolume(id, volume)` | Set volume (0.0 to 1.0) | | `block.getVolume(id)` | Get current volume level | | `block.getAVResourceTotalDuration(id)` | Get total audio file duration | | `block.forceLoadAVResource(id)` | Force load audio resource | | `block.findByType('audio')` | Find all audio blocks in scene | | `asset.findAssets(sourceId, query)` | Query audio assets | ## Audio Type A block for playing audio content. This section describes the properties available for the **Audio Type** (`//ly.img.ubq/audio`) block type. | Property | Type | Default | Description | | ------------------------------ | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `audio/fileURI` | `String` | `""` | A URI referencing an audio file. | | `audio/totalDuration` | `Double` | `"-"` | The total duration of the audio file., *(read-only)* | | `contentFill/mode` | `Enum` | `"Cover"` | Defines how content should be resized to fit its container (e.g., Crop, Cover, Contain)., Possible values: `"Crop"`, `"Cover"`, `"Contain"` | | `playback/duration` | `Double` | `null` | The duration in seconds for which this block should be visible. | | `playback/looping` | `Bool` | `false` | Whether the medium should start from the beginning again or should stop. | | `playback/muted` | `Bool` | `false` | Whether the audio is muted. | | `playback/playing` | `Bool` | `false` | A tag that can be set on elements for their playback time to be progressed. | | `playback/soloPlaybackEnabled` | `Bool` | `false` | A tag for blocks where playback should progress while the scene is paused. | | `playback/speed` | `Float` | `1` | The playback speed multiplier. | | `playback/time` | `Double` | `0` | The current playback time of the block contents in seconds. | | `playback/timeOffset` | `Double` | `0` | The time in seconds relative to its parent at which this block should first appear. | | `playback/trimLength` | `Double` | `"-"` | The relative duration of the clip for playback. | | `playback/trimOffset` | `Double` | `"-"` | The time within the clip at which playback should begin, in seconds. | | `playback/volume` | `Float` | `1` | Audio volume with a range of \[0, 1]. | | `selected` | `Bool` | `false` | Indicates if the block is currently selected. | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Sound Effects" description: "Learn how to use buffers with arbitrary data to generate sound effects programmatically" platform: angular url: "https://img.ly/docs/cesdk/angular/create-audio/audio/add-sound-effects-9e984e/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) > [Add Sound Effects](https://img.ly/docs/cesdk/angular/create-audio/audio/add-sound-effects-9e984e/) --- Generate sound effects programmatically using buffers with arbitrary audio data. Create notification chimes, alert tones, and melodies without external files. ![Sound Effects Demo showing programmatically generated audio on the timeline](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-add-sound-effects-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-add-sound-effects-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-add-sound-effects-browser/) CE.SDK lets you create audio from code using buffers. This approach generates sound effects dynamically without external files—useful for notification tones, procedural audio, or any scenario where you need to synthesize audio at runtime. ```typescript file=@cesdk_web_examples/guides-create-audio-add-sound-effects-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * Creates a WAV file buffer from audio parameters and a sample generator function. * * @param sampleRate - Sample rate in Hz (e.g., 48000) * @param durationSeconds - Duration of the audio in seconds * @param generator - Function that generates sample values (-1.0 to 1.0) for each time point * @returns Uint8Array containing a stereo WAV file */ function createWavBuffer( sampleRate: number, durationSeconds: number, /* eslint-disable-next-line no-unused-vars -- Parameter name documents callback signature */ generator: (time: number) => number ): Uint8Array { const bitsPerSample = 16; const channels = 2; // Stereo output const numSamples = Math.floor(durationSeconds * sampleRate); const dataSize = numSamples * channels * (bitsPerSample / 8); // Create WAV file buffer (44-byte header + audio data) const wavBuffer = new ArrayBuffer(44 + dataSize); const view = new DataView(wavBuffer); // RIFF chunk descriptor view.setUint32(0, 0x52494646, false); // "RIFF" view.setUint32(4, 36 + dataSize, true); // File size - 8 view.setUint32(8, 0x57415645, false); // "WAVE" // fmt sub-chunk view.setUint32(12, 0x666d7420, false); // "fmt " view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM) view.setUint16(20, 1, true); // Audio format (1 = PCM) view.setUint16(22, channels, true); // Number of channels view.setUint32(24, sampleRate, true); // Sample rate view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true); view.setUint16(32, channels * (bitsPerSample / 8), true); // Block align view.setUint16(34, bitsPerSample, true); // Bits per sample // data sub-chunk view.setUint32(36, 0x64617461, false); // "data" view.setUint32(40, dataSize, true); // Data size // Generate audio samples let offset = 44; for (let i = 0; i < numSamples; i++) { const time = i / sampleRate; // Generate mono sample and duplicate to both channels const value = generator(time); const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32767))); view.setInt16(offset, sample, true); // Left channel view.setInt16(offset + 2, sample, true); // Right channel offset += 4; } return new Uint8Array(wavBuffer); } /** * Calculates an ADSR (Attack-Decay-Sustain-Release) envelope value for a note. * The envelope shapes the volume over time, creating natural-sounding tones. * * @param time - Current time in seconds * @param noteStart - When the note starts (seconds) * @param noteDuration - Total note duration including release (seconds) * @param attack - Time to reach peak volume (seconds) * @param decay - Time to fall from peak to sustain level (seconds) * @param sustain - Held volume level (0.0 to 1.0) * @param release - Time to fade to silence (seconds) * @returns Envelope amplitude (0.0 to 1.0) */ function adsr( time: number, noteStart: number, noteDuration: number, attack: number, decay: number, sustain: number, release: number ): number { const t = time - noteStart; if (t < 0) return 0; const noteEnd = noteDuration - release; if (t < attack) { // Attack phase: ramp up from 0 to 1 return t / attack; } else if (t < attack + decay) { // Decay phase: ramp down from 1 to sustain level return 1 - ((t - attack) / decay) * (1 - sustain); } else if (t < noteEnd) { // Sustain phase: hold at sustain level return sustain; } else if (t < noteDuration) { // Release phase: ramp down from sustain to 0 return sustain * (1 - (t - noteEnd) / release); } return 0; } // Musical note frequencies (Hz) for the 4th and 5th octaves const NOTE_FREQUENCIES = { C4: 261.63, D4: 293.66, E4: 329.63, F4: 349.23, G4: 392.0, A4: 440.0, B4: 493.88, C5: 523.25, D5: 587.33, E5: 659.25, F5: 698.46, G5: 783.99, A5: 880.0, B5: 987.77, C6: 1046.5 }; // Sound effect 1: Ascending "success" fanfare (2 seconds) // Creates a triumphant feeling with overlapping notes building to a chord const SUCCESS_CHIME = { notes: [ // Quick ascending arpeggio { freq: NOTE_FREQUENCIES.C4, start: 0.0, duration: 0.3 }, { freq: NOTE_FREQUENCIES.E4, start: 0.1, duration: 0.4 }, { freq: NOTE_FREQUENCIES.G4, start: 0.2, duration: 0.5 }, // Sustained major chord { freq: NOTE_FREQUENCIES.C5, start: 0.35, duration: 1.65 }, { freq: NOTE_FREQUENCIES.E5, start: 0.4, duration: 1.6 }, { freq: NOTE_FREQUENCIES.G5, start: 0.45, duration: 1.55 } ], totalDuration: 2.0 }; // Sound effect 2: Gentle notification melody (2 seconds) // A musical phrase that resolves pleasantly const NOTIFICATION_MELODY = { notes: [ { freq: NOTE_FREQUENCIES.E5, start: 0.0, duration: 0.4 }, { freq: NOTE_FREQUENCIES.G5, start: 0.25, duration: 0.5 }, { freq: NOTE_FREQUENCIES.A5, start: 0.6, duration: 0.3 }, { freq: NOTE_FREQUENCIES.G5, start: 0.85, duration: 0.4 }, { freq: NOTE_FREQUENCIES.E5, start: 1.15, duration: 0.85 } ], totalDuration: 2.0 }; // Sound effect 3: Alert/warning tone (2 seconds) // Descending pattern that grabs attention const ALERT_TONE = { notes: [ // Attention-grabbing high notes { freq: NOTE_FREQUENCIES.A5, start: 0.0, duration: 0.25 }, { freq: NOTE_FREQUENCIES.A5, start: 0.3, duration: 0.25 }, // Descending resolution { freq: NOTE_FREQUENCIES.F5, start: 0.6, duration: 0.4 }, { freq: NOTE_FREQUENCIES.D5, start: 0.9, duration: 0.5 }, { freq: NOTE_FREQUENCIES.A4, start: 1.3, duration: 0.7 } ], totalDuration: 2.0 }; /** * CE.SDK Plugin: Add Sound Effects Guide * * This example demonstrates: * - Creating audio buffers with arbitrary data * - Generating sound effects programmatically (chimes, notifications) * - Using WAV format for in-memory audio * - Positioning sound effects on the timeline */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a video scene (audio blocks require timeline support) await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; // Get the page (timeline) const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Calculate total duration: 3 effects × 2s + 2 gaps × 0.5s = 7s const effectDuration = 2.0; const gapDuration = 0.5; const totalDuration = 3 * effectDuration + 2 * gapDuration; // 7 seconds // Set page duration to match total effects length engine.block.setDuration(page, totalDuration); // Add a centered title text to the canvas const text = engine.block.create('text'); engine.block.appendChild(page, text); engine.block.replaceText(text, 'Sound Effects Demo'); engine.block.setTextColor(text, { r: 1, g: 1, b: 1, a: 1 }); engine.block.setFloat(text, 'text/fontSize', 48); // Center the text on the canvas const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); engine.block.setWidth(text, pageWidth); engine.block.setHeight(text, 70); engine.block.setPositionX(text, 0); engine.block.setPositionY(text, pageHeight / 2 - 35); engine.block.setEnum(text, 'text/horizontalAlignment', 'Center'); // Make text visible for the entire duration engine.block.setTimeOffset(text, 0); engine.block.setDuration(text, totalDuration); const sampleRate = 48000; // Create the "success chime" sound effect const chimeBuffer = engine.editor.createBuffer(); // Generate the chime using our helper function const chimeWav = createWavBuffer( sampleRate, SUCCESS_CHIME.totalDuration, (time) => { let sample = 0; // Mix all notes together for (const note of SUCCESS_CHIME.notes) { // Calculate envelope for this note const envelope = adsr( time, note.start, note.duration, 0.02, // Soft attack (20ms) 0.08, // Gentle decay (80ms) 0.7, // Sustain at 70% 0.25 // Smooth release (250ms) ); if (envelope > 0) { // Generate sine wave with slight harmonics for richness const fundamental = Math.sin(2 * Math.PI * note.freq * time); const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.25; const harmonic3 = Math.sin(6 * Math.PI * note.freq * time) * 0.1; sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.3; } } return sample; } ); // Write WAV data to the buffer engine.editor.setBufferData(chimeBuffer, 0, chimeWav); // Create audio block for the chime (starts at 0s) const chimeBlock = engine.block.create('audio'); engine.block.appendChild(page, chimeBlock); engine.block.setString(chimeBlock, 'audio/fileURI', chimeBuffer); engine.block.setTimeOffset(chimeBlock, 0); engine.block.setDuration(chimeBlock, SUCCESS_CHIME.totalDuration); engine.block.setVolume(chimeBlock, 0.8); // Create the "notification melody" sound effect const melodyBuffer = engine.editor.createBuffer(); const melodyWav = createWavBuffer( sampleRate, NOTIFICATION_MELODY.totalDuration, (time) => { let sample = 0; for (const note of NOTIFICATION_MELODY.notes) { const envelope = adsr( time, note.start, note.duration, 0.01, // Soft attack (10ms) 0.06, // Gentle decay (60ms) 0.6, // Sustain at 60% 0.2 // Smooth release (200ms) ); if (envelope > 0) { // Pure sine wave with light 2nd harmonic for gentle tone const fundamental = Math.sin(2 * Math.PI * note.freq * time); const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.15; sample += (fundamental + harmonic2) * envelope * 0.4; } } return sample; } ); engine.editor.setBufferData(melodyBuffer, 0, melodyWav); // Starts at 2.5s (after 2s effect + 0.5s gap) const melodyBlock = engine.block.create('audio'); engine.block.appendChild(page, melodyBlock); engine.block.setString(melodyBlock, 'audio/fileURI', melodyBuffer); engine.block.setTimeOffset(melodyBlock, effectDuration + gapDuration); // 2.5s engine.block.setDuration(melodyBlock, NOTIFICATION_MELODY.totalDuration); engine.block.setVolume(melodyBlock, 0.8); // Create the "alert" sound effect const alertBuffer = engine.editor.createBuffer(); const alertWav = createWavBuffer( sampleRate, ALERT_TONE.totalDuration, (time) => { let sample = 0; for (const note of ALERT_TONE.notes) { const envelope = adsr( time, note.start, note.duration, 0.005, // Sharp attack (5ms) 0.05, // Quick decay (50ms) 0.5, // Sustain at 50% 0.15 // Medium release (150ms) ); if (envelope > 0) { // Slightly brighter tone for alert const fundamental = Math.sin(2 * Math.PI * note.freq * time); const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.2; const harmonic3 = Math.sin(6 * Math.PI * note.freq * time) * 0.15; sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.35; } } return sample; } ); engine.editor.setBufferData(alertBuffer, 0, alertWav); // Starts at 5s (after 2 effects + 2 gaps) const alertBlock = engine.block.create('audio'); engine.block.appendChild(page, alertBlock); engine.block.setString(alertBlock, 'audio/fileURI', alertBuffer); engine.block.setTimeOffset(alertBlock, 2 * (effectDuration + gapDuration)); // 5s engine.block.setDuration(alertBlock, ALERT_TONE.totalDuration); engine.block.setVolume(alertBlock, 0.75); // Select the chime block to show it in the UI engine.block.select(chimeBlock); // Zoom to fit the timeline await cesdk.actions.run('zoom.toPage', { autoFit: true }); // eslint-disable-next-line no-console console.log( `Sound effects: Success (0s), Melody (2.5s), Alert (5s) - each 2s, total ${totalDuration}s` ); } } export default Example; ``` This guide covers working with buffers to create audio data and position it on the timeline. ## Working with Buffers CE.SDK provides a buffer API for creating and managing arbitrary binary data in memory. Use buffers when you need to generate content programmatically rather than loading from files. ### Creating a Buffer Create a buffer with `createBuffer()`, which returns a URI you can use to reference the buffer: ```typescript highlight-buffer-create // Create the "notification melody" sound effect const melodyBuffer = engine.editor.createBuffer(); ``` ### Writing Data Write data to a buffer using `setBufferData()`. The offset parameter specifies where to start writing: ```typescript highlight-buffer-write engine.editor.setBufferData(melodyBuffer, 0, melodyWav); ``` ### Reading Data Read data back from a buffer: ```typescript const length = engine.editor.getBufferLength(buffer); const data = engine.editor.getBufferData(buffer, 0, length); ``` ### Adding an Audio Track Create an audio block and assign the buffer URI to its `audio/fileURI` property. Append it to the page to add it to the timeline: ```typescript highlight-audio-track // Starts at 2.5s (after 2s effect + 0.5s gap) const melodyBlock = engine.block.create('audio'); engine.block.appendChild(page, melodyBlock); engine.block.setString(melodyBlock, 'audio/fileURI', melodyBuffer); ``` ### Cleanup Destroy buffers when no longer needed (buffers are also cleaned up automatically with the scene): ```typescript engine.editor.destroyBuffer(buffer); ``` ## Generating Audio Data To use buffers for audio, you need valid audio data. The WAV format is straightforward to generate: a 44-byte header followed by raw PCM samples. ```typescript highlight-wav-body const bitsPerSample = 16; const channels = 2; // Stereo output const numSamples = Math.floor(durationSeconds * sampleRate); const dataSize = numSamples * channels * (bitsPerSample / 8); // Create WAV file buffer (44-byte header + audio data) const wavBuffer = new ArrayBuffer(44 + dataSize); const view = new DataView(wavBuffer); // RIFF chunk descriptor view.setUint32(0, 0x52494646, false); // "RIFF" view.setUint32(4, 36 + dataSize, true); // File size - 8 view.setUint32(8, 0x57415645, false); // "WAVE" // fmt sub-chunk view.setUint32(12, 0x666d7420, false); // "fmt " view.setUint32(16, 16, true); // Sub-chunk size (16 for PCM) view.setUint16(20, 1, true); // Audio format (1 = PCM) view.setUint16(22, channels, true); // Number of channels view.setUint32(24, sampleRate, true); // Sample rate view.setUint32(28, sampleRate * channels * (bitsPerSample / 8), true); view.setUint16(32, channels * (bitsPerSample / 8), true); // Block align view.setUint16(34, bitsPerSample, true); // Bits per sample // data sub-chunk view.setUint32(36, 0x64617461, false); // "data" view.setUint32(40, dataSize, true); // Data size // Generate audio samples let offset = 44; for (let i = 0; i < numSamples; i++) { const time = i / sampleRate; // Generate mono sample and duplicate to both channels const value = generator(time); const sample = Math.max(-32768, Math.min(32767, Math.round(value * 32767))); view.setInt16(offset, sample, true); // Left channel view.setInt16(offset + 2, sample, true); // Right channel offset += 4; } return new Uint8Array(wavBuffer); ``` This code builds a stereo WAV file by writing the RIFF header, format chunk, and data chunk, then iterating through time to generate samples from a generator function that returns values between -1.0 and 1.0. ## Creating a Sound Effect Combine the buffer API with the WAV helper to create a complete sound effect. This example generates a notification melody by mixing multiple notes with harmonics: ```typescript highlight-generate-melody const melodyWav = createWavBuffer( sampleRate, NOTIFICATION_MELODY.totalDuration, (time) => { let sample = 0; for (const note of NOTIFICATION_MELODY.notes) { const envelope = adsr( time, note.start, note.duration, 0.01, // Soft attack (10ms) 0.06, // Gentle decay (60ms) 0.6, // Sustain at 60% 0.2 // Smooth release (200ms) ); if (envelope > 0) { // Pure sine wave with light 2nd harmonic for gentle tone const fundamental = Math.sin(2 * Math.PI * note.freq * time); const harmonic2 = Math.sin(4 * Math.PI * note.freq * time) * 0.15; sample += (fundamental + harmonic2) * envelope * 0.4; } } return sample; } ); ``` The generator function mixes overlapping notes, each with its own start time and duration. The `adsr()` function shapes each note's volume over time (attack, decay, sustain, release), preventing harsh clicks. Adding a second harmonic at 15% creates a warmer tone than a pure sine wave. ## Positioning on the Timeline Audio blocks require a video scene with timeline support. Position audio using time offset (when it starts) and duration (how long it plays): ```typescript highlight-timeline-position engine.block.setTimeOffset(melodyBlock, effectDuration + gapDuration); // 2.5s engine.block.setDuration(melodyBlock, NOTIFICATION_MELODY.totalDuration); ``` The timeline in this example spaces three sound effects with 0.5-second gaps: ``` Timeline: |----|----|----|----|----|----|----| 0s 1s 2s 3s 4s 5s 6s 7s Success: |====| ^ 0s (2s) Melody: |====| ^ 2.5s (2s) Alert: |====| ^ 5s (2s) ``` Each effect is 2 seconds with 0.5-second gaps between them, for a total duration of 7 seconds. ## Troubleshooting ### No Sound - **Check timeline** - Audio blocks only work in video scenes - **Verify duration** - Ensure the audio block's duration is greater than 0 - **Check buffer data** - The buffer must contain valid WAV data ### Audio Sounds Wrong - **Clipping** - Reduce sample values if they exceed -1.0 to 1.0 - **Clicking** - Add attack/release envelope to avoid pops - **Wrong pitch** - Verify frequency calculations and sample rate (48 kHz) ### Buffer Errors - **Invalid WAV** - Ensure header size fields match actual data size - **Format mismatch** - Use 16-bit PCM, stereo, 48 kHz for best compatibility --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Loop Audio" description: "Create seamless repeating audio playback for background music and sound effects using CE.SDK's audio looping system." platform: angular url: "https://img.ly/docs/cesdk/angular/create-audio/audio/loop-937be7/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) > [Loop](https://img.ly/docs/cesdk/angular/create-audio/audio/loop-937be7/) --- Create seamless repeating audio playback for background music, sound effects, and rhythmic elements using CE.SDK's audio looping system. ![Loop Audio example showing timeline with looping audio blocks](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-audio-loop-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-audio-loop-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-audio-loop-browser/) Audio looping allows media to play continuously by restarting from the beginning when it reaches the end. When you set a block's duration longer than the audio length and enable looping, CE.SDK automatically repeats the audio to fill the entire duration. This makes looping perfect for background music, ambient soundscapes, and repeating sound effects. ```typescript file=@cesdk_web_examples/guides-create-audio-audio-loop-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Audio Loop Guide * * Demonstrates audio looping capabilities in CE.SDK: * - Creating audio blocks with looping enabled * - Controlling looping behavior with duration * - Querying looping state * - Disabling looping for one-time playback * - Understanding loop and duration interaction */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1280, height: 720, unit: 'Pixel' } }); // Enable video and audio features cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); const engine = cesdk.engine; const scene = engine.scene.get()!; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; engine.block.setDuration(page, 30); // 30 second timeline // Use sample audio from demo assets const audioUri = 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a'; // Create a basic audio block const audioBlock = engine.block.create('audio')!; engine.block.appendChild(page, audioBlock); // Set the audio source URI engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Load the audio resource to access metadata like duration await engine.block.forceLoadAVResource(audioBlock); // Get the total audio duration const audioDuration = engine.block.getDouble( audioBlock, 'audio/totalDuration' ); // eslint-disable-next-line no-console console.log('Audio duration:', audioDuration, 'seconds'); // Enable looping for this audio block engine.block.setLooping(audioBlock, true); // Set the block duration longer than the audio length // The audio will loop to fill the entire duration engine.block.setDuration(audioBlock, 15); // eslint-disable-next-line no-console console.log('Looping enabled - audio will repeat to fill 15 seconds'); // Check if an audio block is set to loop const isLooping = engine.block.isLooping(audioBlock); // eslint-disable-next-line no-console console.log('Is looping:', isLooping); // true // Create a second audio block to demonstrate non-looping behavior const nonLoopingAudio = engine.block.create('audio')!; engine.block.appendChild(page, nonLoopingAudio); engine.block.setString(nonLoopingAudio, 'audio/fileURI', audioUri); await engine.block.forceLoadAVResource(nonLoopingAudio); // Set time offset so it doesn't overlap with first audio engine.block.setTimeOffset(nonLoopingAudio, 16); // Disable looping (or leave it at default false) engine.block.setLooping(nonLoopingAudio, false); // Set duration longer than audio length // Audio will play once and stop (no looping) engine.block.setDuration(nonLoopingAudio, 12); // eslint-disable-next-line no-console console.log('Looping disabled - audio plays once and stops'); // Create a third audio block demonstrating looping with trim const trimmedLoopAudio = engine.block.create('audio')!; engine.block.appendChild(page, trimmedLoopAudio); engine.block.setString(trimmedLoopAudio, 'audio/fileURI', audioUri); await engine.block.forceLoadAVResource(trimmedLoopAudio); // Trim to a 2-second segment engine.block.setTrimOffset(trimmedLoopAudio, 1.0); engine.block.setTrimLength(trimmedLoopAudio, 2.0); // Enable looping and set duration longer than trim length engine.block.setLooping(trimmedLoopAudio, true); engine.block.setDuration(trimmedLoopAudio, 8.0); // Position in timeline engine.block.setTimeOffset(trimmedLoopAudio, 29); // eslint-disable-next-line no-console console.log('Trimmed loop - 2s segment will loop 4 times to fill 8s'); // Select the first audio block to show it in timeline engine.block.setSelected(audioBlock, true); // eslint-disable-next-line no-console console.log('Audio looping guide initialized successfully'); } } export default Example; ``` This guide covers how to enable and disable audio looping, control looping behavior with duration settings, and loop trimmed audio segments. ## Understanding Audio Looping When looping is enabled on an audio block, CE.SDK repeats the audio content from the beginning each time it reaches the end. This continues until the block's duration is filled. For example, a 5-second audio clip with looping enabled and a 15-second duration will play three complete times. The loop transitions are seamless—CE.SDK jumps immediately from the end back to the beginning without gaps or clicks. The audio content itself determines how smooth the loop sounds. Audio files designed for looping (with matching start and end points) create perfectly seamless loops, while non-looping audio may have audible transitions. ## Creating Audio Blocks ### Adding Audio Content Audio blocks use file URIs to reference audio sources. We create the block, add it to the page, and set the audio source. ```typescript highlight-create-audio-block // Create a basic audio block const audioBlock = engine.block.create('audio')!; engine.block.appendChild(page, audioBlock); // Set the audio source URI engine.block.setString(audioBlock, 'audio/fileURI', audioUri); ``` The `audio/fileURI` property points to the audio file. CE.SDK supports common audio formats including MP3, M4A, WAV, and AAC. ## Enabling Audio Looping ### Loading Audio Resources Before working with audio properties like duration or trim, we load the audio resource to ensure metadata is available. ```typescript highlight-load-audio-resource // Load the audio resource to access metadata like duration await engine.block.forceLoadAVResource(audioBlock); // Get the total audio duration const audioDuration = engine.block.getDouble( audioBlock, 'audio/totalDuration' ); // eslint-disable-next-line no-console console.log('Audio duration:', audioDuration, 'seconds'); ``` Loading the resource provides access to the total audio duration, which helps calculate how many times the audio will loop given a specific block duration. ### Setting Looping State We enable looping by calling `setLooping()` with `true`. When combined with a block duration longer than the audio length, the audio repeats to fill the full duration. ```typescript highlight-enable-looping // Enable looping for this audio block engine.block.setLooping(audioBlock, true); // Set the block duration longer than the audio length // The audio will loop to fill the entire duration engine.block.setDuration(audioBlock, 15); // eslint-disable-next-line no-console console.log('Looping enabled - audio will repeat to fill 15 seconds'); ``` In this example, if the audio is 5 seconds long and the block duration is 15 seconds, the audio loops three times (5 seconds × 3 = 15 seconds total). ## Querying and Controlling Looping ### Checking Looping State We can check whether an audio block has looping enabled at any time. ```typescript highlight-query-looping-state // Check if an audio block is set to loop const isLooping = engine.block.isLooping(audioBlock); // eslint-disable-next-line no-console console.log('Is looping:', isLooping); // true ``` This is useful when managing complex compositions with multiple audio tracks, allowing us to query and update looping states dynamically. ### Disabling Looping To play audio once without repeating, we set looping to `false`. ```typescript highlight-non-looping-audio const nonLoopingAudio = engine.block.create('audio')!; engine.block.appendChild(page, nonLoopingAudio); engine.block.setString(nonLoopingAudio, 'audio/fileURI', audioUri); await engine.block.forceLoadAVResource(nonLoopingAudio); // Set time offset so it doesn't overlap with first audio engine.block.setTimeOffset(nonLoopingAudio, 16); // Disable looping (or leave it at default false) engine.block.setLooping(nonLoopingAudio, false); // Set duration longer than audio length // Audio will play once and stop (no looping) engine.block.setDuration(nonLoopingAudio, 12); // eslint-disable-next-line no-console console.log('Looping disabled - audio plays once and stops'); ``` With looping disabled and a duration longer than the audio length, the audio plays once and then stops, leaving silence for the remaining duration. ## Looping with Trim Settings ### Trimming Looped Audio We can combine trimming with looping to create short repeating segments from longer audio files. ```typescript highlight-looping-with-trim // Create a third audio block demonstrating looping with trim const trimmedLoopAudio = engine.block.create('audio')!; engine.block.appendChild(page, trimmedLoopAudio); engine.block.setString(trimmedLoopAudio, 'audio/fileURI', audioUri); await engine.block.forceLoadAVResource(trimmedLoopAudio); // Trim to a 2-second segment engine.block.setTrimOffset(trimmedLoopAudio, 1.0); engine.block.setTrimLength(trimmedLoopAudio, 2.0); // Enable looping and set duration longer than trim length engine.block.setLooping(trimmedLoopAudio, true); engine.block.setDuration(trimmedLoopAudio, 8.0); // Position in timeline engine.block.setTimeOffset(trimmedLoopAudio, 29); // eslint-disable-next-line no-console console.log('Trimmed loop - 2s segment will loop 4 times to fill 8s'); ``` This trims the audio to a 2-second segment (from 1.0s to 3.0s of the source), then loops that segment four times to fill an 8-second duration. This technique is powerful for creating rhythmic loops or extracting repeatable portions from longer audio files. ### Choosing Loop Points For seamless loops, choose trim points where the audio content flows naturally from end to beginning. Audio with consistent rhythm, tone, and volume at trim boundaries creates the smoothest loops. Abrupt changes in content or volume at loop boundaries create noticeable transitions. ## API Reference | Method | Description | Parameters | Returns | | ------------------------------- | ---------------------------------- | ----------------------------------------- | --------------- | | `create(type)` | Create an audio block | `type: 'audio'` | `DesignBlockId` | | `setString(id, property, value)`| Set audio source URI | `id: DesignBlockId, property: string, value: string` | `void` | | `setLooping(id, enabled)` | Enable or disable audio looping | `id: DesignBlockId, enabled: boolean` | `void` | | `isLooping(id)` | Check if audio is set to loop | `id: DesignBlockId` | `boolean` | | `setDuration(id, duration)` | Set block playback duration | `id: DesignBlockId, duration: number` | `void` | | `getDuration(id)` | Get block duration | `id: DesignBlockId` | `number` | | `setTrimOffset(id, offset)` | Set trim start point | `id: DesignBlockId, offset: number` | `void` | | `setTrimLength(id, length)` | Set trim length | `id: DesignBlockId, length: number` | `void` | | `forceLoadAVResource(id)` | Load audio resource with metadata | `id: DesignBlockId` | `Promise` | | `getDouble(id, property)` | Get audio property value | `id: DesignBlockId, property: string` | `number` | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Compositions" description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition-db709c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/create-composition/overview-5b19c5/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions. - [Multi-Page Layouts](https://img.ly/docs/cesdk/angular/create-composition/multi-page-4d2b50/) - Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene. - [Create a Collage](https://img.ly/docs/cesdk/angular/create-composition/collage-f7d28d/) - Combine images into a collage using the CE.SDK. - [Design a Layout](https://img.ly/docs/cesdk/angular/create-composition/layout-b66311/) - Create structured compositions using scene layouts, positioning systems, and hierarchical block organization for collages, magazines, and multi-page documents. - [Add a Background](https://img.ly/docs/cesdk/angular/create-composition/add-background-375a47/) - Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks. - [Positioning and Alignment](https://img.ly/docs/cesdk/angular/insert-media/position-and-align-cc6b6a/) - Precisely position, align, and distribute objects using guides, snapping, and alignment tools. - [Group and Ungroup Objects](https://img.ly/docs/cesdk/angular/create-composition/group-and-ungroup-62565a/) - Group multiple design elements together so they move, scale, and transform as a single unit; ungroup to edit them individually. - [Layer Management](https://img.ly/docs/cesdk/angular/create-composition/layer-management-18f07a/) - Organize design elements using a layer stack for precise control over stacking and visibility. - [Lock Design](https://img.ly/docs/cesdk/angular/create-composition/lock-design-0a81de/) - Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels. - [Blend Modes](https://img.ly/docs/cesdk/angular/create-composition/blend-modes-ad3519/) - Apply blend modes to elements to control how colors and layers interact visually. - [Programmatic Creation](https://img.ly/docs/cesdk/angular/create-composition/programmatic-a688bf/) - Documentation for Programmatic Creation --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add a Background" description: "Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/add-background-375a47/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Add a Background](https://img.ly/docs/cesdk/angular/create-composition/add-background-375a47/) --- Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks. ![Add a Background example showing gradient page fill, text with background color, and image shape](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-add-background-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-add-background-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-add-background-browser/) CE.SDK provides two distinct approaches for adding backgrounds to design elements. Understanding when to use each approach ensures your designs render correctly and efficiently. ```typescript file=@cesdk_web_examples/guides-create-composition-add-background-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Add a Background Guide * * This example demonstrates: * - Applying gradient fills to pages * - Adding background colors to text blocks * - Applying image fills to shapes */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Create a design scene and get the page await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Check if the page supports fill, then apply a pastel gradient if (engine.block.supportsFill(page)) { const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.85, g: 0.75, b: 0.95, a: 1.0 }, stop: 0 }, { color: { r: 0.7, g: 0.9, b: 0.95, a: 1.0 }, stop: 1 } ]); engine.block.setFill(page, gradientFill); } // Create header text (dark, no background) const headerText = engine.block.create('text'); engine.block.setString(headerText, 'text/text', 'Learn cesdk'); engine.block.setFloat(headerText, 'text/fontSize', 56); engine.block.setWidth(headerText, 350); engine.block.setHeightMode(headerText, 'Auto'); engine.block.setPositionX(headerText, 50); engine.block.setPositionY(headerText, 230); engine.block.setColor(headerText, 'fill/solid/color', { r: 0.15, g: 0.15, b: 0.2, a: 1.0 }); engine.block.appendChild(page, headerText); // Create "Backgrounds" text with white background const featuredText = engine.block.create('text'); engine.block.setString(featuredText, 'text/text', 'Backgrounds'); engine.block.setFloat(featuredText, 'text/fontSize', 48); engine.block.setWidth(featuredText, 280); engine.block.setHeightMode(featuredText, 'Auto'); // Offset X by paddingLeft (16) so background aligns with header at X=50 engine.block.setPositionX(featuredText, 66); engine.block.setPositionY(featuredText, 280); engine.block.setColor(featuredText, 'fill/solid/color', { r: 0.2, g: 0.2, b: 0.25, a: 1.0 }); engine.block.appendChild(page, featuredText); // Add white background color to the featured text block if (engine.block.supportsBackgroundColor(featuredText)) { engine.block.setBackgroundColorEnabled(featuredText, true); engine.block.setColor(featuredText, 'backgroundColor/color', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setFloat(featuredText, 'backgroundColor/paddingLeft', 16); engine.block.setFloat(featuredText, 'backgroundColor/paddingRight', 16); engine.block.setFloat(featuredText, 'backgroundColor/paddingTop', 10); engine.block.setFloat(featuredText, 'backgroundColor/paddingBottom', 10); engine.block.setFloat(featuredText, 'backgroundColor/cornerRadius', 8); } // Create an image block on the right side const imageBlock = engine.block.create('graphic'); const imageShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, imageShape); engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusTL', 16); engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusTR', 16); engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusBL', 16); engine.block.setFloat(imageShape, 'shape/rect/cornerRadiusBR', 16); engine.block.setWidth(imageBlock, 340); engine.block.setHeight(imageBlock, 400); engine.block.setPositionX(imageBlock, 420); engine.block.setPositionY(imageBlock, 100); // Check if the block supports fill, then apply an image fill if (engine.block.supportsFill(imageBlock)) { const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); } engine.block.appendChild(page, imageBlock); // Create IMG.LY logo (bottom left) const logoBlock = engine.block.create('graphic'); const logoShape = engine.block.createShape('rect'); engine.block.setShape(logoBlock, logoShape); engine.block.setWidth(logoBlock, 100); engine.block.setHeight(logoBlock, 40); engine.block.setPositionX(logoBlock, 50); engine.block.setPositionY(logoBlock, 530); if (engine.block.supportsFill(logoBlock)) { const logoFill = engine.block.createFill('image'); engine.block.setString( logoFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logoBlock, logoFill); } engine.block.appendChild(page, logoBlock); // Check feature support on different blocks const pageSupportsFill = engine.block.supportsFill(page); const textSupportsBackground = engine.block.supportsBackgroundColor(featuredText); const imageSupportsFill = engine.block.supportsFill(imageBlock); console.log('Page supports fill:', pageSupportsFill); console.log('Text supports backgroundColor:', textSupportsBackground); console.log('Image supports fill:', imageSupportsFill); // Zoom to fit the page await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` ## Setup Create a design scene and get a reference to the page where we'll apply backgrounds. ```typescript highlight=highlight-setup // Create a design scene and get the page await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } ``` ## Fills Fills are visual content applied to pages and graphic blocks. Supported fill types include solid colors, linear gradients, radial gradients, and images. ### Check Fill Support Before applying a fill, verify the block supports it with `supportsFill()`. Pages and graphic blocks typically support fills, while text blocks handle their content differently. ```typescript highlight=highlight-check-support // Check feature support on different blocks const pageSupportsFill = engine.block.supportsFill(page); const textSupportsBackground = engine.block.supportsBackgroundColor(featuredText); const imageSupportsFill = engine.block.supportsFill(imageBlock); console.log('Page supports fill:', pageSupportsFill); console.log('Text supports backgroundColor:', textSupportsBackground); console.log('Image supports fill:', imageSupportsFill); ``` ### Apply a Gradient Fill Create a fill with `createFill()` specifying the type, configure its properties, then apply it with `setFill()`. The example below creates a linear gradient with two color stops. ```typescript highlight=highlight-page-fill // Check if the page supports fill, then apply a pastel gradient if (engine.block.supportsFill(page)) { const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.85, g: 0.75, b: 0.95, a: 1.0 }, stop: 0 }, { color: { r: 0.7, g: 0.9, b: 0.95, a: 1.0 }, stop: 1 } ]); engine.block.setFill(page, gradientFill); } ``` The gradient transitions from a pastel purple at the start to a light cyan at the end. ### Apply an Image Fill Image fills display images within the block's shape bounds. Create an image fill, set its URI, and apply it to a graphic block. ```typescript highlight=highlight-shape-fill // Check if the block supports fill, then apply an image fill if (engine.block.supportsFill(imageBlock)) { const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); } ``` The shape's corner radius creates rounded corners on the image. Image fills automatically scale to cover the shape area. ## Background Color Background color is a dedicated property available specifically on text blocks. Unlike fills, background colors include configurable padding and corner radius, creating highlighted text effects without additional graphic blocks. ### Check Background Color Support Use `supportsBackgroundColor()` to verify a block supports this feature. Currently, only text blocks support background colors. ```typescript highlight=highlight-check-support // Check feature support on different blocks const pageSupportsFill = engine.block.supportsFill(page); const textSupportsBackground = engine.block.supportsBackgroundColor(featuredText); const imageSupportsFill = engine.block.supportsFill(imageBlock); console.log('Page supports fill:', pageSupportsFill); console.log('Text supports backgroundColor:', textSupportsBackground); console.log('Image supports fill:', imageSupportsFill); ``` ### Apply Background Color Enable the background color with `setBackgroundColorEnabled()`, then configure its appearance using property paths for color, padding, and corner radius. ```typescript highlight=highlight-background-color // Add white background color to the featured text block if (engine.block.supportsBackgroundColor(featuredText)) { engine.block.setBackgroundColorEnabled(featuredText, true); engine.block.setColor(featuredText, 'backgroundColor/color', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setFloat(featuredText, 'backgroundColor/paddingLeft', 16); engine.block.setFloat(featuredText, 'backgroundColor/paddingRight', 16); engine.block.setFloat(featuredText, 'backgroundColor/paddingTop', 10); engine.block.setFloat(featuredText, 'backgroundColor/paddingBottom', 10); engine.block.setFloat(featuredText, 'backgroundColor/cornerRadius', 8); } ``` The padding properties (`backgroundColor/paddingLeft`, `backgroundColor/paddingRight`, `backgroundColor/paddingTop`, `backgroundColor/paddingBottom`) control the space between the text and the background edge. The `backgroundColor/cornerRadius` property rounds the corners. ## Troubleshooting ### Fill Not Visible If a fill doesn't appear: - Ensure all color components (r, g, b) are between 0 and 1 - Check that the alpha component is greater than 0 - Verify the block supports fills with `supportsFill()` ### Background Color Not Appearing If a background color doesn't appear: - Confirm the block supports it with `supportsBackgroundColor()` - Verify `setBackgroundColorEnabled(block, true)` was called - Check that the color's alpha value is greater than 0 ### Image Not Loading If an image fill doesn't display: - Verify the image URI is accessible - Check browser console for CORS or network errors - Ensure the image format is supported (PNG, JPEG, WebP) ## API Reference | Method | Description | | --- | --- | | `engine.block.supportsFill(block)` | Check if a block supports fills | | `engine.block.createFill(type)` | Create a fill (color, gradient/linear, gradient/radial, image) | | `engine.block.setFill(block, fill)` | Apply a fill to a block | | `engine.block.getFill(block)` | Get the fill applied to a block | | `engine.block.setGradientColorStops(fill, property, stops)` | Set gradient color stops | | `engine.block.supportsBackgroundColor(block)` | Check if a block supports background color | | `engine.block.setBackgroundColorEnabled(block, enabled)` | Enable or disable background color | | `engine.block.isBackgroundColorEnabled(block)` | Check if background color is enabled | | `engine.block.setColor(block, property, color)` | Set color properties | | `engine.block.setFloat(block, property, value)` | Set float properties (padding, radius) | ## Next Steps Explore related topics: - [Apply Colors](https://img.ly/docs/cesdk/angular/colors/apply-2211e3/) - Work with RGB, CMYK, and spot colors - [Fills Overview](https://img.ly/docs/cesdk/angular/fills/overview-3895ee/) - Learn about all fill types in depth --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Blend Modes" description: "Apply blend modes to elements to control how colors and layers interact visually." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/blend-modes-ad3519/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Blend Modes](https://img.ly/docs/cesdk/angular/create-composition/blend-modes-ad3519/) --- Control how design blocks visually blend with underlying layers using CE.SDK's blend mode system for professional layered compositions. ![Blend Modes example showing layered images with blend effects applied](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-blend-modes-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-blend-modes-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-blend-modes-browser/) Blend modes control how a block's colors combine with underlying layers, similar to blend modes in Photoshop or other design tools. CE.SDK provides 27 blend modes organized into categories: Normal, Darken, Lighten, Contrast, Inversion, and Component. Each category serves different compositing needs—darken modes make images darker, lighten modes make them brighter, and contrast modes increase midtone contrast. ```typescript file=@cesdk_web_examples/guides-create-composition-blend-modes-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Blend Modes Guide * * This example demonstrates: * - Checking if a block supports blend modes * - Setting blend modes on overlay layers * - Getting the current blend mode of a block * - Working with opacity values * - Available blend mode values */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const page = engine.block.findByType('page')[0]; // Grid configuration: 3 columns x 2 rows const cols = 3; const rows = 2; const cellWidth = 280; const cellHeight = 210; const padding = 20; const pageWidth = cols * cellWidth + (cols + 1) * padding; const pageHeight = rows * cellHeight + (rows + 1) * padding; // Set page dimensions engine.block.setWidth(page, pageWidth); engine.block.setHeight(page, pageHeight); // Base and overlay image URLs const baseImageUrl = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const overlayImageUrl = 'https://img.ly/static/ubq_samples/sample_2.jpg'; // Six commonly used blend modes to demonstrate const blendModes: Array< 'Multiply' | 'Screen' | 'Overlay' | 'Darken' | 'Lighten' | 'ColorBurn' > = ['Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorBurn']; // Create 6 image pairs in a grid layout for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { const index = row * cols + col; const x = padding + col * (cellWidth + padding); const y = padding + row * (cellHeight + padding); // Create a background image block as the base layer const backgroundBlock = engine.block.create('graphic'); const backgroundShape = engine.block.createShape('rect'); engine.block.setShape(backgroundBlock, backgroundShape); engine.block.setWidth(backgroundBlock, cellWidth); engine.block.setHeight(backgroundBlock, cellHeight); engine.block.setPositionX(backgroundBlock, x); engine.block.setPositionY(backgroundBlock, y); // Set the image fill for the background const backgroundFill = engine.block.createFill('image'); engine.block.setString( backgroundFill, 'fill/image/imageFileURI', baseImageUrl ); engine.block.setFill(backgroundBlock, backgroundFill); engine.block.setContentFillMode(backgroundBlock, 'Cover'); engine.block.appendChild(page, backgroundBlock); // Create a second image block on top for blending const overlayBlock = engine.block.create('graphic'); const overlayShape = engine.block.createShape('rect'); engine.block.setShape(overlayBlock, overlayShape); engine.block.setWidth(overlayBlock, cellWidth); engine.block.setHeight(overlayBlock, cellHeight); engine.block.setPositionX(overlayBlock, x); engine.block.setPositionY(overlayBlock, y); // Set a different image fill for the overlay const overlayFill = engine.block.createFill('image'); engine.block.setString( overlayFill, 'fill/image/imageFileURI', overlayImageUrl ); engine.block.setFill(overlayBlock, overlayFill); engine.block.setContentFillMode(overlayBlock, 'Cover'); engine.block.appendChild(page, overlayBlock); // Check if the block supports blend modes before applying if (engine.block.supportsBlendMode(overlayBlock)) { // Apply a different blend mode to each overlay const blendMode = blendModes[index]; engine.block.setBlendMode(overlayBlock, blendMode); // Retrieve and log the current blend mode const currentMode = engine.block.getBlendMode(overlayBlock); // eslint-disable-next-line no-console console.log(`Cell ${index + 1} blend mode:`, currentMode); } // Check if the block supports opacity if (engine.block.supportsOpacity(overlayBlock)) { // Set the opacity to 80% for clear visibility engine.block.setOpacity(overlayBlock, 0.8); } // Retrieve and log the opacity value const opacity = engine.block.getOpacity(overlayBlock); // eslint-disable-next-line no-console console.log(`Cell ${index + 1} opacity:`, opacity); } } // Zoom to fit the composition await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` This guide covers how to check blend mode support, apply blend modes programmatically, understand the available blend mode options, and combine blend modes with opacity for fine control over layer compositing. ## Checking Blend Mode Support Before applying a blend mode, verify that the block supports it using `supportsBlendMode()`. Most graphic blocks support blend modes, but always check to avoid errors. ```typescript highlight-check-support // Check if the block supports blend modes before applying if (engine.block.supportsBlendMode(overlayBlock)) { ``` Blend mode support is available for graphic blocks with image or video fills, shape blocks, and text blocks. Page blocks and scene blocks typically do not support blend modes directly. ## Setting and Getting Blend Modes Apply a blend mode with `setBlendMode()` and retrieve the current mode with `getBlendMode()`. The default blend mode is `'Normal'`, which displays the block without any blending effect. ```typescript highlight-set-blend-mode // Apply a different blend mode to each overlay const blendMode = blendModes[index]; engine.block.setBlendMode(overlayBlock, blendMode); ``` After setting a blend mode, you can confirm the change by retrieving the current value: ```typescript highlight-get-blend-mode // Retrieve and log the current blend mode const currentMode = engine.block.getBlendMode(overlayBlock); // eslint-disable-next-line no-console console.log(`Cell ${index + 1} blend mode:`, currentMode); ``` ## Available Blend Modes CE.SDK provides 27 blend modes organized into categories, each producing different visual results: ### Normal Modes - **`PassThrough`** - Allows children of a group to blend with layers below the group - **`Normal`** - Default mode with no blending effect ### Darken Modes These modes darken the result by comparing the base and blend colors: - **`Darken`** - Selects the darker of the base and blend colors - **`Multiply`** - Multiplies colors, producing darker results (great for shadows) - **`ColorBurn`** - Darkens base color by increasing contrast - **`LinearBurn`** - Darkens base color by decreasing brightness - **`DarkenColor`** - Selects the darker color based on luminosity ### Lighten Modes These modes lighten the result by comparing colors: - **`Lighten`** - Selects the lighter of the base and blend colors - **`Screen`** - Multiplies the inverse of colors, producing lighter results (great for highlights) - **`ColorDodge`** - Lightens base color by decreasing contrast - **`LinearDodge`** - Lightens base color by increasing brightness - **`LightenColor`** - Selects the lighter color based on luminosity ### Contrast Modes These modes increase midtone contrast: - **`Overlay`** - Combines Multiply and Screen based on the base color - **`SoftLight`** - Similar to Overlay but with a softer effect - **`HardLight`** - Similar to Overlay but based on the blend color - **`VividLight`** - Burns or dodges colors based on the blend color - **`LinearLight`** - Increases or decreases brightness based on blend color - **`PinLight`** - Replaces colors based on the blend color - **`HardMix`** - Reduces colors to white, black, or primary colors ### Inversion Modes These modes create inverted or subtracted effects: - **`Difference`** - Subtracts the darker from the lighter color - **`Exclusion`** - Similar to Difference with lower contrast - **`Subtract`** - Subtracts blend color from base color - **`Divide`** - Divides base color by blend color ### Component Modes These modes affect specific color components: - **`Hue`** - Uses the hue of the blend color with base saturation and luminosity - **`Saturation`** - Uses the saturation of the blend color - **`Color`** - Uses the hue and saturation of the blend color - **`Luminosity`** - Uses the luminosity of the blend color ## Combining Blend Modes with Opacity For finer control over compositing, combine blend modes with opacity. Opacity reduces overall visibility while the blend mode affects color interaction with underlying layers. ```typescript highlight-set-opacity // Check if the block supports opacity if (engine.block.supportsOpacity(overlayBlock)) { // Set the opacity to 80% for clear visibility engine.block.setOpacity(overlayBlock, 0.8); } ``` You can retrieve the current opacity value to confirm changes or read existing state: ```typescript highlight-get-opacity // Retrieve and log the opacity value const opacity = engine.block.getOpacity(overlayBlock); // eslint-disable-next-line no-console console.log(`Cell ${index + 1} opacity:`, opacity); ``` > **Tip:** Start with full opacity (1.0) when experimenting with blend modes, then reduce > opacity to soften the effect. Common values are 0.5-0.7 for subtle blending > effects. ## Troubleshooting ### Blend Mode Has No Visible Effect If a blend mode doesn't produce visible changes: - Ensure there are underlying layers for the block to blend with. Blend modes only affect compositing with content below. - Verify the blend mode is applied to the correct block using `getBlendMode()`. - Check that the block has visible content (fill or image) to blend. ### Cannot Set Blend Mode If `setBlendMode()` throws an error: - Check that `supportsBlendMode()` returns `true` for the block. - Verify the block ID is valid and the block exists in the scene. - Ensure you're passing a valid blend mode string from the available options. ### Unexpected Blending Results If the visual result doesn't match expectations: - Verify the blend mode category matches your intent (darken vs lighten vs contrast). - Check the stacking order of blocks—blend modes affect content below the block. - Experiment with different blend modes from the same category to find the best visual match. ## API Reference | Method | Description | | ---------------------------------------- | ------------------------------------------------- | | `engine.block.supportsBlendMode(id)` | Check if a block supports blend modes | | `engine.block.setBlendMode(id, mode)` | Set the blend mode for a block | | `engine.block.getBlendMode(id)` | Get the current blend mode of a block | | `engine.block.supportsOpacity(id)` | Check if a block supports opacity | | `engine.block.setOpacity(id, opacity)` | Set the opacity for a block (0-1) | | `engine.block.getOpacity(id)` | Get the current opacity of a block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create a Collage" description: "Combine images into a collage using the CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/collage-f7d28d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Create a Collage](https://img.ly/docs/cesdk/angular/create-composition/collage-f7d28d/) --- This guide shows you how to add a **Collage** feature in your web app with the help of the CE.SDK. A collage allows you to reuse images and assets as you change the layout of the scene. > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/CaseComponent.jsx) > > - [Open in StackBlitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/main/showcase-layouts?title=IMG.LY+CE.SDK%3A+Layouts\&file=src%2Fcomponents%2Fcase%2FCaseComponent.jsx) > > - [Live demo](https://img.ly/showcases/cesdk/layouts/web) **Layouts** in CE.SDK are predefined templates that dictate how to arrange images and assets within a single composition. They allow you to create visually appealing collages by specifying each image’s: - Position - Size - Orientation relative to other assets on the page Layouts instantly create visuals and allow you to skip manually arranging each asset. ## What You’ll Learn In this guide, you will learn how to: - Set up the layout panel in the CE.SDK UI. - Use a layout file for collages. - Use TypeScript to apply layouts in your custom UI. ## When to Use Layouts The CE.SDK **Layout** feature is ideal for: - Photo collages - Grid layouts - Magazine spreads - Social media posts ## Difference Between Layouts and Templates Layouts are [custom assets](https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/) that differ from templates: - **Templates:** Load **new assets** and replace the existing scene. - **Layouts:** Keep existing assets but change their arrangement on the page. Preserving assets while changing the layout **isn’t a native CE.SDK feature**. The following sections explain how to leverage the CE.SDK to do it in your app using JavaScript. ## How Collages Work When you choose a collage layout, the app needs to: 1. Load a new layout file. 2. Extract content from your current design. 3. Map content to new layout positions. 4. Preserve images and text in visual order. You trigger this workflow when a user selects a layout from the **Layouts** panel in the CE.SDK UI. ## Add a Layouts Panel to the CE.SDK UI The CE.SDK allows you to add [custom panels to the UI](https://img.ly/docs/cesdk/angular/user-interface/customization/panel-7ce1ee/). To add a **Layouts** panel for collage selection, you need to create it and load custom assets to configure its appearance and behavior. For this action, you need: 1. A layout file defining the collage structure (use [this one](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/CustomLayouts.json) to get started). 2. Thumbnail images for preview (find a collection [here](https://github.com/imgly/cesdk-web-examples/tree/main/showcase-layouts/public/cases/layouts)). ### 1. Add a Layouts Option to the CE.SDK Menu To **customize the CE.SDK UI** and create a **Layouts** panel, use the CE.SDK UserInterfaceAPI. Add a Layout button with `ui.setComponentOrder({ in: 'ly.img.dock' }, order)`, using the following properties: - `id` - `key` - `label` - `icon` - `entries` For example: ```ts cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ { id: 'ly.img.assetLibrary.dock', key: 'layouts-dock-entry', label: 'Layouts', icon: '@imgly/Layout', entries: ['layouts'], }, ]); ``` ### 2. Customize the Layouts Panel Appearance To configure how the **Layouts** panel looks and behaves, use the `ui.addAssetLibraryEntry()` helper with the following options: - `id`: Unique identifier for the panel. - `sourceIds`: Which assets to display. - `previewLength`: How many preview items to display in the panel. - `gridColumns`: How many columns to contain the previews in the panel. - `gridItemHeight`: The shape of each tile in the panel grid. - `previewBackgroundType`: How to fit the previews inside their tiles (cover/contain). - `gridBackgroundType`: How to display the panel background (cover/contain). For example, the demo uses this configuration: ```jsx title="CustomCase.jsx" instance.ui.addAssetLibraryEntry({ id: 'ly.img.layouts', // Referenced in the dock entry in Step 1 sourceIds: ['ly.img.layouts'], // Points to our custom layout asset source previewLength: 2, // Number of preview items int the compact panel gridColumns: 2, // Organize tiles in 2 columns gridItemHeight: 'square', // Square tiles previewBackgroundType: 'contain', // Fit compact panel background gridBackgroundType: 'contain' // Fit panel background }); ``` To learn more about panel customization, check the [Panel Customization guide](https://img.ly/docs/cesdk/angular/user-interface/customization/panel-7ce1ee/). ### 3. Load Custom Assets The demo uses a [helper function](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/lib/loadAssetSourceFromContentJSON.ts) to load a custom layout asset source from a JSON file. This file defines the available layouts and their metadata. You can reuse this logic by defining both: - A `ContentJSON` object (like `CustomLayouts.json`) - A `baseURL` for your custom assets that replaces the `{{base_url}}`. ```jsx title="CustomCase.jsx" import { createApplyLayoutAsset } from './lib/createApplyLayoutAsset'; import loadAssetSourceFromContentJSON from './lib/loadAssetSourceFromContentJSON'; // ... const caseAssetPath = (path, caseId = 'layouts') => `${process.env.NEXT_PUBLIC_URL_HOSTNAME}${process.env.NEXT_PUBLIC_URL}/cases/${caseId}${path}`; // Call the helper to load the layout assets loadAssetSourceFromContentJSON( instance.engine, // Pss the CE.SDK engine to load assets into LAYOUT_ASSETS, // Pass the JSON bundle caseAssetPath(''), // Base URL for assets createApplyLayoutAsset(instance.engine) // Callback to createApplyLayoutAsset.js helper ); await instance.loadFromURL(caseAssetPath('/custom-layouts.scene')); // Load the scene // ... ``` In the previous example, the helper accepts an optional **applyAsset callback**, so: 1. A user picks a layout from the library. 2. The engine invokes the callback to apply it. 3. The engine replaces the current page’s structure with the layout while keeping the user’s images/text. Asset preservation isn't a CE.SDK native feature. It’s handled in the `createApplyLayoutAsset.js` helper, which you can find in [the repository](https://github.com/imgly/cesdk-web-examples/blob/main/showcase-layouts/src/components/case/lib/createApplyLayoutAsset.js). ## Apply the Collage When applying a collage, the following actions need to be implemented: 1. Changing the structure of the design. 2. Transferring the existing content to the new structure. 3. Deleting the previous scene. You can find this workflow in the `createApplyLayoutAsset()` helper from the demo. Follow these steps to replicate it: ### 1. Prepare and Allow changes - Allow block deletion with `editor.setGlobalScope('lifecycle/destroy', 'Allow')`. - Clear the selected blocks with `block.setSelected(block, false)`. ```js title="createApplyLayoutAsset.js" const scopeBefore = engine.editor.getGlobalScope('lifecycle/destroy'); engine.editor.setGlobalScope('lifecycle/destroy', 'Allow'); const page = engine.scene.getCurrentPage(); engine.block.findAllSelected().forEach((block) => engine.block.setSelected(block, false)); ``` ### 2. Load the New Layout - Load the new layout with `block.loadFromString()`. - Return the first page from the loaded blocks as the layout page. ```js title="createApplyLayoutAsset.js" const sceneString = await fetch(asset.meta.uri).then((response) => response.text()); const blocks = await engine.block.loadFromString(sceneString); const layoutPage = blocks[0]; ``` ### 3. Backup Current Page - Duplicate the current page with `block.duplicate()` to keep a backup of the existing content. - The CE.SDK uses this backup to transfer images and text to the new layout. - Clear the current page structure by destroying all its children. ```js title="createApplyLayoutAsset.js" const oldPage = engine.block.duplicate(page); engine.block.getChildren(page).forEach((child) => { engine.block.destroy(child); }); engine.block.getChildren(layoutPage).forEach((child) => { engine.block.insertChild(page, child, engine.block.getChildren(page).length); }); ``` ### 4. Transfer Content Copy user text and images onto the new layout: ```js title="createApplyLayoutAsset.js" copyAssets(engine, oldPage, page); ``` ### 5. Sort Blocks Visually and Pair Content Grab text and image blocks from both pages in visual order (top to bottom, left to right): ```js title="createApplyLayoutAsset.js" const fromChildren = visuallySortBlocks(engine, getChildrenTree(engine, fromPageId).flat()); const textsOnFromPage = fromChildren.filter((childId) => engine.block.getType(childId).includes('text')); const imagesOnFromPage = fromChildren.filter((childId) => engine.block.getKind(childId) === 'image'); // same for toPageId -> textsOnToPage, imagesOnToPage ``` Then apply the content from the old page to the new layout by looping through the blocks: ```js title="createApplyLayoutAsset.js" const fromText = engine.block.getString(fromBlock, 'text/text'); const fromFontFileUri = engine.block.getString(fromBlock, 'text/fontFileUri'); const fromTypeface = engine.block.getTypeface(fromBlock); engine.block.setFont(toBlock, fromFontFileUri, fromTypeface); const fromTextFillColor = engine.block.getColor(fromBlock, 'fill/solid/color'); engine.block.setString(toBlock, 'text/text', fromText); engine.block.setColor(toBlock, 'fill/solid/color', fromTextFillColor); ``` ```js title="createApplyLayoutAsset.js" const fromImageFill = engine.block.getFill(fromBlock); const toImageFill = engine.block.getFill(toBlock); const fromImageFileUri = engine.block.getString(fromImageFill, 'fill/image/imageFileURI'); engine.block.setString(toImageFill, 'fill/image/imageFileURI', fromImageFileUri); const fromImageSourceSets = engine.block.getSourceSet(fromImageFill, 'fill/image/sourceSet'); engine.block.setSourceSet(toImageFill, 'fill/image/sourceSet', fromImageSourceSets); if (engine.block.supportsPlaceholderBehavior(fromBlock)) { engine.block.setPlaceholderBehaviorEnabled( toBlock, engine.block.isPlaceholderBehaviorEnabled(fromBlock) ); } engine.block.resetCrop(toBlock); ``` ### 6. Cleanup and Restore State Cleanup temporary blocks with `block.destroy()` and restore global scope: ```js title="createApplyLayoutAsset.js" engine.block.destroy(oldPage); engine.block.destroy(layoutPage); engine.editor.setGlobalScope('lifecycle/destroy', scopeBefore); if (config.addUndoStep) { engine.editor.addUndoStep(); } return page; ``` This keeps the editor stable and predictable after the layout changes and prevents: - Stray selections. - Ghost placeholders. - Unused assets left at the scene. ## Advanced Collage Techniques The CE.SDK BlockAPI eases the transfer of [placeholder behavior](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/) when moving images between blocks. You can use it to: 1. Checks if the block supports placeholder behavior with `supportsPlaceholderBehavior`. 2. Apply the same setting to the target image block with: - `isPlaceholderBehaviorEnabled()` - `setPlaceholderBehaviorEnabled()` ```js title="createApplyLayoutAsset.js" if (engine.block.supportsPlaceholderBehavior(fromBlock)) { engine.block.setPlaceholderBehaviorEnabled( toBlock, engine.block.isPlaceholderBehaviorEnabled(fromBlock), ); } ``` This maintains the behavior of current placeholders after the layout swap. To ensure the CE.SDK uses all the existing images when applying a layout, you can: 1. Handle overflow when more images than slots: ```js title="createApplyLayoutAsset.js" for ( let index = 0; index < imagesOnToPage.length && index < imagesOnFromPage.length; index++ ) { ... } ``` 2. Fill empty slots with defaults or placeholders: ```js title="createApplyLayoutAsset.js" // loop condition ends when sources run out, leaving remaining target slots unchanged index < imagesOnToPage.length && index < imagesOnFromPage.length; ``` 3. List content by importance or metadata: ```js title="createApplyLayoutAsset.js" const visuallySortBlocks = (engine, blocks) => { const blocksWithCoordinates = blocks .map((block) => ({ block, coordinates: [ Math.round(engine.block.getPositionX(block)), Math.round(engine.block.getPositionY(block)) ] })) .sort(({ coordinates: [X1, Y1] }, { coordinates: [X2, Y2] }) => { if (Y1 === Y2) return X1 - X2; return Y1 - Y2; }); return blocksWithCoordinates.map(({ block }) => block); }; ``` For a better user experience, consider adding animations when applying layouts: ```tsx title="LoadingSpinner.tsx" const LoadingSpinner = () => { return
; }; ``` You can code more customizations to incorporate these effects into collage transitions: - Fading - Sliding - Morphing
## Optimize the Layout Workflow For a better user experience when creating collages, consider these optimizations: | Topic | Strategies | | --- | --- | | **Asset Loading** | ・ Lazy load thumbnails and cache scene files
・ Preload common layouts and minimize file sizes
・ Use CDN for efficient asset delivery | | **Content Transfer** | ・ Batch block operations to reduce engine calls
・ Optimize sorting algorithms and memory usage
・ Handle large page structures efficiently | | **Visual Cues** | ・ Show loading states and support undo/redo
・ Add error recovery and prevent accidental changes | ## Troubleshooting | ❌ Issue | ✅ Solutions | | --- | --- | | Layout not applying | ・ Verify asset source registration and callback connection
・ Check browser console for scene files or CORS errors
・ Check the validity of scene file URLs | | Content lost during layout change | ・ Debug visual sorting with console logs
・ Verify block type filtering is correct
・ Test with different content settings| | Incorrect visual order | ・ Adjust coordinate rounding tolerance in sorting
・ Flatten complex hierarchies before sorting
・ Test with well-defined layout structures | | Performance degradation | ・ Optimize scene file sizes
・ Batch engine operations
・ Profile and optimize content transfer | | Undo not working | ・ Ensure `addUndoStep()` called after all changes complete
・ Verify undo configuration in engine setup | ## Next Steps Now that know how to create collages with layouts, explore these related guides to expand your CE.SDK knowledge: - [Apply Templates](https://img.ly/docs/cesdk/angular/create-templates/overview-4ebe30/) - Work with templates instead of layouts - [Create Custom Asset Sources](https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/) - Import custom assets - [Customize UI Panels](https://img.ly/docs/cesdk/angular/user-interface/customization/panel-7ce1ee/) - Advanced UI customization - [Work with Images](https://img.ly/docs/cesdk/angular/insert-media/images-63848a/) - Manage image blocks and fills - [Scene Management](https://img.ly/docs/cesdk/angular/open-the-editor/load-scene-478833/) - Load and save scenes --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Group and Ungroup Objects" description: "Group multiple design elements together so they move, scale, and transform as a single unit; ungroup to edit them individually." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/group-and-ungroup-62565a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Group and Ungroup Objects](https://img.ly/docs/cesdk/angular/create-composition/group-and-ungroup-62565a/) --- Group multiple blocks to move, scale, and transform them as a single unit; ungroup to edit them individually. ![Group and Ungroup Objects example showing grouped rectangles in CE.SDK](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-grouping-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-grouping-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-grouping-browser/) Groups let you treat multiple blocks as a cohesive unit. Grouped blocks move, scale, and rotate together while maintaining their relative positions. Groups can contain other groups, enabling hierarchical compositions. > **Note:** Groups are not currently available when editing videos. ```typescript file=@cesdk_web_examples/guides-create-composition-grouping-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Group and Ungroup Objects Guide * * This example demonstrates: * - Creating multiple graphic blocks * - Checking if blocks can be grouped * - Grouping blocks together * - Navigating into and out of groups * - Ungrouping blocks * - Finding and inspecting groups */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Create a design scene and get the page await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Create a graphic block with a colored rectangle shape const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, 120); engine.block.setHeight(block1, 120); engine.block.setPositionX(block1, 200); engine.block.setPositionY(block1, 240); const fill1 = engine.block.createFill('color'); engine.block.setColor(fill1, 'fill/color/value', { r: 0.4, g: 0.6, b: 0.9, a: 1.0 }); engine.block.setFill(block1, fill1); engine.block.appendChild(page, block1); // Create two more blocks for grouping const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, 120); engine.block.setHeight(block2, 120); engine.block.setPositionX(block2, 340); engine.block.setPositionY(block2, 240); const fill2 = engine.block.createFill('color'); engine.block.setColor(fill2, 'fill/color/value', { r: 0.9, g: 0.5, b: 0.4, a: 1.0 }); engine.block.setFill(block2, fill2); engine.block.appendChild(page, block2); const block3 = engine.block.create('graphic'); const shape3 = engine.block.createShape('rect'); engine.block.setShape(block3, shape3); engine.block.setWidth(block3, 120); engine.block.setHeight(block3, 120); engine.block.setPositionX(block3, 480); engine.block.setPositionY(block3, 240); const fill3 = engine.block.createFill('color'); engine.block.setColor(fill3, 'fill/color/value', { r: 0.5, g: 0.8, b: 0.5, a: 1.0 }); engine.block.setFill(block3, fill3); engine.block.appendChild(page, block3); // Check if the blocks can be grouped together const canGroup = engine.block.isGroupable([block1, block2, block3]); console.log('Blocks can be grouped:', canGroup); // Group the blocks together if (canGroup) { const groupId = engine.block.group([block1, block2, block3]); console.log('Created group with ID:', groupId); // Select the group to show it in the UI engine.block.setSelected(groupId, true); // Enter the group to select individual members engine.block.enterGroup(groupId); // Select a specific member within the group engine.block.setSelected(block2, true); console.log('Selected member inside group'); // Exit the group to return selection to the parent group engine.block.exitGroup(block2); console.log('Exited group, group is now selected'); // Find all groups in the scene const allGroups = engine.block.findByType('group'); console.log('Number of groups in scene:', allGroups.length); // Check the type of the group block const groupType = engine.block.getType(groupId); console.log('Group block type:', groupType); // Get the members of the group const members = engine.block.getChildren(groupId); console.log('Group has', members.length, 'members'); // Ungroup the blocks to make them independent again engine.block.ungroup(groupId); console.log('Ungrouped blocks'); // Verify blocks are no longer in a group const groupsAfterUngroup = engine.block.findByType('group'); console.log('Groups after ungrouping:', groupsAfterUngroup.length); // Re-group for the final display const finalGroup = engine.block.group([block1, block2, block3]); engine.block.setSelected(finalGroup, true); } // Enable auto-fit zoom to keep the page centered engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); } } export default Example; ``` This guide covers how to check if blocks can be grouped, create and dissolve groups, navigate into groups to select individual members, and find existing groups in a scene. ## Understanding Groups Groups are blocks with type `'group'` that contain child blocks as members. Transformations applied to a group affect all members proportionally—position, scale, and rotation cascade to all children. Groups can be nested, meaning a group can contain other groups. This enables complex hierarchical structures where multiple logical units can be combined and manipulated together. > **Note:** **What cannot be grouped*** Scene blocks cannot be grouped > * Blocks already part of a group cannot be grouped again until ungrouped ## Create the Blocks We first create several graphic blocks that we'll group together. Each block has a different color fill to make them visually distinct. ```typescript highlight=highlight-create-blocks // Create a graphic block with a colored rectangle shape const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, 120); engine.block.setHeight(block1, 120); engine.block.setPositionX(block1, 200); engine.block.setPositionY(block1, 240); const fill1 = engine.block.createFill('color'); engine.block.setColor(fill1, 'fill/color/value', { r: 0.4, g: 0.6, b: 0.9, a: 1.0 }); engine.block.setFill(block1, fill1); engine.block.appendChild(page, block1); ``` ## Check If Blocks Can Be Grouped Before grouping, verify that the selected blocks can be grouped using `engine.block.isGroupable()`. This method returns `true` if all blocks can be grouped together, or `false` if any block is a scene or already belongs to a group. ```typescript highlight=highlight-check-groupable // Check if the blocks can be grouped together const canGroup = engine.block.isGroupable([block1, block2, block3]); console.log('Blocks can be grouped:', canGroup); ``` ## Create a Group Use `engine.block.group()` to combine multiple blocks into a new group. The method returns the ID of the newly created group block. The group inherits the combined bounding box of its members. ```typescript highlight=highlight-create-group // Group the blocks together if (canGroup) { const groupId = engine.block.group([block1, block2, block3]); console.log('Created group with ID:', groupId); // Select the group to show it in the UI engine.block.setSelected(groupId, true); ``` ## Navigate Group Selection CE.SDK provides methods to navigate into and out of groups while editing. ### Enter a Group When a group is selected, use `engine.block.enterGroup()` to enter editing mode for that group. This allows you to select and modify individual members within the group. ```typescript highlight=highlight-enter-group // Enter the group to select individual members engine.block.enterGroup(groupId); // Select a specific member within the group engine.block.setSelected(block2, true); console.log('Selected member inside group'); ``` ### Exit a Group When editing a member inside a group, use `engine.block.exitGroup()` to return selection to the parent group. This method takes a member block ID and selects its parent group. ```typescript highlight=highlight-exit-group // Exit the group to return selection to the parent group engine.block.exitGroup(block2); console.log('Exited group, group is now selected'); ``` ## Find and Inspect Groups Discover groups in a scene and inspect their contents using `engine.block.findByType()`, `engine.block.getType()`, and `engine.block.getChildren()`. ```typescript highlight=highlight-find-groups // Find all groups in the scene const allGroups = engine.block.findByType('group'); console.log('Number of groups in scene:', allGroups.length); // Check the type of the group block const groupType = engine.block.getType(groupId); console.log('Group block type:', groupType); // Get the members of the group const members = engine.block.getChildren(groupId); console.log('Group has', members.length, 'members'); ``` Use `engine.block.findByType('group')` to get all group blocks in the current scene. Use `engine.block.getType()` to check if a specific block is a group (returns `'//ly.img.ubq/group'`). Use `engine.block.getChildren()` to get the member blocks of a group. ## Ungroup Blocks Use `engine.block.ungroup()` to dissolve a group and release its children back to the parent container. The children maintain their current positions in the scene. ```typescript highlight=highlight-ungroup // Ungroup the blocks to make them independent again engine.block.ungroup(groupId); console.log('Ungrouped blocks'); // Verify blocks are no longer in a group const groupsAfterUngroup = engine.block.findByType('group'); console.log('Groups after ungrouping:', groupsAfterUngroup.length); ``` ## Troubleshooting ### Blocks Cannot Be Grouped If `engine.block.isGroupable()` returns `false`: - Check if any of the blocks is a scene block (scenes cannot be grouped) - Check if any block is already part of a group (use `engine.block.getParent()` to verify) - Ensure all block IDs are valid ### Enter Group Has No Effect If `engine.block.enterGroup()` doesn't change selection: - Verify the block is a group using `engine.block.getType()` - Ensure the `'editor/select'` scope is enabled ### Group Not Visible After Creation If a newly created group is not visible: - Check that the member blocks were visible before grouping - Verify the group's opacity using `engine.block.getOpacity()` ## API Reference | Method | Description | | --- | --- | | `engine.block.isGroupable(ids)` | Check if blocks can be grouped together | | `engine.block.group(ids)` | Create a group from multiple blocks | | `engine.block.ungroup(id)` | Dissolve a group and release its children | | `engine.block.enterGroup(id)` | Enter group editing mode (select member) | | `engine.block.exitGroup(id)` | Exit group editing mode (select parent group) | | `engine.block.findByType(type)` | Find all blocks of a specific type | | `engine.block.getType(id)` | Get the type string of a block | | `engine.block.getParent(id)` | Get the parent block | | `engine.block.getChildren(id)` | Get child blocks of a container | ## Next Steps Explore related topics: - [Layer Management](https://img.ly/docs/cesdk/angular/create-composition/layer-management-18f07a/) - Control z-order and visibility of blocks - [Position and Align](https://img.ly/docs/cesdk/angular/insert-media/position-and-align-cc6b6a/) - Arrange blocks precisely on the canvas - [Lock Design](https://img.ly/docs/cesdk/angular/create-composition/lock-design-0a81de/) - Prevent modifications to specific elements --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Layer Management" description: "Organize design elements using a layer stack for precise control over stacking and visibility." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/layer-management-18f07a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Layers](https://img.ly/docs/cesdk/angular/create-composition/layer-management-18f07a/) --- Organize design elements in CE.SDK using a hierarchical layer stack to control stacking order, visibility, and element relationships. ![Layer Management Hero](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-layer-management-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-layer-management-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-layer-management-browser/) Design elements in CE.SDK are organized in a hierarchical parent-child structure. Children of a block are rendered in order, with the last child appearing on top. This layer stack model gives you precise control over how elements overlap and interact visually. ```typescript file=@cesdk_web_examples/guides-create-composition-layer-management-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Layer Management Guide * * This example demonstrates: * - Navigating parent-child hierarchy * - Adding and positioning blocks in the layer stack * - Changing z-order (bring to front, send to back) * - Controlling visibility * - Duplicating and removing blocks */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; // Create a colored rectangle const redRect = engine.block.create('graphic'); engine.block.setShape(redRect, engine.block.createShape('rect')); const redFill = engine.block.createFill('color'); engine.block.setFill(redRect, redFill); engine.block.setColor(redFill, 'fill/color/value', { r: 0.9, g: 0.2, b: 0.2, a: 1 }); engine.block.setWidth(redRect, 180); engine.block.setHeight(redRect, 180); engine.block.setPositionX(redRect, 220); engine.block.setPositionY(redRect, 120); // Create additional rectangles to demonstrate layer ordering const greenRect = engine.block.create('graphic'); engine.block.setShape(greenRect, engine.block.createShape('rect')); const greenFill = engine.block.createFill('color'); engine.block.setFill(greenRect, greenFill); engine.block.setColor(greenFill, 'fill/color/value', { r: 0.2, g: 0.8, b: 0.2, a: 1 }); engine.block.setWidth(greenRect, 180); engine.block.setHeight(greenRect, 180); engine.block.setPositionX(greenRect, 280); engine.block.setPositionY(greenRect, 180); const blueRect = engine.block.create('graphic'); engine.block.setShape(blueRect, engine.block.createShape('rect')); const blueFill = engine.block.createFill('color'); engine.block.setFill(blueRect, blueFill); engine.block.setColor(blueFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1 }); engine.block.setWidth(blueRect, 180); engine.block.setHeight(blueRect, 180); engine.block.setPositionX(blueRect, 340); engine.block.setPositionY(blueRect, 240); // Add blocks to the page - last appended is on top engine.block.appendChild(page, redRect); engine.block.appendChild(page, greenRect); engine.block.appendChild(page, blueRect); // Get the parent of a block const parent = engine.block.getParent(redRect); console.log('Parent of red rectangle:', parent); // Get all children of the page const children = engine.block.getChildren(page); console.log('Page children (in render order):', children); // Insert a new block at a specific position (index 0 = back) const yellowRect = engine.block.create('graphic'); engine.block.setShape(yellowRect, engine.block.createShape('rect')); const yellowFill = engine.block.createFill('color'); engine.block.setFill(yellowRect, yellowFill); engine.block.setColor(yellowFill, 'fill/color/value', { r: 0.95, g: 0.85, b: 0.2, a: 1 }); engine.block.setWidth(yellowRect, 180); engine.block.setHeight(yellowRect, 180); engine.block.setPositionX(yellowRect, 160); engine.block.setPositionY(yellowRect, 60); engine.block.insertChild(page, yellowRect, 0); // Bring the red rectangle to the front engine.block.bringToFront(redRect); console.log('Red rectangle brought to front'); // Send the blue rectangle to the back engine.block.sendToBack(blueRect); console.log('Blue rectangle sent to back'); // Move the green rectangle forward one layer engine.block.bringForward(greenRect); console.log('Green rectangle moved forward'); // Move the yellow rectangle backward one layer engine.block.sendBackward(yellowRect); console.log('Yellow rectangle moved backward'); // Check and toggle visibility const isVisible = engine.block.isVisible(blueRect); console.log('Blue rectangle visible:', isVisible); // Hide the blue rectangle temporarily engine.block.setVisible(blueRect, false); console.log('Blue rectangle hidden'); // Show it again for the final composition engine.block.setVisible(blueRect, true); console.log('Blue rectangle shown again'); // Duplicate a block const duplicateGreen = engine.block.duplicate(greenRect); engine.block.setPositionX(duplicateGreen, 400); engine.block.setPositionY(duplicateGreen, 300); // Change the duplicate's color to purple const purpleFill = engine.block.createFill('color'); engine.block.setFill(duplicateGreen, purpleFill); engine.block.setColor(purpleFill, 'fill/color/value', { r: 0.6, g: 0.2, b: 0.8, a: 1 }); console.log('Green rectangle duplicated'); // Check if a block is valid before operations const isValidBefore = engine.block.isValid(yellowRect); console.log('Yellow rectangle valid before destroy:', isValidBefore); // Remove a block from the scene engine.block.destroy(yellowRect); console.log('Yellow rectangle destroyed'); // Check validity after destruction const isValidAfter = engine.block.isValid(yellowRect); console.log('Yellow rectangle valid after destroy:', isValidAfter); engine.scene.zoomToBlock(page, 40, 40, 40, 40); } } export default Example; ``` This guide covers how to navigate the block hierarchy, reorder elements in the layer stack, toggle visibility, and manage block lifecycles through duplication and deletion. ## Creating Visual Blocks To demonstrate layer ordering, we create colored rectangles that overlap on the canvas. Each block is created using `engine.block.create()` and configured with a shape, fill color, dimensions, and position. ```typescript highlight=highlight-create-block // Create a colored rectangle const redRect = engine.block.create('graphic'); engine.block.setShape(redRect, engine.block.createShape('rect')); const redFill = engine.block.createFill('color'); engine.block.setFill(redRect, redFill); engine.block.setColor(redFill, 'fill/color/value', { r: 0.9, g: 0.2, b: 0.2, a: 1 }); engine.block.setWidth(redRect, 180); engine.block.setHeight(redRect, 180); engine.block.setPositionX(redRect, 220); engine.block.setPositionY(redRect, 120); ``` ## Navigating the Block Hierarchy CE.SDK organizes blocks in a parent-child tree structure. Every block can have one parent and multiple children. Understanding this hierarchy is essential for programmatic layer management. ### Getting a Block's Parent We retrieve the parent of any block using `engine.block.getParent()`. This returns the parent's block ID, or null if the block has no parent (such as the scene root). ```typescript highlight=highlight-get-parent // Get the parent of a block const parent = engine.block.getParent(redRect); console.log('Parent of red rectangle:', parent); ``` Knowing a block's parent helps you understand where it sits in the hierarchy and enables operations like reparenting or finding sibling blocks. ### Listing Child Blocks We get all direct children of a block using `engine.block.getChildren()`. Children are returned sorted in their rendering order, where the last child renders in front of other children. ```typescript highlight=highlight-get-children // Get all children of the page const children = engine.block.getChildren(page); console.log('Page children (in render order):', children); ``` This method is useful for iterating through all elements on a page or within a group. ## Adding and Positioning Blocks When you create a new block, it exists independently until you add it to the hierarchy. There are two ways to attach blocks to a parent: appending to the end or inserting at a specific position. ### Appending Blocks We add a block as the last child of a parent using `engine.block.appendChild()`. Since the last child renders on top, the appended block becomes the topmost element. ```typescript highlight=highlight-append-child // Add blocks to the page - last appended is on top engine.block.appendChild(page, redRect); engine.block.appendChild(page, greenRect); engine.block.appendChild(page, blueRect); ``` When you append multiple blocks in sequence, each new block appears in front of the previous ones. ### Inserting at a Specific Position We insert a block at a specific index in the layer stack using `engine.block.insertChild()`. Index 0 places the block at the back, behind all other children. ```typescript highlight=highlight-insert-child // Insert a new block at a specific position (index 0 = back) const yellowRect = engine.block.create('graphic'); engine.block.setShape(yellowRect, engine.block.createShape('rect')); const yellowFill = engine.block.createFill('color'); engine.block.setFill(yellowRect, yellowFill); engine.block.setColor(yellowFill, 'fill/color/value', { r: 0.95, g: 0.85, b: 0.2, a: 1 }); engine.block.setWidth(yellowRect, 180); engine.block.setHeight(yellowRect, 180); engine.block.setPositionX(yellowRect, 160); engine.block.setPositionY(yellowRect, 60); engine.block.insertChild(page, yellowRect, 0); ``` This gives you precise control over where new elements appear in the stacking order. ### Reparenting Blocks When you add a block to a new parent using `appendChild()` or `insertChild()`, it is automatically removed from its previous parent. This makes reparenting operations straightforward without needing to manually detach blocks first. ## Changing Z-Order Once blocks are in the hierarchy, you can change their stacking order without removing and re-adding them. CE.SDK provides four methods for z-order manipulation. ### Bring to Front We move an element to the top of its siblings using `engine.block.bringToFront()`. This gives the block the highest stacking order among its siblings. ```typescript highlight=highlight-bring-to-front // Bring the red rectangle to the front engine.block.bringToFront(redRect); console.log('Red rectangle brought to front'); ``` ### Send to Back We move an element behind all its siblings using `engine.block.sendToBack()`. This gives the block the lowest stacking order among its siblings. ```typescript highlight=highlight-send-to-back // Send the blue rectangle to the back engine.block.sendToBack(blueRect); console.log('Blue rectangle sent to back'); ``` ### Move Forward One Layer We move an element one position forward using `engine.block.bringForward()`. This swaps the block with its immediate sibling in front. ```typescript highlight=highlight-bring-forward // Move the green rectangle forward one layer engine.block.bringForward(greenRect); console.log('Green rectangle moved forward'); ``` ### Move Backward One Layer We move an element one position backward using `engine.block.sendBackward()`. This swaps the block with its immediate sibling behind. ```typescript highlight=highlight-send-backward // Move the yellow rectangle backward one layer engine.block.sendBackward(yellowRect); console.log('Yellow rectangle moved backward'); ``` These incremental operations are useful for fine-tuning the layer order without jumping to extremes. ## Controlling Visibility Visibility allows you to temporarily hide elements without removing them from the scene. Hidden elements remain in the hierarchy and preserve their properties, but are not rendered. ### Checking and Toggling Visibility We query the current visibility state using `engine.block.isVisible()` and change it using `engine.block.setVisible()`. ```typescript highlight=highlight-visibility // Check and toggle visibility const isVisible = engine.block.isVisible(blueRect); console.log('Blue rectangle visible:', isVisible); // Hide the blue rectangle temporarily engine.block.setVisible(blueRect, false); console.log('Blue rectangle hidden'); // Show it again for the final composition engine.block.setVisible(blueRect, true); console.log('Blue rectangle shown again'); ``` Visibility is useful for creating before/after comparisons, hiding elements during editing, or implementing show/hide functionality in your application. ## Managing Block Lifecycle CE.SDK provides methods for duplicating blocks to create copies and destroying blocks to remove them permanently. ### Duplicating Blocks We create a copy of a block and all its children using `engine.block.duplicate()`. By default, the duplicate is attached to the same parent as the original. ```typescript highlight=highlight-duplicate // Duplicate a block const duplicateGreen = engine.block.duplicate(greenRect); engine.block.setPositionX(duplicateGreen, 400); engine.block.setPositionY(duplicateGreen, 300); // Change the duplicate's color to purple const purpleFill = engine.block.createFill('color'); engine.block.setFill(duplicateGreen, purpleFill); engine.block.setColor(purpleFill, 'fill/color/value', { r: 0.6, g: 0.2, b: 0.8, a: 1 }); console.log('Green rectangle duplicated'); ``` The duplicated block is positioned at the same location as the original. You typically want to reposition it to make it visible as a separate element. ### Checking Block Validity Before performing operations on a block, we can verify it still exists using `engine.block.isValid()`. A block becomes invalid after it has been destroyed. ```typescript highlight=highlight-is-valid // Check if a block is valid before operations const isValidBefore = engine.block.isValid(yellowRect); console.log('Yellow rectangle valid before destroy:', isValidBefore); ``` ### Removing Blocks We permanently remove a block and all its children from the scene using `engine.block.destroy()`. This operation cannot be undone programmatically. ```typescript highlight=highlight-destroy // Remove a block from the scene engine.block.destroy(yellowRect); console.log('Yellow rectangle destroyed'); // Check validity after destruction const isValidAfter = engine.block.isValid(yellowRect); console.log('Yellow rectangle valid after destroy:', isValidAfter); ``` After destruction, any references to the block become invalid. Attempting to use an invalid block ID will result in errors. ## Framing the Result After making layer changes, we zoom to fit the page in the viewport so the composition is clearly visible. ```typescript highlight=highlight-zoom engine.scene.zoomToBlock(page, 40, 40, 40, 40); ``` ## Troubleshooting **Block not visible after appendChild**: The block may be behind other elements. Use `engine.block.bringToFront()` or adjust the insert index to control stacking order. **getParent returns null**: The block is not attached to any parent. Use `engine.block.appendChild()` or `engine.block.insertChild()` to attach it to a page or container. **Changes not reflected**: The block handle may be invalid. Check with `engine.block.isValid()` before performing operations. **Z-order not updating**: Verify you're operating on the correct block ID and that the block is in the expected parent context. **Duplicate not appearing**: If `attachToParent` is set to false, the duplicate won't be attached automatically. Set it to true or manually attach the duplicate to a parent. ## API Reference | Method | Description | | --- | --- | | `engine.block.getParent(id)` | Get the parent block of a given block | | `engine.block.getChildren(id)` | Get all child blocks in rendering order | | `engine.block.appendChild(parent, child)` | Append a block as the last child | | `engine.block.insertChild(parent, child, index)` | Insert a block at a specific position | | `engine.block.bringToFront(id)` | Bring a block to the front of its siblings | | `engine.block.sendToBack(id)` | Send a block to the back of its siblings | | `engine.block.bringForward(id)` | Move a block one position forward | | `engine.block.sendBackward(id)` | Move a block one position backward | | `engine.block.isVisible(id)` | Check if a block is visible | | `engine.block.setVisible(id, visible)` | Set the visibility of a block | | `engine.block.duplicate(id, attachToParent?)` | Duplicate a block and its children | | `engine.block.destroy(id)` | Remove a block and its children | | `engine.block.isValid(id)` | Check if a block handle is valid | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Design a Layout" description: "Create structured compositions using scene layouts, positioning systems, and hierarchical block organization for collages, magazines, and multi-page documents." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/layout-b66311/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Design a Layout](https://img.ly/docs/cesdk/angular/create-composition/layout-b66311/) --- Create structured compositions using stack layouts that automatically arrange pages vertically or horizontally with consistent spacing. ![Design a Layout](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-layout-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-layout-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-layout-browser/) Stack layouts arrange pages automatically with consistent spacing. Vertical stacks arrange pages top-to-bottom, while horizontal stacks arrange them left-to-right. This eliminates manual positioning for compositions like photo collages, product catalogs, or social media carousels. ```typescript file=@cesdk_web_examples/guides-create-composition-layout-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) throw new Error('CE.SDK instance is required'); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Create a scene with vertical stack layout // Pages arrange top-to-bottom automatically engine.scene.create('VerticalStack'); // Get the stack container created with the scene const [stack] = engine.block.findByType('stack'); // Create two pages that will stack vertically const page1 = engine.block.create('page'); engine.block.setWidth(page1, 400); engine.block.setHeight(page1, 300); engine.block.appendChild(stack, page1); const page2 = engine.block.create('page'); engine.block.setWidth(page2, 400); engine.block.setHeight(page2, 300); engine.block.appendChild(stack, page2); // Configure spacing between stacked pages engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); // Add image content to page 1 const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const block1 = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.setPositionX(block1, 25); engine.block.setPositionY(block1, 25); engine.block.appendChild(page1, block1); // Add a colored rectangle to page 2 const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, 350); engine.block.setHeight(block2, 250); engine.block.setPositionX(block2, 25); engine.block.setPositionY(block2, 25); const fill2 = engine.block.createFill('color'); engine.block.setColor(fill2, 'fill/color/value', { r: 0.3, g: 0.6, b: 0.9, a: 1.0 }); engine.block.setFill(block2, fill2); engine.block.appendChild(page2, block2); // Switch to horizontal stack layout // Pages now arrange left-to-right engine.scene.setLayout('HorizontalStack'); // Verify the layout type const currentLayout = engine.scene.getLayout(); console.log('Current layout:', currentLayout); // Add a new page to the existing stack // The page automatically appears at the end const page3 = engine.block.create('page'); engine.block.setWidth(page3, 400); engine.block.setHeight(page3, 300); engine.block.appendChild(stack, page3); // Add content to the new page const block3 = engine.block.create('graphic'); const shape3 = engine.block.createShape('rect'); engine.block.setShape(block3, shape3); engine.block.setWidth(block3, 350); engine.block.setHeight(block3, 250); engine.block.setPositionX(block3, 25); engine.block.setPositionY(block3, 25); const fill3 = engine.block.createFill('color'); engine.block.setColor(fill3, 'fill/color/value', { r: 0.9, g: 0.5, b: 0.3, a: 1.0 }); engine.block.setFill(block3, fill3); engine.block.appendChild(page3, block3); // Reorder pages using insertChild // Move page3 to the first position engine.block.insertChild(stack, page3, 0); // Verify the new order const pageOrder = engine.block.getChildren(stack); console.log('Page order after reordering:', pageOrder); // Update spacing between stacked pages engine.block.setFloat(stack, 'stack/spacing', 40); // Verify the spacing value const updatedSpacing = engine.block.getFloat(stack, 'stack/spacing'); console.log('Updated spacing:', updatedSpacing); // Zoom to show all pages in the stack await engine.scene.zoomToBlock(stack, { padding: 50 }); } } export default Example; ``` This guide covers how to: - Create vertical and horizontal stack layouts - Add pages and blocks to stacks - Configure spacing between stacked pages - Reorder pages within a stack - Switch between stack and free layouts ## Create a Vertical Stack Layout Vertical stacks arrange pages from top to bottom. Create a scene with `VerticalStack` layout, then add pages to the stack container. ```typescript highlight=highlight-vertical-stack // Create a scene with vertical stack layout // Pages arrange top-to-bottom automatically engine.scene.create('VerticalStack'); // Get the stack container created with the scene const [stack] = engine.block.findByType('stack'); // Create two pages that will stack vertically const page1 = engine.block.create('page'); engine.block.setWidth(page1, 400); engine.block.setHeight(page1, 300); engine.block.appendChild(stack, page1); const page2 = engine.block.create('page'); engine.block.setWidth(page2, 400); engine.block.setHeight(page2, 300); engine.block.appendChild(stack, page2); // Configure spacing between stacked pages engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); ``` When you create a scene with `VerticalStack` layout, CE.SDK automatically creates a stack container. Pages added to this container are positioned vertically with the configured spacing. The `spacingInScreenspace` property ensures spacing remains consistent regardless of zoom level. ## Add Blocks to Pages Each page can contain multiple blocks. Create blocks with shapes and fills, position them within the page, then append them to the page. ```typescript highlight=highlight-add-blocks // Add image content to page 1 const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const block1 = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.setPositionX(block1, 25); engine.block.setPositionY(block1, 25); engine.block.appendChild(page1, block1); // Add a colored rectangle to page 2 const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, 350); engine.block.setHeight(block2, 250); engine.block.setPositionX(block2, 25); engine.block.setPositionY(block2, 25); const fill2 = engine.block.createFill('color'); engine.block.setColor(fill2, 'fill/color/value', { r: 0.3, g: 0.6, b: 0.9, a: 1.0 }); engine.block.setFill(block2, fill2); engine.block.appendChild(page2, block2); ``` Blocks require a shape and fill to be visible. Use `addImage()` for image content, or create graphic blocks with custom shapes and color fills. Position blocks within their parent page using `setPositionX()` and `setPositionY()`. ## Switch to Horizontal Layout Change the layout direction at any time using `setLayout()`. Horizontal stacks arrange pages left-to-right instead of top-to-bottom. ```typescript highlight=highlight-horizontal-stack // Switch to horizontal stack layout // Pages now arrange left-to-right engine.scene.setLayout('HorizontalStack'); // Verify the layout type const currentLayout = engine.scene.getLayout(); console.log('Current layout:', currentLayout); ``` Horizontal layouts work well for carousels, timelines, and horizontal galleries. Existing pages automatically reposition when you change the layout type. ## Add Pages to Existing Stacks Add new pages to an existing stack at any time. Pages automatically appear at the end of the stack with proper spacing. ```typescript highlight=highlight-add-page // Add a new page to the existing stack // The page automatically appears at the end const page3 = engine.block.create('page'); engine.block.setWidth(page3, 400); engine.block.setHeight(page3, 300); engine.block.appendChild(stack, page3); // Add content to the new page const block3 = engine.block.create('graphic'); const shape3 = engine.block.createShape('rect'); engine.block.setShape(block3, shape3); engine.block.setWidth(block3, 350); engine.block.setHeight(block3, 250); engine.block.setPositionX(block3, 25); engine.block.setPositionY(block3, 25); const fill3 = engine.block.createFill('color'); engine.block.setColor(fill3, 'fill/color/value', { r: 0.9, g: 0.5, b: 0.3, a: 1.0 }); engine.block.setFill(block3, fill3); engine.block.appendChild(page3, block3); ``` The stack container manages positioning automatically. You can add content to the new page before or after appending it to the stack. ## Reorder Pages Change page order using `insertChild()` to place a page at a specific index within the stack. ```typescript highlight=highlight-reorder // Reorder pages using insertChild // Move page3 to the first position engine.block.insertChild(stack, page3, 0); // Verify the new order const pageOrder = engine.block.getChildren(stack); console.log('Page order after reordering:', pageOrder); ``` Removing a page from its current position and reinserting it at index 0 moves it to the first position. All other pages shift to accommodate the change. ## Change Stack Spacing Adjust spacing between pages using the `stack/spacing` property on the stack block. ```typescript highlight=highlight-spacing // Update spacing between stacked pages engine.block.setFloat(stack, 'stack/spacing', 40); // Verify the spacing value const updatedSpacing = engine.block.getFloat(stack, 'stack/spacing'); console.log('Updated spacing:', updatedSpacing); ``` Spacing updates immediately and pages reposition automatically. Use `getFloat()` to verify the current spacing value. ## Switch to Free Layout For manual positioning, switch to `Free` layout. Pages keep their positions but stop auto-arranging. ```typescript // Check current layout type const layout = engine.scene.getLayout(); // Convert to free layout for manual positioning engine.scene.setLayout('Free'); // Now position pages manually const [page] = engine.block.findByType('page'); engine.block.setPositionX(page, 100); engine.block.setPositionY(page, 200); ``` Free layout gives full control over page positions. Use this when you need precise positioning that stack layouts cannot provide. ## Troubleshooting **Pages not arranging automatically** — Verify the scene layout type is `VerticalStack` or `HorizontalStack` using `getLayout()`. **Spacing not applying** — Check that you're setting spacing on the stack block, not the scene. Use `findByType('stack')` to get the stack container. **Pages overlapping** — Ensure pages are direct children of the stack container. Nested pages won't auto-arrange properly. **Can't position manually** — Stack layouts override manual positions. Switch to `Free` layout for manual control. **Wrong stacking order** — Child order determines position. Use `insertChild()` to move pages to specific positions. ## API Reference | Method | Description | |--------|-------------| | `engine.scene.create(layout)` | Create a scene with specified layout (`'Free'`, `'VerticalStack'`, `'HorizontalStack'`) | | `engine.scene.setLayout(layout)` | Change the layout type of the current scene | | `engine.scene.getLayout()` | Get the current layout type | | `engine.block.findByType('stack')` | Find the stack container block | | `engine.block.setFloat(id, 'stack/spacing', value)` | Set spacing between stacked pages | | `engine.block.getFloat(id, 'stack/spacing')` | Get current spacing value | | `engine.block.appendChild(parent, child)` | Add a page to the stack | | `engine.block.insertChild(parent, child, index)` | Insert a page at a specific position | | `engine.block.getChildren(id)` | Get child blocks in order | ## Next Steps - [Auto-resize](https://img.ly/docs/cesdk/angular/automation/auto-resize-4c2d58/) — Make blocks fit parent containers - [Manual Positioning](https://img.ly/docs/cesdk/angular/edit-image/transform/move-818dd9/) — Position blocks in free layouts - [Layer Hierarchies](https://img.ly/docs/cesdk/angular/create-composition/layer-management-18f07a/) — Organize blocks in hierarchical structures - [Create a Collage](https://img.ly/docs/cesdk/angular/create-composition/collage-f7d28d/) — Build photo collages with templates --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Lock Design" description: "Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/lock-design-0a81de/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Lock Design](https://img.ly/docs/cesdk/angular/create-composition/lock-design-0a81de/) --- Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. ![Lock Design Hero](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-lock-design-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-lock-design-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-lock-design-browser/) CE.SDK uses a two-layer scope system to control editing permissions. Global scopes set defaults for the entire scene, while block-level scopes override when the global setting is `Defer`. This enables flexible permission models from fully locked to selectively editable designs. ```typescript file=@cesdk_web_examples/guides-create-composition-lock-design-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext, Scope } from '@cesdk/cesdk-js'; import packageJson from './package.json'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; class LockDesign implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]!; // Create sample content to demonstrate different locking techniques const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Column 1: Fully Locked const caption1 = engine.block.create('text'); engine.block.setString(caption1, 'text/text', 'Fully Locked'); engine.block.setFloat(caption1, 'text/fontSize', 32); engine.block.setWidth(caption1, 220); engine.block.setHeight(caption1, 50); engine.block.setPositionX(caption1, 30); engine.block.setPositionY(caption1, 30); engine.block.appendChild(page, caption1); const imageBlock = await engine.block.addImage(imageUri, { x: 30, y: 100, size: { width: 220, height: 165 } }); engine.block.appendChild(page, imageBlock); // Column 2: Text Editing Only const caption2 = engine.block.create('text'); engine.block.setString(caption2, 'text/text', 'Text Editing'); engine.block.setFloat(caption2, 'text/fontSize', 32); engine.block.setWidth(caption2, 220); engine.block.setHeight(caption2, 50); engine.block.setPositionX(caption2, 290); engine.block.setPositionY(caption2, 30); engine.block.appendChild(page, caption2); const textBlock = engine.block.create('text'); engine.block.setString(textBlock, 'text/text', 'Edit Me'); engine.block.setFloat(textBlock, 'text/fontSize', 72); engine.block.setWidth(textBlock, 220); engine.block.setHeight(textBlock, 165); engine.block.setPositionX(textBlock, 290); engine.block.setPositionY(textBlock, 100); engine.block.appendChild(page, textBlock); // Column 3: Image Replace Only const caption3 = engine.block.create('text'); engine.block.setString(caption3, 'text/text', 'Image Replace'); engine.block.setFloat(caption3, 'text/fontSize', 32); engine.block.setWidth(caption3, 220); engine.block.setHeight(caption3, 50); engine.block.setPositionX(caption3, 550); engine.block.setPositionY(caption3, 30); engine.block.appendChild(page, caption3); const placeholderBlock = await engine.block.addImage(imageUri, { x: 550, y: 100, size: { width: 220, height: 165 } }); engine.block.appendChild(page, placeholderBlock); // Lock the entire design by setting all scopes to Deny const scopes = engine.editor.findAllScopes(); for (const scope of scopes) { engine.editor.setGlobalScope(scope, 'Deny'); } // Enable selection for specific blocks engine.editor.setGlobalScope('editor/select', 'Defer'); engine.block.setScopeEnabled(textBlock, 'editor/select', true); engine.block.setScopeEnabled(placeholderBlock, 'editor/select', true); // Enable text editing on the text block engine.editor.setGlobalScope('text/edit', 'Defer'); engine.editor.setGlobalScope('text/character', 'Defer'); engine.block.setScopeEnabled(textBlock, 'text/edit', true); engine.block.setScopeEnabled(textBlock, 'text/character', true); // Enable image replacement on the placeholder block engine.editor.setGlobalScope('fill/change', 'Defer'); engine.block.setScopeEnabled(placeholderBlock, 'fill/change', true); // Check if operations are permitted on blocks const canEditText = engine.block.isAllowedByScope(textBlock, 'text/edit'); const canMoveImage = engine.block.isAllowedByScope( imageBlock, 'layer/move' ); const canReplacePlaceholder = engine.block.isAllowedByScope( placeholderBlock, 'fill/change' ); console.log('Permission status:'); console.log('- Can edit text:', canEditText); // true console.log('- Can move locked image:', canMoveImage); // false console.log('- Can replace placeholder:', canReplacePlaceholder); // true // Discover all available scopes const allScopes: Scope[] = engine.editor.findAllScopes(); console.log('Available scopes:', allScopes); // Check global scope settings const textEditGlobal = engine.editor.getGlobalScope('text/edit'); const layerMoveGlobal = engine.editor.getGlobalScope('layer/move'); console.log('Global text/edit:', textEditGlobal); // 'Defer' console.log('Global layer/move:', layerMoveGlobal); // 'Deny' // Check block-level scope settings const textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit'); console.log('Text block text/edit enabled:', textEditEnabled); // true // Select the text block to demonstrate editability engine.block.select(textBlock); } } export default LockDesign; ``` This guide covers how to lock entire designs, selectively enable specific editing capabilities, and check permissions programmatically. ## Understanding the Scope Permission Model Scopes control what operations users can perform on design elements. CE.SDK combines global scope settings with block-level settings to determine the final permission. | Global Scope | Block Scope | Result | | ------------ | ----------- | --------- | | `Allow` | any | Permitted | | `Deny` | any | Blocked | | `Defer` | enabled | Permitted | | `Defer` | disabled | Blocked | Global scopes have three possible values: - **`Allow`**: The operation is always permitted, regardless of block-level settings - **`Deny`**: The operation is always blocked, regardless of block-level settings - **`Defer`**: The permission depends on the block-level scope setting Block-level scopes are binary: enabled or disabled. They only take effect when the global scope is set to `Defer`. ## Locking an Entire Design To lock all editing operations, iterate through all available scopes and set each to `Deny`. We use `engine.editor.findAllScopes()` to discover all scope names dynamically. ```typescript highlight=highlight-lock-entire-design // Lock the entire design by setting all scopes to Deny const scopes = engine.editor.findAllScopes(); for (const scope of scopes) { engine.editor.setGlobalScope(scope, 'Deny'); } ``` When all scopes are set to `Deny`, users cannot modify any aspect of the design. This includes selecting, moving, editing text, or changing any visual properties. ## Enabling Selection for Interactive Blocks Before users can interact with any block, you must enable the `editor/select` scope. Without selection, users cannot click on or access any blocks, even if other editing capabilities are enabled. ```typescript highlight=highlight-enable-selection // Enable selection for specific blocks engine.editor.setGlobalScope('editor/select', 'Defer'); engine.block.setScopeEnabled(textBlock, 'editor/select', true); engine.block.setScopeEnabled(placeholderBlock, 'editor/select', true); ``` Setting the global `editor/select` scope to `Defer` delegates the decision to each block. We then enable selection only on the specific blocks users should be able to interact with. ## Selective Locking Patterns Lock everything first, then selectively enable specific capabilities on chosen blocks. This pattern provides fine-grained control over what users can modify. ### Text-Only Editing To allow users to edit text content while protecting everything else, enable the `text/edit` scope. For text styling changes like font, size, and color, also enable `text/character`. ```typescript highlight=highlight-text-editing // Enable text editing on the text block engine.editor.setGlobalScope('text/edit', 'Defer'); engine.editor.setGlobalScope('text/character', 'Defer'); engine.block.setScopeEnabled(textBlock, 'text/edit', true); engine.block.setScopeEnabled(textBlock, 'text/character', true); ``` Users can now type new text content in the designated text block but cannot move, resize, or delete it. ### Image Replacement To allow users to swap images while protecting layout and position, enable the `fill/change` scope on placeholder blocks. ```typescript highlight=highlight-image-replacement // Enable image replacement on the placeholder block engine.editor.setGlobalScope('fill/change', 'Defer'); engine.block.setScopeEnabled(placeholderBlock, 'fill/change', true); ``` Users can replace the image content but the block's position, dimensions, and other properties remain locked. ## Checking Permissions Verify whether operations are permitted using `engine.block.isAllowedByScope()`. This method evaluates both global and block-level settings to return the effective permission state. ```typescript highlight=highlight-check-permissions // Check if operations are permitted on blocks const canEditText = engine.block.isAllowedByScope(textBlock, 'text/edit'); const canMoveImage = engine.block.isAllowedByScope( imageBlock, 'layer/move' ); const canReplacePlaceholder = engine.block.isAllowedByScope( placeholderBlock, 'fill/change' ); console.log('Permission status:'); console.log('- Can edit text:', canEditText); // true console.log('- Can move locked image:', canMoveImage); // false console.log('- Can replace placeholder:', canReplacePlaceholder); // true ``` The distinction between checking methods is: - `isAllowedByScope()` returns the **effective permission** after evaluating all scope levels - `isScopeEnabled()` returns only the **block-level setting** - `getGlobalScope()` returns only the **global setting** ## Discovering Available Scopes To work with scopes programmatically, you can discover all available scope names and check their current settings. ```typescript highlight=highlight-get-scopes // Discover all available scopes const allScopes: Scope[] = engine.editor.findAllScopes(); console.log('Available scopes:', allScopes); // Check global scope settings const textEditGlobal = engine.editor.getGlobalScope('text/edit'); const layerMoveGlobal = engine.editor.getGlobalScope('layer/move'); console.log('Global text/edit:', textEditGlobal); // 'Defer' console.log('Global layer/move:', layerMoveGlobal); // 'Deny' // Check block-level scope settings const textEditEnabled = engine.block.isScopeEnabled(textBlock, 'text/edit'); console.log('Text block text/edit enabled:', textEditEnabled); // true ``` ## Available Scopes Reference | Scope | Description | | ------------------------ | --------------------------------------- | | `layer/move` | Move block position | | `layer/resize` | Resize block dimensions | | `layer/rotate` | Rotate block | | `layer/flip` | Flip block horizontally or vertically | | `layer/crop` | Crop block content | | `layer/opacity` | Change block opacity | | `layer/blendMode` | Change blend mode | | `layer/visibility` | Toggle block visibility | | `layer/clipping` | Change clipping behavior | | `fill/change` | Change fill content | | `fill/changeType` | Change fill type | | `stroke/change` | Change stroke properties | | `shape/change` | Change shape type | | `text/edit` | Edit text content | | `text/character` | Change text styling (font, size, color) | | `appearance/adjustments` | Change color adjustments | | `appearance/filter` | Apply or change filters | | `appearance/effect` | Apply or change effects | | `appearance/blur` | Apply or change blur | | `appearance/shadow` | Apply or change shadows | | `appearance/animation` | Apply or change animations | | `lifecycle/destroy` | Delete the block | | `lifecycle/duplicate` | Duplicate the block | | `editor/add` | Add new blocks | | `editor/select` | Select blocks | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Multi-Page Layouts" description: "Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/multi-page-4d2b50/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Multi-Page Layouts](https://img.ly/docs/cesdk/angular/create-composition/multi-page-4d2b50/) --- Create multi-page designs in CE.SDK for brochures, presentations, catalogs, and other documents requiring multiple pages within a single scene. ![Multi-Page Layouts](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-multi-page-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-multi-page-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-multi-page-browser/) Multi-page layouts allow you to create documents with multiple artboards within a single scene. Each page operates as an independent canvas that can contain different content while sharing the same scene context. CE.SDK provides scene layout modes that automatically arrange pages vertically, horizontally, or in a free-form canvas. ```typescript file=@cesdk_web_examples/guides-create-composition-multi-page-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Multi-Page Layouts Guide * * This example demonstrates: * - Creating scenes with multiple pages * - Adding and configuring pages * - Scene layout types (HorizontalStack) * - Stack spacing between pages */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a scene with HorizontalStack layout engine.scene.create('HorizontalStack'); // Get the stack container const [stack] = engine.block.findByType('stack'); // Add spacing between pages (20 pixels in screen space) engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); // Create the first page const firstPage = engine.block.create('page'); engine.block.setWidth(firstPage, 800); engine.block.setHeight(firstPage, 600); engine.block.appendChild(stack, firstPage); // Add content to the first page const imageBlock1 = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: 300, height: 200 } } ); engine.block.setPositionX(imageBlock1, 250); engine.block.setPositionY(imageBlock1, 200); engine.block.appendChild(firstPage, imageBlock1); // Create a second page with different content const secondPage = engine.block.create('page'); engine.block.setWidth(secondPage, 800); engine.block.setHeight(secondPage, 600); engine.block.appendChild(stack, secondPage); // Add a different image to the second page const imageBlock2 = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_2.jpg', { size: { width: 300, height: 200 } } ); engine.block.setPositionX(imageBlock2, 250); engine.block.setPositionY(imageBlock2, 200); engine.block.appendChild(secondPage, imageBlock2); engine.block.select(firstPage); engine.scene.enableZoomAutoFit(firstPage, 'Both'); } } export default Example; ``` This guide covers how to create multi-page scenes, add pages, and configure spacing between pages. ## Using the Built-in Page Management UI The CE.SDK editor provides built-in UI controls for managing pages. Users can interact with the page panel to add new pages, duplicate existing ones, reorder them with drag-and-drop, delete pages, and navigate between pages by clicking. The page panel displays thumbnails of all pages in the scene, making it easy to understand the document structure at a glance. When you click a page thumbnail, the viewport automatically zooms to that page. ## Creating Multi-Page Scenes Programmatically We can create scenes with multiple pages using the engine API. The scene acts as a container for pages, and each page can hold any number of content blocks. ### Creating a Scene with Pages We create a new scene using `engine.scene.create()` and specify the layout type. The layout type determines how pages are arranged in the viewport. After creating the scene, we get the stack container and create pages within it. ```typescript highlight=highlight-create-scene // Create a scene with HorizontalStack layout engine.scene.create('HorizontalStack'); // Get the stack container const [stack] = engine.block.findByType('stack'); // Add spacing between pages (20 pixels in screen space) engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); // Create the first page const firstPage = engine.block.create('page'); engine.block.setWidth(firstPage, 800); engine.block.setHeight(firstPage, 600); engine.block.appendChild(stack, firstPage); ``` The scene is created with a `HorizontalStack` layout, meaning pages are arranged side by side from left to right. We then create a page, set its dimensions, and append it to the stack container. ### Configuring Page Spacing We can add spacing between pages in a stack layout using the `stack/spacing` property. This creates visual separation between pages. ```typescript highlight=highlight-stack-spacing // Add spacing between pages (20 pixels in screen space) engine.block.setFloat(stack, 'stack/spacing', 20); engine.block.setBool(stack, 'stack/spacingInScreenspace', true); ``` Setting `stack/spacingInScreenspace` to `true` means the spacing value is interpreted as screen pixels, maintaining consistent visual spacing regardless of zoom level. ### Adding More Pages To add additional pages, we create new page blocks, set their dimensions, and append them to the stack container. ```typescript highlight=highlight-add-page // Create a second page with different content const secondPage = engine.block.create('page'); engine.block.setWidth(secondPage, 800); engine.block.setHeight(secondPage, 600); engine.block.appendChild(stack, secondPage); // Add a different image to the second page const imageBlock2 = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_2.jpg', { size: { width: 300, height: 200 } } ); engine.block.setPositionX(imageBlock2, 250); engine.block.setPositionY(imageBlock2, 200); engine.block.appendChild(secondPage, imageBlock2); ``` Each page can contain different content. Here we add different images to each page to demonstrate independent page content. ## Scene Layout Types CE.SDK supports different layout modes that control how pages are arranged on the canvas. You specify the layout type when creating the scene with `engine.scene.create()`. **Free Layout** is the default where pages can be positioned anywhere on the canvas. This provides complete control over page placement. **VerticalStack Layout** arranges pages automatically in a vertical stack from top to bottom. This is useful for scroll-based document previews. **HorizontalStack Layout** arranges pages side by side from left to right. This is useful for carousel-style presentations or side-by-side comparisons. ## Setting the Zoom Level We can control the viewport zoom level using `engine.scene.setZoomLevel()`. A value of 1.0 represents 100% zoom. ```typescript highlight=highlight-zoom engine.block.select(firstPage); engine.scene.enableZoomAutoFit(firstPage, 'Both'); ``` ## Troubleshooting **Page not visible after creation**: Ensure the page is attached to the stack with `appendChild()` and has valid dimensions set with `setWidth()` and `setHeight()`. **Cannot add content to page**: Verify you're appending blocks to the page block, not the scene directly. Content blocks should be children of pages. **Pages overlapping**: When using stack layouts, make sure pages are appended to the stack container (found via `findByType('stack')`), not directly to the scene. **Spacing not visible**: Check that `stack/spacing` is set to a positive value and that you're using a stack layout (HorizontalStack or VerticalStack). --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions." platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/overview-5b19c5/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Overview](https://img.ly/docs/cesdk/angular/create-composition/overview-5b19c5/) --- ## Exporting Compositions CE.SDK compositions can be exported in several formats: --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Programmatic Creation" description: "Documentation for Programmatic Creation" platform: angular url: "https://img.ly/docs/cesdk/angular/create-composition/programmatic-a688bf/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) > [Programmatic Creation](https://img.ly/docs/cesdk/angular/create-composition/programmatic-a688bf/) --- Build compositions entirely through code using CE.SDK's APIs for automation, batch processing, and custom interfaces. ![Programmatic Creation](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-programmatic-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-composition-programmatic-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-composition-programmatic-browser/) CE.SDK provides a complete API for building designs through code. Instead of relying on user interactions through the built-in UI, you can create scenes, add blocks like text, images, and shapes, and position them programmatically. This approach enables automation workflows, batch processing, server-side rendering, and integration with custom interfaces. ```typescript file=@cesdk_web_examples/guides-create-composition-programmatic-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Programmatic Creation Guide * * Demonstrates building compositions entirely through code: * - Creating scenes and pages with social media dimensions * - Setting page background colors * - Adding text blocks with mixed styling (bold, italic, colors) * - Adding line shapes as dividers * - Adding images * - Positioning and sizing blocks */ // Roboto typeface with all variants for mixed styling const ROBOTO_TYPEFACE = { name: 'Roboto', fonts: [ { uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf', subFamily: 'Regular' }, { uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf', subFamily: 'Bold', weight: 'bold' as const }, { uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf', subFamily: 'Italic', style: 'italic' as const }, { uri: 'https://cdn.img.ly/assets/v2/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf', subFamily: 'Bold Italic', weight: 'bold' as const, style: 'italic' as const } ] }; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 1080, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const scene = engine.scene.get()!; engine.block.setFloat(scene, 'scene/dpi', 300); const page = engine.block.findByType('page')[0]; // Set page background to light lavender color const backgroundFill = engine.block.createFill('color'); engine.block.setColor(backgroundFill, 'fill/color/value', { r: 0.94, g: 0.93, b: 0.98, a: 1.0 }); engine.block.setFill(page, backgroundFill); // Add main headline text with bold Roboto font const headline = engine.block.create('text'); engine.block.replaceText( headline, 'Integrate\nCreative Editing\ninto your App' ); engine.block.setFont( headline, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE ); engine.block.setFloat(headline, 'text/lineHeight', 0.78); // Make headline bold if (engine.block.canToggleBoldFont(headline)) { engine.block.toggleBoldFont(headline); } engine.block.setTextColor(headline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }); // Set fixed container size and enable automatic font sizing engine.block.setWidthMode(headline, 'Absolute'); engine.block.setHeightMode(headline, 'Absolute'); engine.block.setWidth(headline, 960); engine.block.setHeight(headline, 300); engine.block.setBool(headline, 'text/automaticFontSizeEnabled', true); engine.block.setPositionX(headline, 60); engine.block.setPositionY(headline, 80); engine.block.appendChild(page, headline); // Add tagline with mixed styling using range-based APIs // "in hours," (purple italic) + "not months." (black bold) const tagline = engine.block.create('text'); const taglineText = 'in hours,\nnot months.'; engine.block.replaceText(tagline, taglineText); // Set up Roboto typeface with all variants for mixed styling engine.block.setFont( tagline, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE ); engine.block.setFloat(tagline, 'text/lineHeight', 0.78); // Style "in hours," - purple and italic (characters 0-9) engine.block.setTextColor( tagline, { r: 0.2, g: 0.2, b: 0.8, a: 1.0 }, 0, 9 ); if (engine.block.canToggleItalicFont(tagline, 0, 9)) { engine.block.toggleItalicFont(tagline, 0, 9); } // Style "not months." - black and bold (characters 10-21) engine.block.setTextColor( tagline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, 10, 21 ); if (engine.block.canToggleBoldFont(tagline, 10, 21)) { engine.block.toggleBoldFont(tagline, 10, 21); } // Set fixed container size and enable automatic font sizing engine.block.setWidthMode(tagline, 'Absolute'); engine.block.setHeightMode(tagline, 'Absolute'); engine.block.setWidth(tagline, 960); engine.block.setHeight(tagline, 220); engine.block.setBool(tagline, 'text/automaticFontSizeEnabled', true); engine.block.setPositionX(tagline, 60); engine.block.setPositionY(tagline, 551); engine.block.appendChild(page, tagline); // Add CTA text "Start a Free Trial" with bold font const ctaTitle = engine.block.create('text'); engine.block.replaceText(ctaTitle, 'Start a Free Trial'); engine.block.setFont( ctaTitle, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE ); engine.block.setFloat(ctaTitle, 'text/fontSize', 80); engine.block.setFloat(ctaTitle, 'text/lineHeight', 1.0); if (engine.block.canToggleBoldFont(ctaTitle)) { engine.block.toggleBoldFont(ctaTitle); } engine.block.setTextColor(ctaTitle, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setWidthMode(ctaTitle, 'Absolute'); engine.block.setHeightMode(ctaTitle, 'Auto'); engine.block.setWidth(ctaTitle, 664.6); engine.block.setPositionX(ctaTitle, 64); engine.block.setPositionY(ctaTitle, 952); engine.block.appendChild(page, ctaTitle); // Add website URL with regular font const ctaUrl = engine.block.create('text'); engine.block.replaceText(ctaUrl, 'www.img.ly'); engine.block.setFont(ctaUrl, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE); engine.block.setFloat(ctaUrl, 'text/fontSize', 80); engine.block.setFloat(ctaUrl, 'text/lineHeight', 1.0); engine.block.setTextColor(ctaUrl, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setWidthMode(ctaUrl, 'Absolute'); engine.block.setHeightMode(ctaUrl, 'Auto'); engine.block.setWidth(ctaUrl, 664.6); engine.block.setPositionX(ctaUrl, 64); engine.block.setPositionY(ctaUrl, 1006); engine.block.appendChild(page, ctaUrl); // Add horizontal divider line const dividerLine = engine.block.create('graphic'); const lineShape = engine.block.createShape('line'); engine.block.setShape(dividerLine, lineShape); const lineFill = engine.block.createFill('color'); engine.block.setColor(lineFill, 'fill/color/value', { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setFill(dividerLine, lineFill); engine.block.setWidth(dividerLine, 418); engine.block.setHeight(dividerLine, 11.3); engine.block.setPositionX(dividerLine, 64); engine.block.setPositionY(dividerLine, 460); engine.block.appendChild(page, dividerLine); // Add IMG.LY logo image const logo = engine.block.create('graphic'); const logoShape = engine.block.createShape('rect'); engine.block.setShape(logo, logoShape); const logoFill = engine.block.createFill('image'); engine.block.setString( logoFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logo, logoFill); engine.block.setContentFillMode(logo, 'Contain'); engine.block.setWidth(logo, 200); engine.block.setHeight(logo, 65); engine.block.setPositionX(logo, 820); engine.block.setPositionY(logo, 960); engine.block.appendChild(page, logo); // Export the composition to PNG const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1080, targetHeight: 1080 }); // In browser, create a download link const url = URL.createObjectURL(blob); console.log('Export complete. Download URL:', url); // Zoom to show the page await cesdk.actions.run('zoom.toPage', { autoFit: true }); } } export default Example; ``` This guide covers how to create a scene structure with social media dimensions, set background colors, add text with mixed styling, line shapes, images, and export the finished composition. ## Initialize CE.SDK We start by initializing CE.SDK and loading the asset sources. The asset source plugins (imported from `@cesdk/cesdk-js/plugins`) provide access to fonts, images, and other assets. ```typescript highlight=highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); ``` ## Create Scene Structure We create the foundation of our composition with social media dimensions (1080x1080 pixels for Instagram). A scene contains one or more pages, and pages contain the design blocks. ```typescript highlight=highlight-create-scene await cesdk.actions.run('scene.create', { page: { width: 1080, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const scene = engine.scene.get()!; ``` The `cesdk.actions.run('scene.create')` method creates a new design scene with a page. We set the page dimensions using `setWidth()` and `setHeight()`. ## Set Page Background We set the page background using a color fill. This demonstrates how to create and assign fills to blocks. ```typescript highlight=highlight-add-background // Set page background to light lavender color const backgroundFill = engine.block.createFill('color'); engine.block.setColor(backgroundFill, 'fill/color/value', { r: 0.94, g: 0.93, b: 0.98, a: 1.0 }); engine.block.setFill(page, backgroundFill); ``` We create a color fill using `createFill('color')`, set the color via `setColor()` with the `fill/color/value` property, then assign the fill to the page. ## Add Text Blocks Text blocks allow you to add and style text content. We demonstrate three different approaches to text sizing and styling. ### Create Text and Set Content Create a text block and set its content with `replaceText()`: ```typescript highlight=highlight-text-create // Add main headline text with bold Roboto font const headline = engine.block.create('text'); engine.block.replaceText( headline, 'Integrate\nCreative Editing\ninto your App' ); engine.block.setFont( headline, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE ); engine.block.setFloat(headline, 'text/lineHeight', 0.78); ``` ### Style Entire Text Block Apply styling to the entire text block using `toggleBoldFont()` and `setTextColor()`: ```typescript highlight=highlight-text-style-block // Make headline bold if (engine.block.canToggleBoldFont(headline)) { engine.block.toggleBoldFont(headline); } engine.block.setTextColor(headline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }); ``` ### Enable Automatic Font Sizing Configure the text block to automatically scale its font size to fit within fixed dimensions: ```typescript highlight=highlight-text-auto-size // Set fixed container size and enable automatic font sizing engine.block.setWidthMode(headline, 'Absolute'); engine.block.setHeightMode(headline, 'Absolute'); engine.block.setWidth(headline, 960); engine.block.setHeight(headline, 300); engine.block.setBool(headline, 'text/automaticFontSizeEnabled', true); ``` ### Range-based Text Styling Apply different styles to specific character ranges within a single text block: ```typescript highlight=highlight-text-range-style // Style "in hours," - purple and italic (characters 0-9) engine.block.setTextColor( tagline, { r: 0.2, g: 0.2, b: 0.8, a: 1.0 }, 0, 9 ); if (engine.block.canToggleItalicFont(tagline, 0, 9)) { engine.block.toggleItalicFont(tagline, 0, 9); } // Style "not months." - black and bold (characters 10-21) engine.block.setTextColor( tagline, { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, 10, 21 ); if (engine.block.canToggleBoldFont(tagline, 10, 21)) { engine.block.toggleBoldFont(tagline, 10, 21); } ``` The range-based APIs accept start and end character indices: - `setTextColor(id, color, from, to)` - Apply color to a specific character range - `toggleBoldFont(id, from, to)` - Toggle bold styling for a range - `toggleItalicFont(id, from, to)` - Toggle italic styling for a range ### Fixed Font Size Set an explicit font size instead of using auto-sizing: ```typescript highlight=highlight-text-fixed-size // Add CTA text "Start a Free Trial" with bold font const ctaTitle = engine.block.create('text'); engine.block.replaceText(ctaTitle, 'Start a Free Trial'); engine.block.setFont( ctaTitle, ROBOTO_TYPEFACE.fonts[0].uri, ROBOTO_TYPEFACE ); engine.block.setFloat(ctaTitle, 'text/fontSize', 80); engine.block.setFloat(ctaTitle, 'text/lineHeight', 1.0); ``` ## Add Shapes We create shapes using graphic blocks. CE.SDK supports `rect`, `line`, `ellipse`, `polygon`, `star`, and `vector_path` shapes. ### Create a Shape Block Create a graphic block and assign a shape to it: ```typescript highlight=highlight-shape-create // Add horizontal divider line const dividerLine = engine.block.create('graphic'); const lineShape = engine.block.createShape('line'); engine.block.setShape(dividerLine, lineShape); ``` ### Apply Fill to Shape Create a color fill and apply it to the shape: ```typescript highlight=highlight-shape-fill const lineFill = engine.block.createFill('color'); engine.block.setColor(lineFill, 'fill/color/value', { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setFill(dividerLine, lineFill); ``` ## Add Images We add images using graphic blocks with image fills. ### Create an Image Block Create a graphic block with a rect shape and an image fill: ```typescript highlight=highlight-image-create // Add IMG.LY logo image const logo = engine.block.create('graphic'); const logoShape = engine.block.createShape('rect'); engine.block.setShape(logo, logoShape); const logoFill = engine.block.createFill('image'); engine.block.setString( logoFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logo, logoFill); ``` We set the image URL via `setString()` with the `fill/image/imageFileURI` property. ## Position and Size Blocks All blocks use the same positioning and sizing APIs: ```typescript highlight=highlight-block-position engine.block.setContentFillMode(logo, 'Contain'); engine.block.setWidth(logo, 200); engine.block.setHeight(logo, 65); engine.block.setPositionX(logo, 820); engine.block.setPositionY(logo, 960); engine.block.appendChild(page, logo); ``` - `setWidth()` / `setHeight()` - Set block dimensions - `setPositionX()` / `setPositionY()` - Set block position - `setContentFillMode()` - Control how content fills the block (`Contain`, `Cover`, `Crop`) - `appendChild()` - Add the block to the page hierarchy ## Export the Composition CE.SDK provides two approaches for exporting compositions in browser environments. ### Export Using the Engine API The `engine.block.export()` method exports a block as a blob that you can use programmatically: ```typescript highlight=highlight-export-api // Export the composition to PNG const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1080, targetHeight: 1080 }); // In browser, create a download link const url = URL.createObjectURL(blob); console.log('Export complete. Download URL:', url); ``` In browser environments, you can create a download URL from the blob using `URL.createObjectURL()`. ### Export Using Built-in Actions Alternatively, use the built-in export panel or actions for a complete export dialog: ```typescript await cesdk.actions.run('exportDesign', { mimeType: 'image/png' }); ``` The export panel lets users choose format and settings interactively, while `cesdk.actions.run('export.page', options)` triggers export directly with specified options. ## Troubleshooting - **Blocks not appearing**: Verify that `appendChild()` attaches blocks to the page. Blocks must be part of the scene hierarchy to render. - **Text styling not applied**: Verify character indices are correct for range-based APIs. The indices are UTF-16 based. - **Image stretched**: Use `setContentFillMode(block, 'Contain')` to maintain the image's aspect ratio. - **Export fails**: Verify that page dimensions are set before export. The export requires valid dimensions. ## Next Steps - [Layer Management](https://img.ly/docs/cesdk/angular/create-composition/layer-management-18f07a/) - Control block stacking and organization - [Positioning and Alignment](https://img.ly/docs/cesdk/angular/insert-media/position-and-align-cc6b6a/) - Precise block placement - [Group and Ungroup](https://img.ly/docs/cesdk/angular/create-composition/group-and-ungroup-62565a/) - Group blocks for unified transforms - [Blend Modes](https://img.ly/docs/cesdk/angular/create-composition/blend-modes-ad3519/) - Control how blocks interact visually - [Export](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) - Export options and formats --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Templates" description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates-3aef79/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/create-templates/overview-4ebe30/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK. - [Create From Scratch](https://img.ly/docs/cesdk/angular/create-templates/from-scratch-663cda/) - Build reusable design templates programmatically using CE.SDK's APIs. Create scenes, add text and graphic blocks, configure placeholders and variables, apply editing constraints, and save templates for reuse. - [Import Templates](https://img.ly/docs/cesdk/angular/create-templates/import-e50084/) - Load and import design templates into CE.SDK from URLs, archives, and serialized strings. - [Dynamic Content](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content-53fad7/) - Use variables and placeholders to inject dynamic data into templates at design or runtime. - [Lock the Template](https://img.ly/docs/cesdk/angular/create-templates/lock-131489/) - Restrict editing access to specific elements or properties in a template to enforce design rules. - [Edit or Remove Templates](https://img.ly/docs/cesdk/angular/create-templates/edit-or-remove-38a8be/) - Modify existing templates and manage template lifecycle by loading, editing, saving, and removing templates from asset sources. - [Add to Template Library](https://img.ly/docs/cesdk/angular/create-templates/add-to-template-library-8bfbc7/) - Save and organize templates in an asset source for users to browse and apply from the template library. - [Overview](https://img.ly/docs/cesdk/angular/use-templates/overview-ae74e1/) - Learn how to browse, apply, and dynamically populate templates in CE.SDK to streamline design workflows. - [Template Library](https://img.ly/docs/cesdk/angular/use-templates/library-b3c704/) - Learn how to provide a set of predefined templates in the CreativeEditor SDK. - [Apply a Template](https://img.ly/docs/cesdk/angular/use-templates/apply-template-35c73e/) - Learn how to apply template scenes via API in the CreativeEditor SDK. - [Generate From Template](https://img.ly/docs/cesdk/angular/use-templates/generate-334e15/) - Learn how to generate finished designs from templates by loading, populating variables, and exporting to images, PDFs, or videos. - [Replace Content](https://img.ly/docs/cesdk/angular/use-templates/replace-content-4c482b/) - Learn how to dynamically replace content within templates using CE.SDK's placeholder and variable systems. - [Use Templates Programmatically](https://img.ly/docs/cesdk/angular/use-templates/programmatic-9349f3/) - Work with templates programmatically through CE.SDK's engine APIs to load existing templates, build new templates from scratch, modify template structures, and populate templates with dynamic data for batch processing and automation. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Dynamic Content" description: "Use variables and placeholders to inject dynamic data into templates at design or runtime." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content-53fad7/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content-53fad7/) --- Dynamic content transforms static designs into flexible, data-driven templates. CE.SDK provides three complementary capabilities—text variables, placeholders, and editing constraints—that work together to enable personalization while maintaining design integrity. ![Dynamic Content example showing variables and placeholders](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-dynamic-content-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-dynamic-content-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-add-dynamic-content-browser/) ```typescript file=@cesdk_web_examples/guides-create-templates-add-dynamic-content-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Dynamic Content Overview * * Demonstrates the dynamic content capabilities in CE.SDK templates: * - Text Variables: Insert {{tokens}} that resolve to dynamic values * - Placeholders: Create drop zones for swappable images/videos * - Editing Constraints: Lock properties while allowing controlled changes */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Set editor role to Adopter for template usage engine.editor.setRole('Adopter'); const page = engine.block.findByType('page')[0]; // Content area: 480px wide, centered (left margin = 160px) const contentX = 160; const contentWidth = 480; // TEXT VARIABLES: Define variables for personalization engine.variable.setString('firstName', 'Jane'); engine.variable.setString('lastName', 'Doe'); engine.variable.setString('companyName', 'IMG.LY'); // Create heading with company variable const headingText = engine.block.create('text'); engine.block.replaceText( headingText, 'Welcome to {{companyName}}, {{firstName}} {{lastName}}.' ); engine.block.setWidth(headingText, contentWidth); engine.block.setHeightMode(headingText, 'Auto'); engine.block.setFloat(headingText, 'text/fontSize', 64); engine.block.setEnum(headingText, 'text/horizontalAlignment', 'Left'); engine.block.appendChild(page, headingText); engine.block.setPositionX(headingText, contentX); engine.block.setPositionY(headingText, 200); // Create description with bullet points const descriptionText = engine.block.create('text'); engine.block.replaceText( descriptionText, 'This example demonstrates dynamic templates.\n\n' + '• Text Variables — Personalize content with {{tokens}}\n' + '• Placeholders — Swappable images and media\n' + '• Editing Constraints — Protected brand elements' ); engine.block.setWidth(descriptionText, contentWidth); engine.block.setHeightMode(descriptionText, 'Auto'); engine.block.setFloat(descriptionText, 'text/fontSize', 44); engine.block.setEnum(descriptionText, 'text/horizontalAlignment', 'Left'); engine.block.appendChild(page, descriptionText); engine.block.setPositionX(descriptionText, contentX); engine.block.setPositionY(descriptionText, 300); // Discover all variables in the scene const allVariables = engine.variable.findAll(); console.log('Variables in scene:', allVariables); // PLACEHOLDERS: Create hero image as a swappable drop zone const heroImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: contentWidth, height: 140 } } ); engine.block.appendChild(page, heroImage); engine.block.setPositionX(heroImage, contentX); engine.block.setPositionY(heroImage, 40); // Enable placeholder behavior for the hero image if (engine.block.supportsPlaceholderBehavior(heroImage)) { engine.block.setPlaceholderBehaviorEnabled(heroImage, true); engine.block.setPlaceholderEnabled(heroImage, true); if (engine.block.supportsPlaceholderControls(heroImage)) { engine.block.setPlaceholderControlsOverlayEnabled(heroImage, true); engine.block.setPlaceholderControlsButtonEnabled(heroImage, true); } } // Find all placeholders in the scene const placeholders = engine.block.findAllPlaceholders(); console.log('Placeholders in scene:', placeholders.length); // EDITING CONSTRAINTS: Add logo that cannot be moved or selected const logo = await engine.block.addImage( 'https://img.ly/static/ubq_samples/imgly_logo.jpg', { size: { width: 100, height: 25 } } ); engine.block.appendChild(page, logo); engine.block.setPositionX(logo, 350); engine.block.setPositionY(logo, 540); // Lock the logo: prevent moving, resizing, and selection engine.block.setScopeEnabled(logo, 'layer/move', false); engine.block.setScopeEnabled(logo, 'layer/resize', false); engine.block.setScopeEnabled(logo, 'editor/select', false); // Verify constraints are applied const canSelect = engine.block.isScopeEnabled(logo, 'editor/select'); const canMove = engine.block.isScopeEnabled(logo, 'layer/move'); console.log('Logo - canSelect:', canSelect, 'canMove:', canMove); // Zoom to fit the page with autoFit enabled await cesdk.actions.run('zoom.toBlock', page, { padding: 40, animate: false, autoFit: true }); console.log('Dynamic Content demo initialized.'); cesdk.engine.block.setSelected(page, false); } } export default Example; ``` This guide covers how to use dynamic content capabilities in CE.SDK templates. The example creates a social media card with personalized name and company variables, a replaceable hero image, and a protected logo. ## Dynamic Content Capabilities CE.SDK offers three ways to make templates dynamic: - **Text Variables** — Insert `{{tokens}}` in text that resolve to dynamic values at runtime - **Placeholders** — Mark blocks as drop zones where users can swap images or videos - **Editing Constraints** — Lock specific properties to protect brand elements while allowing controlled changes ## Text Variables Text variables enable data-driven text personalization. Define variables using `engine.variable.setString()`, then reference them in text blocks with `{{variableName}}` tokens. ```typescript highlight-text-variables engine.variable.setString('firstName', 'Jane'); engine.variable.setString('lastName', 'Doe'); engine.variable.setString('companyName', 'IMG.LY'); // Create heading with company variable const headingText = engine.block.create('text'); engine.block.replaceText( headingText, 'Welcome to {{companyName}}, {{firstName}} {{lastName}}.' ); ``` Variables are defined globally and can be referenced in any text block. The `findAll()` method returns all variable keys in the scene, useful for building dynamic editing interfaces. > **Note:** Variable keys are case-sensitive. `{{Name}}` and `{{name}}` are different variables. ## Placeholders Placeholders turn design blocks into drop zones for swappable media. Mark an image block as a placeholder, and users can replace its content while the surrounding design remains fixed. ```typescript highlight-placeholders // Enable placeholder behavior for the hero image if (engine.block.supportsPlaceholderBehavior(heroImage)) { engine.block.setPlaceholderBehaviorEnabled(heroImage, true); engine.block.setPlaceholderEnabled(heroImage, true); if (engine.block.supportsPlaceholderControls(heroImage)) { engine.block.setPlaceholderControlsOverlayEnabled(heroImage, true); engine.block.setPlaceholderControlsButtonEnabled(heroImage, true); } } ``` Enable placeholder behavior with `setPlaceholderBehaviorEnabled()`, then enable user interaction with `setPlaceholderEnabled()`. The visual overlay and replace button are controlled separately via `setPlaceholderControlsOverlayEnabled()` and `setPlaceholderControlsButtonEnabled()`. ## Editing Constraints Editing constraints protect design integrity by limiting what users can modify. Use scope-based APIs to lock specific properties while keeping others editable. ```typescript highlight-editing-constraints // Lock the logo: prevent moving, resizing, and selection engine.block.setScopeEnabled(logo, 'layer/move', false); engine.block.setScopeEnabled(logo, 'layer/resize', false); engine.block.setScopeEnabled(logo, 'editor/select', false); // Verify constraints are applied const canSelect = engine.block.isScopeEnabled(logo, 'editor/select'); const canMove = engine.block.isScopeEnabled(logo, 'layer/move'); console.log('Logo - canSelect:', canSelect, 'canMove:', canMove); ``` The `setScopeEnabled()` method controls individual properties. Setting `'editor/select'` to `false` prevents users from selecting the block entirely, making it completely non-interactive. Combined with `'layer/move'` and `'layer/resize'`, this creates a fully protected element. ## Choosing the Right Capability | Need | Capability | | --- | --- | | Dynamic text content | Text Variables | | Swappable images/videos | Placeholders | | Lock specific properties | Editing Constraints | ## API Reference | Method | Description | | --- | --- | | `engine.editor.setRole()` | Set user role (Creator, Adopter, Viewer) | | `engine.variable.findAll()` | Get all variable keys in the scene | | `engine.variable.setString()` | Create or update a text variable | | `engine.variable.getString()` | Read a variable's current value | | `engine.block.supportsPlaceholderBehavior()` | Check placeholder support | | `engine.block.setPlaceholderBehaviorEnabled()` | Enable placeholder behavior | | `engine.block.setPlaceholderEnabled()` | Enable user interaction | | `engine.block.findAllPlaceholders()` | Find all placeholder blocks | | `engine.block.setScopeEnabled()` | Enable or disable editing scope | | `engine.block.isScopeEnabled()` | Query scope state | --- ## Related Pages - [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) - Define dynamic text elements that can be populated with custom values during design generation. - [Placeholders](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Use placeholders to mark editable image, video, or text areas within a locked template layout. - [Set Editing Constraints](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) - Learn how to control editing capabilities in CE.SDK templates using the Scope system to lock positions, prevent transformations, and create guided editing experiences --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Placeholders" description: "Use placeholders to mark editable image, video, or text areas within a locked template layout." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content-53fad7/) > [Placeholders](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/) --- Placeholders turn design blocks into drop-zones that users can swap content into while maintaining full control over layout and styling. ![Placeholders example showing various configurations of placeholder behavior, controls, and interaction modes](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-dynamic-content-placeholders-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-dynamic-content-placeholders-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-dynamic-content-placeholders-browser/) ```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-placeholders-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Dynamic Content Placeholders * * This example demonstrates three different placeholder configurations: * 1. All placeholder controls enabled (all scopes + behavior + controls) * 2. Fill properties only (fill scopes + behavior + controls) * 3. No placeholder features (default state) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 1200, height: 800, unit: 'Pixel' } }); const engine = cesdk.engine; engine.editor.setRole('Adopter'); // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page dimensions for horizontal layout const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Set page background to light gray const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); // Layout configuration for 3 blocks horizontally const blockWidth = 300; const blockHeight = 300; const spacing = 50; const startX = (pageWidth - blockWidth * 3 - spacing * 2) / 2; const blockY = (pageHeight - blockHeight) / 2 + 40; // Offset for labels const labelY = blockY - 50; // Sample images const imageUri1 = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageUri2 = 'https://img.ly/static/ubq_samples/sample_2.jpg'; const imageUri3 = 'https://img.ly/static/ubq_samples/sample_3.jpg'; // Define ALL available scopes for reference const allScopes: Array< | 'text/edit' | 'text/character' | 'fill/change' | 'fill/changeType' | 'stroke/change' | 'shape/change' | 'layer/move' | 'layer/resize' | 'layer/rotate' | 'layer/flip' | 'layer/crop' | 'layer/opacity' | 'layer/blendMode' | 'layer/visibility' | 'layer/clipping' | 'appearance/adjustments' | 'appearance/filter' | 'appearance/effect' | 'appearance/blur' | 'appearance/shadow' | 'appearance/animation' | 'lifecycle/destroy' | 'lifecycle/duplicate' | 'editor/add' | 'editor/select' > = [ 'text/edit', 'text/character', 'fill/change', 'fill/changeType', 'stroke/change', 'shape/change', 'layer/move', 'layer/resize', 'layer/rotate', 'layer/flip', 'layer/crop', 'layer/opacity', 'layer/blendMode', 'layer/visibility', 'layer/clipping', 'appearance/adjustments', 'appearance/filter', 'appearance/effect', 'appearance/blur', 'appearance/shadow', 'appearance/animation', 'lifecycle/destroy', 'lifecycle/duplicate', 'editor/add', 'editor/select' ]; // Block 1: All Placeholder Controls Enabled const block1 = await engine.block.addImage(imageUri1, { size: { width: blockWidth, height: blockHeight } }); engine.block.appendChild(page, block1); engine.block.setPositionX(block1, startX); engine.block.setPositionY(block1, blockY); // Step 1: Explicitly disable ALL scopes first allScopes.forEach((scope) => { engine.block.setScopeEnabled(block1, scope, false); }); // Step 2: Enable specific scopes for full placeholder functionality // General/Layer options engine.block.setScopeEnabled(block1, 'layer/opacity', true); engine.block.setScopeEnabled(block1, 'layer/blendMode', true); engine.block.setScopeEnabled(block1, 'lifecycle/duplicate', true); engine.block.setScopeEnabled(block1, 'lifecycle/destroy', true); // Arrange scopes engine.block.setScopeEnabled(block1, 'layer/move', true); engine.block.setScopeEnabled(block1, 'layer/resize', true); engine.block.setScopeEnabled(block1, 'layer/rotate', true); engine.block.setScopeEnabled(block1, 'layer/flip', true); // Fill scopes (for image replacement and cropping) engine.block.setScopeEnabled(block1, 'fill/change', true); engine.block.setScopeEnabled(block1, 'fill/changeType', true); engine.block.setScopeEnabled(block1, 'layer/crop', true); // Appearance scopes engine.block.setScopeEnabled(block1, 'appearance/adjustments', true); engine.block.setScopeEnabled(block1, 'appearance/filter', true); engine.block.setScopeEnabled(block1, 'appearance/effect', true); engine.block.setScopeEnabled(block1, 'appearance/blur', true); engine.block.setScopeEnabled(block1, 'appearance/shadow', true); engine.block.setScopeEnabled(block1, 'appearance/animation', true); engine.block.setScopeEnabled(block1, 'editor/select', true); // Step 3: Enable placeholder behavior ("Act as a placeholder") // This makes the block interactive in Adopter mode engine.block.setPlaceholderEnabled(block1, true); // Step 4: Check if block/fill supports placeholder features const fill1 = engine.block.getFill(block1); const supportsBehavior = engine.block.supportsPlaceholderBehavior(fill1); const supportsControls = engine.block.supportsPlaceholderControls(block1); // Enable placeholder behavior on the fill (for graphic blocks) if (supportsBehavior) { engine.block.setPlaceholderBehaviorEnabled(fill1, true); } // Enable placeholder overlay pattern if (supportsControls) { engine.block.setPlaceholderControlsOverlayEnabled(block1, true); } // Enable placeholder button if (supportsControls) { engine.block.setPlaceholderControlsButtonEnabled(block1, true); } // Complete "Act as Placeholder" setup const fillForConfig = engine.block.getFill(block1); if (engine.block.supportsPlaceholderBehavior(fillForConfig)) { engine.block.setPlaceholderBehaviorEnabled(fillForConfig, true); } if (supportsControls) { engine.block.setPlaceholderControlsOverlayEnabled(block1, true); engine.block.setPlaceholderControlsButtonEnabled(block1, true); } // Block 2: Fill Properties Only const block2 = await engine.block.addImage(imageUri2, { size: { width: blockWidth, height: blockHeight } }); engine.block.appendChild(page, block2); engine.block.setPositionX(block2, startX + blockWidth + spacing); engine.block.setPositionY(block2, blockY); // Batch operation: Apply settings to multiple blocks const graphicBlocks = [block1, block2]; graphicBlocks.forEach((block) => { // Enable placeholder for each block engine.block.setPlaceholderEnabled(block, true); const fill = engine.block.getFill(block); if (engine.block.supportsPlaceholderBehavior(fill)) { engine.block.setPlaceholderBehaviorEnabled(fill, true); } }); // Step 1: Explicitly disable ALL scopes first allScopes.forEach((scope) => { engine.block.setScopeEnabled(block2, scope, false); }); // Step 2: Enable ONLY fill-related scopes engine.block.setScopeEnabled(block2, 'fill/change', true); engine.block.setScopeEnabled(block2, 'fill/changeType', true); engine.block.setScopeEnabled(block2, 'layer/crop', true); engine.block.setScopeEnabled(block2, 'editor/select', true); // Step 3: Enable placeholder behavior ("Act as a placeholder") engine.block.setPlaceholderEnabled(block2, true); // Step 4: Enable fill-based placeholder behavior and visual controls const fill2 = engine.block.getFill(block2); if (engine.block.supportsPlaceholderBehavior(fill2)) { engine.block.setPlaceholderBehaviorEnabled(fill2, true); } if (engine.block.supportsPlaceholderControls(block2)) { engine.block.setPlaceholderControlsOverlayEnabled(block2, true); engine.block.setPlaceholderControlsButtonEnabled(block2, true); } // Block 3: No Placeholder Features (Default State) const block3 = await engine.block.addImage(imageUri3, { size: { width: blockWidth, height: blockHeight } }); engine.block.appendChild(page, block3); engine.block.setPositionX(block3, startX + (blockWidth + spacing) * 2); engine.block.setPositionY(block3, blockY); // Explicitly disable ALL scopes to ensure default state allScopes.forEach((scope) => { engine.block.setScopeEnabled(block3, scope, false); }); // No placeholder behavior enabled - this block remains non-interactive // Add descriptive labels above each block const labelConfig = { height: 40, fontSize: 34, fontUri: 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf', fontFamily: 'Roboto' }; // Label for Block 1 const label1 = engine.block.create('text'); engine.block.appendChild(page, label1); engine.block.setPositionX(label1, startX); engine.block.setPositionY(label1, labelY); engine.block.setWidth(label1, blockWidth); engine.block.setHeight(label1, labelConfig.height); engine.block.replaceText(label1, 'All Controls'); engine.block.setTextColor(label1, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 }); engine.block.setFont(label1, labelConfig.fontUri, { name: labelConfig.fontFamily, fonts: [ { uri: labelConfig.fontUri, subFamily: 'Bold' } ] }); engine.block.setFloat(label1, 'text/fontSize', labelConfig.fontSize); engine.block.setEnum(label1, 'text/horizontalAlignment', 'Center'); // Label for Block 2 const label2 = engine.block.create('text'); engine.block.appendChild(page, label2); engine.block.setPositionX(label2, startX + blockWidth + spacing); engine.block.setPositionY(label2, labelY); engine.block.setWidth(label2, blockWidth); engine.block.setHeight(label2, labelConfig.height); engine.block.replaceText(label2, 'Fill Only'); engine.block.setTextColor(label2, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 }); engine.block.setFont(label2, labelConfig.fontUri, { name: labelConfig.fontFamily, fonts: [ { uri: labelConfig.fontUri, subFamily: 'Bold' } ] }); engine.block.setFloat(label2, 'text/fontSize', labelConfig.fontSize); engine.block.setEnum(label2, 'text/horizontalAlignment', 'Center'); // Label for Block 3 const label3 = engine.block.create('text'); engine.block.appendChild(page, label3); engine.block.setPositionX(label3, startX + (blockWidth + spacing) * 2); engine.block.setPositionY(label3, labelY); engine.block.setWidth(label3, blockWidth); engine.block.setHeight(label3, labelConfig.height); engine.block.replaceText(label3, 'Disabled'); engine.block.setTextColor(label3, { r: 0.2, g: 0.2, b: 0.2, a: 1.0 }); engine.block.setFont(label3, labelConfig.fontUri, { name: labelConfig.fontFamily, fonts: [ { uri: labelConfig.fontUri, subFamily: 'Bold' } ] }); engine.block.setFloat(label3, 'text/fontSize', labelConfig.fontSize); engine.block.setEnum(label3, 'text/horizontalAlignment', 'Center'); // Verify configurations // eslint-disable-next-line no-console console.log('Block 1 - All Controls:'); // eslint-disable-next-line no-console console.log( ' Placeholder enabled:', engine.block.isPlaceholderEnabled(block1) ); // eslint-disable-next-line no-console console.log(' Scopes enabled:'); // eslint-disable-next-line no-console console.log( ' - layer/move:', engine.block.isScopeEnabled(block1, 'layer/move') ); // eslint-disable-next-line no-console console.log( ' - layer/resize:', engine.block.isScopeEnabled(block1, 'layer/resize') ); // eslint-disable-next-line no-console console.log( ' - fill/change:', engine.block.isScopeEnabled(block1, 'fill/change') ); // eslint-disable-next-line no-console console.log( ' - layer/crop:', engine.block.isScopeEnabled(block1, 'layer/crop') ); // eslint-disable-next-line no-console console.log( ' - appearance/adjustments:', engine.block.isScopeEnabled(block1, 'appearance/adjustments') ); // eslint-disable-next-line no-console console.log('\nBlock 2 - Fill Only:'); // eslint-disable-next-line no-console console.log( ' Placeholder enabled:', engine.block.isPlaceholderEnabled(block2) ); // eslint-disable-next-line no-console console.log(' Scopes enabled:'); // eslint-disable-next-line no-console console.log( ' - layer/move:', engine.block.isScopeEnabled(block2, 'layer/move') ); // eslint-disable-next-line no-console console.log( ' - fill/change:', engine.block.isScopeEnabled(block2, 'fill/change') ); // eslint-disable-next-line no-console console.log( ' - fill/changeType:', engine.block.isScopeEnabled(block2, 'fill/changeType') ); // eslint-disable-next-line no-console console.log( ' - layer/crop:', engine.block.isScopeEnabled(block2, 'layer/crop') ); // eslint-disable-next-line no-console console.log('\nBlock 3 - Disabled:'); // eslint-disable-next-line no-console console.log( ' Placeholder enabled:', engine.block.isPlaceholderEnabled(block3) ); // eslint-disable-next-line no-console console.log(' Scopes enabled:'); // eslint-disable-next-line no-console console.log( ' - layer/move:', engine.block.isScopeEnabled(block3, 'layer/move') ); // eslint-disable-next-line no-console console.log( ' - fill/change:', engine.block.isScopeEnabled(block3, 'fill/change') ); // eslint-disable-next-line no-console console.log('\nPlaceholder configurations initialized successfully'); } } export default Example; ``` This guide covers placeholder fundamentals, checking support, enabling behavior, and configuring visual controls for graphic and text blocks. ## Placeholder Fundamentals Placeholders convert design blocks into interactive drop-zones where users can swap content while maintaining layout and styling control. ### Two Distinct Features **Placeholder behavior** enables drag-and-drop replacement and exposes scope checks for content validation. **Placeholder controls** provide visual affordances: an overlay pattern and a "Replace" button for guided interaction. ### Block-Level vs Fill-Level Behavior The key distinction in placeholder implementation depends on block type: **For graphic blocks** (images/videos): Placeholder behavior is enabled on the **fill**, while controls are enabled on the **block**. This pattern reflects how graphic blocks contain fills that can be replaced. **For text blocks**: Placeholder behavior and controls are both enabled directly on the **block**. We can check support with `supportsPlaceholderBehavior()` for both blocks and fills, and `supportsPlaceholderControls()` for blocks. ## Checking Placeholder Support Before enabling placeholder features, check if a block supports them: ```typescript highlight-check-support // Step 4: Check if block/fill supports placeholder features const fill1 = engine.block.getFill(block1); const supportsBehavior = engine.block.supportsPlaceholderBehavior(fill1); const supportsControls = engine.block.supportsPlaceholderControls(block1); ``` The `supportsPlaceholderBehavior()` method indicates whether a block can become a drop-zone. The `supportsPlaceholderControls()` method shows if visual controls (overlay and button) are available. ## Enabling Placeholder Behavior To convert a block into a placeholder drop-zone, enable placeholder behavior. The approach differs based on block type. ### For Graphic Blocks (Images/Videos) For graphic blocks, placeholder behavior must be enabled on the **fill**, not the block itself: ```typescript highlight-enable-behavior // Enable placeholder behavior on the fill (for graphic blocks) if (supportsBehavior) { engine.block.setPlaceholderBehaviorEnabled(fill1, true); } ``` This pattern is critical: `setPlaceholderBehaviorEnabled()` is called on the fill obtained from `block.getFill()`. This reflects the underlying architecture where graphic blocks contain replaceable fills. ### For Text Blocks For text blocks, placeholder behavior is enabled directly on the block, as text blocks don't use fills in the same way. We can verify placeholder behavior state with `isPlaceholderBehaviorEnabled()` on the appropriate target (fill for graphics, block for text). ## Enabling Adopter Mode Interaction Placeholder behavior alone isn't enough - blocks must also be enabled for interaction in Adopter mode: ```typescript highlight-enable-adopter-mode // Step 3: Enable placeholder behavior ("Act as a placeholder") // This makes the block interactive in Adopter mode engine.block.setPlaceholderEnabled(block1, true); ``` The `setPlaceholderEnabled()` method controls whether the placeholder is interactive for users in Adopter role. CE.SDK distinguishes Creator (full access) and Adopter (replace-only) roles. ### Automatic Management In practice, `setPlaceholderEnabled()` is typically managed automatically by the editor: when you enable relevant scopes (like `fill/change` for graphics or `text/edit` for text), the placeholder interaction is automatically enabled. When all scopes are disabled, placeholder interaction is automatically disabled. This automatic behavior simplifies template creation workflows. ## Configuring Visual Feedback Placeholders can display visual indicators to guide users through the replacement workflow. ### Combined Setup: The "Act as Placeholder" Pattern In the CE.SDK UI, the "Act as Placeholder" checkbox enables placeholder behavior and both visual controls simultaneously. This combined pattern is the recommended approach: ```typescript highlight-full-configuration // Complete "Act as Placeholder" setup const fillForConfig = engine.block.getFill(block1); if (engine.block.supportsPlaceholderBehavior(fillForConfig)) { engine.block.setPlaceholderBehaviorEnabled(fillForConfig, true); } if (supportsControls) { engine.block.setPlaceholderControlsOverlayEnabled(block1, true); engine.block.setPlaceholderControlsButtonEnabled(block1, true); } ``` This pattern ensures placeholder behavior is enabled on the appropriate target (fill for graphics, block for text) along with both visual controls on the block. ### Individual Control Options Visual controls can also be managed independently if needed: **Overlay Pattern** - The overlay shows a dotted surface indicating a drop-zone: ```typescript highlight-enable-overlay // Enable placeholder overlay pattern if (supportsControls) { engine.block.setPlaceholderControlsOverlayEnabled(block1, true); } ``` **Replace Button** - The button provides a single-tap entry point for touch devices: ```typescript highlight-enable-button // Enable placeholder button if (supportsControls) { engine.block.setPlaceholderControlsButtonEnabled(block1, true); } ``` Individual control is useful when you want specific visual feedback without the full placeholder workflow. ## Scope Requirements and Dependencies Placeholders depend on specific scopes being enabled to function correctly. Understanding these dependencies prevents common configuration errors. For graphic blocks (images/videos), the `fill/change` scope must be enabled before placeholder behavior will work. When you disable `fill/change`, the editor automatically disables placeholder behavior and controls to maintain consistency. For text blocks, the `text/edit` scope must be enabled before placeholder behavior can function. **Optional related scopes** that enhance placeholder functionality: - `fill/changeType` - Allows changing between image, video, and solid color fills - `layer/crop` - Enables cropping replacement images - `text/character` - Allows font and character formatting for text placeholders ## Working with Multiple Placeholders When creating templates with multiple placeholders, apply settings systematically: ```typescript highlight-batch-operation // Batch operation: Apply settings to multiple blocks const graphicBlocks = [block1, block2]; graphicBlocks.forEach((block) => { // Enable placeholder for each block engine.block.setPlaceholderEnabled(block, true); const fill = engine.block.getFill(block); if (engine.block.supportsPlaceholderBehavior(fill)) { engine.block.setPlaceholderBehaviorEnabled(fill, true); } }); ``` This pattern works well for collage templates, product showcases, or any layout requiring multiple content slots. ## API Reference | Method | Description | |--------|-------------| | `engine.block.supportsPlaceholderBehavior()` | Checks whether the block supports placeholder behavior | | `engine.block.setPlaceholderBehaviorEnabled()` | Enables or disables placeholder behavior for a block | | `engine.block.isPlaceholderBehaviorEnabled()` | Queries whether placeholder behavior is enabled | | `engine.block.setPlaceholderEnabled()` | Enables or disables placeholder interaction in Adopter mode | | `engine.block.isPlaceholderEnabled()` | Queries whether placeholder interaction is enabled | | `engine.block.supportsPlaceholderControls()` | Checks whether the block supports placeholder controls | | `engine.block.setPlaceholderControlsOverlayEnabled()` | Enables or disables the placeholder overlay pattern | | `engine.block.isPlaceholderControlsOverlayEnabled()` | Queries whether the overlay pattern is shown | | `engine.block.setPlaceholderControlsButtonEnabled()` | Enables or disables the placeholder button | | `engine.block.isPlaceholderControlsButtonEnabled()` | Queries whether the placeholder button is shown | ## Next Steps - [Lock the Template](https://img.ly/docs/cesdk/angular/create-templates/lock-131489/) - Restrict editing access to specific elements or properties to enforce design rules - [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) - Define dynamic text elements that can be populated with custom values --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Set Editing Constraints" description: "Learn how to control editing capabilities in CE.SDK templates using the Scope system to lock positions, prevent transformations, and create guided editing experiences" platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/set-editing-constraints-c892c0/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content-53fad7/) > [Set Editing Constraints](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) --- Control what users can edit in templates by setting fine-grained permissions on individual blocks or globally across your scene using CE.SDK's Scope system. ![Set Editing Constraints example showing constraint patterns](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-dynamic-content-set-editing-constraints-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-dynamic-content-set-editing-constraints-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-dynamic-content-set-editing-constraints-browser/) Editing constraints in CE.SDK allow you to lock specific properties of design elements while keeping others editable. The Scope system provides granular control over 20+ editing capabilities including movement, resizing, rotation, fill changes, text editing, and lifecycle operations. ```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-set-editing-constraints-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Set Editing Constraints Guide * * This example demonstrates: * - Setting global scopes to respect block-level settings * - Disabling move scope to lock position * - Disabling lifecycle scopes to prevent deletion */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a design scene await cesdk.actions.run('scene.create', { page: { width: 1200, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page background color const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); // ===== Configure Global Scopes ===== // Set global scopes to 'Defer' to respect block-level scope settings // Without this, global 'Allow' settings might override block-level restrictions engine.editor.setGlobalScope('layer/move', 'Defer'); engine.editor.setGlobalScope('layer/resize', 'Defer'); engine.editor.setGlobalScope('lifecycle/destroy', 'Defer'); engine.editor.setGlobalScope('lifecycle/duplicate', 'Defer'); // Global scope modes: // - 'Allow': Always allow (overrides block-level settings) // - 'Deny': Always deny (overrides block-level settings) // - 'Defer': Use block-level settings (respects setScopeEnabled) // Calculate layout for 4 examples (2x2 grid) const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const margin = 40; const spacing = 20; const blockWidth = (pageWidth - margin * 2 - spacing) / 2; const blockHeight = (pageHeight - margin * 2 - spacing) / 2; const getPosition = (index: number) => { const col = index % 2; const row = Math.floor(index / 2); return { x: margin + col * (blockWidth + spacing), y: margin + row * (blockHeight + spacing) }; }; // Helper function to create a labeled example block const createExampleBlock = ( labelText: string, backgroundColor: { r: number; g: number; b: number }, applyScopesCallback?: (blockId: number) => void ): number => { // Create container block const block = engine.block.create('graphic'); const shape = engine.block.createShape('rect'); engine.block.setShape(block, shape); engine.block.setWidth(block, blockWidth); engine.block.setHeight(block, blockHeight); // Set background color const fill = engine.block.createFill('color'); engine.block.setFill(block, fill); engine.block.setColor(fill, 'fill/color/value', { ...backgroundColor, a: 1.0 }); // Add label text const textBlock = engine.block.create('text'); engine.block.setWidth(textBlock, blockWidth * 0.85); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setString(textBlock, 'text/text', labelText); engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); engine.block.setFloat(textBlock, 'text/fontSize', 36); // Append text to get dimensions engine.block.appendChild(block, textBlock); // Center text in container const textWidth = engine.block.getWidth(textBlock); const textHeight = engine.block.getHeight(textBlock); engine.block.setPositionX(textBlock, (blockWidth - textWidth) / 2); engine.block.setPositionY(textBlock, (blockHeight - textHeight) / 2); // Set text color to white const textFill = engine.block.createFill('color'); engine.block.setFill(textBlock, textFill); engine.block.setColor(textFill, 'fill/color/value', { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Apply scope configuration to both blocks if (applyScopesCallback) { applyScopesCallback(block); applyScopesCallback(textBlock); } // Append container to page engine.block.appendChild(page, block); return block; }; // ===== Example 1: Lock Position (Disable Move Scope) ===== const disableMoveScope = (block: number) => { // Disable move scope engine.block.setScopeEnabled(block, 'layer/move', false); // Explicitly enable other transform scopes engine.block.setScopeEnabled(block, 'layer/resize', true); engine.block.setScopeEnabled(block, 'layer/rotate', true); engine.block.setScopeEnabled(block, 'layer/flip', true); // Explicitly enable lifecycle scopes engine.block.setScopeEnabled(block, 'lifecycle/destroy', true); engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true); }; const moveLockedBlock = createExampleBlock( 'Locked\nposition', { r: 0.5, g: 0.75, b: 0.9 }, disableMoveScope ); // Block position is locked - users cannot move or reposition it // Other scopes are explicitly enabled: resizing, rotation, flipping, deletion, duplication // ===== Example 2: Prevent Deletion (Disable Lifecycle Scopes) ===== const disableLifecycleScopes = (block: number) => { // Disable lifecycle scopes engine.block.setScopeEnabled(block, 'lifecycle/destroy', false); engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false); // Explicitly enable transform scopes engine.block.setScopeEnabled(block, 'layer/move', true); engine.block.setScopeEnabled(block, 'layer/resize', true); engine.block.setScopeEnabled(block, 'layer/rotate', true); engine.block.setScopeEnabled(block, 'layer/flip', true); }; const lifecycleLockedBlock = createExampleBlock( 'Cannot\ndelete', { r: 0.75, g: 0.75, b: 0.75 }, disableLifecycleScopes ); // Block cannot be deleted or duplicated // Other scopes are explicitly enabled: moving, resizing, rotation, flipping // ===== Example 3: All Scopes Enabled ===== const enableAllScopes = (block: number) => { // Explicitly enable all transform scopes engine.block.setScopeEnabled(block, 'layer/move', true); engine.block.setScopeEnabled(block, 'layer/resize', true); engine.block.setScopeEnabled(block, 'layer/rotate', true); engine.block.setScopeEnabled(block, 'layer/flip', true); // Explicitly enable lifecycle scopes engine.block.setScopeEnabled(block, 'lifecycle/destroy', true); engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true); // Explicitly enable fill scopes engine.block.setScopeEnabled(block, 'fill/change', true); engine.block.setScopeEnabled(block, 'fill/changeType', true); // Explicitly enable text scopes engine.block.setScopeEnabled(block, 'text/edit', true); engine.block.setScopeEnabled(block, 'text/character', true); }; const fullyEditableBlock = createExampleBlock( 'Fully\neditable', { r: 0.5, g: 0.85, b: 0.5 }, enableAllScopes ); // All scopes are explicitly enabled - users have full editing capabilities // This is the default behavior, but explicitly enabling shows clear intent // ===== Example 4: All Scopes Disabled ===== const disableAllScopes = (block: number) => { // Disable all transform scopes engine.block.setScopeEnabled(block, 'layer/move', false); engine.block.setScopeEnabled(block, 'layer/resize', false); engine.block.setScopeEnabled(block, 'layer/rotate', false); engine.block.setScopeEnabled(block, 'layer/flip', false); engine.block.setScopeEnabled(block, 'layer/crop', false); // Disable lifecycle scopes engine.block.setScopeEnabled(block, 'lifecycle/destroy', false); engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false); // Disable fill scopes engine.block.setScopeEnabled(block, 'fill/change', false); engine.block.setScopeEnabled(block, 'fill/changeType', false); engine.block.setScopeEnabled(block, 'stroke/change', false); // Disable text scopes engine.block.setScopeEnabled(block, 'text/edit', false); engine.block.setScopeEnabled(block, 'text/character', false); // Disable shape scopes engine.block.setScopeEnabled(block, 'shape/change', false); // Disable editor scopes engine.block.setScopeEnabled(block, 'editor/select', false); // Disable appearance scopes engine.block.setScopeEnabled(block, 'layer/opacity', false); engine.block.setScopeEnabled(block, 'layer/blendMode', false); engine.block.setScopeEnabled(block, 'layer/visibility', false); }; const fullyLockedBlock = createExampleBlock( 'Fully\nlocked', { r: 0.9, g: 0.5, b: 0.5 }, disableAllScopes ); // All scopes are disabled - block is completely locked and cannot be edited // Useful for watermarks, logos, or legal disclaimers // ===== Block-Level Scope Setting Example ===== // Check if a scope is enabled for a specific block const canMove = engine.block.isScopeEnabled(moveLockedBlock, 'layer/move'); const canDelete = engine.block.isScopeEnabled( lifecycleLockedBlock, 'lifecycle/destroy' ); const canEditFully = engine.block.isScopeEnabled( fullyEditableBlock, 'layer/move' ); const canEditLocked = engine.block.isScopeEnabled( fullyLockedBlock, 'layer/move' ); // eslint-disable-next-line no-console console.log('Move-locked block - layer/move enabled:', canMove); // false // eslint-disable-next-line no-console console.log( 'Lifecycle-locked block - lifecycle/destroy enabled:', canDelete ); // false // eslint-disable-next-line no-console console.log('Fully editable block - layer/move enabled:', canEditFully); // true // eslint-disable-next-line no-console console.log('Fully locked block - layer/move enabled:', canEditLocked); // false // Position blocks in 2x2 grid const blocks = [ fullyEditableBlock, moveLockedBlock, lifecycleLockedBlock, fullyLockedBlock ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Deselect all blocks engine.block.findAllSelected().forEach((block) => { engine.block.setSelected(block, false); }); // Zoom to fit content await engine.scene.zoomToBlock(page, { padding: 50, animate: false }); // Log instructions // eslint-disable-next-line no-console console.log(` === Editing Constraints Demo === Try interacting with the 4 examples (arranged in 2x2 grid): Top row: 1. "Fully editable" (green): All scopes enabled - complete editing freedom 2. "Locked position" (light blue): Cannot move, but can resize/edit/delete Bottom row: 3. "Cannot delete" (light grey): Cannot delete/duplicate, but can move/resize/edit 4. "Fully locked" (red): All scopes disabled - completely locked Note: Global scopes are set to 'Defer' to respect block-level settings. `); } } export default Example; ``` This guide demonstrates how to apply editing constraints to create brand templates, guided editing experiences, and form-based workflows. ## Understanding Scopes ### What are Scopes? A scope is a permission key that controls a specific editing capability in CE.SDK. Each scope represents a distinct action users can perform, such as moving blocks (`'layer/move'`), changing fills (`'fill/change'`), or editing text content (`'text/edit'`). By enabling or disabling scopes, you control exactly what users can and cannot do with each design element. Scopes exist at two levels: - **Block-level scopes**: Per-block permissions set using `setScopeEnabled()` - **Global scopes**: Default behavior for all blocks set using `setGlobalScope()` ### Available Scope Categories CE.SDK provides scopes organized into logical categories: | Category | Purpose | Example Scopes | | --- | --- | --- | | **Text Editing** | Control text content and formatting | `text/edit`, `text/character` | | **Fill & Stroke** | Manage colors and gradients | `fill/change`, `fill/changeType`, `stroke/change` | | **Shape** | Modify shape properties | `shape/change` | | **Layer Transform** | Control position and dimensions | `layer/move`, `layer/resize`, `layer/rotate`, `layer/flip`, `layer/crop` | | **Layer Appearance** | Manage visual properties | `layer/opacity`, `layer/blendMode`, `layer/visibility` | | **Effects & Filters** | Apply visual effects | `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` | | **Lifecycle** | Control creation and deletion | `lifecycle/destroy`, `lifecycle/duplicate` | | **Editor** | Manage scene-level actions | `editor/add`, `editor/select` | ## Scope Configuration ### Global Scope Modes Global scopes set the default behavior for all blocks in the scene. They have three modes: - **Allow**: Always allow the action, overriding block-level settings - **Deny**: Always deny the action, overriding block-level settings - **Defer**: Use block-level settings (default mode) To ensure block-level scope settings are respected, set relevant global scopes to 'Defer': ```typescript highlight=highlight-global-scopes // Set global scopes to 'Defer' to respect block-level scope settings // Without this, global 'Allow' settings might override block-level restrictions engine.editor.setGlobalScope('layer/move', 'Defer'); engine.editor.setGlobalScope('layer/resize', 'Defer'); engine.editor.setGlobalScope('lifecycle/destroy', 'Defer'); engine.editor.setGlobalScope('lifecycle/duplicate', 'Defer'); // Global scope modes: // - 'Allow': Always allow (overrides block-level settings) // - 'Deny': Always deny (overrides block-level settings) // - 'Defer': Use block-level settings (respects setScopeEnabled) ``` Without setting global scopes to 'Defer', default 'Allow' settings might override your block-level restrictions. This is essential when applying fine-grained constraints. ### Scope Resolution Priority When both global and block-level scopes are set, they resolve in this order: 1. **Global Deny** takes highest priority (action always denied) 2. **Global Allow** takes second priority (action always allowed) 3. **Global Defer** defers to block-level settings (default behavior) ## Setting Block-Level Constraints ### Locking Position Prevent users from moving or repositioning a block while allowing other edits: ```typescript highlight=highlight-disable-move-scope const disableMoveScope = (block: number) => { // Disable move scope engine.block.setScopeEnabled(block, 'layer/move', false); // Explicitly enable other transform scopes engine.block.setScopeEnabled(block, 'layer/resize', true); engine.block.setScopeEnabled(block, 'layer/rotate', true); engine.block.setScopeEnabled(block, 'layer/flip', true); // Explicitly enable lifecycle scopes engine.block.setScopeEnabled(block, 'lifecycle/destroy', true); engine.block.setScopeEnabled(block, 'lifecycle/duplicate', true); }; const moveLockedBlock = createExampleBlock( 'Locked\nposition', { r: 0.5, g: 0.75, b: 0.9 }, disableMoveScope ); // Block position is locked - users cannot move or reposition it // Other scopes are explicitly enabled: resizing, rotation, flipping, deletion, duplication ``` The block position is locked—users cannot move or reposition it. Other scopes remain enabled, allowing resizing, editing, and deletion. This pattern maintains layout integrity while allowing content updates. ### Preventing Deletion Protect blocks from being deleted or duplicated: ```typescript highlight=highlight-disable-lifecycle-scopes const disableLifecycleScopes = (block: number) => { // Disable lifecycle scopes engine.block.setScopeEnabled(block, 'lifecycle/destroy', false); engine.block.setScopeEnabled(block, 'lifecycle/duplicate', false); // Explicitly enable transform scopes engine.block.setScopeEnabled(block, 'layer/move', true); engine.block.setScopeEnabled(block, 'layer/resize', true); engine.block.setScopeEnabled(block, 'layer/rotate', true); engine.block.setScopeEnabled(block, 'layer/flip', true); }; const lifecycleLockedBlock = createExampleBlock( 'Cannot\ndelete', { r: 0.75, g: 0.75, b: 0.75 }, disableLifecycleScopes ); // Block cannot be deleted or duplicated // Other scopes are explicitly enabled: moving, resizing, rotation, flipping ``` Users cannot delete or duplicate the block but can still move, resize, and edit it. Use this for essential template elements that must remain present. ### Checking Scope State Query the current state of any scope for a block: ```typescript highlight=highlight-block-level-scope-check // Check if a scope is enabled for a specific block const canMove = engine.block.isScopeEnabled(moveLockedBlock, 'layer/move'); const canDelete = engine.block.isScopeEnabled( lifecycleLockedBlock, 'lifecycle/destroy' ); const canEditFully = engine.block.isScopeEnabled( fullyEditableBlock, 'layer/move' ); const canEditLocked = engine.block.isScopeEnabled( fullyLockedBlock, 'layer/move' ); // eslint-disable-next-line no-console console.log('Move-locked block - layer/move enabled:', canMove); // false // eslint-disable-next-line no-console console.log( 'Lifecycle-locked block - lifecycle/destroy enabled:', canDelete ); // false // eslint-disable-next-line no-console console.log('Fully editable block - layer/move enabled:', canEditFully); // true // eslint-disable-next-line no-console console.log('Fully locked block - layer/move enabled:', canEditLocked); // false ``` Use `isScopeEnabled()` to check the block-level setting. This returns whether the scope is enabled at the block level, but doesn't consider global scope settings. ### Checking Effective Permissions Check the effective permission considering both block and global settings: ```typescript // Check if scope is allowed (considers global + block settings) const moveAllowed = engine.block.isAllowedByScope(block, 'layer/move'); ``` `isAllowedByScope()` returns the final permission after resolving block-level and global scope settings. Use this when you need to know if an action is actually permitted. ## API Reference | Method | Description | | --- | --- | | `engine.block.setScopeEnabled()` | Enable or disable a scope for a specific block | | `engine.block.isScopeEnabled()` | Check if a scope is enabled at the block level | | `engine.block.isAllowedByScope()` | Check if a scope is allowed considering both block and global settings | | `engine.editor.setGlobalScope()` | Set global scope policy (`'Allow'`, `'Deny'`, or `'Defer'`) | | `engine.editor.findAllScopes()` | List all available scope keys | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Variables" description: "Define dynamic text elements that can be populated with custom values during design generation." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content-53fad7/) > [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) --- Text variables enable data-driven template personalization in CE.SDK. Insert placeholder tokens like `{{ firstName }}` into text blocks, then populate them with actual values programmatically. This separates design from content, enabling automated document generation, batch processing, and mass personalization workflows. ![Text Variables example showing personalized text with dynamic data](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-dynamic-content-text-variables-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-dynamic-content-text-variables-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-dynamic-content-text-variables-browser/) ```typescript file=@cesdk_web_examples/guides-create-templates-dynamic-content-text-variables-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Text Variables Guide * * Demonstrates text variable management in CE.SDK with a single comprehensive example: * - Discovering variables with findAll() * - Creating and updating variables with setString() * - Reading variable values with getString() * - Binding variables to text blocks with {{variable}} tokens * - Detecting variable references with referencesAnyVariables() * - Removing variables with remove() * - Localizing variable labels */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Localize variable labels that appear in the Variables panel UI cesdk.i18n.setTranslations({ en: { 'variables.firstName.label': 'First Name', 'variables.lastName.label': 'Last Name', 'variables.email.label': 'Email Address', 'variables.company.label': 'Company Name', 'variables.title.label': 'Job Title' } }); // Pattern 1: Discover all existing variables in the scene // This is useful when loading templates to see what variables need values const existingVariables = engine.variable.findAll(); // eslint-disable-next-line no-console console.log('Existing variables:', existingVariables); // [] // Pattern 2: Create and update text variables // If a variable doesn't exist, setString() creates it // If it already exists, setString() updates its value engine.variable.setString('firstName', 'Alex'); engine.variable.setString('lastName', 'Smith'); engine.variable.setString('email', 'alex.smith@example.com'); engine.variable.setString('company', 'IMG.LY'); engine.variable.setString('title', 'Creative Developer'); // Pattern 3: Read variable values at runtime const firstName = engine.variable.getString('firstName'); // eslint-disable-next-line no-console console.log('First name variable:', firstName); // 'Alex' // Create a single comprehensive text block demonstrating all variable patterns const textBlock = engine.block.create('text'); // Multi-line text combining: // - Single variable ({{firstName}}) // - Multiple variables ({{firstName}} {{lastName}}) // - Variables in context (Email: {{email}}) const textContent = `Hello, {{firstName}}! Full Name: {{firstName}} {{lastName}} Email: {{email}} Position: {{title}} Company: {{company}}`; engine.block.replaceText(textBlock, textContent); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setFloat(textBlock, 'text/fontSize', 52); engine.block.appendChild(page, textBlock); // Center the text block on the page (after font size is set) // Get the actual frame dimensions of the block (including its bounds) const frameX = engine.block.getFrameX(textBlock); const frameY = engine.block.getFrameY(textBlock); const frameWidth = engine.block.getFrameWidth(textBlock); const frameHeight = engine.block.getFrameHeight(textBlock); // Calculate centered position accounting for frame offset engine.block.setPositionX(textBlock, (pageWidth - frameWidth) / 2 - frameX); engine.block.setPositionY( textBlock, (pageHeight - frameHeight) / 2 - frameY ); // Check if the block contains variable references const hasVariables = engine.block.referencesAnyVariables(textBlock); // eslint-disable-next-line no-console console.log('Text block has variables:', hasVariables); // true // Create and then remove a temporary variable to demonstrate removal engine.variable.setString('tempVariable', 'Temporary Value'); // eslint-disable-next-line no-console console.log('Variables before removal:', engine.variable.findAll()); // Remove the temporary variable engine.variable.remove('tempVariable'); // eslint-disable-next-line no-console console.log('Variables after removal:', engine.variable.findAll()); // Select the text block to show the Variables panel engine.block.setSelected(textBlock, true); // Final check: List all variables in the scene const finalVariables = engine.variable.findAll(); // eslint-disable-next-line no-console console.log('Final variables in scene:', finalVariables); // Expected: ['firstName', 'lastName', 'email', 'company', 'title'] // Build a custom Variables Manager panel // CE.SDK doesn't include a built-in UI for creating/managing variables, // so you can build one using the Panel Builder API cesdk.ui.registerPanel( 'ly.img.variablesManager', ({ builder, engine: panelEngine, state }) => { const { Section, TextInput, Button } = builder; // State for creating new variables const newVariableName = state('newVariableName', ''); const newVariableValue = state('newVariableValue', ''); // Section: Create New Variable Section('create-variable', { title: 'Create New Variable', children: () => { TextInput('new-name', { inputLabel: 'Variable Name', ...newVariableName }); TextInput('new-value', { inputLabel: 'Default Value', ...newVariableValue }); Button('create-btn', { label: 'Create Variable', color: 'accent', isDisabled: !newVariableName.value.trim(), onClick: () => { const name = newVariableName.value.trim(); if (name) { panelEngine.variable.setString(name, newVariableValue.value); newVariableName.setValue(''); newVariableValue.setValue(''); } } }); } }); // Section: Existing Variables const variables = panelEngine.variable.findAll(); Section('existing-variables', { title: `Manage Variables (${variables.length})`, children: () => { if (variables.length === 0) { builder.Text('no-vars', { content: 'No variables defined yet.' }); return; } variables.forEach(varName => { TextInput(`var-${varName}`, { inputLabel: varName, value: panelEngine.variable.getString(varName), setValue: value => { panelEngine.variable.setString(varName, value); }, suffix: { icon: '@imgly/TrashBin', tooltip: 'Delete variable', onClick: () => { panelEngine.variable.remove(varName); } } }); }); } }); } ); // Set the panel title cesdk.i18n.setTranslations({ en: { 'panel.ly.img.variablesManager': 'Custom Variables Panel' } }); // Add a dock button to open the panel cesdk.ui.registerComponent('variablesManager.dock', ({ builder: b }) => { const isPanelOpen = cesdk.ui.isPanelOpen('ly.img.variablesManager'); b.Button('variables-dock-btn', { label: 'Variables', icon: '@imgly/Text', onClick: () => { if (isPanelOpen) { cesdk.ui.closePanel('ly.img.variablesManager'); } else { cesdk.ui.openPanel('ly.img.variablesManager'); } }, isActive: isPanelOpen }); }); // Add button to dock cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }), 'ly.img.spacer', 'variablesManager.dock' ]); } } export default Example; ``` This guide covers how to discover, create, update, and manage text variables both through the UI and programmatically using the Variables API. ## Introduction Text variables allow you to design templates once and personalize them with different content for each use. At render time, CE.SDK replaces variable tokens with actual values provided through the Variables API. This approach is ideal for: - **Automated document generation** - Certificates, invoices, reports - **Mass personalization** - Marketing materials with recipient data - **Data-driven design** - Templates populated from JSON, CSV, or APIs - **Form-based editing** - Expose variables through custom interfaces ## Using the Built-in Insert Variable UI CE.SDK includes an *Insert Variable* dropdown in the text editing canvas menu that allows template authors to insert variable tokens into text blocks. > **Caution:** The Insert Variable dropdown **only appears when at least one variable is > already defined** in the scene. If no variables exist, the dropdown will not > be visible. You must first create variables programmatically using > `engine.variable.setString()` before the UI becomes available. To use the Insert Variable UI: 1. **Define variables** using `engine.variable.setString()` — the dropdown won't appear without this step 2. **Enter text edit mode** by double-clicking a text block 3. **Click the Insert Variable dropdown** in the canvas menu (or press `Ctrl+Shift+L` / `Cmd+Shift+L`) The dropdown allows you to: - **Insert tokens** into text blocks using `{{variableName}}` syntax - **Select from all defined variables** in the current scene Variables appear with localized labels when you configure translations through the i18n API. > **Note:** CE.SDK does not include a built-in UI for end users to *create* or *manage* > variables — the Insert Variable dropdown only lets users insert existing > variables into text. If you need a UI for creating and editing variables, see > [Building a Variables Manager Panel](#building-a-variables-manager-panel) > below. ## Discovering Variables When working with templates that already contain variables, discover what variables exist before populating them with values. ```typescript highlight-discover-variables // Pattern 1: Discover all existing variables in the scene // This is useful when loading templates to see what variables need values const existingVariables = engine.variable.findAll(); // eslint-disable-next-line no-console console.log('Existing variables:', existingVariables); // [] ``` The `findAll()` method returns an array of all variable keys defined in the scene. This is essential when loading templates to understand what data needs to be provided. ## Creating and Updating Variables Create or update variables using `setString()`. If the variable doesn't exist, it will be created. If it already exists, its value will be updated. ```typescript highlight-create-update-variables // Pattern 2: Create and update text variables // If a variable doesn't exist, setString() creates it // If it already exists, setString() updates its value engine.variable.setString('firstName', 'Alex'); engine.variable.setString('lastName', 'Smith'); engine.variable.setString('email', 'alex.smith@example.com'); engine.variable.setString('company', 'IMG.LY'); engine.variable.setString('title', 'Creative Developer'); ``` > **Note:** Variable keys are case-sensitive. `{{ Name }}` and `{{ name }}` are different > variables. ## Reading Variable Values Retrieve the current value of a variable at runtime using `getString()`. This is useful for validation or displaying current values in custom UI. ```typescript highlight-read-variable-value // Pattern 3: Read variable values at runtime const firstName = engine.variable.getString('firstName'); // eslint-disable-next-line no-console console.log('First name variable:', firstName); // 'Alex' ``` ## Binding Variables to Text Blocks Insert variable tokens directly into text block content using the `{{variableName}}` syntax. CE.SDK automatically detects and resolves these tokens at render time. ### Single Variable ```typescript highlight-single-variable-binding // Create a single comprehensive text block demonstrating all variable patterns const textBlock = engine.block.create('text'); // Multi-line text combining: // - Single variable ({{firstName}}) // - Multiple variables ({{firstName}} {{lastName}}) // - Variables in context (Email: {{email}}) const textContent = `Hello, {{firstName}}! Full Name: {{firstName}} {{lastName}} Email: {{email}} Position: {{title}} Company: {{company}}`; engine.block.replaceText(textBlock, textContent); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setFloat(textBlock, 'text/fontSize', 52); engine.block.appendChild(page, textBlock); ``` ### Multiple Variables Combine multiple variables in a single text block: ```typescript highlight-multiple-variable-binding // Create a single comprehensive text block demonstrating all variable patterns const textBlock = engine.block.create('text'); // Multi-line text combining: // - Single variable ({{firstName}}) // - Multiple variables ({{firstName}} {{lastName}}) // - Variables in context (Email: {{email}}) const textContent = `Hello, {{firstName}}! Full Name: {{firstName}} {{lastName}} Email: {{email}} Position: {{title}} Company: {{company}}`; engine.block.replaceText(textBlock, textContent); engine.block.setWidthMode(textBlock, 'Auto'); engine.block.setHeightMode(textBlock, 'Auto'); engine.block.setFloat(textBlock, 'text/fontSize', 52); engine.block.appendChild(page, textBlock); ``` The variables resolve in place, maintaining the surrounding text and formatting. ## Detecting Variable References Check if a block contains variable references using `referencesAnyVariables()`. This returns `true` if the block's text contains any `{{variable}}` tokens. ```typescript highlight-detect-variable-references // Check if the block contains variable references const hasVariables = engine.block.referencesAnyVariables(textBlock); // eslint-disable-next-line no-console console.log('Text block has variables:', hasVariables); // true ``` This is useful for identifying which blocks need variable values before export or for implementing validation logic. ## Removing Variables Remove unused variables from the scene with `remove()`. This cleans up the variable store when certain variables are no longer needed. ```typescript highlight-remove-variable // Create and then remove a temporary variable to demonstrate removal engine.variable.setString('tempVariable', 'Temporary Value'); // eslint-disable-next-line no-console console.log('Variables before removal:', engine.variable.findAll()); // Remove the temporary variable engine.variable.remove('tempVariable'); // eslint-disable-next-line no-console console.log('Variables after removal:', engine.variable.findAll()); ``` After removal, the variable no longer exists in the scene. Text blocks that reference removed variables will display the token literally (e.g., `{{removedVar}}`). ## Localizing Variable Labels In CE.SDK (with UI), display friendly labels for variables in the inspector panel using i18n translations. Map variable keys to human-readable names that appear in the UI. ```typescript highlight-localize-variables // Localize variable labels that appear in the Variables panel UI cesdk.i18n.setTranslations({ en: { 'variables.firstName.label': 'First Name', 'variables.lastName.label': 'Last Name', 'variables.email.label': 'Email Address', 'variables.company.label': 'Company Name', 'variables.title.label': 'Job Title' } }); ``` Without localization, the Insert Variable dropdown shows the technical variable key (e.g., `firstName`). With localization, it shows the friendly label (e.g., "First Name"). ## Combining with Other Features Text variables work seamlessly with other CE.SDK template features: ### With Placeholders Use **placeholders** for dynamic images and videos while **variables** personalize text content. This combination enables fully dynamic templates where both visuals and copy change per use case. ### With Editing Constraints Lock layout elements while allowing only variable token editing. This ensures brand consistency while enabling content personalization. ### With Role-Based Editing Show the Insert Variable dropdown only to template authors (Creator role) and hide it from end users (Adopter role). This guides the editing experience based on user permissions. ## Building a Variables Manager Panel CE.SDK's built-in Insert Variable dropdown only allows users to insert existing variables — it doesn't provide a UI for creating or managing variables. If you want end users to create, edit, and delete variables through the editor interface, you can build a custom panel using the Panel Builder API. The following example creates a Variables Manager panel with: - A form to create new variables with a name and default value - A list of existing variables with editable values and delete buttons - A dock button to toggle the panel ```typescript highlight-variables-manager-panel // Build a custom Variables Manager panel // CE.SDK doesn't include a built-in UI for creating/managing variables, // so you can build one using the Panel Builder API cesdk.ui.registerPanel( 'ly.img.variablesManager', ({ builder, engine: panelEngine, state }) => { const { Section, TextInput, Button } = builder; // State for creating new variables const newVariableName = state('newVariableName', ''); const newVariableValue = state('newVariableValue', ''); // Section: Create New Variable Section('create-variable', { title: 'Create New Variable', children: () => { TextInput('new-name', { inputLabel: 'Variable Name', ...newVariableName }); TextInput('new-value', { inputLabel: 'Default Value', ...newVariableValue }); Button('create-btn', { label: 'Create Variable', color: 'accent', isDisabled: !newVariableName.value.trim(), onClick: () => { const name = newVariableName.value.trim(); if (name) { panelEngine.variable.setString(name, newVariableValue.value); newVariableName.setValue(''); newVariableValue.setValue(''); } } }); } }); // Section: Existing Variables const variables = panelEngine.variable.findAll(); Section('existing-variables', { title: `Manage Variables (${variables.length})`, children: () => { if (variables.length === 0) { builder.Text('no-vars', { content: 'No variables defined yet.' }); return; } variables.forEach(varName => { TextInput(`var-${varName}`, { inputLabel: varName, value: panelEngine.variable.getString(varName), setValue: value => { panelEngine.variable.setString(varName, value); }, suffix: { icon: '@imgly/TrashBin', tooltip: 'Delete variable', onClick: () => { panelEngine.variable.remove(varName); } } }); }); } }); } ); // Set the panel title cesdk.i18n.setTranslations({ en: { 'panel.ly.img.variablesManager': 'Custom Variables Panel' } }); // Add a dock button to open the panel cesdk.ui.registerComponent('variablesManager.dock', ({ builder: b }) => { const isPanelOpen = cesdk.ui.isPanelOpen('ly.img.variablesManager'); b.Button('variables-dock-btn', { label: 'Variables', icon: '@imgly/Text', onClick: () => { if (isPanelOpen) { cesdk.ui.closePanel('ly.img.variablesManager'); } else { cesdk.ui.openPanel('ly.img.variablesManager'); } }, isActive: isPanelOpen }); }); // Add button to dock cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }), 'ly.img.spacer', 'variablesManager.dock' ]); ``` This panel integrates with the dock and provides a complete variable management experience. Users can create new variables, edit existing values, and remove variables they no longer need. > **Note:** For more information on building custom panels, see the > [Create a Custom Panel](https://img.ly/docs/cesdk/angular/user-interface/ui-extensions/create-custom-panel-d87b83/) guide. ## API Reference | Method | Description | | --------------------------------------- | ------------------------------------------- | | `engine.variable.findAll()` | Get array of all variable keys in the scene | | `engine.variable.setString()` | Create or update a text variable | | `engine.variable.getString()` | Read the current value of a variable | | `engine.variable.remove()` | Delete a variable from the scene | | `engine.block.referencesAnyVariables()` | Check if a block contains variable tokens | | `engine.block.replaceText()` | Set text content (supports variable tokens) | | `cesdk.i18n.setTranslations()` | Set UI labels for variable names | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add to Template Library" description: "Save and organize templates in an asset source for users to browse and apply from the template library." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/add-to-template-library-8bfbc7/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Add to Template Library](https://img.ly/docs/cesdk/angular/create-templates/add-to-template-library-8bfbc7/) --- Create a template library where users can browse, preview, and apply templates from a custom asset source. ![Add to Template Library](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-to-template-library-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-add-to-template-library-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-add-to-template-library-browser/) Templates in CE.SDK are stored and accessed through the asset system. A template library is a local asset source configured to hold and serve template assets, allowing users to browse thumbnails and apply templates to their designs. ```typescript file=@cesdk_web_examples/guides-create-templates-add-to-template-library-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Add to Template Library * * This example demonstrates how to create a template library by: * 1. Creating a local asset source for templates * 2. Adding templates with metadata (label, thumbnail, URI) * 3. Configuring the UI to display the template library * 4. Saving scenes as templates */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a local asset source for templates engine.asset.addLocalSource('my-templates', undefined, async (asset) => { // Apply the selected template to the current scene await engine.scene.applyTemplateFromURL(asset.meta!.uri as string); // Set zoom to auto-fit after applying template await cesdk.actions.run('zoom.toPage', { autoFit: true }); return undefined; }); // Add a template to the source with metadata engine.asset.addAssetToSource('my-templates', { id: 'template-postcard', label: { en: 'Postcard' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_postcard_1.jpg' } }); // Add more templates engine.asset.addAssetToSource('my-templates', { id: 'template-business-card', label: { en: 'Business Card' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_business_card_1.scene', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_business_card_1.jpg' } }); engine.asset.addAssetToSource('my-templates', { id: 'template-social-media', label: { en: 'Social Media Post' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_post_1.scene', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_instagram_post_1.jpg' } }); // Add translation for the library entry cesdk.i18n.setTranslations({ en: { 'libraries.my-templates-entry.label': 'My Templates' } }); // Add the template source to the asset library cesdk.ui.addAssetLibraryEntry({ id: 'my-templates-entry', sourceIds: ['my-templates'], sceneMode: 'Design', previewLength: 3, previewBackgroundType: 'cover', gridBackgroundType: 'cover', gridColumns: 2, cardLabelPosition: () => 'below' }); // Add template library to the dock cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }), 'ly.img.spacer', { id: 'ly.img.assetLibrary.dock', key: 'my-templates-dock', label: 'My Templates', icon: '@imgly/Template', entries: ['my-templates-entry'] } ]); // Load the first template await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); // Set zoom to auto-fit await cesdk.actions.run('zoom.toPage', { autoFit: true }); // Open the template library panel by default cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['my-templates-entry'] } }); // Save as string format (lightweight, references remote assets) const templateString = await engine.scene.saveToString(); console.log('Template saved as string. Length:', templateString.length); // Save as archive format (self-contained with bundled assets) const templateBlob = await engine.scene.saveToArchive(); console.log('Template saved as archive. Size:', templateBlob.size, 'bytes'); // List all registered asset sources const sources = engine.asset.findAllSources(); console.log('Registered sources:', sources); // Notify UI when source contents change engine.asset.assetSourceContentsChanged('my-templates'); // Query templates from the source const queryResult = await engine.asset.findAssets('my-templates', { page: 0, perPage: 10 }); console.log('Templates in library:', queryResult.total); // Remove a template from the source engine.asset.removeAssetFromSource('my-templates', 'template-social-media'); console.log('Removed template-social-media from library'); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ 'ly.img.saveScene.navigationBar', 'ly.img.exportArchive.navigationBar' ] }); } } export default Example; ``` This guide covers how to save scenes as templates, create a template asset source, and add templates with metadata. ## Saving Templates Scenes can be exported in two formats for use as templates. ### String Format Use `engine.scene.saveToString()` to serialize the scene to a base64 string. This lightweight format references remote assets by URL and is ideal for templates where assets are hosted on a CDN. ```typescript highlight=highlight-save-string // Save as string format (lightweight, references remote assets) const templateString = await engine.scene.saveToString(); console.log('Template saved as string. Length:', templateString.length); ``` ### Archive Format For self-contained templates that bundle all assets, use `engine.scene.saveToArchive()`. This returns a Blob containing all assets bundled together, making templates portable without external dependencies. ```typescript highlight=highlight-save-archive // Save as archive format (self-contained with bundled assets) const templateBlob = await engine.scene.saveToArchive(); console.log('Template saved as archive. Size:', templateBlob.size, 'bytes'); ``` ## Creating a Template Asset Source Register a local asset source using `engine.asset.addLocalSource()` with an ID and `applyAsset` callback. ```typescript highlight=highlight-create-source // Create a local asset source for templates engine.asset.addLocalSource('my-templates', undefined, async (asset) => { // Apply the selected template to the current scene await engine.scene.applyTemplateFromURL(asset.meta!.uri as string); // Set zoom to auto-fit after applying template await cesdk.actions.run('zoom.toPage', { autoFit: true }); return undefined; }); ``` The `applyAsset` callback receives the selected asset and determines how to apply it. We use `engine.scene.applyTemplateFromURL()` to load the template from the asset's `meta.uri` property. The template is applied to the current scene, adjusting content to fit the existing page dimensions. ## Adding Templates to the Source Register templates using `engine.asset.addAssetToSource()` with an asset definition that includes metadata for display and loading. ```typescript highlight=highlight-add-templates // Add a template to the source with metadata engine.asset.addAssetToSource('my-templates', { id: 'template-postcard', label: { en: 'Postcard' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.template/thumbnails/cesdk_postcard_1.jpg' } }); ``` Each template asset requires: - `id` - Unique identifier for the template - `label` - Localized display name shown in the template library - `meta.uri` - URL to the `.scene` file that will be loaded when the template is selected - `meta.thumbUri` - URL to a preview image displayed in the template library grid ## Managing Templates After the initial setup, you can manage templates programmatically. ```typescript highlight=highlight-manage-templates // List all registered asset sources const sources = engine.asset.findAllSources(); console.log('Registered sources:', sources); // Notify UI when source contents change engine.asset.assetSourceContentsChanged('my-templates'); // Query templates from the source const queryResult = await engine.asset.findAssets('my-templates', { page: 0, perPage: 10 }); console.log('Templates in library:', queryResult.total); // Remove a template from the source engine.asset.removeAssetFromSource('my-templates', 'template-social-media'); console.log('Removed template-social-media from library'); ``` Use `engine.asset.findAllSources()` to list registered sources. When you add or remove templates from a source, call `engine.asset.assetSourceContentsChanged()` to refresh the UI. To remove a template, use `engine.asset.removeAssetFromSource()`. ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Templates not appearing in UI | Asset source not added to library entry | Ensure `sourceIds` includes the template source ID | | Template fails to load | Incorrect URI in asset meta | Verify the `uri` points to a valid `.scene` file | | Thumbnails not displaying | Missing or incorrect `thumbUri` | Check the thumbnail URL is accessible | | Apply callback not triggered | `applyAsset` not defined in `addLocalSource` | Provide the callback when creating the source | ## API Reference | Method | Description | | --- | --- | | `engine.asset.addLocalSource()` | Register a local asset source with an apply callback | | `engine.asset.addAssetToSource()` | Add an asset to a registered source | | `engine.asset.removeAssetFromSource()` | Remove an asset from a source by ID | | `engine.asset.assetSourceContentsChanged()` | Notify UI that source contents changed | | `engine.scene.saveToString()` | Serialize scene to base64 string format | | `engine.scene.saveToArchive()` | Save scene as self-contained archive blob | | `engine.scene.applyTemplateFromURL()` | Apply a template to the current scene | | `cesdk.ui.addAssetLibraryEntry()` | Add a library entry to the asset library | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit or Remove Templates" description: "Modify existing templates and manage template lifecycle by loading, editing, saving, and removing templates from asset sources." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/edit-or-remove-38a8be/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Edit or Remove Templates](https://img.ly/docs/cesdk/angular/create-templates/edit-or-remove-38a8be/) --- Modify existing templates and manage template lifecycle in your asset library using CE.SDK. ![Edit or Remove Templates example showing template management](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-edit-or-remove-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-edit-or-remove-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-edit-or-remove-browser/) Templates evolve as designs change. You might need to update branding, fix content errors, or remove outdated templates from your library. CE.SDK provides APIs for adding, editing, and removing templates from asset sources. ```typescript file=@cesdk_web_examples/guides-create-templates-edit-or-remove-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Edit or Remove Templates Guide * * Demonstrates template management workflows: * - Adding templates to local asset sources with thumbnails * - Editing template content and updating in asset sources * - Removing templates from asset sources * - Saving updated templates with new content */ // Helper function to generate SVG thumbnail with text label function generateThumbnail(label: string): string { const svg = ` ${label} `; return `data:image/svg+xml,${encodeURIComponent(svg)}`; } class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a local asset source for managing templates engine.asset.addLocalSource('my-templates', undefined, async (asset) => { const uri = asset.meta?.uri; if (!uri) return undefined; const base64Content = uri.split(',')[1]; if (!base64Content) return undefined; await engine.scene.loadFromString(base64Content); return engine.scene.get() ?? undefined; }); // Add the template source to the dock as an asset library entry cesdk.ui.addAssetLibraryEntry({ id: 'my-templates-entry', sourceIds: ['my-templates'], title: 'My Templates', icon: '@imgly/Template', gridColumns: 2, gridItemHeight: 'square' }); // Add a spacer to push "My Templates" to the bottom of the dock cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }), 'ly.img.spacer', { id: 'ly.img.assetLibrary.dock', key: 'my-templates', icon: '@imgly/Template', label: 'My Templates', entries: ['my-templates-entry'] } ]); // Create the template with text blocks const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Original Template'); engine.block.setFloat(titleBlock, 'text/fontSize', 64); engine.block.setWidthMode(titleBlock, 'Auto'); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); const subtitleBlock = engine.block.create('text'); engine.block.replaceText( subtitleBlock, 'Browse "My Templates" at the bottom of the dock' ); engine.block.setFloat(subtitleBlock, 'text/fontSize', 42); engine.block.setWidthMode(subtitleBlock, 'Auto'); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); // Position text blocks centered on the page const titleWidth = engine.block.getFrameWidth(titleBlock); const titleHeight = engine.block.getFrameHeight(titleBlock); engine.block.setPositionX(titleBlock, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleBlock, pageHeight / 2 - titleHeight - 20); const subtitleWidth = engine.block.getFrameWidth(subtitleBlock); engine.block.setPositionX(subtitleBlock, (pageWidth - subtitleWidth) / 2); engine.block.setPositionY(subtitleBlock, pageHeight / 2 + 20); // Save template content and add to asset source const originalContent = await engine.scene.saveToString(); engine.asset.addAssetToSource('my-templates', { id: 'template-original', label: { en: 'Original Template' }, meta: { uri: `data:application/octet-stream;base64,${originalContent}`, thumbUri: generateThumbnail('Original Template') } }); // eslint-disable-next-line no-console console.log('Original template added to asset source'); // Edit the template content and save as a new version engine.block.replaceText(titleBlock, 'Updated Template'); engine.block.replaceText( subtitleBlock, 'This template was edited and saved' ); const updatedContent = await engine.scene.saveToString(); engine.asset.addAssetToSource('my-templates', { id: 'template-updated', label: { en: 'Updated Template' }, meta: { uri: `data:application/octet-stream;base64,${updatedContent}`, thumbUri: generateThumbnail('Updated Template') } }); // Re-center after modification const newTitleWidth = engine.block.getFrameWidth(titleBlock); const newTitleHeight = engine.block.getFrameHeight(titleBlock); engine.block.setPositionX(titleBlock, (pageWidth - newTitleWidth) / 2); engine.block.setPositionY(titleBlock, pageHeight / 2 - newTitleHeight - 20); const newSubtitleWidth = engine.block.getFrameWidth(subtitleBlock); engine.block.setPositionX( subtitleBlock, (pageWidth - newSubtitleWidth) / 2 ); // eslint-disable-next-line no-console console.log('Updated template added to asset source'); // Add a temporary template to demonstrate removal engine.asset.addAssetToSource('my-templates', { id: 'template-temporary', label: { en: 'Temporary Template' }, meta: { uri: `data:application/octet-stream;base64,${originalContent}`, thumbUri: generateThumbnail('Temporary Template') } }); // Remove the temporary template from the asset source engine.asset.removeAssetFromSource('my-templates', 'template-temporary'); // eslint-disable-next-line no-console console.log('Temporary template removed from asset source'); // Update an existing template by removing and re-adding with same ID engine.block.replaceText(subtitleBlock, 'Updated again with new content'); const reUpdatedContent = await engine.scene.saveToString(); engine.asset.removeAssetFromSource('my-templates', 'template-updated'); engine.asset.addAssetToSource('my-templates', { id: 'template-updated', label: { en: 'Updated Template' }, meta: { uri: `data:application/octet-stream;base64,${reUpdatedContent}`, thumbUri: generateThumbnail('Updated Template') } }); // Notify that the asset source contents have changed engine.asset.assetSourceContentsChanged('my-templates'); // Re-center subtitle after final update const reUpdatedSubtitleWidth = engine.block.getFrameWidth(subtitleBlock); engine.block.setPositionX( subtitleBlock, (pageWidth - reUpdatedSubtitleWidth) / 2 ); // eslint-disable-next-line no-console console.log('Template updated in asset source'); // Apply the original template to show the starting point await engine.scene.loadFromString(originalContent); // eslint-disable-next-line no-console console.log( 'Original template applied - browse "My Templates" in the dock' ); } } export default Example; ``` This guide covers how to add templates to asset sources, edit template content, remove templates, and save updated versions. ## Adding Templates First, create a local asset source to store your templates: ```typescript highlight-create-source // Create a local asset source for managing templates engine.asset.addLocalSource('my-templates', undefined, async (asset) => { const uri = asset.meta?.uri; if (!uri) return undefined; const base64Content = uri.split(',')[1]; if (!base64Content) return undefined; await engine.scene.loadFromString(base64Content); return engine.scene.get() ?? undefined; }); ``` Next, create your template content using block APIs: ```typescript highlight-create-template // Create the template with text blocks const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Original Template'); engine.block.setFloat(titleBlock, 'text/fontSize', 64); engine.block.setWidthMode(titleBlock, 'Auto'); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); const subtitleBlock = engine.block.create('text'); engine.block.replaceText( subtitleBlock, 'Browse "My Templates" at the bottom of the dock' ); engine.block.setFloat(subtitleBlock, 'text/fontSize', 42); engine.block.setWidthMode(subtitleBlock, 'Auto'); engine.block.setHeightMode(subtitleBlock, 'Auto'); engine.block.appendChild(page, subtitleBlock); ``` Then save the template and add it to the asset source using `addAssetToSource()`. Each template needs a unique ID, a label, and metadata containing the template URI and thumbnail: ```typescript highlight-add-to-source // Save template content and add to asset source const originalContent = await engine.scene.saveToString(); engine.asset.addAssetToSource('my-templates', { id: 'template-original', label: { en: 'Original Template' }, meta: { uri: `data:application/octet-stream;base64,${originalContent}`, thumbUri: generateThumbnail('Original Template') } }); ``` The `meta.uri` field contains the template content as a data URI. The `meta.thumbUri` provides a thumbnail image for display in the asset library. ## Editing Templates Modify template content using block APIs. You can update text, change images, adjust positions, and reconfigure any block properties. ```typescript highlight-modify-template // Edit the template content and save as a new version engine.block.replaceText(titleBlock, 'Updated Template'); engine.block.replaceText( subtitleBlock, 'This template was edited and saved' ); const updatedContent = await engine.scene.saveToString(); engine.asset.addAssetToSource('my-templates', { id: 'template-updated', label: { en: 'Updated Template' }, meta: { uri: `data:application/octet-stream;base64,${updatedContent}`, thumbUri: generateThumbnail('Updated Template') } }); ``` After editing, save the modified template as a new asset or update an existing one. ## Removing Templates Remove templates from asset sources using `removeAssetFromSource()`. This permanently deletes the template entry from the source. ```typescript highlight-remove-template // Add a temporary template to demonstrate removal engine.asset.addAssetToSource('my-templates', { id: 'template-temporary', label: { en: 'Temporary Template' }, meta: { uri: `data:application/octet-stream;base64,${originalContent}`, thumbUri: generateThumbnail('Temporary Template') } }); // Remove the temporary template from the asset source engine.asset.removeAssetFromSource('my-templates', 'template-temporary'); ``` > **Warning:** Removal is permanent. The template is no longer accessible from the asset source after removal. If you need to restore templates, maintain backups or implement a soft-delete mechanism. ## Saving Updated Templates To update an existing template, first remove it using `removeAssetFromSource()`, then add the updated version with `addAssetToSource()` using the same asset ID. ```typescript highlight-update-in-source // Update an existing template by removing and re-adding with same ID engine.block.replaceText(subtitleBlock, 'Updated again with new content'); const reUpdatedContent = await engine.scene.saveToString(); engine.asset.removeAssetFromSource('my-templates', 'template-updated'); engine.asset.addAssetToSource('my-templates', { id: 'template-updated', label: { en: 'Updated Template' }, meta: { uri: `data:application/octet-stream;base64,${reUpdatedContent}`, thumbUri: generateThumbnail('Updated Template') } }); // Notify that the asset source contents have changed engine.asset.assetSourceContentsChanged('my-templates'); ``` After updating templates, call `assetSourceContentsChanged()` to notify the UI that the asset source contents have changed. ## Best Practices ### Versioning Strategies When managing template updates, consider these approaches: - **Replace in place**: Use the same asset ID to update templates without changing references. Existing designs using the template won't break. - **Version suffixes**: Create new entries with version identifiers (e.g., `template-v2`). This preserves old versions while introducing new ones. - **Archive old versions**: Move deprecated templates to a separate source before removal. This maintains a history without cluttering the main library. ### Batch Operations When adding, updating, or removing multiple templates, call `assetSourceContentsChanged()` once after all operations complete rather than after each individual change. This reduces UI refreshes and improves performance. ### Template IDs Use descriptive, unique IDs that reflect the template's purpose (e.g., `marketing-banner-2024`, `social-post-square`). Consistent naming conventions make templates easier to find and manage programmatically. ### Thumbnails Generate meaningful thumbnails that accurately represent template content. Good thumbnails improve discoverability in the asset library and help users quickly identify the right template. ### Memory Considerations Templates stored as base64 data URIs remain in memory. For production applications with many templates, consider storing template content externally and using URLs in the `meta.uri` field instead of inline data URIs. ## API Reference | Method | Description | | --- | --- | | `engine.asset.addLocalSource()` | Create a local asset source | | `engine.asset.addAssetToSource()` | Add template to asset source | | `engine.asset.removeAssetFromSource()` | Remove template from asset source | | `engine.asset.assetSourceContentsChanged()` | Notify UI of asset source changes | | `engine.scene.saveToString()` | Save scene as base64 string | | `engine.scene.loadFromString()` | Load scene from base64 string | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create From Scratch" description: "Build reusable design templates programmatically using CE.SDK's APIs. Create scenes, add text and graphic blocks, configure placeholders and variables, apply editing constraints, and save templates for reuse." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/from-scratch-663cda/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Create From Scratch](https://img.ly/docs/cesdk/angular/create-templates/from-scratch-663cda/) --- Build reusable design templates entirely through code using CE.SDK's programmatic APIs for automation, batch generation, and custom template creation tools. ![Create Templates From Scratch](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-from-scratch-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-from-scratch-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-from-scratch-browser/) CE.SDK provides a complete API for building design templates through code. Instead of starting from an existing template, you can create a blank scene, define page dimensions, add text and graphic blocks, configure placeholders for swappable media, add text variables for dynamic content, apply editing constraints to protect layout integrity, and save the template for reuse. This approach enables automation workflows, batch template generation, and integration with custom template creation tools. ```typescript file=@cesdk_web_examples/guides-create-templates-from-scratch-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Create Templates From Scratch Guide * * Demonstrates building a reusable promotional card template entirely in code: * - Creating a blank scene with print-ready dimensions (1200x1600) * - Adding text blocks with variable tokens and proper font styling * - Adding graphic blocks as image placeholders using addImage() * - Configuring placeholder behavior for swappable media * - Applying editing constraints (scopes) to protect layout integrity * - Saving the template in multiple formats */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Template layout constants for a promotional card const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 1000; const PADDING = 40; const CONTENT_WIDTH = CANVAS_WIDTH - PADDING * 2; // Create a blank scene with custom dimensions engine.scene.create('Free', { page: { size: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT } } }); // Set design unit to Pixel for precise coordinate mapping engine.scene.setDesignUnit('Pixel'); // Get the page that was automatically created const page = engine.block.findByType('page')[0]; // Set a gradient background for the template const backgroundFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(backgroundFill, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.2, b: 0.6, a: 1.0 }, stop: 0 }, // Purple { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } // Blue ]); engine.block.setFill(page, backgroundFill); // Font URIs for consistent typography const FONT_BOLD = 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf'; const FONT_REGULAR = 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Regular.ttf'; // Create headline text block with {{title}} variable const headline = engine.block.create('text'); engine.block.replaceText(headline, '{{title}}'); // Set font with proper typeface for consistent rendering engine.block.setFont(headline, FONT_BOLD, { name: 'Roboto', fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }] }); engine.block.setFloat(headline, 'text/fontSize', 28); engine.block.setTextColor(headline, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Position and size the headline engine.block.setWidthMode(headline, 'Absolute'); engine.block.setHeightMode(headline, 'Auto'); engine.block.setWidth(headline, CONTENT_WIDTH); engine.block.setPositionX(headline, PADDING); engine.block.setPositionY(headline, 50); engine.block.setEnum(headline, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, headline); // Set default value for the title variable engine.variable.setString('title', 'Summer Sale'); // Create subheadline text block with {{subtitle}} variable const subheadline = engine.block.create('text'); engine.block.replaceText(subheadline, '{{subtitle}}'); engine.block.setFont(subheadline, FONT_REGULAR, { name: 'Roboto', fonts: [{ uri: FONT_REGULAR, subFamily: 'Regular', weight: 'normal' }] }); engine.block.setFloat(subheadline, 'text/fontSize', 14); engine.block.setTextColor(subheadline, { r: 0.9, g: 0.9, b: 0.95, a: 1.0 }); engine.block.setWidthMode(subheadline, 'Absolute'); engine.block.setHeightMode(subheadline, 'Auto'); engine.block.setWidth(subheadline, CONTENT_WIDTH); engine.block.setPositionX(subheadline, PADDING); engine.block.setPositionY(subheadline, 175); engine.block.setEnum(subheadline, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, subheadline); engine.variable.setString('subtitle', 'Up to 50% off all items'); // Create image placeholder in the center of the card const imageBlock = engine.block.create('graphic'); const imageShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, imageShape); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setWidth(imageBlock, CONTENT_WIDTH); engine.block.setHeight(imageBlock, 420); engine.block.setPositionX(imageBlock, PADDING); engine.block.setPositionY(imageBlock, 295); engine.block.appendChild(page, imageBlock); // Enable placeholder behavior on the image fill const fill = engine.block.getFill(imageBlock); if (fill !== null && engine.block.supportsPlaceholderBehavior(fill)) { engine.block.setPlaceholderBehaviorEnabled(fill, true); } engine.block.setPlaceholderEnabled(imageBlock, true); // Enable visual controls for the placeholder engine.block.setPlaceholderControlsOverlayEnabled(imageBlock, true); engine.block.setPlaceholderControlsButtonEnabled(imageBlock, true); // Create CTA (call-to-action) text block with {{cta}} variable const cta = engine.block.create('text'); engine.block.replaceText(cta, '{{cta}}'); engine.block.setFont(cta, FONT_BOLD, { name: 'Roboto', fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }] }); engine.block.setFloat(cta, 'text/fontSize', 8.4); engine.block.setTextColor(cta, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setWidthMode(cta, 'Absolute'); engine.block.setHeightMode(cta, 'Auto'); engine.block.setWidth(cta, CONTENT_WIDTH); engine.block.setPositionX(cta, PADDING); engine.block.setPositionY(cta, 765); engine.block.setEnum(cta, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, cta); engine.variable.setString('cta', 'Learn More'); // Set global scope to 'Defer' for per-block control engine.editor.setGlobalScope('layer/move', 'Defer'); engine.editor.setGlobalScope('layer/resize', 'Defer'); // Lock all text block positions but allow text editing const textBlocks = [headline, subheadline, cta]; textBlocks.forEach((block) => { engine.block.setScopeEnabled(block, 'layer/move', false); engine.block.setScopeEnabled(block, 'layer/resize', false); }); // Lock image position but allow fill replacement engine.block.setScopeEnabled(imageBlock, 'layer/move', false); engine.block.setScopeEnabled(imageBlock, 'layer/resize', false); engine.block.setScopeEnabled(imageBlock, 'fill/change', true); // Register role toggle component for switching between Creator and Adopter cesdk.ui.registerComponent('role.toggle', ({ builder }) => { const role = engine.editor.getRole(); builder.ButtonGroup('role-toggle', { children: () => { builder.Button('creator-btn', { label: 'Creator', isActive: role === 'Creator', onClick: () => engine.editor.setRole('Creator') }); builder.Button('adopter-btn', { label: 'Adopter', isActive: role === 'Adopter', onClick: () => engine.editor.setRole('Adopter') }); } }); }); // Register button component for saving template as string cesdk.ui.registerComponent('save.string', ({ builder }) => { builder.Button('save-string-btn', { label: 'Save String', icon: '@imgly/Download', variant: 'regular', onClick: async () => { const templateString = await engine.scene.saveToString(); console.log( 'Template saved as string:', templateString.substring(0, 100) + '...' ); // Download the string as a file const blob = new Blob([templateString], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'template.scene'; link.click(); URL.revokeObjectURL(url); } }); }); // Register button component for saving template as archive cesdk.ui.registerComponent('save.archive', ({ builder }) => { builder.Button('save-archive-btn', { label: 'Save Archive', icon: '@imgly/Download', variant: 'regular', onClick: async () => { const templateArchive = await engine.scene.saveToArchive(); console.log( 'Template saved as archive:', templateArchive.size, 'bytes' ); // Download the archive as a file const url = URL.createObjectURL(templateArchive); const link = document.createElement('a'); link.href = url; link.download = 'template.zip'; link.click(); URL.revokeObjectURL(url); } }); }); // Add role toggle and save buttons to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'role.toggle'); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.string'); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.archive'); // Enable auto-fit zoom to continuously fit the page with padding engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); } } export default Example; ``` This guide covers how to create a blank scene, add text blocks with variables, add image placeholders, apply editing constraints, and save the template. ## Initialize CE.SDK We start by initializing CE.SDK and loading the asset sources. The asset source plugins (imported from `@cesdk/cesdk-js/plugins`) provide access to fonts, images, and other assets. ```typescript highlight=highlight-setup const engine = cesdk.engine; ``` ## Create a Blank Scene We create the foundation of our template with custom page dimensions. The `engine.scene.create()` method accepts page options to set width, height, and background color. ```typescript highlight=highlight-create-scene // Template layout constants for a promotional card const CANVAS_WIDTH = 800; const CANVAS_HEIGHT = 1000; const PADDING = 40; const CONTENT_WIDTH = CANVAS_WIDTH - PADDING * 2; // Create a blank scene with custom dimensions engine.scene.create('Free', { page: { size: { width: CANVAS_WIDTH, height: CANVAS_HEIGHT } } }); // Set design unit to Pixel for precise coordinate mapping engine.scene.setDesignUnit('Pixel'); ``` The scene creation method accepts a layout mode (`'Free'` for design mode) and optional page configuration. When options are provided, the scene automatically includes a page with the specified dimensions. ## Set Page Background We set a light background color to give the template a consistent base appearance. ```typescript highlight=highlight-add-background // Set a gradient background for the template const backgroundFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(backgroundFill, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.2, b: 0.6, a: 1.0 }, stop: 0 }, // Purple { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } // Blue ]); engine.block.setFill(page, backgroundFill); ``` We create a color fill using `engine.block.createFill('color')`, set the color via `engine.block.setColor()` with the `fill/color/value` property, then assign the fill to the page using `engine.block.setFill()`. ## Add Text Blocks Text blocks allow you to add styled text content. We create a headline that includes a variable token for dynamic content. ```typescript highlight=highlight-add-text // Create headline text block with {{title}} variable const headline = engine.block.create('text'); engine.block.replaceText(headline, '{{title}}'); // Set font with proper typeface for consistent rendering engine.block.setFont(headline, FONT_BOLD, { name: 'Roboto', fonts: [{ uri: FONT_BOLD, subFamily: 'Bold', weight: 'bold' }] }); engine.block.setFloat(headline, 'text/fontSize', 28); engine.block.setTextColor(headline, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Position and size the headline engine.block.setWidthMode(headline, 'Absolute'); engine.block.setHeightMode(headline, 'Auto'); engine.block.setWidth(headline, CONTENT_WIDTH); engine.block.setPositionX(headline, PADDING); engine.block.setPositionY(headline, 50); engine.block.setEnum(headline, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, headline); ``` We create a text block using `engine.block.create('text')`, set its content with `engine.block.replaceText()`, configure dimensions and position, and append it to the page using `engine.block.appendChild()`. ## Add Text Variables Text variables enable data-driven personalization. By using `{{variableName}}` tokens in text blocks, you can populate content programmatically. ```typescript highlight=highlight-add-variable // Set default value for the title variable engine.variable.setString('title', 'Summer Sale'); ``` The `engine.variable.setString()` method sets the default value for the variable. When the template is used, this value can be changed to personalize the content. ## Add Graphic Blocks Graphic blocks serve as containers for images. We create an image block that will become a placeholder for swappable media. ```typescript highlight=highlight-add-graphic // Create image placeholder in the center of the card const imageBlock = engine.block.create('graphic'); const imageShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, imageShape); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setWidth(imageBlock, CONTENT_WIDTH); engine.block.setHeight(imageBlock, 420); engine.block.setPositionX(imageBlock, PADDING); engine.block.setPositionY(imageBlock, 295); engine.block.appendChild(page, imageBlock); ``` We create a graphic block with `engine.block.create('graphic')`, assign a rectangle shape using `engine.block.createShape('rect')` and `engine.block.setShape()`, create an image fill with `engine.block.createFill('image')`, set the image URI via `engine.block.setString()`, and position it on the page. ## Configure Placeholders Placeholders turn design blocks into drop-zones where users can swap content while maintaining layout integrity. We enable placeholder behavior on the image fill and configure visual controls. ```typescript highlight=highlight-configure-placeholder // Enable placeholder behavior on the image fill const fill = engine.block.getFill(imageBlock); if (fill !== null && engine.block.supportsPlaceholderBehavior(fill)) { engine.block.setPlaceholderBehaviorEnabled(fill, true); } engine.block.setPlaceholderEnabled(imageBlock, true); // Enable visual controls for the placeholder engine.block.setPlaceholderControlsOverlayEnabled(imageBlock, true); engine.block.setPlaceholderControlsButtonEnabled(imageBlock, true); ``` Placeholder behavior is enabled on the fill (not the block) for graphic blocks. We also enable the overlay pattern and replace button for visual guidance. ## Apply Editing Constraints Editing constraints protect template elements by restricting what users can modify. We use scopes to lock position and size while allowing content changes. ```typescript highlight=highlight-apply-constraints // Set global scope to 'Defer' for per-block control engine.editor.setGlobalScope('layer/move', 'Defer'); engine.editor.setGlobalScope('layer/resize', 'Defer'); // Lock all text block positions but allow text editing const textBlocks = [headline, subheadline, cta]; textBlocks.forEach((block) => { engine.block.setScopeEnabled(block, 'layer/move', false); engine.block.setScopeEnabled(block, 'layer/resize', false); }); // Lock image position but allow fill replacement engine.block.setScopeEnabled(imageBlock, 'layer/move', false); engine.block.setScopeEnabled(imageBlock, 'layer/resize', false); engine.block.setScopeEnabled(imageBlock, 'fill/change', true); ``` Setting global scope to `'Defer'` enables per-block control. We then disable movement and resizing for both blocks while enabling fill changes for the image placeholder. ## Save the Template We persist the template in two formats: a lightweight string for CDN-hosted assets and a self-contained archive with embedded assets. ```typescript highlight=highlight-save-template // Register button component for saving template as string cesdk.ui.registerComponent('save.string', ({ builder }) => { builder.Button('save-string-btn', { label: 'Save String', icon: '@imgly/Download', variant: 'regular', onClick: async () => { const templateString = await engine.scene.saveToString(); console.log( 'Template saved as string:', templateString.substring(0, 100) + '...' ); // Download the string as a file const blob = new Blob([templateString], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'template.scene'; link.click(); URL.revokeObjectURL(url); } }); }); // Register button component for saving template as archive cesdk.ui.registerComponent('save.archive', ({ builder }) => { builder.Button('save-archive-btn', { label: 'Save Archive', icon: '@imgly/Download', variant: 'regular', onClick: async () => { const templateArchive = await engine.scene.saveToArchive(); console.log( 'Template saved as archive:', templateArchive.size, 'bytes' ); // Download the archive as a file const url = URL.createObjectURL(templateArchive); const link = document.createElement('a'); link.href = url; link.download = 'template.zip'; link.click(); URL.revokeObjectURL(url); } }); }); // Add role toggle and save buttons to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'role.toggle'); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.string'); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, 'save.archive'); ``` The `engine.scene.saveToString()` method creates a compact string format suitable for storage when assets are hosted externally. The `engine.scene.saveToArchive()` method creates a ZIP bundle containing all assets, ideal for offline use or distribution. ## Troubleshooting - **Blocks not appearing**: Verify that `engine.block.appendChild()` attaches blocks to the page. Blocks must be part of the scene hierarchy to render. - **Variables not resolving**: Verify the variable name in the text matches exactly, including curly braces syntax `{{variableName}}`. - **Placeholder not interactive**: Ensure `engine.block.setPlaceholderEnabled()` is called on the block and the appropriate scope (`fill/change`) is enabled. - **Constraints not enforced**: Verify `engine.editor.setGlobalScope()` is set to `'Defer'` before setting per-block scopes. ## API Reference | Method | Description | | --- | --- | | `engine.scene.create()` | Create a new design scene with optional page size | | `engine.scene.setDesignUnit()` | Set the design unit (Pixel, Millimeter, Inch) | | `engine.scene.saveToString()` | Save scene to string format | | `engine.scene.saveToArchive()` | Save scene to ZIP archive | | `engine.block.create()` | Create a design block (page, text, graphic) | | `engine.block.appendChild()` | Append a child block to a parent | | `engine.block.findByType()` | Find blocks by their type | | `engine.block.createFill()` | Create a fill (color, image, etc.) | | `engine.block.setFill()` | Assign a fill to a block | | `engine.block.getFill()` | Get the fill of a block | | `engine.block.createShape()` | Create a shape (rect, ellipse, etc.) | | `engine.block.setShape()` | Assign a shape to a graphic block | | `engine.block.setString()` | Set a string property on a block | | `engine.block.setColor()` | Set a color property | | `engine.block.replaceText()` | Set text content | | `engine.block.setFont()` | Set font with typeface | | `engine.block.setPlaceholderBehaviorEnabled()` | Enable placeholder behavior on fill | | `engine.block.setPlaceholderEnabled()` | Enable placeholder interaction on block | | `engine.block.setPlaceholderControlsOverlayEnabled()` | Enable overlay visual control | | `engine.block.setPlaceholderControlsButtonEnabled()` | Enable button visual control | | `engine.variable.setString()` | Set a text variable value | | `engine.editor.setGlobalScope()` | Set global scope permission | | `engine.block.setScopeEnabled()` | Enable/disable scope on a block | ## Next Steps - [Placeholders](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Configure placeholder behavior and visual controls in depth - [Text Variables](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/text-variables-7ecb50/) - Implement dynamic text personalization with variables - [Set Editing Constraints](https://img.ly/docs/cesdk/angular/create-templates/add-dynamic-content/set-editing-constraints-c892c0/) - Lock layout properties to protect design integrity - [Add to Template Library](https://img.ly/docs/cesdk/angular/create-templates/add-to-template-library-8bfbc7/) - Register templates in the asset library for users to discover --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Import Templates" description: "Load and import design templates into CE.SDK from URLs, archives, and serialized strings." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/import-e50084/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Import Templates](https://img.ly/docs/cesdk/angular/create-templates/import-e50084/) --- Load design templates into CE.SDK from archive URLs, scene URLs, and serialized strings. ![Import Templates](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-import-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-import-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-import-browser/) Templates are pre-designed scenes that provide starting points for user projects. CE.SDK supports loading templates from archive URLs with bundled assets, remote scene URLs, or serialized strings stored in databases. ```typescript file=@cesdk_web_examples/guides-create-templates-import-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; // Import scene file as string for loadFromString demonstration import businessCardSceneString from './assets/business-card.scene?raw'; // Template sources const fashionAdArchiveUrl = 'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip'; const postcardSceneUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene'; /** * CE.SDK Plugin: Import Templates * * Demonstrates loading templates from different sources: * - Archive URLs (.zip files with bundled assets) * - Scene URLs (.scene files) * - Serialized strings (imported scene content) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (cesdk == null) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Load template from a scene file URL await engine.scene.loadFromURL(postcardSceneUrl); // Zoom viewport to fit the loaded scene const scene = engine.scene.get(); if (scene != null) { await engine.scene.zoomToBlock(scene, { padding: 40 }); } // Verify the loaded scene const loadedScene = engine.scene.get(); if (loadedScene != null) { const pages = engine.scene.getPages(); // eslint-disable-next-line no-console console.log(`Template loaded with ${pages.length} page(s)`); } // Configure navigation bar with template loading buttons cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', key: 'load-archive', label: 'Import Archive', icon: '@imgly/Download', variant: 'regular', onClick: async () => { // Load template from archive URL (bundled assets) await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl); const s = engine.scene.get(); if (s != null) { await engine.scene.zoomToBlock(s, { padding: 40 }); } } }, { id: 'ly.img.action.navigationBar', key: 'load-url', label: 'Import URL', icon: '@imgly/Download', variant: 'regular', onClick: async () => { // Load template from scene URL await engine.scene.loadFromURL(postcardSceneUrl); const s = engine.scene.get(); if (s != null) { await engine.scene.zoomToBlock(s, { padding: 40 }); } } }, { id: 'ly.img.action.navigationBar', key: 'load-string', label: 'Import String', icon: '@imgly/Download', variant: 'regular', onClick: async () => { // Load template from serialized string await engine.scene.loadFromString(businessCardSceneString); const s = engine.scene.get(); if (s != null) { await engine.scene.zoomToBlock(s, { padding: 40 }); } } } ]); } } export default Example; ``` This guide covers how to load templates from archives, URLs, and strings, and work with the loaded content. ## Load from Archive Load a template from an archive URL using `loadFromArchiveURL()`. Archives are `.zip` files that bundle the scene with all its assets, making them portable and self-contained. ```typescript highlight=highlight-load-from-archive // Load template from archive URL (bundled assets) await engine.scene.loadFromArchiveURL(fashionAdArchiveUrl); ``` ## Load from URL Load a template from a remote `.scene` file URL using `loadFromURL()`. The scene file is a JSON-based format that references assets via URLs. ```typescript highlight=highlight-load-from-url // Load template from a scene file URL await engine.scene.loadFromURL(postcardSceneUrl); ``` ## Load from String For templates stored in databases or received from APIs, load from a serialized string using `loadFromString()`. This method works with content previously saved using `engine.scene.saveToString()`. ```typescript highlight=highlight-load-from-string // Load template from serialized string await engine.scene.loadFromString(businessCardSceneString); ``` ## Working with the Loaded Scene After loading a template, you can verify its contents and adjust the viewport. ### Verify the Scene Use `engine.scene.get()` to retrieve the scene block and `engine.scene.getPages()` to inspect its pages. ```typescript highlight=highlight-get-scene // Verify the loaded scene const loadedScene = engine.scene.get(); if (loadedScene != null) { const pages = engine.scene.getPages(); // eslint-disable-next-line no-console console.log(`Template loaded with ${pages.length} page(s)`); } ``` ### Zoom to Content Fit the loaded template in the viewport using `zoomToBlock()` with optional padding. ```typescript highlight=highlight-zoom-to-scene // Zoom viewport to fit the loaded scene const scene = engine.scene.get(); if (scene != null) { await engine.scene.zoomToBlock(scene, { padding: 40 }); } ``` --- ## Related Pages - [Import Templates from Scene Files](https://img.ly/docs/cesdk/angular/create-templates/import/from-scene-file-52a01e/) - Load and import templates from scene files in CE.SDK for web applications --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Import Templates from Scene Files" description: "Load and import templates from scene files in CE.SDK for web applications" platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/import/from-scene-file-52a01e/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Import Templates](https://img.ly/docs/cesdk/angular/create-templates/import-e50084/) > [From Scene File](https://img.ly/docs/cesdk/angular/create-templates/import/from-scene-file-52a01e/) --- CE.SDK lets you load complete design templates from scene files to start projects from pre-designed templates, implement template galleries, and build template management systems. ![Import Templates from Scene Files example showing CE.SDK interface with loaded template](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-import-from-scene-file-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-import-from-scene-file-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-import-from-scene-file-browser/) Scene files are portable design templates that preserve the entire design structure including blocks, assets, styles, and layout. ```typescript file=@cesdk_web_examples/guides-create-templates-import-from-scene-file-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Import Templates from Scene Files * * This example demonstrates: * - Loading scenes from .scene file URLs * - Loading scenes from .archive (ZIP) URLs * - Applying templates while preserving page dimensions * - Understanding the difference between loading and applying templates */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // ===== Example: Load Scene from Archive URL ===== // This is the recommended approach for loading complete templates // with all their assets embedded in a ZIP file // Load a complete template from an archive (ZIP) file // This loads both the scene structure and all embedded assets await engine.scene.loadFromArchiveURL( 'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip' ); // Alternative: Load scene from URL (.scene file) // This loads only the scene structure - assets must be accessible via URLs // Uncomment to try: // await engine.scene.loadFromURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' // ); // Alternative: Apply template while preserving current page dimensions // This is useful when you want to load template content into an existing scene // with specific dimensions // Uncomment to try: // // First create a scene with specific dimensions // await cesdk.actions.run('scene.create', { page: { width: 1920, height: 1080, unit: 'Pixel' } }); // const page = engine.block.findByType('page')[0]; // // // Now apply template - content will be adjusted to fit // await engine.scene.applyTemplateFromURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene' // ); // Get the loaded scene const scene = engine.scene.get(); if (scene) { // eslint-disable-next-line no-console console.log('Scene loaded successfully:', scene); // Get information about the loaded scene const pages = engine.scene.getPages(); // eslint-disable-next-line no-console console.log(`Scene has ${pages.length} page(s)`); // Get scene mode const sceneMode = engine.scene.getMode(); // eslint-disable-next-line no-console console.log('Scene mode:', sceneMode); // Get design unit const designUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Design unit:', designUnit); } // Zoom to fit the loaded content if (scene) { await engine.scene.zoomToBlock(scene, { padding: 40 }); } } } export default Example; ``` This guide covers loading scenes from archives, loading from URLs, applying templates while preserving dimensions, and understanding scene file formats. ## Scene File Formats CE.SDK supports two scene file formats for importing templates: ### Scene Format (.scene) Scene files are JSON-based representations of design structures. They reference external assets via URLs, making them lightweight and suitable for database storage. However, the referenced assets must remain accessible at their URLs. **When to use:** - Templates stored in databases - Templates with hosted assets - Lightweight transmission ### Archive Format (.archive or .zip) Archive files are self-contained packages that bundle the scene structure with all referenced assets in a ZIP file. This makes them portable and suitable for offline use. **When to use:** - Template distribution - Offline-capable templates - Complete portability - **Recommended for most use cases** ## Load Scene from Archive The most common way to load templates is from archive URLs. This method loads both the scene structure and all embedded assets: ```typescript file=@cesdk_web_examples/guides-create-templates-import-from-scene-file-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Import Templates from Scene Files * * This example demonstrates: * - Loading scenes from .scene file URLs * - Loading scenes from .archive (ZIP) URLs * - Applying templates while preserving page dimensions * - Understanding the difference between loading and applying templates */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // ===== Example: Load Scene from Archive URL ===== // This is the recommended approach for loading complete templates // with all their assets embedded in a ZIP file // Load a complete template from an archive (ZIP) file // This loads both the scene structure and all embedded assets await engine.scene.loadFromArchiveURL( 'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip' ); // Alternative: Load scene from URL (.scene file) // This loads only the scene structure - assets must be accessible via URLs // Uncomment to try: // await engine.scene.loadFromURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' // ); // Alternative: Apply template while preserving current page dimensions // This is useful when you want to load template content into an existing scene // with specific dimensions // Uncomment to try: // // First create a scene with specific dimensions // await cesdk.actions.run('scene.create', { page: { width: 1920, height: 1080, unit: 'Pixel' } }); // const page = engine.block.findByType('page')[0]; // // // Now apply template - content will be adjusted to fit // await engine.scene.applyTemplateFromURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene' // ); // Get the loaded scene const scene = engine.scene.get(); if (scene) { // eslint-disable-next-line no-console console.log('Scene loaded successfully:', scene); // Get information about the loaded scene const pages = engine.scene.getPages(); // eslint-disable-next-line no-console console.log(`Scene has ${pages.length} page(s)`); // Get scene mode const sceneMode = engine.scene.getMode(); // eslint-disable-next-line no-console console.log('Scene mode:', sceneMode); // Get design unit const designUnit = engine.scene.getDesignUnit(); // eslint-disable-next-line no-console console.log('Design unit:', designUnit); } // Zoom to fit the loaded content if (scene) { await engine.scene.zoomToBlock(scene, { padding: 40 }); } } } export default Example; ``` ```typescript highlight-load-from-archive // Load a complete template from an archive (ZIP) file // This loads both the scene structure and all embedded assets await engine.scene.loadFromArchiveURL( 'https://cdn.img.ly/assets/templates/starterkits/16-9-fashion-ad.zip' ); ``` When you load from an archive: - The ZIP file is fetched and extracted - All assets are registered with CE.SDK - The scene structure is loaded - Asset paths are automatically resolved ## Load Scene from URL You can also load scenes directly from .scene file URLs. This approach requires that all referenced assets remain accessible at their original URLs: ```typescript highlight-load-from-url // await engine.scene.loadFromURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' // ); ``` **Important:** With this method, if asset URLs become unavailable (404 errors, CORS issues, etc.), those assets won't load and your template may appear incomplete. ## Apply Template vs Load Scene CE.SDK provides two approaches for working with templates, each serving different purposes: ### Load Scene When you use `loadFromURL()` or `loadFromArchiveURL()`, CE.SDK: - Replaces the entire current scene - Adopts the template's page dimensions - Loads all content as-is This is appropriate when starting a new project from a template. ### Apply Template When you use `applyTemplateFromURL()` or `applyTemplateFromString()`, CE.SDK: - Keeps your current page dimensions - Adjusts template content to fit - Preserves your scene structure This is useful when you want to load template content into an existing scene with specific dimensions: ```typescript highlight-apply-template // // First create a scene with specific dimensions // await cesdk.actions.run('scene.create', { page: { width: 1920, height: 1080, unit: 'Pixel' } }); // const page = engine.block.findByType('page')[0]; // // // Now apply template - content will be adjusted to fit // await engine.scene.applyTemplateFromURL( // 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_instagram_photo_1.scene' // ); ``` ## Error Handling When loading templates, several issues can occur: ### Network Errors Template URLs might be unreachable: ```typescript try { await engine.scene.loadFromArchiveURL(templateUrl); } catch (error) { console.error('Failed to load template:', error); // Show error message to user // Fall back to default template or empty scene } ``` ### Invalid Scene Format The file might not be a valid scene: ```typescript try { await engine.scene.loadFromURL(sceneUrl); } catch (error) { if (error.message.includes('parse')) { console.error('Invalid scene file format'); } } ``` ### Missing Assets For .scene files, referenced assets might be unavailable. The scene loads but assets appear missing. Consider using archives to avoid this issue. ## Performance Considerations ### Loading Time Archive size directly impacts loading time: - Small archives (\< 1MB): Nearly instant - Medium archives (1-5MB): 1-2 seconds - Large archives (> 5MB): Several seconds Show loading indicators for better user experience. ## CORS Considerations When loading templates from external URLs, ensure proper CORS headers are set on the server hosting the files. Archives must be accessible with appropriate CORS policies. ## API Reference | Method | Description | | ---------------------------------------- | --------------------------------------------------------- | | `engine.scene.loadFromArchiveURL()` | Loads a complete scene from an archive (ZIP) file | | `engine.scene.loadFromURL()` | Loads a scene from a .scene file URL | | `engine.scene.applyTemplateFromURL()` | Applies a template while preserving page dimensions | | `engine.scene.get()` | Returns the current scene block ID | | `engine.scene.getPages()` | Returns all page IDs in the scene | | `engine.scene.getMode()` | Returns the scene mode (Design or Video) | | `engine.scene.getDesignUnit()` | Returns the measurement unit | | `engine.scene.zoomToBlock()` | Zooms the viewport to fit a specific block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Lock the Template" description: "Restrict editing access to specific elements or properties in a template to enforce design rules." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/lock-131489/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Lock the Template](https://img.ly/docs/cesdk/angular/create-templates/lock-131489/) --- Set up a two-surface integration where template creators have full editing access while template adopters can only modify designated areas. ![Lock the Template](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-lock-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-templates-lock-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-templates-lock-browser/) Many integrations need two different editing experiences: one for designers who build templates, and one for end users who customize them. The Creator and Adopter roles make this possible—same CE.SDK, different permissions based on who's using it. For detailed scope configuration patterns, see [Lock Content](https://img.ly/docs/cesdk/angular/rules/lock-content-9fa727/). In the live example, the headline text is pre-selected and the Placeholder panel is open, showing the scope settings that control what Adopters can edit. Toggle the role to Adopter and try selecting the logo to see the restrictions in action. ```typescript file=@cesdk_web_examples/guides-create-templates-lock-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Lock the Template * * This example demonstrates the two-surface pattern for template workflows: * - Creator role: Full editing access for designers building templates * - Adopter role: Restricted access for users customizing templates */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page and set dimensions const page = engine.block.findByType('page')[0]; // Create a brand template with a logo and headline const logoBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/imgly_logo.jpg', { size: { width: 120, height: 30 } } ); engine.block.appendChild(page, logoBlock); engine.block.setPositionX(logoBlock, 40); engine.block.setPositionY(logoBlock, 40); engine.block.setName(logoBlock, 'Logo'); const headlineBlock = engine.block.create('text'); engine.block.replaceText(headlineBlock, 'Edit this headline'); engine.block.setWidth(headlineBlock, 720); engine.block.setHeightMode(headlineBlock, 'Auto'); engine.block.setFloat(headlineBlock, 'text/fontSize', 48); engine.block.setEnum(headlineBlock, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, headlineBlock); engine.block.setPositionX(headlineBlock, 40); engine.block.setPositionY(headlineBlock, 200); engine.block.setName(headlineBlock, 'Headline'); // Configure which elements Adopters can edit // Enable selection and text editing on the headline engine.block.setScopeEnabled(headlineBlock, 'editor/select', true); engine.block.setScopeEnabled(headlineBlock, 'text/edit', true); // Leave all scopes disabled on the logo (default state) // This prevents Adopters from selecting or modifying the logo // The Creator role ignores all scope restrictions engine.editor.setRole('Creator'); // Add a role toggle to the navigation bar (engine calls are reactive) cesdk.ui.registerComponent( 'ly.img.roleToggle.navigationBar', ({ builder }) => { const role = engine.editor.getRole(); builder.ButtonGroup('role-toggle', { children: () => { builder.Button('creator', { label: 'Creator', isActive: role === 'Creator', onClick: () => engine.editor.setRole('Creator') }); builder.Button('adopter', { label: 'Adopter', isActive: role === 'Adopter', onClick: () => { // Close the placeholder panel since Adopters can't configure scopes cesdk.ui.closePanel( '//ly.img.panel/inspector/placeholderSettings' ); engine.editor.setRole('Adopter'); } }); } }); } ); cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.roleToggle.navigationBar', 'ly.img.spacer' ]); await engine.scene.zoomToBlock(page, { padding: 40 }); // Select the headline and open the placeholder panel so users see the scope settings engine.block.select(headlineBlock); setTimeout(() => { cesdk.ui.openPanel('//ly.img.panel/inspector/placeholderSettings'); }, 300); } } export default Example; ``` This guide covers how to understand the two-surface pattern, configure roles for different user groups, and set up scope restrictions that control what Adopters can edit. ## Understanding the Two-Surface Pattern Template-based workflows typically involve two distinct user groups with different needs: | Surface | Users | Role | What they can do | |---------|-------|------|------------------| | Creator Surface | Designers, admins | `Creator` | Full editing—build templates, set locks | | Adopter Surface | End users, marketers | `Adopter` | Restricted editing—only modify unlocked areas | This separation protects design intent while enabling customization. The Creator role ignores all locks, giving full access. The Adopter role respects locks, restricting users to what's explicitly allowed. ## Setting Up the Creator Surface The Creator surface is where templates are built. We use `engine.editor.setRole('Creator')` to give designers unrestricted access. ```typescript highlight=highlight-creator-surface // The Creator role ignores all scope restrictions engine.editor.setRole('Creator'); ``` In Creator mode, all operations are permitted regardless of scope settings. This is where designers build the template layout, configure which elements should be editable, set scope restrictions using `engine.block.setScopeEnabled()`, and save the template for distribution. ## Setting Up the Adopter Surface The Adopter surface is where templates are used. Call `engine.editor.setRole('Adopter')` to enforce the restrictions configured by creators. In Adopter mode, users can only interact with blocks that have the appropriate scopes enabled. The Adopter role respects all lock configurations, ensuring brand consistency and design intent are maintained. ## When to Use This Pattern This two-surface approach works well for: - **Brand template systems**: Marketing teams customize approved templates - **Design approval workflows**: Creators build, reviewers can't accidentally modify - **Self-service customization**: End users personalize within guardrails - **White-label products**: Customers can only edit designated areas For simpler use cases where all users have the same permissions, you may not need separate surfaces. ## Configuring What Users Can Edit The scope system controls what Adopters can modify. In Creator mode, we enable specific scopes on blocks that should be editable. ```typescript highlight=highlight-configure-scopes // Configure which elements Adopters can edit // Enable selection and text editing on the headline engine.block.setScopeEnabled(headlineBlock, 'editor/select', true); engine.block.setScopeEnabled(headlineBlock, 'text/edit', true); // Leave all scopes disabled on the logo (default state) // This prevents Adopters from selecting or modifying the logo ``` When Adopters load this template, they can edit the headline text but nothing else. The `editor/select` scope must be enabled for users to interact with a block at all. For comprehensive scope configuration patterns, see [Lock Content](https://img.ly/docs/cesdk/angular/rules/lock-content-9fa727/). ## Configuring Scopes in the Editor UI Designers can also configure scopes visually without writing code. In Creator mode, select any block and open the Placeholder panel in the inspector. This panel provides toggles for each scope: - **Allow selecting** (`editor/select`): Users can click to select the block - **Allow editing text** (`text/edit`): Users can modify text content - **Allow changing fill** (`fill/change`): Users can swap images or change colors - **Allow moving** (`layer/move`): Users can reposition the block - **Allow deleting** (`lifecycle/destroy`): Users can remove the block Changes made in the Placeholder panel are equivalent to calling `engine.block.setScopeEnabled()` programmatically. When the template is saved, these settings persist and apply when Adopters load the template. ## Adding a Role Toggle Add a segmented control to the navigation bar that switches between Creator and Adopter modes. Engine calls inside the builder are automatically reactive—the component re-renders when the role changes. ```typescript highlight=highlight-toggle-role // Add a role toggle to the navigation bar (engine calls are reactive) cesdk.ui.registerComponent( 'ly.img.roleToggle.navigationBar', ({ builder }) => { const role = engine.editor.getRole(); builder.ButtonGroup('role-toggle', { children: () => { builder.Button('creator', { label: 'Creator', isActive: role === 'Creator', onClick: () => engine.editor.setRole('Creator') }); builder.Button('adopter', { label: 'Adopter', isActive: role === 'Adopter', onClick: () => { // Close the placeholder panel since Adopters can't configure scopes cesdk.ui.closePanel( '//ly.img.panel/inspector/placeholderSettings' ); engine.editor.setRole('Adopter'); } }); } }); } ); cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.roleToggle.navigationBar', 'ly.img.spacer' ]); ``` ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Adopter can edit everything | Wrong role or scopes not configured | Verify role is `Adopter` and scopes are set in Creator mode | | Adopter can't edit anything | `editor/select` scope not enabled | Enable `editor/select` on blocks users should interact with | | Creator can't set locks | Wrong role | Switch to Creator role before configuring scopes | | Changes not persisting | Template not saved after scope changes | Save template after configuring scopes in Creator mode | ## API Reference | Method | Description | |--------|-------------| | `engine.editor.setRole(role)` | Set the editing role (`'Creator'`, `'Adopter'`, or `'Viewer'`) | | `engine.editor.getRole()` | Get the current editing role | | `engine.block.setScopeEnabled(block, scope, enabled)` | Enable or disable a scope on a block | | `engine.block.isScopeEnabled(block, scope)` | Check if a scope is enabled on a block | | `cesdk.ui.registerComponent(id, renderFn)` | Register a custom UI component | | `builder.ButtonGroup(id, { children })` | Create a segmented control | | `builder.Button(id, { label, isActive, onClick })` | Create a button | ### Common Scopes | Scope | Description | |-------|-------------| | `'editor/select'` | Allow selecting the block (required for any interaction) | | `'fill/change'` | Allow changing the block's fill (images, colors) | | `'text/edit'` | Allow editing text content | | `'text/character'` | Allow changing text formatting (font, size, color) | | `'layer/move'` | Allow moving the block | | `'layer/resize'` | Allow resizing the block | | `'layer/rotate'` | Allow rotating the block | | `'layer/crop'` | Allow cropping the block | | `'lifecycle/destroy'` | Allow deleting the block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/create-templates/overview-4ebe30/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) > [Overview](https://img.ly/docs/cesdk/angular/create-templates/overview-4ebe30/) --- These imported designs can then be adapted into editable, structured templates inside CE.SDK. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Videos" description: "Learn how to create and customize videos in CE.SDK using scenes, assets, and timeline-based editing." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video-c41a08/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) --- --- ## Related Pages - [Create Videos Overview](https://img.ly/docs/cesdk/angular/create-video/overview-b06512/) - Learn how to create and customize videos in CE.SDK using scenes, assets, and timeline-based editing. - [Video and Audio Timeline Web Editor](https://img.ly/docs/cesdk/angular/create-video/timeline-editor-912252/) - Use the timeline editor to arrange and edit video clips, audio, and animations frame by frame. - [Control Audio and Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) - Learn to play, pause, seek, and preview audio and video content in CE.SDK using playback controls and solo mode. - [Trim Video Clips](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) - Learn how to trim video clips in CE.SDK to control which portion of media plays back. - [Split Video and Audio](https://img.ly/docs/cesdk/angular/edit-video/split-464167/) - Learn how to split video and audio clips at specific time points in CE.SDK, creating two independent segments from a single clip. - [Join and Arrange Video Clips](https://img.ly/docs/cesdk/angular/edit-video/join-and-arrange-3bbc30/) - Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK. - [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) - Documentation for Transform - [Add Captions](https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/) - Documentation for adding captions to videos - [Update Caption Presets](https://img.ly/docs/cesdk/angular/create-video/update-caption-presets-e9c385/) - Extend video captions with custom caption styles using simple content.json updates - [Add Watermark](https://img.ly/docs/cesdk/angular/edit-video/add-watermark-762ce6/) - Add text and image watermarks to videos using CE.SDK for copyright protection, branding, and content attribution with timeline management and visibility controls. - [Redact Sensitive Content in Videos](https://img.ly/docs/cesdk/angular/edit-video/redaction-cf6d03/) - Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information. - [Video Editor SDK](https://img.ly/docs/cesdk/angular/overview-7d12d5/) - Explore video editing features in CE.SDK including trimming, splitting, captions, and programmatic editing. - [Video Limitations](https://img.ly/docs/cesdk/angular/create-video/limitations-6a740d/) - Understand resolution limits, duration constraints, codec support, and browser-specific restrictions when working with video in CE.SDK. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Adjust Audio Playback Speed" description: "Learn how to adjust audio playback speed in CE.SDK to create slow-motion, time-stretched, and fast-forward audio effects." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/audio/adjust-speed-908d57/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) > [Adjust Speed](https://img.ly/docs/cesdk/angular/create-video/audio/adjust-speed-908d57/) --- Control audio playback speed by adjusting the speed multiplier using CE.SDK's timeline UI and programmatic speed adjustment API. ![Audio Speed Adjustment example showing timeline with audio blocks at different speeds](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-audio-adjust-speed-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-audio-adjust-speed-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-audio-adjust-speed-browser/) Playback speed adjustment changes how fast or slow audio plays without changing its pitch (though pitch shifting may occur depending on the audio processing implementation). A speed multiplier of 1.0 represents normal speed, values below 1.0 slow down playback, and values above 1.0 speed it up. This technique is commonly used for podcast speed controls, time-compressed narration, slow-motion audio effects, and accessibility features. ```typescript file=@cesdk_web_examples/guides-create-audio-audio-adjust-speed-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Adjust Audio Speed Guide * * Demonstrates audio playback speed adjustment in CE.SDK: * - Loading audio files * - Adjusting playback speed with setPlaybackSpeed * - Three speed presets: slow-motion (0.5x), normal (1.0x), and maximum (3.0x) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable audio and video features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.audio'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Use a sample audio file const audioUri = 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a'; // Create an audio block and load the audio file const audioBlock = engine.block.create('audio'); engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Wait for audio resource to load await engine.block.forceLoadAVResource(audioBlock); // Slow Motion Audio (0.5x - half speed) const slowAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, slowAudioBlock); engine.block.setPositionX(slowAudioBlock, 100); engine.block.setPositionY(slowAudioBlock, 200); engine.block.setPlaybackSpeed(slowAudioBlock, 0.5); // Normal Speed Audio (1.0x) const normalAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, normalAudioBlock); engine.block.setPositionX(normalAudioBlock, 100); engine.block.setPositionY(normalAudioBlock, 400); engine.block.setPlaybackSpeed(normalAudioBlock, 1.0); // Maximum Speed Audio (3.0x - triple speed) const maxSpeedAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, maxSpeedAudioBlock); engine.block.setPositionX(maxSpeedAudioBlock, 100); engine.block.setPositionY(maxSpeedAudioBlock, 600); engine.block.setPlaybackSpeed(maxSpeedAudioBlock, 3.0); // Log duration changes for demonstration const slowDuration = engine.block.getDuration(slowAudioBlock); const normalDuration = engine.block.getDuration(normalAudioBlock); const maxDuration = engine.block.getDuration(maxSpeedAudioBlock); // eslint-disable-next-line no-console console.log(`Slow motion (0.5x) duration: ${slowDuration.toFixed(2)}s`); // eslint-disable-next-line no-console console.log(`Normal speed (1.0x) duration: ${normalDuration.toFixed(2)}s`); // eslint-disable-next-line no-console console.log(`Maximum speed (3.0x) duration: ${maxDuration.toFixed(2)}s`); // Remove the original audio block (we only need the duplicates) engine.block.destroy(audioBlock); // Zoom to fit all audio blocks and labels engine.scene.zoomToBlock(page, 40, 40, 40, 40); } } export default Example; ``` This guide covers how to adjust audio playback speed programmatically using the Engine API, understand speed constraints, and manage how speed changes affect timeline duration. ## Understanding Speed Concepts CE.SDK supports playback speeds from **0.25x** (quarter speed) to **3.0x** (triple speed), with **1.0x** as the default normal speed. Values below 1.0 slow down playback, values above 1.0 speed it up. **Speed and Duration**: Adjusting speed automatically changes the block's timeline duration following an inverse relationship: `perceived_duration = original_duration / speed_multiplier`. A 10-second clip at 2.0x speed plays in 5 seconds; at 0.5x speed it takes 20 seconds. This automatic adjustment maintains timeline synchronization when coordinating audio with other elements. **Common use cases**: Podcast playback controls (1.5x-2.0x), accessibility features (0.75x for easier comprehension), time-compressed narration, dramatic slow-motion effects (0.25x-0.5x), transcription work, and music tempo adjustments. ## Setting Up Audio for Speed Adjustment ### Loading Audio Files We create an audio block and load an audio file by setting its `fileURI` property. ```typescript highlight-create-audio // Create an audio block and load the audio file const audioBlock = engine.block.create('audio'); engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Wait for audio resource to load await engine.block.forceLoadAVResource(audioBlock); ``` Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself using the `audio/fileURI` property. The `forceLoadAVResource` call ensures CE.SDK has downloaded the audio file and loaded its metadata, which is essential for accurate duration information and playback speed control. ## Adjusting Playback Speed ### Setting Normal Speed By default, audio plays at normal speed (1.0x). We can explicitly set this to ensure consistent baseline behavior. ```typescript highlight-set-normal-speed // Normal Speed Audio (1.0x) const normalAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, normalAudioBlock); engine.block.setPositionX(normalAudioBlock, 100); engine.block.setPositionY(normalAudioBlock, 400); engine.block.setPlaybackSpeed(normalAudioBlock, 1.0); ``` Setting speed to 1.0 ensures the audio plays at its original recorded rate. This is useful after experimenting with different speeds and wanting to return to normal, or when initializing audio blocks programmatically to ensure consistent starting states. ### Querying Current Speed We can check the current playback speed at any time using `getPlaybackSpeed`. ```typescript highlight-set-normal-speed // Normal Speed Audio (1.0x) const normalAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, normalAudioBlock); engine.block.setPositionX(normalAudioBlock, 100); engine.block.setPositionY(normalAudioBlock, 400); engine.block.setPlaybackSpeed(normalAudioBlock, 1.0); ``` This returns the current speed multiplier as a number. Use this to populate UI controls, validate that speed changes were applied, or make relative adjustments based on existing speeds. ## Common Speed Presets ### Slow Motion Audio (0.5x) Slowing audio to half speed creates a slow-motion effect that's useful for careful listening or transcription. ```typescript highlight-set-slow-motion // Slow Motion Audio (0.5x - half speed) const slowAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, slowAudioBlock); engine.block.setPositionX(slowAudioBlock, 100); engine.block.setPositionY(slowAudioBlock, 200); engine.block.setPlaybackSpeed(slowAudioBlock, 0.5); ``` At 0.5x speed, a 10-second audio clip will take 20 seconds to play. This slower pace makes it easier to catch details, transcribe speech accurately, or create dramatic slow-motion audio effects in creative projects. ### Maximum Speed (3.0x) The maximum supported speed is 3.0x, three times normal playback rate. ```typescript highlight-set-maximum-speed // Maximum Speed Audio (3.0x - triple speed) const maxSpeedAudioBlock = engine.block.duplicate(audioBlock); engine.block.appendChild(page, maxSpeedAudioBlock); engine.block.setPositionX(maxSpeedAudioBlock, 100); engine.block.setPositionY(maxSpeedAudioBlock, 600); engine.block.setPlaybackSpeed(maxSpeedAudioBlock, 3.0); ``` At maximum speed, audio plays very quickly—a 10-second clip finishes in just 3.33 seconds. This extreme speed is useful for rapidly skimming through content to find specific moments, though comprehension becomes challenging at this rate. ## Speed and Timeline Duration ### Understanding Duration Changes When we change playback speed, CE.SDK automatically adjusts the block's timeline duration to reflect the new playback time. ```typescript highlight-speed-and-duration // Log duration changes for demonstration const slowDuration = engine.block.getDuration(slowAudioBlock); const normalDuration = engine.block.getDuration(normalAudioBlock); const maxDuration = engine.block.getDuration(maxSpeedAudioBlock); // eslint-disable-next-line no-console console.log(`Slow motion (0.5x) duration: ${slowDuration.toFixed(2)}s`); // eslint-disable-next-line no-console console.log(`Normal speed (1.0x) duration: ${normalDuration.toFixed(2)}s`); // eslint-disable-next-line no-console console.log(`Maximum speed (3.0x) duration: ${maxDuration.toFixed(2)}s`); ``` The original duration represents how long the audio takes to play at normal speed. When we double the speed to 2.0x, the duration is automatically halved. The audio content is the same, but it plays through in half the time, so the timeline block shrinks accordingly. This automatic adjustment keeps your timeline synchronized. If you have multiple audio tracks or need to coordinate audio with video, the timeline will accurately reflect the new playback duration after speed changes. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Adjust Audio Volume" description: "Learn how to adjust audio volume in CE.SDK to control playback levels, mute audio, and balance multiple audio sources in video projects." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/audio/adjust-volume-7ecc4a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) > [Adjust Volume](https://img.ly/docs/cesdk/angular/create-video/audio/adjust-volume-7ecc4a/) --- Control audio playback volume using CE.SDK's timeline UI and the programmatic volume control API, from silent (0.0) to full volume (1.0). ![Audio Volume Adjustment example showing timeline with audio blocks at different volume levels](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-audio-adjust-volume-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-audio-audio-adjust-volume-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-audio-audio-adjust-volume-browser/) Volume control adjusts how loud or quiet audio plays during playback. CE.SDK uses a normalized 0.0-1.0 range where 0.0 is completely silent and 1.0 is full volume. This applies to both audio blocks and video fills with embedded audio. Volume settings are commonly used for balancing multiple audio sources, creating fade effects, and allowing users to adjust playback levels. ```typescript file=@cesdk_web_examples/guides-create-audio-audio-adjust-volume-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Adjust Audio Volume Guide * * Demonstrates audio volume control in CE.SDK: * - Setting volume levels with setVolume * - Muting and unmuting with setMuted * - Querying volume and mute states * - Volume levels for multiple audio sources */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable audio and video features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.audio'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Use a sample audio file const audioUri = 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a'; // Create an audio block and load the audio file const audioBlock = engine.block.create('audio'); engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Wait for audio resource to load await engine.block.forceLoadAVResource(audioBlock); // Set volume to 80% (0.8 on a 0.0-1.0 scale) const fullVolumeAudio = engine.block.duplicate(audioBlock); engine.block.appendChild(page, fullVolumeAudio); engine.block.setTimeOffset(fullVolumeAudio, 0); engine.block.setVolume(fullVolumeAudio, 0.8); // Set volume to 30% for background music const lowVolumeAudio = engine.block.duplicate(audioBlock); engine.block.appendChild(page, lowVolumeAudio); engine.block.setTimeOffset(lowVolumeAudio, 5); engine.block.setVolume(lowVolumeAudio, 0.3); // Mute an audio block (preserves volume setting) const mutedAudio = engine.block.duplicate(audioBlock); engine.block.appendChild(page, mutedAudio); engine.block.setTimeOffset(mutedAudio, 10); engine.block.setVolume(mutedAudio, 1.0); engine.block.setMuted(mutedAudio, true); // Query current volume and mute states const currentVolume = engine.block.getVolume(fullVolumeAudio); const isMuted = engine.block.isMuted(mutedAudio); const isForceMuted = engine.block.isForceMuted(mutedAudio); // eslint-disable-next-line no-console console.log(`Full volume audio: ${(currentVolume * 100).toFixed(0)}%`); // eslint-disable-next-line no-console console.log( `Low volume audio: ${( engine.block.getVolume(lowVolumeAudio) * 100 ).toFixed(0)}%` ); // eslint-disable-next-line no-console console.log( `Muted audio - isMuted: ${isMuted}, isForceMuted: ${isForceMuted}` ); // Remove the original audio block (we only need the duplicates) engine.block.destroy(audioBlock); // Zoom to fit all audio blocks engine.scene.zoomToBlock(page, 40, 40, 40, 40); } } export default Example; ``` This guide covers how to adjust audio volume programmatically using the Engine API, mute and unmute audio, and query volume and mute states. ## Understanding Volume Concepts CE.SDK supports volume levels from **0.0** (silent) to **1.0** (full volume), with **1.0** as the default for new audio blocks. Values in between represent proportional volume levels—0.5 is half volume, 0.25 is quarter volume. **Volume vs Muting**: Setting volume to 0.0 makes audio silent, but muting with `setMuted()` is preferred when you want to temporarily silence audio without losing the volume setting. Unmuting restores the previous volume level. **Common use cases**: Background music mixing (0.3-0.5 under voiceover), user volume controls, audio balancing for multi-track projects, fade effects (gradually adjusting volume over time), and accessibility features. ## Setting Up Audio for Volume Control ### Enabling Audio Features Before working with audio, we need to enable the required features in CE.SDK. ```typescript highlight-enable-audio-features // Enable audio and video features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.audio'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); ``` These features enable video mode (required for audio timeline), audio support, the timeline UI for visual audio editing, and playback controls. ### Loading Audio Files We create an audio block and load an audio file by setting its `fileURI` property. ```typescript highlight-create-audio // Create an audio block and load the audio file const audioBlock = engine.block.create('audio'); engine.block.setString(audioBlock, 'audio/fileURI', audioUri); // Wait for audio resource to load await engine.block.forceLoadAVResource(audioBlock); ``` Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself using the `audio/fileURI` property. The `forceLoadAVResource` call ensures CE.SDK has downloaded the audio file and loaded its metadata before we manipulate it. ## Adjusting Volume ### Setting Volume We can set volume using `setVolume()` with a value between 0.0 and 1.0. ```typescript highlight-set-volume // Set volume to 80% (0.8 on a 0.0-1.0 scale) const fullVolumeAudio = engine.block.duplicate(audioBlock); engine.block.appendChild(page, fullVolumeAudio); engine.block.setTimeOffset(fullVolumeAudio, 0); engine.block.setVolume(fullVolumeAudio, 0.8); ``` Setting volume to 0.8 (80%) is useful when you want prominent audio that isn't at maximum level, leaving headroom for other audio sources or preventing distortion. ### Setting Low Volume for Background Audio For background music that should be audible but not prominent, use lower volume levels. ```typescript highlight-set-low-volume // Set volume to 30% for background music const lowVolumeAudio = engine.block.duplicate(audioBlock); engine.block.appendChild(page, lowVolumeAudio); engine.block.setTimeOffset(lowVolumeAudio, 5); engine.block.setVolume(lowVolumeAudio, 0.3); ``` At 0.3 (30%) volume, the audio is clearly audible but stays in the background. This is a common level for background music under voiceover or dialogue. ## Muting Audio ### Mute and Unmute Use `setMuted()` to mute audio without changing its volume setting. This is useful for toggle controls. ```typescript highlight-mute-audio // Mute an audio block (preserves volume setting) const mutedAudio = engine.block.duplicate(audioBlock); engine.block.appendChild(page, mutedAudio); engine.block.setTimeOffset(mutedAudio, 10); engine.block.setVolume(mutedAudio, 1.0); engine.block.setMuted(mutedAudio, true); ``` When you mute an audio block, the volume setting (1.0 in this case) is preserved. Unmuting later with `setMuted(block, false)` restores playback at the same volume level. ### Querying Volume and Mute States You can query the current volume and mute states at any time. ```typescript highlight-query-volume // Query current volume and mute states const currentVolume = engine.block.getVolume(fullVolumeAudio); const isMuted = engine.block.isMuted(mutedAudio); const isForceMuted = engine.block.isForceMuted(mutedAudio); // eslint-disable-next-line no-console console.log(`Full volume audio: ${(currentVolume * 100).toFixed(0)}%`); // eslint-disable-next-line no-console console.log( `Low volume audio: ${( engine.block.getVolume(lowVolumeAudio) * 100 ).toFixed(0)}%` ); // eslint-disable-next-line no-console console.log( `Muted audio - isMuted: ${isMuted}, isForceMuted: ${isForceMuted}` ); ``` Use `getVolume()` to read the current volume level, `isMuted()` to check if the block is muted by the user, and `isForceMuted()` to check if the engine has automatically muted the block due to playback rules. ## Mixing Multiple Audio Sources ### Balancing Tracks When working with multiple audio sources, use different volume levels to create a balanced mix. A common approach is to keep voiceover or dialogue at higher levels (0.8-1.0) and background music at lower levels (0.3-0.5). ### Common Mixing Patterns **Voiceover prominent**: Set background music to 0.3 and voiceover to 1.0 for clear narration with musical accompaniment. **Balanced dialogue and music**: Set both to 0.6-0.7 when both elements are equally important. **Sound effects as accents**: Set sound effects to 0.5-0.8 depending on how prominent they should be in the mix. ## Building Volume Controls ### Volume Slider When building a volume slider UI, map the slider value directly to the 0.0-1.0 range. Display percentages (0-100%) for user-friendly labels. ```typescript // Example: Update volume from slider (0-100) const sliderValue = 75; // User drags slider to 75% const volume = sliderValue / 100; // Convert to 0.0-1.0 engine.block.setVolume(audioBlock, volume); ``` ### Mute Toggle Implement mute buttons using `setMuted()` and indicate the current state using `isMuted()`. Show a different icon when `isForceMuted()` returns true to indicate the engine has automatically muted the audio. ```typescript // Example: Toggle mute state const currentlyMuted = engine.block.isMuted(audioBlock); engine.block.setMuted(audioBlock, !currentlyMuted); // Check if engine force-muted (e.g., high playback speed) if (engine.block.isForceMuted(audioBlock)) { // Show "force muted" indicator } ``` ## Troubleshooting ### Volume Changes Not Audible Check if the block is muted with `isMuted()` or force muted with `isForceMuted()`. Also verify the audio resource has loaded successfully. ### Force Muted State Video fills at playback speeds above 3.0x are automatically force muted by the engine. Reduce the playback speed to restore audio output. ### Volume Not Persisting Ensure you're setting volume on the correct block ID. Volume settings are block-specific and don't propagate to duplicates or other instances. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Control Audio and Video" description: "Learn to play, pause, seek, and preview audio and video content in CE.SDK using playback controls and solo mode." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/control-daba54/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Control Audio and Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) --- Play, pause, seek, and preview audio and video content programmatically using CE.SDK's playback control APIs. ![Control Audio and Video example showing video playback controls](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-control-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-control-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-control-browser/) CE.SDK provides playback control for audio and video through the Block API. Playback state, seeking, and solo preview are controlled programmatically. Resources must be loaded before accessing metadata like duration and dimensions. ```typescript file=@cesdk_web_examples/guides-create-video-control-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page and set to 16:9 landscape for video const page = engine.block.findByType('page')[0]!; // Create a track for video blocks const track = engine.block.create('track'); engine.block.appendChild(page, track); // Create a video block and add it to the track const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; const videoBlock = engine.block.create('graphic'); engine.block.setShape(videoBlock, engine.block.createShape('rect')); engine.block.setWidth(videoBlock, 1920); engine.block.setHeight(videoBlock, 1080); // Create and configure video fill const videoFill = engine.block.createFill('video'); engine.block.setString(videoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(videoBlock, videoFill); // Add to track and set duration engine.block.appendChild(track, videoBlock); engine.block.setDuration(videoBlock, 10); await engine.block.forceLoadAVResource(videoFill); const videoWidth = engine.block.getVideoWidth(videoFill); const videoHeight = engine.block.getVideoHeight(videoFill); const totalDuration = engine.block.getAVResourceTotalDuration(videoFill); console.log(`Video dimensions: ${videoWidth}x${videoHeight}`); console.log(`Total duration: ${totalDuration}s`); if (engine.block.supportsPlaybackControl(page)) { console.log(`Is playing: ${engine.block.isPlaying(page)}`); engine.block.setPlaying(page, true); } if (engine.block.supportsPlaybackTime(page)) { engine.block.setPlaybackTime(page, 1.0); console.log(`Playback time: ${engine.block.getPlaybackTime(page)}s`); } console.log( `Visible at current time: ${engine.block.isVisibleAtCurrentPlaybackTime( videoBlock )}` ); engine.block.setSoloPlaybackEnabled(videoFill, true); console.log( `Solo enabled: ${engine.block.isSoloPlaybackEnabled(videoFill)}` ); engine.block.setSoloPlaybackEnabled(videoFill, false); // Select the video block for inspection engine.block.select(videoBlock); } } export default Example; ``` This guide covers how to play and pause media, seek to specific positions, preview individual blocks with solo mode, check visibility at playback time, and access video resource metadata. ## Force Loading Resources Media resource metadata is unavailable until the resource is loaded. Call `forceLoadAVResource` on the video fill to ensure dimensions and duration are accessible. ```typescript highlight=highlight-force-load await engine.block.forceLoadAVResource(videoFill); ``` Without loading the resource first, accessing properties like duration or dimensions throws an error. ## Getting Video Metadata Once the resource is loaded, query the video dimensions and total duration. ```typescript highlight=highlight-get-metadata const videoWidth = engine.block.getVideoWidth(videoFill); const videoHeight = engine.block.getVideoHeight(videoFill); const totalDuration = engine.block.getAVResourceTotalDuration(videoFill); ``` The `getVideoWidth` and `getVideoHeight` methods return the original video dimensions in pixels. The `getAVResourceTotalDuration` method returns the full duration of the source media in seconds. ## Playing and Pausing Check if the block supports playback control using `supportsPlaybackControl`, then start or stop playback with `setPlaying`. ```typescript highlight=highlight-playback-control if (engine.block.supportsPlaybackControl(page)) { console.log(`Is playing: ${engine.block.isPlaying(page)}`); engine.block.setPlaying(page, true); } ``` The `isPlaying` method returns the current playback state. ## Seeking To jump to a specific position in the timeline, use `setPlaybackTime`. First, check if the block supports playback time with `supportsPlaybackTime`. ```typescript highlight=highlight-seeking if (engine.block.supportsPlaybackTime(page)) { engine.block.setPlaybackTime(page, 1.0); console.log(`Playback time: ${engine.block.getPlaybackTime(page)}s`); } ``` Playback time is specified in seconds. The `getPlaybackTime` method returns the current position. ## Visibility at Current Time Check if a block is visible at the current playback position using `isVisibleAtCurrentPlaybackTime`. This is useful when blocks have different time offsets or durations. ```typescript highlight=highlight-visibility console.log( `Visible at current time: ${engine.block.isVisibleAtCurrentPlaybackTime( videoBlock )}` ); ``` ## Solo Playback Solo playback allows you to preview an individual block while the rest of the scene stays frozen. Enable it on a video fill or audio block with `setSoloPlaybackEnabled`. ```typescript highlight=highlight-solo-playback engine.block.setSoloPlaybackEnabled(videoFill, true); console.log( `Solo enabled: ${engine.block.isSoloPlaybackEnabled(videoFill)}` ); engine.block.setSoloPlaybackEnabled(videoFill, false); ``` Enabling solo on one block automatically disables it on all others. This is useful for previewing a specific clip without affecting the overall scene playback. ## Troubleshooting ### Properties Unavailable Before Resource Load **Symptom**: Accessing duration, dimensions, or trim values throws an error. **Cause**: Media resource not yet loaded. **Solution**: Always `await engine.block.forceLoadAVResource()` before accessing these properties. ### Block Not Playing **Symptom**: Calling `setPlaying(true)` has no effect. **Cause**: Block doesn't support playback control or scene not in playback mode. **Solution**: Check `supportsPlaybackControl()` returns true; ensure scene playback is active. ### Solo Playback Not Working **Symptom**: Enabling solo doesn't isolate the block. **Cause**: Solo applied to wrong block type or block not visible. **Solution**: Apply solo to video fills or audio blocks, ensure block is at current playback time. ## Next Steps
  • [Trim Video and Audio](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) - Control which portion of source media plays
  • [Loop Audio](https://img.ly/docs/cesdk/angular/create-audio/audio/loop-937be7/) - Enable repeating playback for audio blocks
  • [Adjust Volume](https://img.ly/docs/cesdk/angular/create-video/audio/adjust-volume-7ecc4a/) - Control audio volume and muting
  • [Adjust Speed](https://img.ly/docs/cesdk/angular/create-video/audio/adjust-speed-908d57/) - Change playback speed for audio
  • [Video Timeline Overview](https://img.ly/docs/cesdk/angular/create-video/timeline-editor-912252/) - Timeline editing system
--- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Video Limitations" description: "Understand resolution limits, duration constraints, codec support, and browser-specific restrictions when working with video in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/limitations-6a740d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Limitations](https://img.ly/docs/cesdk/angular/create-video/limitations-6a740d/) --- CE.SDK performs video processing client-side, providing privacy and responsiveness while introducing hardware-dependent constraints. This reference covers resolution limits, codec support, and platform-specific restrictions to help you plan video workflows within platform capabilities. ![Video Limitations example showing the CE.SDK video editor](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-limitations-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-limitations-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-limitations-browser/) Client-side video processing provides significant advantages for privacy and user experience, but it operates within the constraints of the user's device. Understanding these limitations helps you build applications that work reliably across different hardware configurations and browsers. ```typescript file=@cesdk_web_examples/guides-create-video-limitations-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Video Limitations Guide * * Demonstrates how to query video processing limitations in CE.SDK: * - Querying maximum export size * - Monitoring memory usage and availability * - Understanding resolution and duration constraints */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; // Query the maximum export dimensions supported by this device const maxExportSize = engine.editor.getMaxExportSize(); console.log('Maximum export size:', maxExportSize, 'pixels'); // The maximum export size depends on the GPU texture size limit // Typical values: 4096, 8192, or 16384 pixels // Query current memory consumption const usedMemory = engine.editor.getUsedMemory(); const usedMemoryMB = (usedMemory / (1024 * 1024)).toFixed(2); console.log('Memory used:', usedMemoryMB, 'MB'); // Query available memory for video processing const availableMemory = engine.editor.getAvailableMemory(); const availableMemoryMB = (availableMemory / (1024 * 1024)).toFixed(2); console.log('Memory available:', availableMemoryMB, 'MB'); // Browser tabs typically cap around 2GB due to WebAssembly's 32-bit address space // Calculate memory utilization percentage const totalMemory = usedMemory + availableMemory; const memoryUtilization = ((usedMemory / totalMemory) * 100).toFixed(1); console.log('Memory utilization:', memoryUtilization, '%'); // Check if a specific export size is feasible const desiredWidth = 3840; // 4K UHD const desiredHeight = 2160; const canExport4K = desiredWidth <= maxExportSize && desiredHeight <= maxExportSize; console.log( 'Can export at 4K UHD (3840x2160):', canExport4K ? 'Yes' : 'No' ); // Add a sample video to demonstrate the editor with video content const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a video block that fills the page const videoBlock = await engine.block.addVideo( videoUri, pageWidth, pageHeight ); // Position the video at the center of the page engine.block.setPositionX(videoBlock, 0); engine.block.setPositionY(videoBlock, 0); // Select the video block engine.block.setSelected(videoBlock, true); // Re-check memory after loading video content const usedAfterLoad = engine.editor.getUsedMemory(); const availableAfterLoad = engine.editor.getAvailableMemory(); const usedAfterLoadMB = (usedAfterLoad / (1024 * 1024)).toFixed(2); const availableAfterLoadMB = (availableAfterLoad / (1024 * 1024)).toFixed( 2 ); console.log('After loading video:'); console.log(' Memory used:', usedAfterLoadMB, 'MB'); console.log(' Memory available:', availableAfterLoadMB, 'MB'); // Log summary of device capabilities console.log('--- Device Capabilities Summary ---'); console.log('Max export dimension:', maxExportSize, 'px'); console.log('4K UHD support:', canExport4K ? 'Supported' : 'Not supported'); console.log( 'Initial memory:', usedMemoryMB, 'MB used /', availableMemoryMB, 'MB available' ); console.log( 'Open the browser console to view detailed limitation information.' ); } } export default Example; ``` ## Resolution Limits Video resolution capabilities depend on hardware resources and WebAssembly memory constraints. CE.SDK supports up to 4K UHD for playback and export on capable hardware. Import resolution is bounded by WebAssembly's 32-bit address space and browser tab memory limits, which typically cap around 2GB. This means very high resolution video files may exceed available memory during processing. Playback and export at 4K depends on available GPU resources, and higher resolutions require proportionally more memory and processing power. Query the maximum export size before initiating exports to avoid failures: ```typescript highlight-query-max-export-size // Query the maximum export dimensions supported by this device const maxExportSize = engine.editor.getMaxExportSize(); console.log('Maximum export size:', maxExportSize, 'pixels'); // The maximum export size depends on the GPU texture size limit // Typical values: 4096, 8192, or 16384 pixels ``` The maximum export size varies by device GPU capabilities. Typical values range from 4096 to 16384 pixels depending on the graphics hardware. Before exporting at high resolutions, verify the target dimensions don't exceed this limit: ```typescript highlight-check-export-feasibility // Check if a specific export size is feasible const desiredWidth = 3840; // 4K UHD const desiredHeight = 2160; const canExport4K = desiredWidth <= maxExportSize && desiredHeight <= maxExportSize; console.log( 'Can export at 4K UHD (3840x2160):', canExport4K ? 'Yes' : 'No' ); ``` ## Duration Limits Video duration affects editing responsiveness and export time. CE.SDK optimizes for short-form content while supporting longer videos with performance trade-offs. Stories and reels up to 2 minutes are fully supported with smooth editing performance. Videos up to 10 minutes work well on modern hardware, with export times typically around 1 minute for this length. Longer videos are technically possible but may impact editing responsiveness on less capable devices. For long-form content, consider these approaches: - Split longer videos into shorter segments for editing - Use lower resolution previews during editing, then export at full quality - Test on target devices to establish acceptable duration limits for your use case ## Frame Rate Support Frame rate affects both playback smoothness and export performance. Hardware acceleration significantly impacts high frame rate capabilities. 30 FPS at 1080p is broadly supported across devices and provides smooth playback on most hardware. 60 FPS and high-resolution combinations benefit from hardware acceleration. When hardware acceleration is unavailable, high frame rate video may drop frames during preview playback, though exports will maintain the correct timing. Variable frame rate sources may have timing precision limitations. For best results with variable frame rate content, consider transcoding to constant frame rate before importing into CE.SDK. ## Supported Codecs CE.SDK supports widely-adopted video and audio codecs, with some platform-specific variations in availability. ### Video Codecs H.264/AVC in `.mp4` containers has universal support across all browsers and platforms. This is the most reliable codec choice for broad compatibility. H.265/HEVC in `.mp4` containers has platform-dependent support. Safari on macOS and iOS supports HEVC natively, while Chrome and Firefox support varies by operating system and codec availability. ### Audio Codecs MP3 works in `.mp3` files or within `.mp4` containers, with universal browser support. AAC in `.m4a`, `.mp4`, or `.mov` containers is widely supported, though some browsers may require system codecs for encoding during export. ## Browser and Platform Restrictions Browser capabilities depend on the host operating system, introducing platform-specific limitations that affect video processing. ### Windows Limitations H.265 transparency is not supported on Windows hosts. If your workflow requires alpha channel video with HEVC, consider processing on macOS or using H.264 which supports alpha on all platforms. ### Linux Limitations Chrome on Linux typically lacks encoder support for H.264 and AAC due to licensing restrictions in the Chrome Linux build. This means video imports and playback may work correctly, but exports may fail. If you're targeting Linux users, consider: - Recommending Firefox, which may have different codec support - Providing fallback export options - Using server-side export processing for Linux users Use the `video.decode.checkSupport` and `video.encode.checkSupport` actions to detect video capabilities programmatically and display appropriate user feedback. Alternatively, use `cesdk.utils.supportsVideoDecode()` and `cesdk.utils.supportsVideoEncode()` to check support silently without showing dialogs. See the [Actions API](https://img.ly/docs/cesdk/angular/actions-6ch24x/) for implementation details. ### Chromium Limitations Chromium-based browsers (without proprietary codecs) don't include video codecs due to licensing. They may fall back to system libraries on some platforms, such as macOS, but support is not guaranteed. Video editing functionality may not work reliably in pure Chromium builds. ### Mobile Browser Limitations Video editing is not supported on mobile browsers on any platform due to technical limitations causing performance issues. For mobile video editing capabilities, use the native mobile SDKs for iOS and Android, which provide full video support. ## Hardware Requirements Device capabilities directly affect video processing performance. CE.SDK scales with available hardware resources. ### Recommended Hardware | Platform | Minimum Hardware | | ---------------- | --------------------------------------------------------------- | | Desktop | Notebook or desktop released in the last 7 years with at least 4GB memory | | Mobile (Apple) | iPhone 8, iPad (6th gen) or newer | | Mobile (Android) | Phones and tablets released in the last 4 years | ### GPU Considerations Hardware acceleration improves encoding and decoding performance significantly. High-resolution and high-frame-rate exports benefit most from GPU support. The maximum export size depends on the maximum texture size the device's GPU can allocate. Integrated graphics can handle most common video editing tasks. Discrete GPUs provide better performance for 4K content and complex compositions with multiple video layers. ## Memory Constraints Client-side video processing operates within browser memory limits. Use the memory APIs to monitor consumption and make informed decisions about resource loading. Query current memory usage to understand how much has been consumed: ```typescript highlight-query-memory-usage // Query current memory consumption const usedMemory = engine.editor.getUsedMemory(); const usedMemoryMB = (usedMemory / (1024 * 1024)).toFixed(2); console.log('Memory used:', usedMemoryMB, 'MB'); ``` Check how much memory remains available for additional resources: ```typescript highlight-query-available-memory // Query available memory for video processing const availableMemory = engine.editor.getAvailableMemory(); const availableMemoryMB = (availableMemory / (1024 * 1024)).toFixed(2); console.log('Memory available:', availableMemoryMB, 'MB'); // Browser tabs typically cap around 2GB due to WebAssembly's 32-bit address space ``` WebAssembly uses a 32-bit address space, limiting the maximum addressable memory. Browser tabs typically cap around 2GB of memory, though this varies by browser and system configuration. Multiple video tracks and effects increase memory usage proportionally. Query memory APIs before loading additional video files to avoid out-of-memory conditions: ```typescript highlight-monitor-memory-after-load // Re-check memory after loading video content const usedAfterLoad = engine.editor.getUsedMemory(); const availableAfterLoad = engine.editor.getAvailableMemory(); const usedAfterLoadMB = (usedAfterLoad / (1024 * 1024)).toFixed(2); const availableAfterLoadMB = (availableAfterLoad / (1024 * 1024)).toFixed( 2 ); console.log('After loading video:'); console.log(' Memory used:', usedAfterLoadMB, 'MB'); console.log(' Memory available:', availableAfterLoadMB, 'MB'); ``` ## Export Size Limitations Export dimensions are bounded by GPU texture size limits. Always query `getMaxExportSize()` before initiating exports to ensure the requested dimensions are supported. The maximum export size varies by device GPU capabilities. Common limits include: - **4096 pixels**: Older integrated graphics - **8192 pixels**: Most modern integrated and discrete GPUs - **16384 pixels**: High-end discrete GPUs Consider target platform requirements when planning export dimensions. Mobile devices and web playback rarely benefit from resolutions above 1080p or 4K, so exporting at extreme resolutions may not provide practical value. ## Troubleshooting Common issues developers encounter related to video limitations: | Issue | Cause | Solution | | ---------------------------------- | --------------------------------------- | --------------------------------------------------- | | Video export fails on Linux | Chrome lacks H.264/AAC encoder support | Use Firefox or implement server-side export | | Slow playback at high resolution | Hardware cannot keep up with decoding | Reduce preview resolution or use proxy editing | | Export fails with large video | Memory limits exceeded | Reduce resolution or split into shorter segments | | H.265 transparency not working | Windows platform limitation | Use H.264 or process on macOS | | Mobile browser video not working | Mobile browsers don't support video editing | Use native mobile SDK instead | | Export size rejected | Exceeds device GPU texture limits | Query `getMaxExportSize()` and reduce dimensions | ## API Reference | Method | Description | | ---------------------------------- | -------------------------------------------------------- | | `engine.editor.getMaxExportSize()` | Query the maximum export dimensions supported by the device | | `engine.editor.getAvailableMemory()` | Get available memory in bytes for video processing | | `engine.editor.getUsedMemory()` | Get current memory usage in bytes | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Videos Overview" description: "Learn how to create and customize videos in CE.SDK using scenes, assets, and timeline-based editing." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/overview-b06512/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Overview](https://img.ly/docs/cesdk/angular/create-video/overview-b06512/) --- 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 `"//ly.img.ubq/fill/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. `//ly.img.ubq/audio` blocks can be added to the page 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 `block.exportVideo` function. ## A Note on Browser Support Video mode heavily relies on modern features like web codecs. A detailed list of supported browser versions can be found in our [Supported Browsers](https://img.ly/docs/cesdk/angular/browser-support-28c1b0/). Please also take note of [possible restrictions based on the host platform](https://img.ly/docs/cesdk/angular/file-format-support-3c4b2a/) browsers are running on. ## Creating a Video Scene First, we create a scene that is set up for video editing by calling the `scene.createVideo()` API. Then we create a page, add it to the scene and define its dimensions. This page will hold our composition. ```javascript const scene = engine.scene.createVideo(); const page = engine.block.create('page'); engine.block.appendChild(scene, page); engine.block.setWidth(page, 1280); engine.block.setHeight(page, 720); ``` ## Setting Page Durations Next, we define the duration of the page using the `setDuration(block: number, duration: number): void` API to be 20 seconds long. This will be the total duration of our exported video in the end. ```javascript engine.block.setDuration(page, 20); ``` ## 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. ```javascript const video1 = engine.block.create('graphic'); engine.block.setShape(video1, engine.block.createShape('rect')); const videoFill = engine.block.createFill('video'); engine.block.setString( videoFill, 'fill/video/fileURI', 'https://cdn.img.ly/assets/demo/v4/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4' ); engine.block.setFill(video1, videoFill); const video2 = engine.block.create('graphic'); engine.block.setShape(video2, engine.block.createShape('rect')); const videoFill2 = engine.block.createFill('video'); engine.block.setString( videoFill2, 'fill/video/fileURI', 'https://cdn.img.ly/assets/demo/v4/ly.img.video/videos/pexels-kampus-production-8154913.mp4' ); engine.block.setFill(video2, videoFill2); ``` ## Creating a Track While we could add the two blocks directly to the page and and manually set their sizes and time offsets, we can alternatively also use the `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 `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. ```javascript const track = engine.block.create('track'); engine.block.appendChild(page, track); engine.block.appendChild(track, video1); engine.block.appendChild(track, 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. ```javascript engine.block.setDuration(video1, 15); ``` 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 graphic using the `setTrimOffset(block: number, offset: number): void` and `setTrimLength(block: number, length: number): void` 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. Since our graphic is 15 seconds long, the trimmed video will be played fully once and then start looping for the remaining 5 seconds. ```javascript /* Make sure that the video is loaded before calling the trim APIs. */ await engine.block.forceLoadAVResource(videoFill); engine.block.setTrimOffset(videoFill, 1); engine.block.setTrimLength(videoFill, 10); ``` We can control if a video will loop back to its beginning by calling `setLooping(block: number, looping: boolean): void`. Otherwise, the video will simply hold its last frame instead and audio will stop playing. Looping behavior is activated for all blocks by default. ```javascript engine.block.setLooping(videoFill, 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 `setMuted(block: number, muted: boolean): void`. ```javascript engine.block.setMuted(videoFill, true); ``` We can also add audio-only files to play together with the contents of the page by adding an `'audio'` block to the page and assigning it the URL of the audio file. ```javascript const audio = engine.block.create('audio'); engine.block.appendChild(page, audio); engine.block.setString( audio, 'audio/fileURI', 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a' ); ``` We can adjust the volume level of any audio block or video fill by calling `setVolume(block: number, volume: number): void`. The volume is given as a fraction in the range of 0 to 1. ```javascript /* Set the volume level to 70%. */ engine.block.setVolume(audio, 0.7); ``` 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 scene it should begin to play using the `setTimeOffset(block: number, offset: number): void` API. ```javascript /* Start the audio after two seconds of playback. */ engine.block.setTimeOffset(audio, 2); ``` 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 `setDuration(block: number, duration: number): void` API. ```javascript /* Give the Audio block a duration of 7 seconds. */ engine.block.setDuration(audio, 7); ``` ## Exporting Video You can start exporting the entire page as a video file by calling `exportVideo()`. The encoding process will run in the background. You can get notified about the progress of the encoding process by the `progressCallback`. It will be called whenever another frame has been encoded. 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. ```javascript /* Export page as mp4 video. */ const videoBlob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', onProgress: (renderedFrames, encodedFrames, totalFrames) => { console.log( 'Rendered', renderedFrames, 'frames and encoded', encodedFrames, 'frames out of', totalFrames ); } }); /* Download video blob. */ let anchor = document.createElement('a'); anchor.href = URL.createObjectURL(videoBlob); anchor.download = 'exported-video.mp4'; anchor.click(); ``` ## Exporting Audio You can export just the audio from your video scene by calling `exportAudio()`. This allows you to extract the audio track separately, whether from an entire page, a single audio block, a video block with audio, or a track containing multiple audio sources. The audio export process runs in a background worker, similar to video export, keeping the main engine responsive. You can monitor the progress through the `onProgress` callback. ```javascript /* Export page audio as an WAV audio. */ const audioBlob = await engine.block.exportAudio(page, { mimeType: 'audio/wav', onProgress: (renderedFrames, encodedFrames, totalFrames) => { console.log( 'Rendered', renderedFrames, 'frames and encoded', encodedFrames, 'frames out of', totalFrames ); } }); /* Download audio blob. */ anchor = document.createElement('a'); anchor.href = URL.createObjectURL(audioBlob); anchor.download = 'exported-audio.wav'; anchor.click(); ``` --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Video and Audio Timeline Web Editor" description: "Use the timeline editor to arrange and edit video clips, audio, and animations frame by frame." platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/timeline-editor-912252/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Timeline Editor](https://img.ly/docs/cesdk/angular/create-video/timeline-editor-912252/) --- The CreativeEditor SDK (CE.SDK) offers features for editing the video timeline, the horizontal layout where you arrange video, audio, and effects in the sequence they play. This tutorial will help you create advanced video editing tools for high-quality video exports. This tutorial focuses on the user interface components. For programmatic timeline manipulation, refer to the [Video Overview](https://img.ly/docs/cesdk/angular/create-video/overview-b06512/) guide. ## When to Use Use the CE.SDK timeline features in web applications that incorporate the following tools: - Video montages - Marketing video editing - Social media content creation ## What You’ll Learn This tutorial will show you how to: - The video timeline works. - To create video scenes. - To manage video layers (tracks). - To edit a clip’s duration and offset. - To manage video playback. - To generate and display video thumbnails. ## How the Timeline Works This tutorial refers to the timeline, which is the horizontal area below the video editing canvas. The video timeline displays: - All clips on parallel tracks - A playhead for navigation - Controls for playback and editing Use the visual timeline for editing actions like: - Arranging clips along a time axis to control when each element **appears**. - Trimming clips to change their duration. - Layer content visually. ### Activate CE.SDK Video Mode To work with the CE.SDK Editor in video mode, specify the scene’s design as follows: ```ts import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource, } from '@cesdk/cesdk-js/plugins'; // Add default asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Add demo and upload sources await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin(new DemoAssetSources({ sceneMode: 'Video' })); await cesdk.actions.run('scene.create', { mode: 'Video' }); ``` This tells the CreativeEditor: - To use the Video editing mode (rather than the Design one). - To load demo video assets to test the editor. - To create a video scene, ready for editing. ### Open/Close the Timeline Editing area The default CreativeEditor settings display the timeline when launching the UI. Close it to increase canvas space when the scene doesn’t need timeline editing. To close it, click the **Timeline** toggle: To open it and access the visual timeline editing tools, click the same toggle: ### Timeline Structure The timeline’s structure in the CE.SDK is the following: 1. Scene (root block) 2. Pages (as video segments) 3. Tracks (as parallel layers) 4. Clips (as graphic blocks) ### Track Structure The track is a container that handles the content layer. The timeline organizes content into three track types: - Clip tracks (for video content) - Overlay track (for text and graphics over the video) - Audio tracks The order in which track appears determines its position in the scene: - Moving a track to the top brings it to the front of the scene. - Moving a track to the bottom of the timeline sends the content the back of the scene. ### Control the Content Position You can adjust the position of the content using the vertical playhead line, which indicates the current playhead time. The playhead moves along the timeline, displaying the time code. Change the playhead position (when the clip starts playing) by either: - Clicking on any area in the time ruler. - Dragging the playhead with your cursor. ### Scroll and Zoom on the Timeline Adjust the time scale to increase the level of details per frame: 1. Click anywhere in the timeline area. 2. Zoom into the timeline using: - Keyboard shortcuts - A mouse - A trackpad When zoomed in, the clip stretches visually—each pixel now represents fewer milliseconds—so you can fine-tune edits frame by frame. ## Control How the Clips Play ### Play/Pause the Scene The playback bar contains: - The **play/pause button** that plays the clip from the current playhead position. - A **loop toggle** that repeats the video when activated. - The **current timestamp** The scene plays while synchronizing: - All videos - Overlays - Audio ### Preview Frames Drag and drop (scrub) the playhead back and forth to preview frames without playing the clip in real time. This allows you to quickly find the exact moment you want to edit. The timestamp is the current playhead time in HH:MM:SS:FF format. ## Edit Video Clips The CreativeEditor’s timeline allows users to visually edit clips and scenes in the browser. This section lists all timeline editing features. ### Select Clips Before editing any clip, you need to select it to apply modifications to it. To **select a clip**, click it either: - From the page - From the timeline Selecting multiple clips at once is available either: - With cursor clicks on each clip while holding modifier keys. - By drawing a frame in the scene with the cursor. Selecting a clip reveals editing handles to: - Crop the clip. - Flip the clip. - Trim the clip. ### Edit Clip’s Duration In the timeline, selected clips show drag handles at their beginning and end. Use these handles based on your goals: - Trim the clip from the beginning: use the left handle (at the start of the clip). - Trim the clip from the end: use the right handle (at the end of the clip). This operation is called **“trimming” a clip**. As you move the clip, its position on the timeline automatically updates. Trimming shortens the clip, and the cut portions: - Don’t play anymore. - Disappear from the timeline. ### Split Clips The Split Control splits the selected clip into two separate clips at the playhead. Each resulting clip is then independently editable. **Splitting doesn’t remove any part of the original content.** ## Add Content to the Scene Once you’ve edited the clips, you can add more elements to the scene to customize it further, such as: - Additional clips - Audio tracks - Graphics and text ### Add Another Clip The web version of the CreativeEditor allows you to add more clips to the scene in two ways: - By clicking the "Add Clip" button: 1. Opens the video assets gallery. 2. Automatically adds any selected clips from the gallery to the timeline. - By dragging and dropping from your computer to either: 1. The page 2. The timeline Dragging the clip highlights the zone before dropping it. The choice of the zone makes no difference: the clip’s position is automatically set at the beginning of the timeline. You can then reposition and edit it as needed. ### Add Audio and Overlays Furthermore, there are two types of content that you can incorporate into the setting: - Overlays (text, images, stickers) - Audio **Add the new content** to the scene by either: - Clicking the kind of content to add from the **lateral menu**. - Using the **drag and drop** feature from your computer or another source. Each type of content appears automatically on its own track, at the start of the timeline. ## Arrange Clips Use the CreativeEditor UI’s timeline to edit: - The order in which clips appear. - Each clip starting point. - Using advanced settings. ### Reorder Clips Drag clips along the horizontal axis to move them earlier or later in the timeline. This makes a clip’s order relative to the other clip change. The CreativeEditor enhances visual clip arrangement in the timeline by: - Auto-adjusting positions to prevent overlapping clips on the same track. - Showing the clip’s predicted position using drop indicators. ### Edit the Clip’s Starting Point Each clip’s position along the horizontal axis indicates its start time in the composition. To change the start time of a clip, drag it either: - Left to make it start **earlier**. - Right to make it start **later**. Gaps between clips display as empty frames showing the background color when the scene plays. ### Change the Background color To change the background color, click the **Background** button on the left side of the play bar. This action opens a full color editing menu to customize: - Color settings (RGB, CMYK, Hex, Hue) - Transparency - Solid or gradients - Color picking from the scene This menu provides an option to **deactivate the background** as well. Deactivating the background makes the scene play on a transparent background. ## Configure the Timeline ### Activate/Deactivate the Timeline The CreativeEditor SDK ships a UI with the timeline editor activated. To change the settings and deactivate the timeline , use the `ly.img.video.timeline` feature flag: ```ts // Enable the timeline (default for video scenes) cesdk.feature.enable('ly.img.video.timeline'); ``` ```ts // Disable the timeline for a simplified interface cesdk.feature.disable('ly.img.video.timeline'); ``` Pass the option to selectively hide the timeline unless the scene is in **Video Mode**: ```ts // Only show timeline when in Video mode (this is the default behavior) cesdk.feature.set('ly.img.video.timeline', ({ engine }) => { return engine.scene.getMode() === 'Video'; }); cesdk.feature.set('ly.img.video.controls.split', ({ engine }) => { const selected = engine.block.findAllSelected(); return selected.length === 1; }); ``` ### Hide or Show Tracks Simplify the CreativeEditor interface by leveraging the track visibility settings. Hide or display tracks based on specific use cases. **Enable** video features with: ```ts cesdk.feature.enable('ly.img.video.clips'); ``` **Hide** video feature with: ```ts cesdk.feature.disable('ly.img.video.clips'); ``` This will hide **Display** overlays tracks with: ```ts cesdk.feature.enable('ly.img.video.overlays'); ``` **Hide** overlay tracks with: ```ts cesdk.feature.disable('ly.img.video.overlays'); ``` **Display** audio tracks with: ```ts cesdk.feature.enable('ly.img.video.audio'); ``` **Hide** audio tracks with: ```ts cesdk.feature.disable('ly.img.video.audio'); ``` **Display** all tracks with: ```ts cesdk.feature.enable([ 'ly.img.video.clips', 'ly.img.video.overlays', 'ly.img.video.audio' ]); ``` **Hide** all tracks with: ```ts cesdk.feature.disable([ 'ly.img.video.clips', 'ly.img.video.overlays', 'ly.img.video.audio' ]); ``` ### Configure the Play Bar Simplify the play bar by hiding or displaying controls in the UI: **Display** the play bar with: ```ts cesdk.feature.enable('ly.img.video.controls.playback'); ``` **Hide** the play bar with: ```ts cesdk.feature.disable('ly.img.video.controls.playback'); ``` **Display** the loop control with: ```ts cesdk.feature.enable('ly.img.video.controls.loop'); ``` **Hide** the loop control with: ```ts cesdk.feature.disable('ly.img.video.controls.loop'); ``` **Display** the zoom on the timeline with: ```ts cesdk.feature.enable('ly.img.video.controls.timelineZoom'); ``` **Hide** the zoom into the timeline with: ```ts cesdk.feature.disable('ly.img.video.controls.timelineZoom'); ``` ### Fine-tune Editing Actions Restrict or allow editing actions by hiding or displaying the editing controls: **Hide** the split button with: ```ts cesdk.feature.disable('ly.img.video.controls.split'); ``` **Hide** the **Add Clip** button with: ```ts cesdk.feature.disable('ly.img.video.addClip'); ``` **Hide** the background button with: ```ts cesdk.feature.disable('ly.img.video.controls.background'); ``` **Activate** all video features at once using global patterns with `/*`: ```ts // Enable all video features cesdk.feature.enable('ly.img.video.*'); ``` Or **deactivate** video features at once: ```ts // Disable all video control features cesdk.feature.disable('ly.img.video.*'); ``` ### Activate Features Dynamically You can activate or deactivate timeline features dynamically, instead of hard-coding their **on/off** state. The following example detects the scene state: - When a clip is selected, it activates the Split button. - When nothing is selected, it hides the Split button. ```ts // Disable split control when nothing is selected cesdk.feature.set('ly.img.video.controls.split', ({ engine }) => { const selected = engine.block.findAllSelected(); return selected.length === 1; }); ``` ### Feature Reference | Feature ID | Description | |------------|-------------| | `ly.img.video.timeline` | Show or hide the entire timeline panel | | `ly.img.video.clips` | Show or hide the video clips track | | `ly.img.video.overlays` | Show or hide the overlay track | | `ly.img.video.audio` | Show or hide the audio track | | `ly.img.video.addClip` | Enable or disable adding new clips | | `ly.img.video.controls` | Base feature for all video controls | | `ly.img.video.controls.toggle` | Show or hide timeline collapse/expand button | | `ly.img.video.controls.playback` | Show or hide play/pause and timestamp | | `ly.img.video.controls.loop` | Show or hide loop toggle | | `ly.img.video.controls.split` | Show or hide split clip control | | `ly.img.video.controls.background` | Show or hide background color controls | | `ly.img.video.controls.timelineZoom` | Show or hide zoom controls | ## Troubleshooting | Issue | Solution| | ----- | ------- | | Timeline not displaying | ・ Verify the scene is in video mode.
・ Check that `ly.img.video.timeline` feature is enabled. | | Trim handles not displaying | ・ Click the clip first to reveal handles.
・ Check if the clip contains video/audio content. | | Play not starting | ・ Ensure the video has loaded.
・ Check the browser console for codec errors.
・ Check that the playhead falls within the page duration. | | Split not working | ・ Check that you’ve selected the clip to split.
・ Check that the playhead is within the selected clip’s duration
. Make sure you’ve enabled `ly.img.video.controls.split`. | ## Next Steps - [Trim Video Clips](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) — Detailed trimming techniques, including frame-accurate editing. - [Control Audio and Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) — Master volume, playback speed, and timing. - [Activate or Deactivate Features](https://img.ly/docs/cesdk/angular/user-interface/customization/disable-or-enable-f058e2/) - Full feature flag reference. - [Compress and Export the Video](https://img.ly/docs/cesdk/angular/export-save-publish/export/compress-29105e/). --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Update Caption Presets" description: "Extend video captions with custom caption styles using simple content.json updates" platform: angular url: "https://img.ly/docs/cesdk/angular/create-video/update-caption-presets-e9c385/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Update Caption Presets](https://img.ly/docs/cesdk/angular/create-video/update-caption-presets-e9c385/) --- Extend CE.SDK's video caption feature with custom caption presets by updating the content.json file. Caption presets let your users apply predefined styles to video captions with a single click. ![Update Caption Presets example showing a styled neon glow caption preset](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-update-caption-presets-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-update-caption-presets-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-update-caption-presets-browser/) Video captions have become an essential part of digital content, improving accessibility and engagement. With CE.SDK's caption presets feature, you can offer your users a selection of predefined caption styles that they can apply with a single click. This guide shows you how to create styled text blocks, serialize them as preset files, and structure the content.json to make them available in the caption presets panel. ```typescript file=@cesdk_web_examples/guides-create-video-update-caption-presets-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Update Caption Presets Guide * * Demonstrates creating custom caption presets in CE.SDK: * - Creating a styled text block as a preset base * - Applying neon glow styling with colors and drop shadow * - Serializing the block for use as a preset file * - Understanding the content.json structure for caption presets */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features for caption presets cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; // Create a text block to use as the preset base // Text blocks support all the styling properties needed for captions const textBlock = engine.block.create('text'); // Set sample caption text engine.block.setString(textBlock, 'text/text', 'NEON GLOW'); // Position and size the text block engine.block.setPositionX(textBlock, 50); engine.block.setPositionY(textBlock, 200); engine.block.setWidth(textBlock, 600); engine.block.setHeightMode(textBlock, 'Auto'); // Style the text with a bright neon cyan color // This will be the fill/solid/color property in the preset engine.block.setColor(textBlock, 'fill/solid/color', { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }); // Set font properties for the caption style engine.block.setFloat(textBlock, 'text/fontSize', 48); // Use a bold font for better visibility // Load and set a typeface const typefaceResult = await engine.asset.findAssets('ly.img.typeface', { query: 'Roboto', page: 0, perPage: 10 }); if (typefaceResult.assets.length > 0) { const typefaceAsset = typefaceResult.assets[0]; const typeface = typefaceAsset.payload?.typeface; if (typeface && typeface.fonts?.[0]?.uri) { engine.block.setFont(textBlock, typeface.fonts[0].uri, typeface); } } // Add a glowing drop shadow effect for the neon look // This creates the characteristic neon glow effect engine.block.setDropShadowEnabled(textBlock, true); // Set glow color (bright cyan to match text) engine.block.setColor(textBlock, 'dropShadow/color', { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }); // Configure shadow properties for a soft glow engine.block.setFloat(textBlock, 'dropShadow/blurRadius/x', 20); engine.block.setFloat(textBlock, 'dropShadow/blurRadius/y', 20); engine.block.setFloat(textBlock, 'dropShadow/offset/x', 0); engine.block.setFloat(textBlock, 'dropShadow/offset/y', 0); // Optionally add a semi-transparent dark background // This helps the caption stand out against video content engine.block.setBackgroundColorEnabled(textBlock, true); engine.block.setColor(textBlock, 'backgroundColor/color', { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }); // Add the styled text block to the page const pages = engine.block.findByType('page'); if (pages.length > 0) { engine.block.appendChild(pages[0], textBlock); } // Select the block and zoom to it so it's visible in the editor engine.block.select(textBlock); await engine.scene.zoomToBlock(textBlock, { padding: 40 }); // Serialize the styled text block to create a preset file // This serialized string can be saved as a .blocks or .preset file // Include 'bundle' scheme to allow serialization of blocks with bundled fonts const serializedPreset = await engine.block.saveToString( [textBlock], ['buffer', 'http', 'https', 'bundle'] ); // eslint-disable-next-line no-console console.log('=== Serialized Preset ==='); // eslint-disable-next-line no-console console.log('Save this as a .preset file (e.g., neon-glow.preset):'); // eslint-disable-next-line no-console console.log(serializedPreset); // Example content.json entry for the custom preset // This shows the structure needed to add the preset to content.json const contentJsonEntry = { id: '//ly.img.caption.presets/neon-glow', label: { en: 'Neon Glow' }, meta: { uri: '{{base_url}}/ly.img.caption.presets/presets/neon-glow.preset', thumbUri: '{{base_url}}/ly.img.caption.presets/thumbnails/neon-glow.png', mimeType: 'application/ubq-blocks-string' }, payload: { properties: [ { type: 'Color', property: 'fill/solid/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 } }, { type: 'Color', property: 'dropShadow/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 } }, { type: 'Color', property: 'backgroundColor/color', value: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }, defaultValue: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 } } ] } }; // eslint-disable-next-line no-console console.log('\n=== content.json Entry ==='); // eslint-disable-next-line no-console console.log('Add this entry to your content.json assets array:'); // eslint-disable-next-line no-console console.log(JSON.stringify(contentJsonEntry, null, 2)); // Example of a complete content.json file structure const completeContentJson = { version: '3.0.0', id: 'ly.img.caption.presets', assets: [contentJsonEntry] }; // eslint-disable-next-line no-console console.log('\n=== Complete content.json Example ==='); // eslint-disable-next-line no-console console.log(JSON.stringify(completeContentJson, null, 2)); // eslint-disable-next-line no-console console.log('\n=== Caption Preset Guide ==='); // eslint-disable-next-line no-console console.log( 'The styled text block above demonstrates a "Neon Glow" caption preset.' ); // eslint-disable-next-line no-console console.log('To use this preset:'); // eslint-disable-next-line no-console console.log('1. Save the serialized preset string as a .preset file'); // eslint-disable-next-line no-console console.log('2. Create a thumbnail image showing the preset appearance'); // eslint-disable-next-line no-console console.log('3. Add the content.json entry to your assets folder'); // eslint-disable-next-line no-console console.log('4. Configure CE.SDK baseURL to point to your assets location'); } } export default Example; ``` This guide covers how to understand the caption presets folder structure, create custom caption styles from text blocks, serialize presets for hosting, define customizable properties, and configure CE.SDK to load your custom presets. ## Understanding the Caption Presets Structure ### Folder Organization CE.SDK's caption presets use a specific directory structure that the engine expects when loading presets. The base path is `assets/v5/ly.img.caption.presets/` and contains: ``` assets/v5/ly.img.caption.presets/ ├── content.json # Master index of all presets ├── presets/ # Folder containing preset files │ ├── my-custom-preset.preset # Serialized caption block with styling │ └── ... └── thumbnails/ # Folder containing preview images ├── my-custom-preset.png # Preview image for preset └── ... ``` The main `content.json` file acts as an index that lists all available presets with their metadata. When CE.SDK loads caption presets, it reads this file to discover available presets and their locations. ### content.json Format The content.json file follows a specific format with version, asset source ID, and an assets array: ```json { "version": "3.0.0", "id": "ly.img.caption.presets", "assets": [ { "id": "ly.img.caption.presets.my-preset", "label": { "en": "My Preset" }, "meta": { "uri": "{{base_url}}/ly.img.caption.presets/presets/my-preset.preset", "thumbUri": "{{base_url}}/ly.img.caption.presets/thumbnails/my-preset.png", "mimeType": "application/ubq-blocks-string" }, "payload": { "properties": [] } } ] } ``` Each asset entry requires a unique ID with namespace, localized label, meta with URIs and mime type, and optional payload properties for customization. ## Creating Custom Caption Presets ### Designing a Caption Style We create a styled text block as the basis for our preset. Text blocks support all the styling properties needed for captions including colors, fonts, backgrounds, shadows, and effects. ```typescript highlight-create-text-block // Create a text block to use as the preset base // Text blocks support all the styling properties needed for captions const textBlock = engine.block.create('text'); // Set sample caption text engine.block.setString(textBlock, 'text/text', 'NEON GLOW'); // Position and size the text block engine.block.setPositionX(textBlock, 50); engine.block.setPositionY(textBlock, 200); engine.block.setWidth(textBlock, 600); engine.block.setHeightMode(textBlock, 'Auto'); ``` We position and size the text block, then set sample caption text. The text block serves as our canvas for applying the styling that will define the preset's appearance. ### Styling with Colors and Fonts We style the text with colors and configure font properties. The fill color becomes the `fill/solid/color` property in the preset: ```typescript highlight-style-text-color // Style the text with a bright neon cyan color // This will be the fill/solid/color property in the preset engine.block.setColor(textBlock, 'fill/solid/color', { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }); ``` We also configure font size and load a typeface. When users apply this preset, their captions will inherit these font settings: ```typescript highlight-style-font // Set font properties for the caption style engine.block.setFloat(textBlock, 'text/fontSize', 48); // Use a bold font for better visibility // Load and set a typeface const typefaceResult = await engine.asset.findAssets('ly.img.typeface', { query: 'Roboto', page: 0, perPage: 10 }); if (typefaceResult.assets.length > 0) { const typefaceAsset = typefaceResult.assets[0]; const typeface = typefaceAsset.payload?.typeface; if (typeface && typeface.fonts?.[0]?.uri) { engine.block.setFont(textBlock, typeface.fonts[0].uri, typeface); } } ``` ### Adding Visual Effects We add a glowing drop shadow effect for the neon look. Drop shadow creates the characteristic glow effect that makes caption presets visually distinctive: ```typescript highlight-style-drop-shadow // Add a glowing drop shadow effect for the neon look // This creates the characteristic neon glow effect engine.block.setDropShadowEnabled(textBlock, true); // Set glow color (bright cyan to match text) engine.block.setColor(textBlock, 'dropShadow/color', { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }); // Configure shadow properties for a soft glow engine.block.setFloat(textBlock, 'dropShadow/blurRadius/x', 20); engine.block.setFloat(textBlock, 'dropShadow/blurRadius/y', 20); engine.block.setFloat(textBlock, 'dropShadow/offset/x', 0); engine.block.setFloat(textBlock, 'dropShadow/offset/y', 0); ``` Optionally, we add a semi-transparent background to help the caption stand out against video content: ```typescript highlight-style-background // Optionally add a semi-transparent dark background // This helps the caption stand out against video content engine.block.setBackgroundColorEnabled(textBlock, true); engine.block.setColor(textBlock, 'backgroundColor/color', { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }); ``` ### Serializing the Preset We serialize the styled text block using `block.saveToString()`. This creates a serialized string that can be saved as a `.preset` or `.blocks` file: ```typescript highlight-serialize-preset // Serialize the styled text block to create a preset file // This serialized string can be saved as a .blocks or .preset file // Include 'bundle' scheme to allow serialization of blocks with bundled fonts const serializedPreset = await engine.block.saveToString( [textBlock], ['buffer', 'http', 'https', 'bundle'] ); // eslint-disable-next-line no-console console.log('=== Serialized Preset ==='); // eslint-disable-next-line no-console console.log('Save this as a .preset file (e.g., neon-glow.preset):'); // eslint-disable-next-line no-console console.log(serializedPreset); ``` The serialized string contains all block properties and styling. Save this output as a file (e.g., `neon-glow.preset`) and create a thumbnail image showing the preset appearance. ## Defining Customizable Properties ### Color Properties We define which properties users can customize without changing the entire preset. Color properties allow users to modify specific color aspects of a preset: ```typescript highlight-content-json-structure // Example content.json entry for the custom preset // This shows the structure needed to add the preset to content.json const contentJsonEntry = { id: '//ly.img.caption.presets/neon-glow', label: { en: 'Neon Glow' }, meta: { uri: '{{base_url}}/ly.img.caption.presets/presets/neon-glow.preset', thumbUri: '{{base_url}}/ly.img.caption.presets/thumbnails/neon-glow.png', mimeType: 'application/ubq-blocks-string' }, payload: { properties: [ { type: 'Color', property: 'fill/solid/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 1.0 } }, { type: 'Color', property: 'dropShadow/color', value: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 }, defaultValue: { r: 0.0, g: 1.0, b: 1.0, a: 0.8 } }, { type: 'Color', property: 'backgroundColor/color', value: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 }, defaultValue: { r: 0.0, g: 0.0, b: 0.1, a: 0.7 } } ] } }; // eslint-disable-next-line no-console console.log('\n=== content.json Entry ==='); // eslint-disable-next-line no-console console.log('Add this entry to your content.json assets array:'); // eslint-disable-next-line no-console console.log(JSON.stringify(contentJsonEntry, null, 2)); ``` Each property in the `payload.properties` array needs: - `type`: Must be `"Color"` for color properties - `property`: Property path (e.g., `"fill/solid/color"`, `"backgroundColor/color"`, `"dropShadow/color"`) - `value`: Current RGBA color object with `r`, `g`, `b`, `a` values (0-1 range) - `defaultValue`: Initial RGBA color object ### Supported Property Paths Available property paths for caption customization: - `fill/solid/color`: Text fill color - `backgroundColor/color`: Background color behind text - `dropShadow/color`: Drop shadow color - `stroke/color`: Stroke/outline color ## Updating the content.json File ### Adding a New Preset Entry Add a new object to the `assets` array with all required fields. The complete structure for a preset entry: ```json { "id": "ly.img.caption.presets.neon-glow", "label": { "en": "Neon Glow" }, "meta": { "uri": "{{base_url}}/ly.img.caption.presets/presets/neon-glow.preset", "thumbUri": "{{base_url}}/ly.img.caption.presets/thumbnails/neon-glow.png", "mimeType": "application/ubq-blocks-string" }, "payload": { "properties": [ { "type": "Color", "property": "fill/solid/color", "value": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 1.0 }, "defaultValue": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 1.0 } }, { "type": "Color", "property": "dropShadow/color", "value": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 0.8 }, "defaultValue": { "r": 0.0, "g": 1.0, "b": 1.0, "a": 0.8 } } ] } } ``` Ensure the `mimeType` is set to `"application/ubq-blocks-string"` and use the `{{base_url}}` placeholder for dynamic path resolution. ### Complete content.json Example The complete content.json file structure wraps preset entries in the assets array: ```typescript highlight-complete-content-json // Example of a complete content.json file structure const completeContentJson = { version: '3.0.0', id: 'ly.img.caption.presets', assets: [contentJsonEntry] }; // eslint-disable-next-line no-console console.log('\n=== Complete content.json Example ==='); // eslint-disable-next-line no-console console.log(JSON.stringify(completeContentJson, null, 2)); ``` ## Hosting and Serving Custom Presets ### Server Setup Prepare the folder structure and upload files to your server: 1. Create folder structure matching `assets/v5/ly.img.caption.presets/` 2. Upload `content.json` to the root folder 3. Upload `.preset` files to `presets/` subfolder 4. Upload thumbnail images to `thumbnails/` subfolder 5. Ensure files are accessible via HTTP/HTTPS 6. Configure CORS headers if serving cross-origin ### Verifying File Access Test that all files are accessible before configuring CE.SDK: - Access `content.json` directly in browser - Access preset files and thumbnails via their URLs - Check browser console for CORS errors ## Loading Custom Presets into CE.SDK ### Base URL Configuration To load your custom caption presets into CE.SDK, you need to tell the engine where to find your updated content.json file. Since CE.SDK already includes a caption presets asset source with the ID "ly.img.caption.presets", we'll update this existing source rather than creating a new one. Set the base URL to point to your asset hosting location. CE.SDK automatically looks for `ly.img.caption.presets/content.json` relative to the base URL: ```typescript const config = { baseURL: 'https://your-server.com/assets/' }; CreativeEditorSDK.create('#cesdk_container', config).then(async (cesdk) => { // Caption presets load automatically from baseURL + 'ly.img.caption.presets/content.json' }); ``` Your custom presets will seamlessly integrate with any built-in presets and automatically appear in the caption presets panel in the UI. No additional source registration is needed when replacing the default presets. ## Troubleshooting ### Preset Not Loading - Verify `content.json` is accessible at expected URL - Check browser console for 404 errors on preset files - Ensure `mimeType` is set to `"application/ubq-blocks-string"` - Verify `{{base_url}}` placeholder is used correctly ### Preset Styles Not Applying - Ensure preset was serialized from a text block (not other block types) - Verify the serialized block contains styling properties - Check that property paths in `payload.properties` are correct ### Thumbnail Not Displaying - Verify thumbnail file exists at the `thumbUri` path - Check image format is PNG - Ensure CORS headers allow image loading ### Custom Colors Not Working - Verify `properties` array structure in content.json - Check property `type` is `"Color"` - Ensure `value` and `defaultValue` have correct RGBA format (0-1 range) ## API Reference | Method | Category | Purpose | | -------------------------------------------- | -------- | ------------------------------------------- | | `engine.block.create('text')` | Block | Create text block for preset styling | | `engine.block.saveToString(blocks)` | Block | Serialize styled block to preset format | | `engine.block.setColor(id, property, color)` | Block | Set color property (fill, background, etc.) | | `engine.block.setBackgroundColorEnabled()` | Block | Enable background color | | `engine.block.setDropShadowEnabled()` | Block | Enable drop shadow | | `engine.block.setFloat(id, property, value)` | Block | Set numeric properties (font size, etc.) | | `engine.block.setString(id, property, val)` | Block | Set string properties (text, font URI) | | `engine.asset.findAssets(sourceId, query)` | Asset | Find assets like typefaces | | `CreativeEngine.init(config)` | Engine | Initialize engine with base URL config | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit Image" description: "Use CE.SDK to crop, transform, annotate, or enhance images with editing tools and programmatic APIs." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image-c64912/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) --- --- ## Related Pages - [Image Editor SDK](https://img.ly/docs/cesdk/angular/edit-image/overview-5249ea/) - The CreativeEditor SDK provides a robust and user-friendly solution for photo and image editing. - [Replace Colors](https://img.ly/docs/cesdk/angular/edit-image/replace-colors-6ede17/) - Replace specific colors in images using CE.SDK's Recolor and Green Screen effects with programmatic control. - [Remove Background](https://img.ly/docs/cesdk/angular/edit-image/remove-bg-9dfcf7/) - Remove image backgrounds to isolate subjects or prepare assets for compositing and reuse. - [Vectorize](https://img.ly/docs/cesdk/angular/edit-image/vectorize-2b4c7f/) - Convert raster images into scalable vector graphics for flexible resizing and editing. - [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) - Crop, resize, rotate, scale, or flip images using CE.SDK's built-in transformation tools. - [Add Watermark](https://img.ly/docs/cesdk/angular/edit-image/add-watermark-679de0/) - Add text and image watermarks to protect images, indicate ownership, or add branding using CE.SDK. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Watermark" description: "Add text and image watermarks to protect images, indicate ownership, or add branding using CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/add-watermark-679de0/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Add Watermark](https://img.ly/docs/cesdk/angular/edit-image/add-watermark-679de0/) --- Add text and image watermarks to designs programmatically using CE.SDK's block API. ![Add Watermark example showing a design with text and logo watermarks](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-add-watermark-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-add-watermark-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-add-watermark-browser/) Watermarks protect intellectual property, indicate ownership, add branding, or mark content as drafts. CE.SDK supports two types of watermarks: **text watermarks** created from text blocks for copyright notices and brand names, and **image watermarks** created from graphic blocks with image fills for logos and symbols. ```typescript file=@cesdk_web_examples/guides-edit-image-add-watermark-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Add Watermark Guide * * Demonstrates adding text and image watermarks to designs: * - Creating text watermarks with custom styling * - Creating logo watermarks using graphic blocks * - Positioning watermarks side by side at the bottom * - Applying drop shadows for visibility * - Exporting watermarked designs */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Create a scene with custom page dimensions const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a gradient background for the page const gradientFill = engine.block.createFill('gradient/linear'); // Set a modern purple-to-cyan gradient engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.39, g: 0.4, b: 0.95, a: 1 }, stop: 0 }, // Indigo { color: { r: 0.02, g: 0.71, b: 0.83, a: 1 }, stop: 1 } // Cyan ]); // Set diagonal gradient direction (top-left to bottom-right) engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointY', 1); // Apply gradient to page engine.block.setFill(page, gradientFill); // Create a centered title text const titleText = engine.block.create('text'); engine.block.setString(titleText, 'text/text', 'Add Watermark'); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, titleText); // Style the title engine.block.setTextFontSize(titleText, 14); engine.block.setTextColor(titleText, { r: 1, g: 1, b: 1, a: 1 }); engine.block.setWidthMode(titleText, 'Auto'); engine.block.setHeightMode(titleText, 'Auto'); // Center the title on the page const titleWidth = engine.block.getFrameWidth(titleText); const titleHeight = engine.block.getFrameHeight(titleText); engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleText, (pageHeight - titleHeight) / 2); // Create a text block for the watermark const textWatermark = engine.block.create('text'); // Set the watermark text content engine.block.setString(textWatermark, 'text/text', '© 2024 img.ly'); // Left-align the text for the watermark engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Add the text block to the page engine.block.appendChild(page, textWatermark); // Set font size for the watermark engine.block.setTextFontSize(textWatermark, 4); // Set text color to white for contrast engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // Set opacity to make it semi-transparent engine.block.setOpacity(textWatermark, 0.8); // Set width mode to auto so text fits its content engine.block.setWidthMode(textWatermark, 'Auto'); engine.block.setHeightMode(textWatermark, 'Auto'); // Create a graphic block for the logo watermark const logoWatermark = engine.block.create('graphic'); // Create a rect shape for the logo const rectShape = engine.block.createShape('rect'); engine.block.setShape(logoWatermark, rectShape); // Create an image fill with a logo const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); // Apply the fill to the graphic block engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain the image within bounds engine.block.setContentFillMode(logoWatermark, 'Contain'); // Add to page engine.block.appendChild(page, logoWatermark); // Size the logo watermark const logoWidth = 80; const logoHeight = 50; engine.block.setWidth(logoWatermark, logoWidth); engine.block.setHeight(logoWatermark, logoHeight); // Set opacity for the logo watermark engine.block.setOpacity(logoWatermark, 0.8); // Position padding from edges const padding = 15; // Position text watermark at bottom-left engine.block.setPositionX(textWatermark, padding); engine.block.setPositionY(textWatermark, pageHeight - padding - 20); // Position logo watermark at top-right engine.block.setPositionX(logoWatermark, pageWidth - padding - logoWidth); engine.block.setPositionY(logoWatermark, padding); // Add drop shadow to text watermark for better visibility engine.block.setDropShadowEnabled(textWatermark, true); engine.block.setDropShadowOffsetX(textWatermark, 1); engine.block.setDropShadowOffsetY(textWatermark, 1); engine.block.setDropShadowBlurRadiusX(textWatermark, 2); engine.block.setDropShadowBlurRadiusY(textWatermark, 2); engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.5 }); // Add drop shadow to logo watermark engine.block.setDropShadowEnabled(logoWatermark, true); engine.block.setDropShadowOffsetX(logoWatermark, 1); engine.block.setDropShadowOffsetY(logoWatermark, 1); engine.block.setDropShadowBlurRadiusX(logoWatermark, 2); engine.block.setDropShadowBlurRadiusY(logoWatermark, 2); engine.block.setDropShadowColor(logoWatermark, { r: 0, g: 0, b: 0, a: 0.5 }); // Add export button to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-watermarked', label: 'Export', icon: '@imgly/Download', onClick: async () => { // Export the watermarked design const blob = await engine.block.export(page, { mimeType: 'image/png' }); // Download the watermarked image await cesdk.utils.downloadFile(blob, 'image/png'); } } ] }); // Zoom to fit the page in view with padding and enable auto-fit await engine.scene.zoomToBlock(page, { padding: 40 }); engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); } } export default Example; ``` This guide covers how to create text and logo watermarks, position them on a design, style them for visibility, and export the watermarked result. ## Setup and Prerequisites We start by initializing CE.SDK, loading asset sources, and creating a scene with a custom page size. The page provides the canvas where we'll add our watermarks. ```typescript highlight=highlight-setup const engine = cesdk.engine; // Create a scene with custom page dimensions const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); ``` We use `engine.scene.create('VerticalStack', {...})` to create a scene with custom page dimensions. The page dimensions are retrieved for positioning calculations later. ## Creating Text Watermarks Text watermarks display copyright notices, URLs, or brand names. We create a text block, set its content, and add it to the page. ```typescript highlight=highlight-create-text-watermark // Create a text block for the watermark const textWatermark = engine.block.create('text'); // Set the watermark text content engine.block.setString(textWatermark, 'text/text', '© 2024 img.ly'); // Left-align the text for the watermark engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Add the text block to the page engine.block.appendChild(page, textWatermark); ``` The `engine.block.create('text')` method creates a new text block. We set the text content using `engine.block.setString()` with the `'text/text'` property. ### Styling the Text We configure the font size, color, and opacity to make the watermark visible but unobtrusive. ```typescript highlight=highlight-style-text-watermark // Set font size for the watermark engine.block.setTextFontSize(textWatermark, 4); // Set text color to white for contrast engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // Set opacity to make it semi-transparent engine.block.setOpacity(textWatermark, 0.8); // Set width mode to auto so text fits its content engine.block.setWidthMode(textWatermark, 'Auto'); engine.block.setHeightMode(textWatermark, 'Auto'); ``` Key styling options: - **Font size** - Use sizes between 14-18px for subtle watermarks - **Text color** - White or black depending on the image background - **Opacity** - Values between 0.5-0.7 provide a balanced, semi-transparent appearance - **Size mode** - Set to `'Auto'` so the block automatically fits its text content ### Positioning the Watermarks We calculate the watermark positions based on the page dimensions and place the logo and text side-by-side at the bottom center of the page. ```typescript highlight=highlight-position-text-watermark // Position padding from edges const padding = 15; // Position text watermark at bottom-left engine.block.setPositionX(textWatermark, padding); engine.block.setPositionY(textWatermark, pageHeight - padding - 20); // Position logo watermark at top-right engine.block.setPositionX(logoWatermark, pageWidth - padding - logoWidth); engine.block.setPositionY(logoWatermark, padding); ``` We retrieve the rendered frame dimensions using `engine.block.getFrameWidth()` and `engine.block.getFrameHeight()`, calculate the total width of both watermarks with spacing, then center them horizontally. The vertical position places them near the bottom with padding from the edge. ## Creating Logo Watermarks Logo watermarks use graphic blocks with image fills to display brand symbols or company logos. ```typescript highlight=highlight-create-logo-watermark // Create a graphic block for the logo watermark const logoWatermark = engine.block.create('graphic'); // Create a rect shape for the logo const rectShape = engine.block.createShape('rect'); engine.block.setShape(logoWatermark, rectShape); // Create an image fill with a logo const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); // Apply the fill to the graphic block engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain the image within bounds engine.block.setContentFillMode(logoWatermark, 'Contain'); // Add to page engine.block.appendChild(page, logoWatermark); ``` We create a graphic block, assign a rect shape, then create an image fill with the logo URI. The fill is applied to the graphic block before adding it to the page. ### Sizing the Logo We set fixed dimensions for the logo and apply opacity to match the text watermark. ```typescript highlight=highlight-size-logo-watermark // Size the logo watermark const logoWidth = 80; const logoHeight = 50; engine.block.setWidth(logoWatermark, logoWidth); engine.block.setHeight(logoWatermark, logoHeight); // Set opacity for the logo watermark engine.block.setOpacity(logoWatermark, 0.8); ``` A good rule is to size logos to 10-20% of the page width. This keeps them visible without dominating the design. ## Enhancing Visibility with Drop Shadows Drop shadows improve watermark readability against varied backgrounds by adding contrast. ```typescript highlight=highlight-add-drop-shadow // Add drop shadow to text watermark for better visibility engine.block.setDropShadowEnabled(textWatermark, true); engine.block.setDropShadowOffsetX(textWatermark, 1); engine.block.setDropShadowOffsetY(textWatermark, 1); engine.block.setDropShadowBlurRadiusX(textWatermark, 2); engine.block.setDropShadowBlurRadiusY(textWatermark, 2); engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.5 }); // Add drop shadow to logo watermark engine.block.setDropShadowEnabled(logoWatermark, true); engine.block.setDropShadowOffsetX(logoWatermark, 1); engine.block.setDropShadowOffsetY(logoWatermark, 1); engine.block.setDropShadowBlurRadiusX(logoWatermark, 2); engine.block.setDropShadowBlurRadiusY(logoWatermark, 2); engine.block.setDropShadowColor(logoWatermark, { r: 0, g: 0, b: 0, a: 0.5 }); ``` Drop shadow parameters: - **Offset X/Y** - Distance from the block (2-4 pixels works well) - **Blur Radius X/Y** - Softness of the shadow (4-8 pixels for subtle effect) - **Color** - Black with 0.5 alpha provides soft contrast without being harsh ## Exporting Watermarked Images After adding watermarks, we add an export button to the navigation bar that downloads the watermarked image when clicked. ```typescript highlight=highlight-export-watermarked // Add export button to the navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-watermarked', label: 'Export', icon: '@imgly/Download', onClick: async () => { // Export the watermarked design const blob = await engine.block.export(page, { mimeType: 'image/png' }); // Download the watermarked image await cesdk.utils.downloadFile(blob, 'image/png'); } } ] }); ``` We use `cesdk.ui.insertOrderComponent()` to add a custom button to the editor's navigation bar. When clicked, `engine.block.export()` renders the page with all watermarks and returns a blob that `cesdk.utils.downloadFile()` downloads to the user's device. Supported formats include PNG, JPEG, and WebP. ## Troubleshooting **Watermark not visible** - Verify the block is within page bounds using position values - Check opacity is between 0.3-1.0 - Ensure `engine.block.appendChild()` was called to add the block to the page **Position appears incorrect** - Recalculate positions using current `pageWidth` and `pageHeight` - Account for watermark dimensions when calculating corner positions - Remember that coordinates start from the top-left corner **Text not legible** - Increase font size to at least 36px - Add a drop shadow for contrast against complex backgrounds - Increase opacity if the watermark is too faint **Logo quality issues** - Use a higher resolution source image for the logo - Avoid scaling the logo beyond its original dimensions ## API Reference | Method | Purpose | |--------|---------| | `engine.block.create(type)` | Create text or graphic blocks | | `engine.block.createShape(type)` | Create shapes for graphic blocks | | `engine.block.createFill(type)` | Create image fills for logos | | `engine.block.setString(id, property, value)` | Set text content or image URI | | `engine.block.setTextFontSize(id, size)` | Set text font size | | `engine.block.setTextColor(id, color)` | Set text color | | `engine.block.setOpacity(id, opacity)` | Set block transparency | | `engine.block.setPositionX(id, value)` | Set horizontal position | | `engine.block.setPositionY(id, value)` | Set vertical position | | `engine.block.setWidth(id, value)` | Set block width | | `engine.block.setHeight(id, value)` | Set block height | | `engine.block.getFrameWidth(id)` | Get rendered frame width | | `engine.block.getFrameHeight(id)` | Get rendered frame height | | `engine.block.setDropShadowEnabled(id, enabled)` | Enable drop shadow | | `engine.block.setDropShadowOffsetX(id, offset)` | Set shadow X offset | | `engine.block.setDropShadowOffsetY(id, offset)` | Set shadow Y offset | | `engine.block.setDropShadowBlurRadiusX(id, radius)` | Set shadow blur | | `engine.block.setDropShadowColor(id, color)` | Set shadow color | | `engine.block.export(id, options)` | Export block to blob | | `cesdk.ui.insertOrderComponent(options, component)` | Add custom button to navigation bar | | `cesdk.utils.downloadFile(blob, mimeType)` | Download blob as file | ## Next Steps - [Text Styling](https://img.ly/docs/cesdk/angular/text/styling-269c48/) — Style text blocks with fonts, colors, and effects - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) — Export options and formats for watermarked images - [Crop Images](https://img.ly/docs/cesdk/angular/edit-image/transform/crop-f67a47/) — Transform images before watermarking --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Image Editor SDK" description: "The CreativeEditor SDK provides a robust and user-friendly solution for photo and image editing." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/overview-5249ea/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Overview](https://img.ly/docs/cesdk/angular/edit-image/overview-5249ea/) --- The CreativeEditor SDK (CE.SDK) offers powerful image editing capabilities designed for seamless integration into your application. You can give your users full control through an intuitive user interface or implement fully automated workflows via the SDK’s programmatic API. Image editing with CE.SDK is fully client-side, ensuring fast performance, data privacy, and offline compatibility. Whether you're building a photo editor, design tool, or automation workflow, CE.SDK provides everything you need—plus the flexibility to integrate AI tools for tasks like adding or removing objects, swapping backgrounds, or creating variants. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Remove Background" description: "Remove image backgrounds to isolate subjects or prepare assets for compositing and reuse." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/remove-bg-9dfcf7/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Remove Background](https://img.ly/docs/cesdk/angular/edit-image/remove-bg-9dfcf7/) --- Remove image backgrounds to isolate subjects for compositing, product photography, or creating transparent overlays. ![Remove Background example showing an image with its background removed](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-remove-bg-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-remove-bg-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-remove-bg-browser/) The `@imgly/plugin-background-removal-web` plugin adds AI-powered background removal directly to the CE.SDK editor. Processing runs locally in the browser using WebAssembly and WebGPU, ensuring privacy since images never leave the client. ```typescript file=@cesdk_web_examples/guides-edit-image-remove-bg-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; import packageJson from './package.json'; /** * CE.SDK Browser Guide: Remove Background with Plugin * * Demonstrates adding and configuring the background removal plugin * for the CE.SDK editor with various UI placement options. */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); // Get page and set dimensions const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Add the background removal plugin with canvas menu button await cesdk.addPlugin( BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'] } }) ); // Create a gradient background (deep teal to soft purple) const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { stop: 0, color: { r: 0.08, g: 0.22, b: 0.35, a: 1 } }, // Deep teal { stop: 1, color: { r: 0.35, g: 0.2, b: 0.45, a: 1 } } // Soft purple ]); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointY', 1); engine.block.setFill(page, gradientFill); // Create centered title text const titleBlock = engine.block.create('text'); engine.block.setString(titleBlock, 'text/text', 'Remove Background'); engine.block.setFloat(titleBlock, 'text/fontSize', 140); engine.block.setEnum(titleBlock, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleBlock, pageWidth); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.appendChild(page, titleBlock); engine.block.setTextColor(titleBlock, { r: 1, g: 1, b: 1, a: 1 }); // Create image block with a portrait photo const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_4.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setContentFillMode(imageBlock, 'Cover'); const imageWidth = 202; const imageHeight = 230; engine.block.setWidth(imageBlock, imageWidth); engine.block.setHeight(imageBlock, imageHeight); engine.block.appendChild(page, imageBlock); // Create img.ly logo at bottom center const logoBlock = engine.block.create('graphic'); const logoShape = engine.block.createShape('rect'); engine.block.setShape(logoBlock, logoShape); const logoFill = engine.block.createFill('image'); engine.block.setString( logoFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logoBlock, logoFill); engine.block.setContentFillMode(logoBlock, 'Contain'); const logoWidth = 72; const logoHeight = 45; engine.block.setWidth(logoBlock, logoWidth); engine.block.setHeight(logoBlock, logoHeight); engine.block.setOpacity(logoBlock, 0.9); engine.block.appendChild(page, logoBlock); // Position elements const titleHeight = engine.block.getFrameHeight(titleBlock); const imageGap = 30; const padding = 20; // Calculate vertical layout - title and image centered const totalContentHeight = titleHeight + imageGap + imageHeight; const startY = (pageHeight - totalContentHeight) / 2; // Position title at top of content area engine.block.setPositionX(titleBlock, 0); engine.block.setPositionY(titleBlock, startY); // Position image centered below title engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2); engine.block.setPositionY(imageBlock, startY + titleHeight + imageGap); // Position logo at bottom center engine.block.setPositionX(logoBlock, (pageWidth - logoWidth) / 2); engine.block.setPositionY(logoBlock, pageHeight - logoHeight - padding); // Select the image to show the canvas menu with BG Removal button engine.block.select(imageBlock); // Zoom to fit await engine.scene.zoomToBlock(page, { padding: 40 }); engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); } } export default Example; ``` This guide covers installing the plugin, configuring UI placement, and customizing the background removal process. ## Installing the Plugin Install the background removal plugin and its ONNX runtime peer dependency: ```bash npm install @imgly/plugin-background-removal-web onnxruntime-web@1.21.0 ``` Import the plugin in your application: ```typescript highlight-import import BackgroundRemovalPlugin from '@imgly/plugin-background-removal-web'; ``` ## Initializing the Editor Set up the CE.SDK editor with asset sources before adding the plugin: ```typescript highlight-setup const engine = cesdk.engine; await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); ``` ## Adding the Plugin Add the plugin to the editor using `cesdk.addPlugin()`. The `ui.locations` option controls where the background removal button appears: ```typescript highlight-add-plugin // Add the background removal plugin with canvas menu button await cesdk.addPlugin( BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'] } }) ); ``` When a user selects an image and clicks the button, the plugin handles the entire workflow: exporting the image, processing it through the AI model, and applying the result back to the scene. ![A BG Removal button added to the Canvas Menu](./screenshot-bg-removal-button-v1.43.0.gif) > **Note:** The plugin requires a correctly configured upload setting. 'local' works for testing but production requires stable storage. See the [Upload Images](https://img.ly/docs/cesdk/angular/import-media/from-local-source/user-upload-c6c7d9/) guide for details. ## UI Placement Options The plugin supports multiple UI locations: | Location | Description | | --- | --- | | `'canvasMenu'` | Context menu when selecting an image on canvas | | `'dock'` | Panel in the left dock sidebar | | `'inspectorBar'` | Top bar of the inspector panel | | `'navigationBar'` | Main navigation bar | | `'canvasBarTop'` | Top bar above the canvas | | `'canvasBarBottom'` | Bottom bar below the canvas | You can specify a single location or an array: ```typescript BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu', 'dock'] } }) ``` ## Provider Configuration Configure the underlying background removal library through the `provider` option: ```typescript BackgroundRemovalPlugin({ ui: { locations: ['canvasMenu'] }, provider: { type: '@imgly/background-removal', configuration: { model: 'medium', // 'small' | 'medium' | 'large' output: { format: 'image/png', quality: 0.9 } } } }) ``` | Option | Type | Description | | --- | --- | --- | | `model` | `'small'` | `'medium'` | `'large'` | Model size for quality/speed trade-off | | `output.format` | string | Output format: `'image/png'`, `'image/webp'` | | `output.quality` | number | Quality for lossy formats (0-1) | The `'medium'` model provides the best balance of quality and speed. Use `'small'` for faster processing or `'large'` for maximum edge quality. ## Custom Provider For advanced use cases, implement a custom background removal provider: ```typescript BackgroundRemovalPlugin({ provider: { type: 'custom', processImageFileURI: async (imageFileURI: string) => { // Call your own background removal service const response = await fetch('/api/remove-background', { method: 'POST', body: JSON.stringify({ imageUrl: imageFileURI }) }); const { processedUrl } = await response.json(); return processedUrl; }, processSourceSet: async (sourceSet) => { // Handle multi-resolution source sets // Process the highest resolution and resize for others return sourceSet; } } }) ``` ## Creating an Image Block Add an image to the scene for background removal: ```typescript highlight-create-image // Create image block with a portrait photo const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_4.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setContentFillMode(imageBlock, 'Cover'); const imageWidth = 202; const imageHeight = 230; engine.block.setWidth(imageBlock, imageWidth); engine.block.setHeight(imageBlock, imageHeight); engine.block.appendChild(page, imageBlock); ``` Select the image block to display the canvas menu with the background removal button. ## Performance Considerations The first background removal operation downloads AI models (~40MB) which are then cached: - **Model caching**: First run fetches models; subsequent runs use the cache - **GPU acceleration**: WebGPU provides faster processing than WebGL fallback - **CORS headers**: For optimal performance, configure these headers on your server: ``` Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp ``` ## Troubleshooting | Issue | Solution | | --- | --- | | Model download slow | First run fetches models; subsequent runs use cache | | Poor edge quality | Use higher resolution input or 'medium'/'large' model | | Out of memory | Reduce image size before processing | | WebGL errors | Check browser WebGL support; try different device setting | | Plugin not showing | Verify plugin added and UI location configured | | Result not transparent | Ensure export uses PNG format (JPEG doesn't support transparency) | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Export options for images with transparency - [Vectorize Images](https://img.ly/docs/cesdk/angular/edit-image/vectorize-2b4c7f/) - Convert images to vector graphics - [Replace Colors](https://img.ly/docs/cesdk/angular/edit-image/replace-colors-6ede17/) - Replace specific colors in images --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Replace Colors" description: "Replace specific colors in images using CE.SDK's Recolor and Green Screen effects with programmatic control." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/replace-colors-6ede17/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Replace Colors](https://img.ly/docs/cesdk/angular/edit-image/replace-colors-6ede17/) --- Transform images by swapping specific colors using the Recolor effect or remove backgrounds with the Green Screen effect in CE.SDK. ![Replace Colors example showing color replacement and background removal](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-replace-colors-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-replace-colors-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-replace-colors-browser/) CE.SDK offers two color replacement effects. The **Recolor** effect swaps one color for another while preserving image details. The **Green Screen** effect removes background colors with transparency. Both effects provide precise control over color matching, edge smoothness, and intensity. ```typescript file=@cesdk_web_examples/guides-edit-image-replace-colors-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Replace Colors Guide * * Demonstrates color replacement using Recolor and Green Screen effects: * - Using the built-in effects UI * - Creating and applying Recolor effects * - Creating and applying Green Screen effects * - Configuring effect properties * - Managing multiple effects */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Enable effects in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Enable all effects including recolor and green screen // Calculate responsive grid layout for 6 examples const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Use sample images for demonstrations const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const blockSize = { width: blockWidth, height: blockHeight }; // Create a Recolor effect to swap red colors to blue const block1 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block1); const recolorEffect = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); // Red source color engine.block.setColor(recolorEffect, 'effect/recolor/toColor', { r: 0.0, g: 0.5, b: 1.0, a: 1.0 }); // Blue target color engine.block.appendEffect(block1, recolorEffect); // Select this block to show the effects panel engine.block.setSelected(block1, true); // Configure color matching precision for Recolor effect const block2 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block2); const recolorEffect2 = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', { r: 0.8, g: 0.6, b: 0.4, a: 1.0 }); // Skin tone source engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', { r: 0.3, g: 0.7, b: 0.3, a: 1.0 }); // Green tint // Adjust color match tolerance (0-1, higher = more inclusive) engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3); // Adjust brightness match tolerance engine.block.setFloat( recolorEffect2, 'effect/recolor/brightnessMatch', 0.2 ); // Adjust edge smoothness engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1); engine.block.appendEffect(block2, recolorEffect2); // Create a Green Screen effect to remove green backgrounds const block3 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block3); const greenScreenEffect = engine.block.createEffect('green_screen'); // Specify the color to remove (green) engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.appendEffect(block3, greenScreenEffect); // Fine-tune Green Screen removal parameters const block4 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block4); const greenScreenEffect2 = engine.block.createEffect('green_screen'); engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', { r: 0.2, g: 0.8, b: 0.3, a: 1.0 }); // Specific green shade // Adjust color match tolerance engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/colorMatch', 0.4 ); // Adjust edge smoothness for cleaner removal engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/smoothness', 0.2 ); // Reduce color spill from green background engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5); engine.block.appendEffect(block4, greenScreenEffect2); // Demonstrate managing multiple effects on a block const block5 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block5); // Add multiple effects to the same block const recolor1 = engine.block.createEffect('recolor'); engine.block.setColor(recolor1, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor1, 'effect/recolor/toColor', { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }); engine.block.appendEffect(block5, recolor1); const recolor2 = engine.block.createEffect('recolor'); engine.block.setColor(recolor2, 'effect/recolor/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor2, 'effect/recolor/toColor', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); engine.block.appendEffect(block5, recolor2); // Get all effects on the block const effects = engine.block.getEffects(block5); // eslint-disable-next-line no-console console.log('Number of effects:', effects.length); // 2 // Disable the first effect without removing it engine.block.setEffectEnabled(effects[0], false); // Check if effect is enabled const isEnabled = engine.block.isEffectEnabled(effects[0]); // eslint-disable-next-line no-console console.log('First effect enabled:', isEnabled); // false // Apply consistent color replacement across multiple blocks const block6 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block6); // Find all image blocks in the scene const allBlocks = engine.block.findByType('//ly.img.ubq/graphic'); // Apply a consistent recolor effect to each block allBlocks.forEach((blockId) => { // Skip if block already has effects if (engine.block.getEffects(blockId).length > 0) { return; } const batchRecolor = engine.block.createEffect('recolor'); engine.block.setColor(batchRecolor, 'effect/recolor/fromColor', { r: 0.8, g: 0.7, b: 0.6, a: 1.0 }); engine.block.setColor(batchRecolor, 'effect/recolor/toColor', { r: 0.6, g: 0.7, b: 0.9, a: 1.0 }); engine.block.setFloat(batchRecolor, 'effect/recolor/colorMatch', 0.25); engine.block.appendEffect(blockId, batchRecolor); }); // Position all blocks in a grid layout const blocks = [block1, block2, block3, block4, block5, block6]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Zoom to show all blocks engine.block.setSelected(block1, true); cesdk.engine.scene.zoomToBlock(page); } } export default Example; ``` This guide shows how to enable the built-in effects UI for interactive color replacement and apply effects programmatically using the block API. ## Using the Built-in Effects UI ### Enable Effects Panel We enable the effects feature using CE.SDK's Feature API. The effects panel appears in the inspector when users select a compatible graphic block. ```typescript highlight-enable-effects-panel // Enable effects in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Enable all effects including recolor and green screen ``` Enabling `ly.img.effect` makes all effect options available in the inspector panel, including Recolor and Green Screen. ### User Workflow With effects enabled, users can replace colors through the inspector panel: 1. **Select an image block** - Click an image or graphic block on the canvas 2. **Open inspector** - The inspector shows available options for the selected element 3. **Find effects section** - Scroll to the effects section 4. **Choose Recolor or Green Screen** - Click the desired effect 5. **Select colors** - Use the color picker to specify source and target colors 6. **Adjust parameters** - Fine-tune color matching, smoothness, and intensity 7. **Toggle effects** - Enable or disable effects to compare results Users can experiment with color replacements and see results immediately. ## Programmatic Color Replacement ### Initialize CE.SDK To apply color replacement programmatically, we set up CE.SDK with the proper configuration. ```typescript highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); ``` This initializes CE.SDK with the effects panel enabled, providing both UI and API access. ### Creating and Applying Recolor Effects The Recolor effect swaps one color for another throughout an image. We create a Recolor effect using `engine.block.createEffect('recolor')` and specify the source and target colors. ```typescript highlight-create-recolor-effect // Create a Recolor effect to swap red colors to blue const block1 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block1); const recolorEffect = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); // Red source color engine.block.setColor(recolorEffect, 'effect/recolor/toColor', { r: 0.0, g: 0.5, b: 1.0, a: 1.0 }); // Blue target color engine.block.appendEffect(block1, recolorEffect); // Select this block to show the effects panel engine.block.setSelected(block1, true); ``` The Recolor effect identifies pixels matching the source color (fromColor) and replaces them with the target color (toColor). Color values use RGBA format with values from 0.0 to 1.0. > **Tip:** The example code uses the `engine.block.addImage()` convenience API. This simplifies image block creation compared to manually constructing graphic blocks with image fills. ### Configuring Color Matching We adjust the matching tolerance and smoothness parameters to control how precisely colors must match before replacement. ```typescript highlight-configure-recolor-matching // Configure color matching precision for Recolor effect const block2 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block2); const recolorEffect2 = engine.block.createEffect('recolor'); engine.block.setColor(recolorEffect2, 'effect/recolor/fromColor', { r: 0.8, g: 0.6, b: 0.4, a: 1.0 }); // Skin tone source engine.block.setColor(recolorEffect2, 'effect/recolor/toColor', { r: 0.3, g: 0.7, b: 0.3, a: 1.0 }); // Green tint // Adjust color match tolerance (0-1, higher = more inclusive) engine.block.setFloat(recolorEffect2, 'effect/recolor/colorMatch', 0.3); // Adjust brightness match tolerance engine.block.setFloat( recolorEffect2, 'effect/recolor/brightnessMatch', 0.2 ); // Adjust edge smoothness engine.block.setFloat(recolorEffect2, 'effect/recolor/smoothness', 0.1); engine.block.appendEffect(block2, recolorEffect2); ``` The Recolor effect provides these parameters: - **colorMatch** (0-1) - How closely colors must match the source. Lower values match exact colors, higher values match broader ranges - **brightnessMatch** (0-1) - Tolerance for brightness variations - **smoothness** (0-1) - Edge blending to reduce artifacts These parameters help handle images where colors vary due to lighting, shadows, or compression. ### Creating and Applying Green Screen Effects The Green Screen effect removes backgrounds by making specific colors transparent. ```typescript highlight-create-green-screen-effect // Create a Green Screen effect to remove green backgrounds const block3 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block3); const greenScreenEffect = engine.block.createEffect('green_screen'); // Specify the color to remove (green) engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.appendEffect(block3, greenScreenEffect); ``` The Green Screen effect identifies pixels matching the specified color (fromColor) and makes them transparent. This works best with solid-color backgrounds like green screens or blue screens. ### Fine-Tuning Green Screen Removal We adjust the color matching tolerance, edge smoothness, and spill suppression parameters. ```typescript highlight-configure-green-screen // Fine-tune Green Screen removal parameters const block4 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block4); const greenScreenEffect2 = engine.block.createEffect('green_screen'); engine.block.setColor(greenScreenEffect2, 'effect/green_screen/fromColor', { r: 0.2, g: 0.8, b: 0.3, a: 1.0 }); // Specific green shade // Adjust color match tolerance engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/colorMatch', 0.4 ); // Adjust edge smoothness for cleaner removal engine.block.setFloat( greenScreenEffect2, 'effect/green_screen/smoothness', 0.2 ); // Reduce color spill from green background engine.block.setFloat(greenScreenEffect2, 'effect/green_screen/spill', 0.5); engine.block.appendEffect(block4, greenScreenEffect2); ``` The Green Screen effect provides these parameters: - **colorMatch** (0-1) - Tolerance for color variations in the background - **smoothness** (0-1) - Edge feathering for natural transitions - **spill** (0-1) - Reduces color spill from the background onto foreground objects These parameters help create clean composites without harsh edges or color artifacts. ## Managing Multiple Effects We can apply multiple color replacement effects to the same block. CE.SDK maintains an effect stack for each block, applying effects in the order they were added. ```typescript highlight-manage-effects // Demonstrate managing multiple effects on a block const block5 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block5); // Add multiple effects to the same block const recolor1 = engine.block.createEffect('recolor'); engine.block.setColor(recolor1, 'effect/recolor/fromColor', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor1, 'effect/recolor/toColor', { r: 0.0, g: 0.0, b: 1.0, a: 1.0 }); engine.block.appendEffect(block5, recolor1); const recolor2 = engine.block.createEffect('recolor'); engine.block.setColor(recolor2, 'effect/recolor/fromColor', { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }); engine.block.setColor(recolor2, 'effect/recolor/toColor', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); engine.block.appendEffect(block5, recolor2); // Get all effects on the block const effects = engine.block.getEffects(block5); // eslint-disable-next-line no-console console.log('Number of effects:', effects.length); // 2 // Disable the first effect without removing it engine.block.setEffectEnabled(effects[0], false); // Check if effect is enabled const isEnabled = engine.block.isEffectEnabled(effects[0]); // eslint-disable-next-line no-console console.log('First effect enabled:', isEnabled); // false ``` Effect management capabilities: - **Get effects** - Retrieve all effects with `engine.block.getEffects()` - **Enable/disable** - Toggle effects with `engine.block.setEffectEnabled()` without removing them - **Check status** - Query effect state with `engine.block.isEffectEnabled()` - **Remove effects** - Delete effects by index with `engine.block.removeEffect()` Disabling effects is useful for before/after comparisons or performance optimization. ## Batch Processing Multiple Images We can loop through all image blocks in a scene and apply the same effect configuration to each. ```typescript highlight-batch-processing // Apply consistent color replacement across multiple blocks const block6 = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, block6); // Find all image blocks in the scene const allBlocks = engine.block.findByType('//ly.img.ubq/graphic'); // Apply a consistent recolor effect to each block allBlocks.forEach((blockId) => { // Skip if block already has effects if (engine.block.getEffects(blockId).length > 0) { return; } const batchRecolor = engine.block.createEffect('recolor'); engine.block.setColor(batchRecolor, 'effect/recolor/fromColor', { r: 0.8, g: 0.7, b: 0.6, a: 1.0 }); engine.block.setColor(batchRecolor, 'effect/recolor/toColor', { r: 0.6, g: 0.7, b: 0.9, a: 1.0 }); engine.block.setFloat(batchRecolor, 'effect/recolor/colorMatch', 0.25); engine.block.appendEffect(blockId, batchRecolor); }); ``` Batch processing use cases: - **Product variations** - Generate multiple color variants - **Brand consistency** - Apply consistent color corrections - **Automated workflows** - Process multiple images with the same adjustments The `engine.block.findByType()` method locates all graphic blocks in the scene. ## Troubleshooting Common issues and solutions when working with color replacement effects: **Effect not visible** - Verify the effect is enabled with `engine.block.isEffectEnabled()` - Check that the effect is attached to the correct block using `engine.block.getEffects()` - Ensure the block type supports effects with `engine.block.supportsEffects()` **Wrong colors being replaced** - Decrease `colorMatch` for more precise matching - Increase `colorMatch` to capture broader color ranges - Adjust `brightnessMatch` for Recolor effects with lighting variations **Harsh edges or artifacts** - Increase `smoothness` to blend edges more gradually - For Green Screen, adjust `spill` to reduce color contamination - Use higher resolution images for smoother results **Performance issues** - Limit active effects on a single block - Use `engine.block.setEffectEnabled(false)` to disable effects during editing - Process effects sequentially rather than simultaneously ## API Reference | Method | Category | Purpose | |--------|----------|---------| | `engine.block.createEffect()` | Block | Create a new Recolor or Green Screen effect block | | `engine.block.appendEffect()` | Block | Attach an effect to an image block | | `engine.block.insertEffect()` | Block | Insert an effect at a specific position in the effect stack | | `engine.block.removeEffect()` | Block | Remove an effect from a block by index | | `engine.block.getEffects()` | Block | Get all effects attached to a block | | `engine.block.supportsEffects()` | Block | Check if a block can render effects | | `engine.block.setColor()` | Block | Set color properties on effect blocks | | `engine.block.getColor()` | Block | Get color properties from effect blocks | | `engine.block.setFloat()` | Block | Set numeric effect properties | | `engine.block.getFloat()` | Block | Get numeric effect properties | | `engine.block.setEffectEnabled()` | Block | Enable or disable an effect | | `engine.block.isEffectEnabled()` | Block | Check if an effect is enabled | | `engine.block.findByType()` | Block | Find all blocks of a specific type | ## Next Steps - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) — Explore other visual effects available in CE.SDK - [Export Designs](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) — Save your color-replaced images in various formats --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Transform" description: "Crop, resize, rotate, scale, or flip images using CE.SDK's built-in transformation tools." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) --- --- ## Related Pages - [Move Images](https://img.ly/docs/cesdk/angular/edit-image/transform/move-818dd9/) - Position images precisely on the canvas using absolute or percentage-based coordinates. - [Crop](https://img.ly/docs/cesdk/angular/edit-image/transform/crop-f67a47/) - Cut out specific areas of an image to focus on key content or change aspect ratio. - [Rotate Images](https://img.ly/docs/cesdk/angular/edit-image/transform/rotate-5f39c9/) - Rotate images to adjust orientation, correct crooked photos, or create creative effects using CE.SDK. - [Resize](https://img.ly/docs/cesdk/angular/edit-image/transform/resize-407242/) - Change image dimensions by setting explicit width and height values using absolute units, percentage-based sizing, or auto-sizing modes. - [Scale Images](https://img.ly/docs/cesdk/angular/edit-image/transform/scale-ebe367/) - Scale image blocks uniformly to preserve aspect ratio or non-uniformly to stretch along a single axis. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Crop" description: "Cut out specific areas of an image to focus on key content or change aspect ratio." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/transform/crop-f67a47/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) > [Crop](https://img.ly/docs/cesdk/angular/edit-image/transform/crop-f67a47/) --- Crop images to focus on key subjects, remove unwanted elements, or prepare visuals for specific formats like social media or print using CE.SDK's crop system. ![Crop images example showing CE.SDK editor with cropped image content](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-crop-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-crop-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-transform-crop-browser/) Image cropping in CreativeEditor SDK (CE.SDK) lets you select a region inside an image and discard everything outside that frame. Unlike resizing which changes overall dimensions uniformly, cropping removes unwanted areas while preserving original pixel quality in the selected region. ```typescript file=@cesdk_web_examples/guides-edit-image-transform-crop-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page from the scene const pages = engine.block.findByType('page'); const page = pages[0]; // Add an image using the convenience API const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { size: { width: 300, height: 200 } }); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 50); engine.block.setPositionY(imageBlock, 50); // Verify the block supports cropping before applying crop operations const supportsCrop = engine.block.supportsCrop(imageBlock); console.log('Block supports crop:', supportsCrop); // Check if the block supports content fill modes const supportsFillMode = engine.block.supportsContentFillMode(imageBlock); console.log('Supports content fill mode:', supportsFillMode); // Get the current content fill mode const currentMode = engine.block.getContentFillMode(imageBlock); console.log('Current fill mode:', currentMode); // Set content fill mode - options are 'Crop', 'Cover', 'Contain' // 'Cover' automatically scales and positions to fill the entire frame engine.block.setContentFillMode(imageBlock, 'Cover'); // Create another image block to demonstrate crop scaling const scaleBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, scaleBlock); engine.block.setPositionX(scaleBlock, 400); engine.block.setPositionY(scaleBlock, 50); // Set content fill mode to 'Crop' for manual control engine.block.setContentFillMode(scaleBlock, 'Crop'); // Scale the content within the crop frame // Values > 1 zoom in, values < 1 zoom out engine.block.setCropScaleX(scaleBlock, 1.5); engine.block.setCropScaleY(scaleBlock, 1.5); // Or use uniform scaling from center engine.block.setCropScaleRatio(scaleBlock, 1.2); // Get the current scale values const scaleX = engine.block.getCropScaleX(scaleBlock); const scaleY = engine.block.getCropScaleY(scaleBlock); const scaleRatio = engine.block.getCropScaleRatio(scaleBlock); console.log('Crop scale:', { scaleX, scaleY, scaleRatio }); // Pan the content within the crop frame using translation // Values are in percentage of the crop frame dimensions engine.block.setCropTranslationX(scaleBlock, 0.1); // Move 10% right engine.block.setCropTranslationY(scaleBlock, -0.1); // Move 10% up // Get the current translation values const translationX = engine.block.getCropTranslationX(scaleBlock); const translationY = engine.block.getCropTranslationY(scaleBlock); console.log('Crop translation:', { translationX, translationY }); // Ensure content covers the entire frame without gaps // The minScaleRatio parameter sets the minimum scale allowed const adjustedRatio = engine.block.adjustCropToFillFrame(scaleBlock, 1.0); console.log('Adjusted scale ratio:', adjustedRatio); // Create an image block to demonstrate crop rotation const rotateBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, rotateBlock); engine.block.setPositionX(rotateBlock, 50); engine.block.setPositionY(rotateBlock, 300); engine.block.setContentFillMode(rotateBlock, 'Crop'); // Rotate the content within the crop frame (in radians) // Math.PI / 4 = 45 degrees engine.block.setCropRotation(rotateBlock, Math.PI / 12); // Get the current rotation const rotation = engine.block.getCropRotation(rotateBlock); console.log('Crop rotation (radians):', rotation); // Ensure content still fills the frame after rotation engine.block.adjustCropToFillFrame(rotateBlock, 1.0); // Create an image block to demonstrate flipping const flipBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, flipBlock); engine.block.setPositionX(flipBlock, 300); engine.block.setPositionY(flipBlock, 300); engine.block.setContentFillMode(flipBlock, 'Crop'); // Flip the content horizontally engine.block.flipCropHorizontal(flipBlock); // Create an image block to demonstrate aspect ratio locking const lockBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, lockBlock); engine.block.setPositionX(lockBlock, 550); engine.block.setPositionY(lockBlock, 300); engine.block.setContentFillMode(lockBlock, 'Crop'); // Lock the crop aspect ratio - when locked, crop handles maintain // the current aspect ratio during resize operations engine.block.setCropAspectRatioLocked(lockBlock, true); // Check if aspect ratio is locked const isLocked = engine.block.isCropAspectRatioLocked(lockBlock); console.log('Aspect ratio locked:', isLocked); // Reset crop to default state (sets content fill mode to 'Cover') engine.block.resetCrop(lockBlock); // Select the first image block to show it in the UI engine.block.select(imageBlock); // Zoom to page for better visibility cesdk.engine.scene.zoomToBlock(page, 0.5, 0.5, 0.9); } } export default Example; ``` This guide covers both interactive cropping using the built-in UI and programmatic cropping using the engine API for automation workflows. ## Using the Built-in Crop UI The CE.SDK default UI provides an interactive crop tool that allows users to visually adjust crop areas with real-time feedback. ### User Workflow With the default UI configuration, users can crop images through a visual workflow: 1. **Select an image** - Click on any image block in the canvas 2. **Enter crop mode** - Double-click the image or click the crop icon in the toolbar 3. **Adjust the crop area** - Drag corners or edges to resize the visible region 4. **Choose aspect ratio** - Select a preset ratio if available (e.g., 1:1, 4:3, 16:9) 5. **Apply the crop** - Click "Done" or press Enter to apply changes The cropped image appears in your project, but the underlying original image preserves its values, even when you rotate or resize the cropped image. ### Enable Crop Features in Custom UI For developers building custom UIs, you can enable crop functionality using editor settings: ```typescript // Enable double-click to enter crop mode cesdk.editor.setSettingBool('doubleClickToCropEnabled', true); // Show crop handles on the control gizmo cesdk.editor.setSettingBool('controlGizmo/showCropHandles', true); // Show crop scale handles cesdk.editor.setSettingBool('controlGizmo/showCropScaleHandles', true); ``` ### Define Preset Aspect Ratios You can define available aspect ratios to guide user choices: ```typescript const aspectRatios = [ 'Free', // Unconstrained '1:1', // Square '4:5', // Instagram Portrait '16:9', // Widescreen 'Custom:3:2' // Custom ratios ]; cesdk.editor.setSettingString('ui/crop/aspectRatios', aspectRatios.join(',')); ``` > **Tip:** You can hide the aspect ratio selector entirely by setting `ui/crop/allowAspectRatioSelection` to `false`. ## Programmatic Cropping For automation, batch processing, or dynamic applications, you can control cropping entirely through the engine API. ### Check Crop Support Before applying crop operations, verify the block supports cropping using `engine.block.supportsCrop()`. Graphic blocks with image fills support cropping: ```typescript highlight-check-crop-support // Verify the block supports cropping before applying crop operations const supportsCrop = engine.block.supportsCrop(imageBlock); console.log('Block supports crop:', supportsCrop); ``` ### Content Fill Modes CE.SDK provides three content fill modes that control how images fit within their frame. Set the mode using `engine.block.setContentFillMode()`: ```typescript highlight-content-fill-mode // Check if the block supports content fill modes const supportsFillMode = engine.block.supportsContentFillMode(imageBlock); console.log('Supports content fill mode:', supportsFillMode); // Get the current content fill mode const currentMode = engine.block.getContentFillMode(imageBlock); console.log('Current fill mode:', currentMode); // Set content fill mode - options are 'Crop', 'Cover', 'Contain' // 'Cover' automatically scales and positions to fill the entire frame engine.block.setContentFillMode(imageBlock, 'Cover'); ``` The available modes are: - **Crop** - Manual control over the exact crop region using scale, translation, and rotation - **Cover** - Automatically scale and position content to fill the entire frame (no empty areas) - **Contain** - Automatically scale and position content to fit entirely within the frame (may show background) ### Scale Crop Scale the image content within its frame using crop scale APIs. Values greater than 1.0 zoom in, values less than 1.0 zoom out: ```typescript highlight-scale-crop // Create another image block to demonstrate crop scaling const scaleBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, scaleBlock); engine.block.setPositionX(scaleBlock, 400); engine.block.setPositionY(scaleBlock, 50); // Set content fill mode to 'Crop' for manual control engine.block.setContentFillMode(scaleBlock, 'Crop'); // Scale the content within the crop frame // Values > 1 zoom in, values < 1 zoom out engine.block.setCropScaleX(scaleBlock, 1.5); engine.block.setCropScaleY(scaleBlock, 1.5); // Or use uniform scaling from center engine.block.setCropScaleRatio(scaleBlock, 1.2); // Get the current scale values const scaleX = engine.block.getCropScaleX(scaleBlock); const scaleY = engine.block.getCropScaleY(scaleBlock); const scaleRatio = engine.block.getCropScaleRatio(scaleBlock); console.log('Crop scale:', { scaleX, scaleY, scaleRatio }); ``` Use `setCropScaleRatio()` for uniform scaling from the center, or `setCropScaleX()` and `setCropScaleY()` for non-uniform scaling. ### Translate Crop Pan the image content within the crop frame using translation. Values are percentages of the crop frame dimensions: ```typescript highlight-translate-crop // Pan the content within the crop frame using translation // Values are in percentage of the crop frame dimensions engine.block.setCropTranslationX(scaleBlock, 0.1); // Move 10% right engine.block.setCropTranslationY(scaleBlock, -0.1); // Move 10% up // Get the current translation values const translationX = engine.block.getCropTranslationX(scaleBlock); const translationY = engine.block.getCropTranslationY(scaleBlock); console.log('Crop translation:', { translationX, translationY }); ``` Positive X moves content right, positive Y moves content down. ### Fill Frame Adjust the crop to ensure content fills the frame without empty areas using `engine.block.adjustCropToFillFrame()`. The `minScaleRatio` parameter sets the minimum allowed scale: ```typescript highlight-fill-frame // Ensure content covers the entire frame without gaps // The minScaleRatio parameter sets the minimum scale allowed const adjustedRatio = engine.block.adjustCropToFillFrame(scaleBlock, 1.0); console.log('Adjusted scale ratio:', adjustedRatio); ``` This is useful after applying translations or rotations that might reveal empty areas. ### Rotate Crop Rotate the image content within its frame using `engine.block.setCropRotation()`. Rotation is specified in radians: ```typescript highlight-rotate-crop // Create an image block to demonstrate crop rotation const rotateBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, rotateBlock); engine.block.setPositionX(rotateBlock, 50); engine.block.setPositionY(rotateBlock, 300); engine.block.setContentFillMode(rotateBlock, 'Crop'); // Rotate the content within the crop frame (in radians) // Math.PI / 4 = 45 degrees engine.block.setCropRotation(rotateBlock, Math.PI / 12); // Get the current rotation const rotation = engine.block.getCropRotation(rotateBlock); console.log('Crop rotation (radians):', rotation); // Ensure content still fills the frame after rotation engine.block.adjustCropToFillFrame(rotateBlock, 1.0); ``` After rotation, call `adjustCropToFillFrame()` to ensure content still covers the frame. ### Flip Crop Flip the image content horizontally or vertically within its crop frame. This flips the content itself, not the block: ```typescript highlight-flip-crop // Create an image block to demonstrate flipping const flipBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, flipBlock); engine.block.setPositionX(flipBlock, 300); engine.block.setPositionY(flipBlock, 300); engine.block.setContentFillMode(flipBlock, 'Crop'); // Flip the content horizontally engine.block.flipCropHorizontal(flipBlock); ``` ### Lock Aspect Ratio Lock the crop aspect ratio during interactive editing using `engine.block.setCropAspectRatioLocked()`. When locked, crop handles maintain the current aspect ratio: ```typescript highlight-lock-aspect-ratio // Create an image block to demonstrate aspect ratio locking const lockBlock = await engine.block.addImage(imageUri, { size: { width: 200, height: 200 } }); engine.block.appendChild(page, lockBlock); engine.block.setPositionX(lockBlock, 550); engine.block.setPositionY(lockBlock, 300); engine.block.setContentFillMode(lockBlock, 'Crop'); // Lock the crop aspect ratio - when locked, crop handles maintain // the current aspect ratio during resize operations engine.block.setCropAspectRatioLocked(lockBlock, true); // Check if aspect ratio is locked const isLocked = engine.block.isCropAspectRatioLocked(lockBlock); console.log('Aspect ratio locked:', isLocked); ``` ### Reset Crop Reset all crop transformations to their default state using `engine.block.resetCrop()`: ```typescript highlight-reset-crop // Reset crop to default state (sets content fill mode to 'Cover') engine.block.resetCrop(lockBlock); ``` ## Coordinate System Crop transforms use normalized values: | Property | Value Type | Description | | --- | --- | --- | | Scale | Float (0.0+) | 1.0 is original size, 2.0 is double, 0.5 is half | | Translation | Float (-1.0 to 1.0) | Percentage of frame dimensions | | Rotation | Float (radians) | Math.PI = 180°, Math.PI/2 = 90° | All crop values are independent of canvas zoom level. ## Combining Crop with Other Transforms You can combine crop operations with other block transforms like position, rotation, and scale. Crop transforms affect the content within the block, while block transforms affect the block itself on the canvas: ```typescript // Crop the content (scales/pans the image within its frame) engine.block.setCropScaleRatio(imageBlock, 1.5); engine.block.setCropRotation(imageBlock, Math.PI / 12); // Transform the block itself (moves/rotates the entire block on canvas) engine.block.setRotation(imageBlock, Math.PI / 6); engine.block.setWidth(imageBlock, 400); ``` ## Troubleshooting | Issue | Cause / Fix | |-------|-------------| | Crop functions throw error | Verify block supports crop with `supportsCrop()` | | Black bars after scaling | Call `adjustCropToFillFrame()` or increase scale ratio | | Content not filling frame | Check content fill mode - use 'Cover' for automatic fill | | Unexpected crop behavior | Ensure content fill mode is set to 'Crop' for manual control | | Crop handles not visible in UI | Enable with `cesdk.editor.setSettingBool('controlGizmo/showCropHandles', true)` | ## API Reference | Method | Description | |--------|-------------| | `block.supportsCrop(id)` | Check if a block supports cropping | | `block.supportsContentFillMode(id)` | Check if block supports fill modes | | `block.setContentFillMode(id, mode)` | Set content fill mode (Crop, Cover, Contain) | | `block.getContentFillMode(id)` | Get current content fill mode | | `block.setCropScaleRatio(id, ratio)` | Set uniform crop scale from center | | `block.setCropScaleX(id, scaleX)` | Set horizontal crop scale | | `block.setCropScaleY(id, scaleY)` | Set vertical crop scale | | `block.setCropTranslationX(id, x)` | Set horizontal crop translation | | `block.setCropTranslationY(id, y)` | Set vertical crop translation | | `block.setCropRotation(id, rotation)` | Set crop rotation in radians | | `block.getCropScaleRatio(id)` | Get uniform crop scale ratio | | `block.getCropScaleX(id)` | Get horizontal crop scale | | `block.getCropScaleY(id)` | Get vertical crop scale | | `block.getCropTranslationX(id)` | Get horizontal crop translation | | `block.getCropTranslationY(id)` | Get vertical crop translation | | `block.getCropRotation(id)` | Get crop rotation in radians | | `block.adjustCropToFillFrame(id, minRatio)` | Adjust crop to fill frame | | `block.flipCropHorizontal(id)` | Flip content horizontally | | `block.flipCropVertical(id)` | Flip content vertically | | `block.setCropAspectRatioLocked(id, locked)` | Lock/unlock aspect ratio | | `block.isCropAspectRatioLocked(id)` | Check if aspect ratio is locked | | `block.resetCrop(id)` | Reset crop to default state | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Move Images" description: "Position images precisely on the canvas using absolute or percentage-based coordinates." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/transform/move-818dd9/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) > [Move](https://img.ly/docs/cesdk/angular/edit-image/transform/move-818dd9/) --- Position images on the canvas using absolute pixel coordinates or percentage-based positioning for responsive layouts. ![Move images example showing positioned images with labels](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-move-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-move-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-transform-move-browser/) Position images on the canvas using coordinates that start at the top-left corner (0, 0). X increases right, Y increases down. Values are relative to the parent block, simplifying nested layouts. ```typescript file=@cesdk_web_examples/guides-edit-image-transform-move-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; class Example implements EditorPlugin { name = 'guides-edit-image-transform-move-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Demo 1: Movable Image - Can be freely repositioned by user const movableImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 200, height: 200 } } ); engine.block.appendChild(page, movableImage); engine.block.setPositionX(movableImage, 0); engine.block.setPositionY(movableImage, 100); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Movable'); engine.block.setFloat(text1, 'text/fontSize', 32); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 200); engine.block.setPositionX(text1, 50); engine.block.setPositionY(text1, 360); engine.block.appendChild(page, text1); // Demo 2: Percentage Positioning - Responsive layout const percentImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_5.jpg', { size: { width: 200, height: 200 } } ); engine.block.appendChild(page, percentImage); // Set position mode to percentage (0.0 to 1.0) engine.block.setPositionXMode(percentImage, 'Percent'); engine.block.setPositionYMode(percentImage, 'Percent'); // Position at 37.5% from left (300px), 30% from top (150px) engine.block.setPositionX(percentImage, 0.375); engine.block.setPositionY(percentImage, 0.3); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', 'Percentage'); engine.block.setFloat(text2, 'text/fontSize', 32); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 200); engine.block.setPositionX(text2, 300); engine.block.setPositionY(text2, 360); engine.block.appendChild(page, text2); // Demo 3: Locked Image - Cannot be moved, rotated, or scaled const lockedImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: { width: 200, height: 200 } } ); engine.block.appendChild(page, lockedImage); engine.block.setPositionX(lockedImage, 550); engine.block.setPositionY(lockedImage, 150); // Lock the transform to prevent user interaction engine.block.setBool(lockedImage, 'transformLocked', true); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Locked'); engine.block.setFloat(text3, 'text/fontSize', 32); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 200); engine.block.setPositionX(text3, 550); engine.block.setPositionY(text3, 360); engine.block.appendChild(page, text3); // Get current position values const currentX = engine.block.getPositionX(movableImage); const currentY = engine.block.getPositionY(movableImage); console.log('Current position:', currentX, currentY); // Move relative to current position const offsetX = engine.block.getPositionX(movableImage); const offsetY = engine.block.getPositionY(movableImage); engine.block.setPositionX(movableImage, offsetX + 50); engine.block.setPositionY(movableImage, offsetY + 50); } } export default Example; ``` This guide covers positioning images with absolute or percentage coordinates, configuring position modes, and locking transforms to prevent repositioning. ## Position Coordinates Coordinates originate at the top-left (0, 0) of the parent container. Use **absolute** mode for fixed pixel values or **percentage** mode (0.0 to 1.0) for responsive layouts that adapt to parent size changes. ## Positioning Images Position images using `engine.block.setPositionX()` and `engine.block.setPositionY()` with absolute pixel coordinates: ```typescript highlight-movable-image engine.block.appendChild(page, movableImage); engine.block.setPositionX(movableImage, 0); engine.block.setPositionY(movableImage, 100); ``` ## Getting Current Position Read current position values using `engine.block.getPositionX()` and `engine.block.getPositionY()`. Values are returned in the current position mode (absolute pixels or percentage 0.0-1.0): ```typescript highlight-get-position // Get current position values const currentX = engine.block.getPositionX(movableImage); const currentY = engine.block.getPositionY(movableImage); ``` ## Configuring Position Modes Control how position values are interpreted using `engine.block.setPositionXMode()` and `engine.block.setPositionYMode()`. Set to `'Absolute'` for pixels or `'Percent'` for percentage values (0.0 to 1.0). Check the current mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. The Percentage Positioning section below demonstrates setting these modes. ## Percentage Positioning Position images using percentage values (0.0 to 1.0) for responsive layouts. Set the position mode to `'Percent'`, then use values between 0.0 and 1.0: ```typescript highlight-percentage-positioning // Set position mode to percentage (0.0 to 1.0) engine.block.setPositionXMode(percentImage, 'Percent'); engine.block.setPositionYMode(percentImage, 'Percent'); ``` Percentage positioning adapts automatically when the parent block dimensions change, maintaining relative positions in responsive designs. ## Relative Positioning Move images relative to their current position by getting the current coordinates and adding offset values: ```typescript highlight-relative-positioning // Move relative to current position const offsetX = engine.block.getPositionX(movableImage); const offsetY = engine.block.getPositionY(movableImage); engine.block.setPositionX(movableImage, offsetX + 50); engine.block.setPositionY(movableImage, offsetY + 50); ``` ## Locking Transforms Lock transforms to prevent repositioning, rotation, and scaling by setting `transformLocked` to true: ```typescript highlight-locked-image // Lock the transform to prevent user interaction engine.block.setBool(lockedImage, 'transformLocked', true); ``` ## Troubleshooting ### Image Not Moving Check if transforms are locked using `engine.block.getBool(block, 'transformLocked')`. Ensure the image block exists and values are within parent bounds. ### Unexpected Position Values Check position mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. Verify if using absolute (pixels) vs percentage (0.0-1.0) values. Review parent block dimensions if using percentage positioning. ### Positioned Outside Visible Area Verify parent block dimensions and boundaries. Check coordinate system: origin is top-left, not center. Review X/Y values for calculation errors. ### Percentage Positioning Not Responsive Ensure position mode is set to `'Percent'` using `engine.block.setPositionXMode(block, 'Percent')`. Verify percentage values are between 0.0 and 1.0. Check that parent block dimensions can change. ## API Reference | Method | Description | | ------------------------------------- | ------------------------------------------ | | `engine.block.addImage()` | Create and position image in one operation | | `engine.block.setPositionX()` | Set X coordinate value | | `engine.block.setPositionY()` | Set Y coordinate value | | `engine.block.getPositionX()` | Get current X coordinate value | | `engine.block.getPositionY()` | Get current Y coordinate value | | `engine.block.setPositionXMode()` | Set position mode for X coordinate | | `engine.block.setPositionYMode()` | Set position mode for Y coordinate | | `engine.block.getPositionXMode()` | Get position mode for X coordinate | | `engine.block.getPositionYMode()` | Get position mode for Y coordinate | | `engine.block.setBool()` | Set transform lock state | | `engine.block.getBool()` | Get transform lock state | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Resize" description: "Change image dimensions by setting explicit width and height values using absolute units, percentage-based sizing, or auto-sizing modes." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/transform/resize-407242/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) > [Resize](https://img.ly/docs/cesdk/angular/edit-image/transform/resize-407242/) --- Change image dimensions using absolute pixel values, percentage-based sizing for responsive layouts, or auto-sizing based on content. ![Resize images example showing different sizing modes](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-resize-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-resize-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-transform-resize-browser/) Image resizing changes actual dimensions rather than applying scale multipliers. Use `engine.block.setWidth()` and `engine.block.setHeight()` for individual dimensions, or `engine.block.setSize()` for both at once. ```typescript file=@cesdk_web_examples/guides-edit-image-transform-resize-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; class Example implements EditorPlugin { name = 'guides-edit-image-transform-resize-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Demo 1: Absolute Sizing - Fixed dimensions const absoluteImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 180, height: 180 } } ); engine.block.appendChild(page, absoluteImage); engine.block.setPositionX(absoluteImage, 20); engine.block.setPositionY(absoluteImage, 80); // Set explicit dimensions using setSize engine.block.setSize(absoluteImage, 180, 180, { sizeMode: 'Absolute' }); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Absolute'); engine.block.setFloat(text1, 'text/fontSize', 28); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 180); engine.block.setPositionX(text1, 20); engine.block.setPositionY(text1, 280); engine.block.appendChild(page, text1); // Demo 2: Percentage Sizing - Responsive layout const percentImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_5.jpg', { size: { width: 180, height: 180 } } ); engine.block.appendChild(page, percentImage); engine.block.setPositionX(percentImage, 220); engine.block.setPositionY(percentImage, 80); // Set size mode to percentage for responsive sizing engine.block.setWidthMode(percentImage, 'Percent'); engine.block.setHeightMode(percentImage, 'Percent'); // Values 0.0 to 1.0 represent percentage of parent engine.block.setWidth(percentImage, 0.225); engine.block.setHeight(percentImage, 0.36); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', 'Percentage'); engine.block.setFloat(text2, 'text/fontSize', 28); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 180); engine.block.setPositionX(text2, 220); engine.block.setPositionY(text2, 280); engine.block.appendChild(page, text2); // Demo 3: Resized with maintainCrop const cropImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: { width: 180, height: 180 } } ); engine.block.appendChild(page, cropImage); engine.block.setPositionX(cropImage, 420); engine.block.setPositionY(cropImage, 80); // Resize while preserving crop settings engine.block.setWidth(cropImage, 180, true); engine.block.setHeight(cropImage, 180, true); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Maintain Crop'); engine.block.setFloat(text3, 'text/fontSize', 28); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 180); engine.block.setPositionX(text3, 420); engine.block.setPositionY(text3, 280); engine.block.appendChild(page, text3); // Get current dimensions const currentWidth = engine.block.getWidth(absoluteImage); const currentHeight = engine.block.getHeight(absoluteImage); const widthMode = engine.block.getWidthMode(absoluteImage); const heightMode = engine.block.getHeightMode(absoluteImage); console.log('Current dimensions:', currentWidth, 'x', currentHeight); console.log('Size modes:', widthMode, heightMode); // Get calculated frame dimensions after layout const frameWidth = engine.block.getFrameWidth(absoluteImage); const frameHeight = engine.block.getFrameHeight(absoluteImage); console.log('Frame dimensions:', frameWidth, 'x', frameHeight); // Title text at top const titleText = engine.block.create('text'); engine.block.setString(titleText, 'text/text', 'Image Resize Examples'); engine.block.setFloat(titleText, 'text/fontSize', 36); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleText, 800); engine.block.setPositionX(titleText, 0); engine.block.setPositionY(titleText, 20); engine.block.appendChild(page, titleText); } } export default Example; ``` This guide covers resizing images with absolute or percentage sizing, configuring size modes, and maintaining crop settings during resize. ## Understanding Size Modes Size values are interpreted in three modes. 'Absolute' uses fixed design units, 'Percent' uses parent-relative values (0.0-1.0), and 'Auto' sizes based on content. Use `engine.block.getWidth()` for the configured value and `engine.block.getFrameWidth()` for actual rendered size after layout. ## Setting Absolute Dimensions Set explicit dimensions using `engine.block.setSize()` with absolute pixel values: ```typescript highlight-set-size // Set explicit dimensions using setSize engine.block.setSize(absoluteImage, 180, 180, { sizeMode: 'Absolute' }); ``` ## Percentage Sizing Use percentage mode for responsive sizing. Values range from 0.0 to 1.0 representing percentage of parent container: ```typescript highlight-percentage-mode // Set size mode to percentage for responsive sizing engine.block.setWidthMode(percentImage, 'Percent'); engine.block.setHeightMode(percentImage, 'Percent'); // Values 0.0 to 1.0 represent percentage of parent engine.block.setWidth(percentImage, 0.225); engine.block.setHeight(percentImage, 0.36); ``` Percentage sizing adapts automatically when the parent block dimensions change, maintaining relative sizes in responsive designs. ## Maintaining Crop During Resize Use the `maintainCrop` parameter to preserve existing crop settings when resizing: ```typescript highlight-maintain-crop // Resize while preserving crop settings engine.block.setWidth(cropImage, 180, true); engine.block.setHeight(cropImage, 180, true); ``` Setting `maintainCrop` to `true` automatically adjusts crop values to preserve the visible area. ## Getting Current Dimensions Read current configured dimensions and size modes: ```typescript highlight-get-dimensions // Get current dimensions const currentWidth = engine.block.getWidth(absoluteImage); const currentHeight = engine.block.getHeight(absoluteImage); const widthMode = engine.block.getWidthMode(absoluteImage); const heightMode = engine.block.getHeightMode(absoluteImage); ``` ## Getting Frame Dimensions Get calculated frame dimensions after layout: ```typescript highlight-frame-dimensions // Get calculated frame dimensions after layout const frameWidth = engine.block.getFrameWidth(absoluteImage); const frameHeight = engine.block.getFrameHeight(absoluteImage); ``` The difference between configured values and frame dimensions matters when using percentage or auto sizing modes. ## Troubleshooting ### Image Not Resizing Check if locked using `engine.block.getBool(block, 'constraints/size/locked')`. Verify size constraints aren't limiting changes. Ensure the block exists and confirm correct units for the size mode. ### Unexpected Size Values Check mode using `engine.block.getWidthMode()` and `engine.block.getHeightMode()`. Verify absolute (design units) vs percentage (0.0-1.0) values. For percentage mode, review parent block dimensions. ### Image Appears Stretched Calculate and set both dimensions proportionally. Use `maintainCrop: true` when resizing cropped images. Check `scene/aspectRatioLock` for scenes. ## API Reference | Method | Description | | ----------------------------------- | ---------------------------------------- | | `engine.block.addImage()` | Create and size image in one operation | | `engine.block.setSize()` | Set width and height with optional mode | | `engine.block.setWidth()` | Set width value | | `engine.block.setHeight()` | Set height value | | `engine.block.getWidth()` | Get current width value | | `engine.block.getHeight()` | Get current height value | | `engine.block.setWidthMode()` | Set width interpretation mode | | `engine.block.setHeightMode()` | Set height interpretation mode | | `engine.block.getWidthMode()` | Get width interpretation mode | | `engine.block.getHeightMode()` | Get height interpretation mode | | `engine.block.getFrameWidth()` | Get calculated frame width | | `engine.block.getFrameHeight()` | Get calculated frame height | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Rotate Images" description: "Rotate images to adjust orientation, correct crooked photos, or create creative effects using CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/transform/rotate-5f39c9/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) > [Rotate](https://img.ly/docs/cesdk/angular/edit-image/transform/rotate-5f39c9/) --- Rotate images to adjust orientation, correct crooked photos, or create creative effects using CE.SDK's rotation APIs. ![Rotate images example showing images at different rotation angles](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-rotate-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-rotate-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-transform-rotate-browser/) Rotation uses radians where `Math.PI / 2` equals 90°, `Math.PI` equals 180°, and negative values rotate clockwise. Values are relative to the block's center point. ```typescript file=@cesdk_web_examples/guides-edit-image-transform-rotate-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; class Example implements EditorPlugin { name = 'guides-edit-image-transform-rotate-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Demo 1: Original image (no rotation) const originalImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, originalImage); engine.block.setPositionX(originalImage, 50); engine.block.setPositionY(originalImage, 50); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Original'); engine.block.setFloat(text1, 'text/fontSize', 24); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 150); engine.block.setPositionX(text1, 50); engine.block.setPositionY(text1, 210); engine.block.appendChild(page, text1); // Demo 2: Rotate 45 degrees const rotated45Image = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, rotated45Image); engine.block.setPositionX(rotated45Image, 225); engine.block.setPositionY(rotated45Image, 50); // Rotate the block by 45 degrees (π/4 radians) engine.block.setRotation(rotated45Image, Math.PI / 4); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', '45°'); engine.block.setFloat(text2, 'text/fontSize', 24); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 150); engine.block.setPositionX(text2, 225); engine.block.setPositionY(text2, 210); engine.block.appendChild(page, text2); // Demo 3: Rotate 90 degrees const rotated90Image = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, rotated90Image); engine.block.setPositionX(rotated90Image, 400); engine.block.setPositionY(rotated90Image, 50); // Rotate the block by 90 degrees (π/2 radians) engine.block.setRotation(rotated90Image, Math.PI / 2); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', '90°'); engine.block.setFloat(text3, 'text/fontSize', 24); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 150); engine.block.setPositionX(text3, 400); engine.block.setPositionY(text3, 210); engine.block.appendChild(page, text3); // Demo 4: Rotate 180 degrees const rotated180Image = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, rotated180Image); engine.block.setPositionX(rotated180Image, 575); engine.block.setPositionY(rotated180Image, 50); // Rotate the block by 180 degrees (π radians) engine.block.setRotation(rotated180Image, Math.PI); const text4 = engine.block.create('text'); engine.block.setString(text4, 'text/text', '180°'); engine.block.setFloat(text4, 'text/fontSize', 24); engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text4, 150); engine.block.setPositionX(text4, 575); engine.block.setPositionY(text4, 210); engine.block.appendChild(page, text4); // Demo 5: Grouped rotation const groupedImage1 = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_5.jpg', { size: { width: 100, height: 100 } } ); engine.block.appendChild(page, groupedImage1); engine.block.setPositionX(groupedImage1, 150); engine.block.setPositionY(groupedImage1, 300); const groupedImage2 = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: { width: 100, height: 100 } } ); engine.block.appendChild(page, groupedImage2); engine.block.setPositionX(groupedImage2, 260); engine.block.setPositionY(groupedImage2, 300); // Group blocks and rotate them together const groupId = engine.block.group([groupedImage1, groupedImage2]); engine.block.setRotation(groupId, Math.PI / 8); const text5 = engine.block.create('text'); engine.block.setString(text5, 'text/text', 'Grouped'); engine.block.setFloat(text5, 'text/fontSize', 24); engine.block.setEnum(text5, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text5, 200); engine.block.setPositionX(text5, 150); engine.block.setPositionY(text5, 440); engine.block.appendChild(page, text5); // Demo 6: Locked rotation const lockedImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, lockedImage); engine.block.setPositionX(lockedImage, 500); engine.block.setPositionY(lockedImage, 300); engine.block.setRotation(lockedImage, Math.PI / 6); // Lock rotation for a single block engine.block.setScopeEnabled(lockedImage, 'layer/rotate', false); const text6 = engine.block.create('text'); engine.block.setString(text6, 'text/text', 'Locked'); engine.block.setFloat(text6, 'text/fontSize', 24); engine.block.setEnum(text6, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text6, 150); engine.block.setPositionX(text6, 500); engine.block.setPositionY(text6, 460); engine.block.appendChild(page, text6); // Get current rotation value const currentRotation = engine.block.getRotation(rotated45Image); console.log('Current rotation (radians):', currentRotation); console.log( 'Current rotation (degrees):', (currentRotation * 180) / Math.PI ); // Helpers for degree/radian conversion const toRadians = (degrees: number) => (degrees * Math.PI) / 180; const toDegrees = (radians: number) => (radians * 180) / Math.PI; // Example: rotate by 30 degrees using helper const targetRadians = toRadians(30); console.log('30 degrees in radians:', targetRadians); console.log('Converted back to degrees:', toDegrees(targetRadians)); } } export default Example; ``` This guide covers rotating images by specific angles, reading rotation values, converting between degrees and radians, rotating grouped elements together, and locking rotation on blocks. ## Initialize the Editor Set up the editor with default assets and create a design scene: ```typescript highlight=highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 500, unit: 'Pixel' } }); ``` ## Rotate an Image Rotate blocks using `engine.block.setRotation()` with angle values in radians. Use `Math.PI` for 180° or divide for smaller increments: ```typescript highlight=highlight-rotate-45 // Rotate the block by 45 degrees (π/4 radians) engine.block.setRotation(rotated45Image, Math.PI / 4); ``` ## Rotate by 90 Degrees Rotate a block by 90 degrees using `Math.PI / 2`: ```typescript highlight=highlight-rotate-90 // Rotate the block by 90 degrees (π/2 radians) engine.block.setRotation(rotated90Image, Math.PI / 2); ``` ## Rotate by 180 Degrees Flip a block upside down by rotating 180 degrees using `Math.PI`: ```typescript highlight=highlight-rotate-180 // Rotate the block by 180 degrees (π radians) engine.block.setRotation(rotated180Image, Math.PI); ``` ## Get Current Rotation Read the current rotation value using `engine.block.getRotation()`. The returned value is in radians: ```typescript highlight=highlight-get-rotation // Get current rotation value const currentRotation = engine.block.getRotation(rotated45Image); console.log('Current rotation (radians):', currentRotation); console.log( 'Current rotation (degrees):', (currentRotation * 180) / Math.PI ); ``` ## Convert Between Degrees and Radians Create helper functions to convert between degrees and radians for more intuitive angle values: ```typescript highlight=highlight-convert-radians // Helpers for degree/radian conversion const toRadians = (degrees: number) => (degrees * Math.PI) / 180; const toDegrees = (radians: number) => (radians * 180) / Math.PI; // Example: rotate by 30 degrees using helper const targetRadians = toRadians(30); console.log('30 degrees in radians:', targetRadians); console.log('Converted back to degrees:', toDegrees(targetRadians)); ``` ## Rotate Groups Together Group multiple blocks and rotate them as a unit to maintain their relative positions: ```typescript highlight=highlight-rotate-group // Group blocks and rotate them together const groupId = engine.block.group([groupedImage1, groupedImage2]); engine.block.setRotation(groupId, Math.PI / 8); ``` ## Lock Rotation Disable rotation for a specific block using `engine.block.setScopeEnabled()` with the `layer/rotate` scope: ```typescript highlight=highlight-lock-rotation // Lock rotation for a single block engine.block.setScopeEnabled(lockedImage, 'layer/rotate', false); ``` ## Troubleshooting ### Rotation Has No Effect Ensure the block exists and is appended to a page before calling `setRotation()`. Verify the block ID is valid using `engine.block.isValid()`. ### Unexpected Rotation Direction Positive values rotate counterclockwise, negative values rotate clockwise. Double-check your angle calculation if the rotation appears inverted. ### Block Appears Skewed After Rotation Rotation uses the block's center as the pivot point. If the block appears off-center, check that no unexpected scaling or positioning was applied. ### Locked Block Won't Rotate Check if the block's `layer/rotate` scope is disabled using `engine.block.isScopeEnabled()`. Re-enable with `engine.block.setScopeEnabled(block, 'layer/rotate', true)`. ## API Reference | Method | Description | | ---------------------------------- | ------------------------------------------ | | `engine.block.setRotation()` | Set rotation angle in radians | | `engine.block.getRotation()` | Get current rotation angle in radians | | `engine.block.group()` | Group blocks for collective transforms | | `engine.block.setScopeEnabled()` | Enable or disable specific block scopes | | `engine.block.isScopeEnabled()` | Check if a scope is enabled for a block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Scale Images" description: "Scale image blocks uniformly to preserve aspect ratio or non-uniformly to stretch along a single axis." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/transform/scale-ebe367/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/angular/edit-image/transform-9d189b/) > [Scale](https://img.ly/docs/cesdk/angular/edit-image/transform/scale-ebe367/) --- Scale images proportionally with `engine.block.scale()` using configurable anchor points, or stretch individual axes with direct width/height manipulation. ![Scale images example showing uniform and non-uniform scaling](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-scale-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-transform-scale-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-transform-scale-browser/) Scaling transforms a block proportionally using a factor, while resizing changes dimensions directly. Use scaling to maintain aspect ratio or apply consistent size changes across multiple elements. ```typescript file=@cesdk_web_examples/guides-edit-image-transform-scale-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; class Example implements EditorPlugin { name = 'guides-edit-image-transform-scale-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Demo 1: Uniform Scaling - Scale from center anchor const scaledImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, scaledImage); engine.block.setPositionX(scaledImage, 50); engine.block.setPositionY(scaledImage, 100); // Scale uniformly to 150% from center anchor engine.block.scale(scaledImage, 1.5, 0.5, 0.5); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Uniform Scale'); engine.block.setFloat(text1, 'text/fontSize', 28); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 225); engine.block.setPositionX(text1, 50); engine.block.setPositionY(text1, 360); engine.block.appendChild(page, text1); // Demo 2: Non-Uniform Scaling - Stretch width only const stretchedImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, stretchedImage); engine.block.setPositionX(stretchedImage, 300); engine.block.setPositionY(stretchedImage, 150); // Stretch width by 50% while keeping height engine.block.setWidthMode(stretchedImage, 'Absolute'); const currentWidth = engine.block.getWidth(stretchedImage); engine.block.setWidth(stretchedImage, currentWidth * 1.5, true); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', 'Non-Uniform'); engine.block.setFloat(text2, 'text/fontSize', 28); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 225); engine.block.setPositionX(text2, 300); engine.block.setPositionY(text2, 360); engine.block.appendChild(page, text2); // Demo 3: Locked Image - Cannot be scaled const lockedImage = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_5.jpg', { size: { width: 150, height: 150 } } ); engine.block.appendChild(page, lockedImage); engine.block.setPositionX(lockedImage, 575); engine.block.setPositionY(lockedImage, 150); // Lock transforms to prevent scaling engine.block.setTransformLocked(lockedImage, true); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Locked'); engine.block.setFloat(text3, 'text/fontSize', 28); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 150); engine.block.setPositionX(text3, 575); engine.block.setPositionY(text3, 360); engine.block.appendChild(page, text3); // Scale with different anchor points // Top-left anchor (0, 0) - default // Center anchor (0.5, 0.5) - scales from center // Bottom-right anchor (1, 1) - scales from bottom-right corner const anchorX = 0.5; const anchorY = 0.5; const scaleFactor = 1.2; engine.block.scale(scaledImage, scaleFactor, anchorX, anchorY); // Restrict scaling through scopes engine.block.setScopeEnabled(lockedImage, 'layer/resize', false); // Select the scaled image to show the result engine.block.select(scaledImage); } } export default Example; ``` This guide covers uniform scaling with anchor points, non-uniform axis stretching, and locking transforms to prevent scaling in templates. ## Uniform Scaling Apply a scale factor with `engine.block.scale()` where 1.0 keeps the original size, values greater than 1 enlarge, and values less than 1 shrink. The third and fourth parameters control the anchor point (0 to 1 range): ```typescript highlight-uniform-scale // Scale uniformly to 150% from center anchor engine.block.scale(scaledImage, 1.5, 0.5, 0.5); ``` ## Anchor Point Control Control the scaling origin with `anchorX` and `anchorY` parameters. Use (0, 0) for top-left, (0.5, 0.5) for center, or (1, 1) for bottom-right. Center anchor expands equally in all directions: ```typescript highlight-anchor-points // Scale with different anchor points // Top-left anchor (0, 0) - default // Center anchor (0.5, 0.5) - scales from center // Bottom-right anchor (1, 1) - scales from bottom-right corner const anchorX = 0.5; const anchorY = 0.5; const scaleFactor = 1.2; engine.block.scale(scaledImage, scaleFactor, anchorX, anchorY); ``` ## Non-Uniform Scaling Stretch a single axis by setting absolute mode and modifying width or height independently. This changes the aspect ratio: ```typescript highlight-non-uniform-scale // Stretch width by 50% while keeping height engine.block.setWidthMode(stretchedImage, 'Absolute'); const currentWidth = engine.block.getWidth(stretchedImage); engine.block.setWidth(stretchedImage, currentWidth * 1.5, true); ``` ## Locking Transforms Lock transforms to prevent scaling, rotation, and repositioning using `setTransformLocked`: ```typescript highlight-lock-scaling // Lock transforms to prevent scaling engine.block.setTransformLocked(lockedImage, true); ``` ## Scope Restrictions Disable specific capabilities using scopes. Use `'layer/resize'` to prevent resizing while allowing other operations: ```typescript highlight-scope-restriction // Restrict scaling through scopes engine.block.setScopeEnabled(lockedImage, 'layer/resize', false); ``` ## Troubleshooting ### Image Scales Unevenly Use the same anchor values for both X and Y (e.g., 0.5, 0.5 for center). Use `scale()` instead of separate width/height changes to maintain proportions. ### Scaling Doesn't Apply Verify the block is valid using `engine.block.isValid(blockId)`. Ensure the block is appended to the scene hierarchy with `engine.block.appendChild()`. ### Users Can Still Scale Locked Blocks Check that the `'layer/resize'` scope is disabled using `engine.block.isScopeEnabled()`. Transform locks prevent UI manipulation but not API calls. ### Export Shows Original Size Confirm scaling was applied before export. Use `engine.block.getWidth()` and `engine.block.getHeight()` to verify dimensions after scaling. ## API Reference | Method | Description | | ----------------------------------- | --------------------------------------------- | | `engine.block.scale()` | Scale block and children proportionally | | `engine.block.getWidth()` | Get current width | | `engine.block.setWidth()` | Set width with optional crop maintenance | | `engine.block.getHeight()` | Get current height | | `engine.block.setHeight()` | Set height with optional crop maintenance | | `engine.block.setWidthMode()` | Set width mode (Absolute, Percent, Auto) | | `engine.block.setHeightMode()` | Set height mode (Absolute, Percent, Auto) | | `engine.block.setTransformLocked()` | Lock all transformations | | `engine.block.isTransformLocked()` | Check if transforms are locked | | `engine.block.setScopeEnabled()` | Enable or disable a scope | | `engine.block.isScopeEnabled()` | Check if scope is enabled | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Vectorize" description: "Convert raster images into scalable vector graphics for flexible resizing and editing." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-image/vectorize-2b4c7f/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/angular/edit-image-c64912/) > [Vectorize](https://img.ly/docs/cesdk/angular/edit-image/vectorize-2b4c7f/) --- Convert raster images into scalable vector graphics that resize without quality loss using CE.SDK's vectorizer plugin. ![Vectorize Images example showing an image ready for vectorization](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-vectorize-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-image-vectorize-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-image-vectorize-browser/) Vectorization transforms pixel-based images into vector paths that can be scaled to any size without losing quality. The `@imgly/plugin-vectorizer-web` plugin provides one-click UI conversion directly in the canvas menu. Common use cases include converting logos for scalable branding, creating cutout outlines from photographs, and extracting editable paths from illustrations. ```typescript file=@cesdk_web_examples/guides-edit-image-vectorize-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import VectorizerPlugin from '@imgly/plugin-vectorizer-web'; import packageJson from './package.json'; /** * CE.SDK Plugin: Vectorize Images Guide * * Demonstrates converting raster images to vector graphics: * - Using the vectorizer plugin for UI-based conversion * - Programmatically vectorizing with createCutoutFromBlocks() * - Configuring threshold parameters for quality control */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; // Add the vectorizer plugin with configuration options await cesdk.addPlugin( VectorizerPlugin({ // Display the vectorize button in the canvas menu ui: { locations: 'canvasMenu' }, // Set processing timeout to 30 seconds timeout: 30000, // Combine paths into a single shape when exceeding 500 paths groupingThreshold: 500 }) ); // Show only the vectorizer button in the canvas menu cesdk.ui.setComponentOrder({ in: 'ly.img.canvas.menu' }, ['@imgly/plugin-vectorizer-web.canvasMenu']); // Create a design scene with a page const scene = engine.scene.create(); const page = engine.block.create('page'); engine.block.setWidth(page, 800); engine.block.setHeight(page, 600); engine.block.appendChild(scene, page); // Create an image block to vectorize const imageBlock = engine.block.create('graphic'); const rectShape = engine.block.createShape('rect'); engine.block.setShape(imageBlock, rectShape); // Load a sample image with clear contours for vectorization const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(imageBlock, imageFill); engine.block.setContentFillMode(imageBlock, 'Contain'); // Center the image on the page const imageWidth = 400; const imageHeight = 300; engine.block.setWidth(imageBlock, imageWidth); engine.block.setHeight(imageBlock, imageHeight); engine.block.setPositionX(imageBlock, (800 - imageWidth) / 2); engine.block.setPositionY(imageBlock, (600 - imageHeight) / 2); engine.block.appendChild(page, imageBlock); // Select the image to reveal the vectorize button in the canvas menu engine.block.select(imageBlock); // Zoom to fit the page in view await engine.scene.zoomToBlock(page, { padding: 40 }); engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); } } export default Example; ``` This guide covers how to install and configure the vectorizer plugin, customize the canvas menu, and troubleshoot common vectorization issues. ## Using the Vectorizer Plugin The `@imgly/plugin-vectorizer-web` plugin adds a vectorize button to the canvas menu when you select an image block. Processing runs entirely in the browser using the [@imgly/vectorizer](https://www.npmjs.com/package/@imgly/vectorizer) library. ### Installation Install the plugin via npm or yarn: ```sh yarn add @imgly/plugin-vectorizer-web npm install @imgly/plugin-vectorizer-web ``` ### Adding the Plugin We register the plugin using `cesdk.addPlugin()` with the `ui.locations` option to display the vectorize button in the canvas menu. To show only the vectorizer button, we use `setComponentOrder({ in: 'ly.img.canvas.menu' }, order)` to filter out other menu items. ```typescript highlight-add-plugin // Add the vectorizer plugin with configuration options await cesdk.addPlugin( VectorizerPlugin({ // Display the vectorize button in the canvas menu ui: { locations: 'canvasMenu' }, // Set processing timeout to 30 seconds timeout: 30000, // Combine paths into a single shape when exceeding 500 paths groupingThreshold: 500 }) ); // Show only the vectorizer button in the canvas menu cesdk.ui.setComponentOrder({ in: 'ly.img.canvas.menu' }, ['@imgly/plugin-vectorizer-web.canvasMenu']); ``` ### Configuration Options You can customize the plugin behavior with two configuration options: - **timeout**: Processing time limit in milliseconds (default: 30000). Increase this for complex images that take longer to process. - **groupingThreshold**: Maximum path count before combining into a single shape (default: 500). Lower values combine paths earlier, reducing selectable elements. ## Programmatic Vectorization For automation workflows, you can create cutout blocks from source blocks using `engine.block.createCutoutFromBlocks()`. This method traces rasterized content or extracts existing vector paths. ### Threshold Parameters The `createCutoutFromBlocks()` method accepts three parameters that control vectorization quality: - **vectorizeDistanceThreshold** (default: 2): Maximum contour deviation during tracing. Lower values increase accuracy but produce more complex paths. - **simplifyDistanceThreshold** (default: 4): Maximum deviation for path smoothing. Set to 0 to disable smoothing entirely. - **useExistingShapeInformation** (default: true): When true, extracts existing vector paths from shapes and SVGs without re-tracing. ### Threshold Recommendations Start with the default values (2, 4) and adjust based on your source content: | Content Type | vectorizeDistanceThreshold | simplifyDistanceThreshold | |--------------|----------------------------|---------------------------| | Photographs | 4-8 | 6-10 | | Logos and icons | 1-2 | 2-4 | | Illustrations | 2-4 | 4-6 | Lower thresholds increase path complexity and processing time. For photographs with many details, higher thresholds reduce the number of paths while maintaining overall shape recognition. ## Troubleshooting Common issues and solutions: - **Processing timeout**: Increase the `timeout` option or use higher threshold values to reduce complexity. - **Jagged edges**: Increase `simplifyDistanceThreshold` to smooth the paths. - **Lost details**: Decrease both threshold values to capture finer contours. - **Vectorize button not appearing**: Verify `ui: { locations: 'canvasMenu' }` is set and that you've selected an image block. - **Memory issues with complex images**: Increase `groupingThreshold` to combine more paths into single shapes. ## API Reference | Method | Category | Purpose | |--------|----------|---------| | `cesdk.addPlugin(VectorizerPlugin(options))` | Plugin | Register the vectorizer plugin | | `cesdk.ui.setComponentOrder({ in: 'ly.img.canvas.menu' }, ids)` | UI | Control which items appear in the canvas menu | | `engine.block.createCutoutFromBlocks(ids, vectorizeDistanceThreshold?, simplifyDistanceThreshold?, useExistingShapeInformation?)` | Block | Create a cutout from block contours | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Captions" description: "Documentation for adding captions to videos" platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Add Captions](https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/) --- Add synchronized captions to video projects using CE.SDK's caption system, with support for importing subtitle files, styling with presets, and burning captions into video exports. ![Video captions example showing timeline with caption track and styled captions](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-captions-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-captions-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-add-captions-browser/) Captions in CE.SDK follow a hierarchy: **Page → CaptionTrack → Caption blocks**. Each caption has text, timing (time offset and duration), and styling properties. Captions appear and disappear based on their timing, synchronized with video playback. ```typescript file=@cesdk_web_examples/guides-create-video-add-captions-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Add Captions Guide * * Demonstrates adding synchronized captions to video projects: * - Importing captions from SRT/VTT files * - Creating and styling captions programmatically * - Applying caption presets * - Controlling caption timing and positioning * - Adding animations to captions */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features including captions cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; engine.block.setDuration(page, 40); // Add a video clip as the base content const videoUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4'; const track = engine.block.create('track'); engine.block.appendChild(page, track); const videoClip = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 40, timeOffset: 0 } }); engine.block.appendChild(track, videoClip); engine.block.fillParent(track); // Import captions from SRT file // createCaptionsFromURI parses SRT/VTT and creates caption blocks with timing const captionSrtUrl = 'https://img.ly/static/examples/captions.srt'; const captionBlocks = await engine.block.createCaptionsFromURI( captionSrtUrl ); // eslint-disable-next-line no-console console.log(`Imported ${captionBlocks.length} captions from SRT file`); // Adjust caption timing to start at the beginning of the video // The SRT file may have different timing, so we reset to start at 0 let currentOffset = 0; for (const captionId of captionBlocks) { const duration = engine.block.getDuration(captionId); engine.block.setTimeOffset(captionId, currentOffset); currentOffset += duration; } // Create a caption track and add captions to it // Caption tracks organize captions in the timeline const captionTrack = engine.block.create('//ly.img.ubq/captionTrack'); engine.block.appendChild(page, captionTrack); // Add each caption block to the track for (const captionId of captionBlocks) { engine.block.appendChild(captionTrack, captionId); } // eslint-disable-next-line no-console console.log(`Caption track created with ${captionBlocks.length} captions`); // Apply a caption preset for consistent styling // Caption presets provide pre-configured styles (fonts, colors, backgrounds) const captionPresetsSourceId = 'ly.img.caption.presets'; const comicPresetId = '//ly.img.caption.presets/comic'; // Fetch the preset asset const comicPreset = await engine.asset.fetchAsset( captionPresetsSourceId, comicPresetId ); // Apply preset to the first caption (styling syncs across all captions) if (comicPreset && captionBlocks.length > 0) { await engine.asset.applyToBlock( captionPresetsSourceId, comicPreset, captionBlocks[0] ); // eslint-disable-next-line no-console console.log('Applied comic preset to captions'); } // Position captions at the bottom of the video frame // Caption position and size sync across all captions, so we only set it once if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Use percentage-based positioning for responsive layout engine.block.setPositionXMode(firstCaption, 'Percent'); engine.block.setPositionYMode(firstCaption, 'Percent'); engine.block.setWidthMode(firstCaption, 'Percent'); engine.block.setHeightMode(firstCaption, 'Percent'); // Position at bottom center with padding engine.block.setPositionX(firstCaption, 0.05); // 5% from left engine.block.setPositionY(firstCaption, 0.8); // 80% from top (near bottom) engine.block.setWidth(firstCaption, 0.9); // 90% width engine.block.setHeight(firstCaption, 0.15); // 15% height } // Modify a specific caption's text and timing if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Get current text const currentText = engine.block.getString(firstCaption, 'caption/text'); // eslint-disable-next-line no-console console.log('First caption text:', currentText); // Get timing info const offset = engine.block.getTimeOffset(firstCaption); const duration = engine.block.getDuration(firstCaption); // eslint-disable-next-line no-console console.log(`First caption: offset=${offset}s, duration=${duration}s`); } // Add fade-in animation to the first caption if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Create and apply entry animation const fadeIn = engine.block.createAnimation('fade'); engine.block.setDuration(fadeIn, 0.3); engine.block.setInAnimation(firstCaption, fadeIn); // eslint-disable-next-line no-console console.log('Added fade-in animation to first caption'); } // Select the first caption to show it in the inspector if (captionBlocks.length > 0) { engine.block.select(captionBlocks[0]); } // Seek to show the first caption at 1 second engine.block.setPlaybackTime(page, 1); // Open the caption inspector panel cesdk.ui.openPanel('//ly.img.panel/inspector/caption'); // eslint-disable-next-line no-console console.log( 'Add Captions guide initialized. Captions imported and styled.' ); } } export default Example; ``` This guide covers how to import captions from SRT/VTT files, style them using presets and custom properties, create captions programmatically, and export videos with burned-in captions. ## Understanding Caption Structure ### Caption Hierarchy CE.SDK organizes captions in a parent-child hierarchy. The page contains one or more caption tracks, and each caption track contains individual caption blocks. This structure allows for multiple caption tracks (for different languages or purposes) while keeping captions organized. When you import captions from a subtitle file, CE.SDK automatically creates the caption track and populates it with caption blocks. Each caption block stores its text content, start time, duration, and styling properties. ### Caption Timing Each caption has two timing properties: **time offset** (when the caption appears) and **duration** (how long it stays visible). These values are in seconds and synchronize with the video timeline. A caption with a time offset of 2.0 and duration of 3.0 appears at the 2-second mark and disappears at the 5-second mark. ## Importing Captions from Subtitle Files ### Using createCaptionsFromURI The fastest way to add captions is importing from an SRT or VTT subtitle file. CE.SDK parses the file and creates caption blocks with timing already configured. ```typescript highlight-import-captions // Import captions from SRT file // createCaptionsFromURI parses SRT/VTT and creates caption blocks with timing const captionSrtUrl = 'https://img.ly/static/examples/captions.srt'; const captionBlocks = await engine.block.createCaptionsFromURI( captionSrtUrl ); // eslint-disable-next-line no-console console.log(`Imported ${captionBlocks.length} captions from SRT file`); // Adjust caption timing to start at the beginning of the video // The SRT file may have different timing, so we reset to start at 0 let currentOffset = 0; for (const captionId of captionBlocks) { const duration = engine.block.getDuration(captionId); engine.block.setTimeOffset(captionId, currentOffset); currentOffset += duration; } ``` The `createCaptionsFromURI` method downloads the subtitle file, parses the timing and text, and creates a caption track with all captions positioned correctly. It returns an array of caption block IDs for the imported captions. ### Creating the Caption Track After importing captions, create a caption track to organize them in the timeline. The caption track manages caption positioning and display. ```typescript highlight-create-caption-track // Create a caption track and add captions to it // Caption tracks organize captions in the timeline const captionTrack = engine.block.create('//ly.img.ubq/captionTrack'); engine.block.appendChild(page, captionTrack); // Add each caption block to the track for (const captionId of captionBlocks) { engine.block.appendChild(captionTrack, captionId); } // eslint-disable-next-line no-console console.log(`Caption track created with ${captionBlocks.length} captions`); ``` Create a caption track with `engine.block.create('//ly.img.ubq/captionTrack')` and append it to the page. Then add each caption block to the track using `appendChild`. ## Using the Built-in Caption UI ### Caption Panel CE.SDK provides a caption panel in the inspector for visual caption management. When you select a caption, the panel shows timing controls, text editing, and styling options. Users can drag caption edges in the timeline to adjust timing or double-click to edit text. ### Importing via UI The caption panel includes an import button for uploading SRT or VTT files. The interface guides users through file selection and automatically extracts timing information. ### Styling with Presets Caption presets provide pre-configured styling combinations including font, color, background, and animations. Select a caption and choose from available presets to apply consistent styling. Presets are especially useful for maintaining brand consistency across videos. ### Editing Text and Timing Double-click a caption in the timeline or panel to edit its text. Drag the edges of caption blocks in the timeline to adjust start time and duration. The timeline provides visual feedback showing caption positions relative to video content. ## Creating Captions Programmatically ### Caption Track Setup For full control over captions, create them programmatically. First, create a caption track and append it to the page. ```typescript const captionTrack = engine.block.create('//ly.img.ubq/captionTrack'); engine.block.appendChild(page, captionTrack); ``` ### Creating Caption Blocks Create individual captions with text and timing. ```typescript const caption = engine.block.create('//ly.img.ubq/caption'); engine.block.appendChild(captionTrack, caption); // Set caption text engine.block.setString(caption, 'caption/text', 'Hello, world!'); // Set timing - appears at 2 seconds for 3 seconds engine.block.setTimeOffset(caption, 2); engine.block.setDuration(caption, 3); ``` Set the caption text using `setString` with the `caption/text` property. Position the caption in time using `setTimeOffset` (when it appears) and `setDuration` (how long it shows). ## Styling Captions ### Applying Presets The fastest way to style captions is using presets. Presets provide pre-configured styling including fonts, colors, backgrounds, and effects. ```typescript highlight-apply-preset // Apply a caption preset for consistent styling // Caption presets provide pre-configured styles (fonts, colors, backgrounds) const captionPresetsSourceId = 'ly.img.caption.presets'; const comicPresetId = '//ly.img.caption.presets/comic'; // Fetch the preset asset const comicPreset = await engine.asset.fetchAsset( captionPresetsSourceId, comicPresetId ); // Apply preset to the first caption (styling syncs across all captions) if (comicPreset && captionBlocks.length > 0) { await engine.asset.applyToBlock( captionPresetsSourceId, comicPreset, captionBlocks[0] ); // eslint-disable-next-line no-console console.log('Applied comic preset to captions'); } ``` Fetch a preset using `engine.asset.fetchAsset` and apply it with `engine.asset.applyToBlock`. Caption styling automatically syncs across all captions, so applying a preset to one caption styles them all. ### Positioning Captions Position captions at the bottom of the video frame using percentage-based positioning for responsive layout. ```typescript highlight-position-captions // Position captions at the bottom of the video frame // Caption position and size sync across all captions, so we only set it once if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Use percentage-based positioning for responsive layout engine.block.setPositionXMode(firstCaption, 'Percent'); engine.block.setPositionYMode(firstCaption, 'Percent'); engine.block.setWidthMode(firstCaption, 'Percent'); engine.block.setHeightMode(firstCaption, 'Percent'); // Position at bottom center with padding engine.block.setPositionX(firstCaption, 0.05); // 5% from left engine.block.setPositionY(firstCaption, 0.8); // 80% from top (near bottom) engine.block.setWidth(firstCaption, 0.9); // 90% width engine.block.setHeight(firstCaption, 0.15); // 15% height } ``` Use percentage mode (`setPositionXMode`, `setPositionYMode`) for positions that adapt to different video resolutions. Caption position and size sync across all captions automatically. ### Background Enable a background behind caption text for better readability over video content. Use `setBool` to enable `backgroundColor/enabled` and `setColor` to set `backgroundColor/color` with RGBA values. A semi-transparent black background (alpha 0.7) is common for video captions. ### Automatic Font Sizing CE.SDK can automatically adjust font size to fit caption text within bounds. Enable automatic sizing and set minimum and maximum size limits. ```typescript engine.block.setBool(captionId, 'caption/automaticFontSizeEnabled', true); engine.block.setFloat(captionId, 'caption/minAutomaticFontSize', 24); engine.block.setFloat(captionId, 'caption/maxAutomaticFontSize', 72); ``` This prevents text from overflowing while maintaining readability. ## Applying Presets Programmatically ### Finding Available Presets Query available caption presets from the asset library. ```typescript const presetsResult = await engine.asset.findAssets('ly.img.caption.presets', { page: 0, perPage: 100 }); const presets = presetsResult.assets; ``` The `findAssets` method returns preset metadata including IDs and preview thumbnails. ### Applying a Preset Apply a preset to a caption using `applyToBlock`. ```typescript const preset = presets[0]; await engine.asset.applyToBlock('ly.img.caption.presets', preset, captionId); ``` The preset applies all styling properties at once—font, colors, background, and any animations defined in the preset. ## Caption Animations ### Adding Entry Animations Make captions more engaging by adding entry animations. ```typescript highlight-add-animation // Add fade-in animation to the first caption if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Create and apply entry animation const fadeIn = engine.block.createAnimation('fade'); engine.block.setDuration(fadeIn, 0.3); engine.block.setInAnimation(firstCaption, fadeIn); // eslint-disable-next-line no-console console.log('Added fade-in animation to first caption'); } ``` Create an animation using `createAnimation` with types like 'fade', 'slide', or 'scale'. Set the animation duration and apply it with `setInAnimation`. ### Animation Types CE.SDK supports several animation types for captions: - **fade** - Opacity transition - **slide** - Position movement - **scale** - Size change - **blur** - Focus effect Set loop animations with `setLoopAnimation` for continuous effects, or exit animations with `setOutAnimation` for departure transitions. ## Reading Caption Properties ### Getting Text and Timing Retrieve caption properties to display in custom UI or for processing. ```typescript highlight-modify-caption // Modify a specific caption's text and timing if (captionBlocks.length > 0) { const firstCaption = captionBlocks[0]; // Get current text const currentText = engine.block.getString(firstCaption, 'caption/text'); // eslint-disable-next-line no-console console.log('First caption text:', currentText); // Get timing info const offset = engine.block.getTimeOffset(firstCaption); const duration = engine.block.getDuration(firstCaption); // eslint-disable-next-line no-console console.log(`First caption: offset=${offset}s, duration=${duration}s`); } ``` Use `getString` for text, `getTimeOffset` for start time, and `getDuration` for display length. These values are useful for building custom caption editors or synchronization tools. ## Exporting Videos with Captions ### Burned-In Captions When you export a video, captions are burned into the video as pixels. They become part of the video image and cannot be turned off by viewers. This ensures captions display correctly on any platform. ```typescript const videoBlob = await engine.block.exportVideo(page, { mimeType: 'video/mp4' }); ``` The export process renders each frame with captions overlaid at the correct timing. Export time depends on video length and resolution. > **Note:** For accessibility, consider also providing separate subtitle files (SRT/VTT) alongside burned-in captions. This allows viewers to customize caption appearance in their video player. ## Troubleshooting | Issue | Cause | Solution | | --- | --- | --- | | Captions not visible | Not in caption track hierarchy | Check `getParent()`: page → captionTrack → caption | | Wrong timing | Time offset/duration incorrect | Verify `getTimeOffset()` and `getDuration()` | | Import fails | Unsupported format | Use valid SRT or VTT file | | Styling not applying | Property path wrong | Use `caption/` prefix for caption properties | ### Captions Not Appearing If captions don't show in the preview, verify the caption hierarchy. Each caption must be a child of a caption track, which must be a child of the page. Use `getParent()` to trace the hierarchy. Also check that the playhead position matches caption timing. Captions only appear during their time offset and duration window. ### Import Errors If `createCaptionsFromURI` fails, verify the URL is accessible and returns valid SRT or VTT content. Common issues include CORS restrictions and malformed subtitle files. Test the URL in a browser to confirm accessibility. ## API Reference | Method | Purpose | | --- | --- | | `engine.block.createCaptionsFromURI(uri)` | Import captions from SRT/VTT file | | `engine.block.create('//ly.img.ubq/captionTrack')` | Create caption track container | | `engine.block.create('//ly.img.ubq/caption')` | Create caption block | | `engine.block.setString(id, property, value)` | Set caption text | | `engine.block.setTimeOffset(id, offset)` | Set caption start time | | `engine.block.setDuration(id, duration)` | Set caption display duration | | `engine.block.setFloat(id, property, value)` | Set font size, spacing | | `engine.block.setEnum(id, property, value)` | Set alignment | | `engine.block.setBool(id, property, value)` | Enable background | | `engine.block.setColor(id, property, value)` | Set colors | | `engine.block.createAnimation(type)` | Create animation | | `engine.block.setInAnimation(id, animation)` | Set entry animation | | `engine.block.exportVideo(id, options)` | Export video with captions | | `engine.asset.findAssets(sourceId, params)` | Find presets | | `engine.asset.applyToBlock(sourceId, asset, block)` | Apply preset | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Watermark" description: "Add text and image watermarks to videos using CE.SDK for copyright protection, branding, and content attribution with timeline management and visibility controls." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/add-watermark-762ce6/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Add Watermark](https://img.ly/docs/cesdk/angular/edit-video/add-watermark-762ce6/) --- Add text and image watermarks to video content for copyright protection, branding, and content attribution using CE.SDK's timeline-aware block system. ![Add Watermark example showing video with text and logo watermarks](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-watermark-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-add-watermark-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-add-watermark-browser/) Video watermarks in CE.SDK are design blocks positioned over video content. **Text watermarks** display copyright notices, URLs, or branding text, while **image watermarks** show logos or graphics. Both watermark types require timeline management to ensure they remain visible throughout video playback. The key difference from static image watermarking is setting the watermark's `duration` to match the video duration. ```typescript file=@cesdk_web_examples/guides-create-video-add-watermark-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Add Watermark to Video Guide * * Demonstrates adding text and image watermarks to videos: * - Creating text watermarks with styling * - Creating image watermarks from logos * - Positioning watermarks on the canvas * - Setting watermark duration to match video * - Adding drop shadows for visibility * - Configuring opacity and blend modes */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); // Create a video scene from a sample video const videoUrl = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; await cesdk.engine.scene.createFromVideo(videoUrl); const engine = cesdk.engine; const page = engine.scene.getCurrentPage()!; // Get page dimensions for watermark positioning const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Get the page duration (set automatically from the video) const videoDuration = engine.block.getDuration(page); // eslint-disable-next-line no-console console.log('Video duration from page:', videoDuration); // ===== TEXT WATERMARK ===== // Create a text watermark for copyright notice const textWatermark = engine.block.create('text'); // Use Auto sizing so the text block grows to fit its content engine.block.setWidthMode(textWatermark, 'Auto'); engine.block.setHeightMode(textWatermark, 'Auto'); // Set the watermark text content using replaceText engine.block.replaceText(textWatermark, 'All rights reserved © 2025'); // Position in bottom-left corner with padding const textPadding = 20; engine.block.setPositionX(textWatermark, textPadding); engine.block.setPositionY(textWatermark, pageHeight - textPadding - 20); // Style the text watermark with a subtle font size engine.block.setFloat(textWatermark, 'text/fontSize', 4); engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // White text // Set text alignment to left engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Set watermark opacity for subtle appearance engine.block.setOpacity(textWatermark, 0.7); // Add drop shadow for visibility across different backgrounds engine.block.setDropShadowEnabled(textWatermark, true); engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.8 }); engine.block.setDropShadowOffsetX(textWatermark, 2); engine.block.setDropShadowOffsetY(textWatermark, 2); engine.block.setDropShadowBlurRadiusX(textWatermark, 4); engine.block.setDropShadowBlurRadiusY(textWatermark, 4); // Set the text watermark duration to match the video engine.block.setDuration(textWatermark, videoDuration); engine.block.setTimeOffset(textWatermark, 0); // Add the text watermark to the page engine.block.appendChild(page, textWatermark); // ===== IMAGE WATERMARK (LOGO) ===== // Create a graphic block for the logo watermark const logoWatermark = engine.block.create('graphic'); // Create a rectangular shape for the logo const rectShape = engine.block.createShape('rect'); engine.block.setShape(logoWatermark, rectShape); // Create an image fill with the logo const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain so the logo fits within bounds engine.block.setContentFillMode(logoWatermark, 'Contain'); // Size and position the logo in the top-right corner const logoSize = 80; const logoPadding = 20; engine.block.setWidth(logoWatermark, logoSize); engine.block.setHeight(logoWatermark, logoSize); engine.block.setPositionX(logoWatermark, pageWidth - logoSize - logoPadding); engine.block.setPositionY(logoWatermark, logoPadding); // Set opacity for the logo watermark engine.block.setOpacity(logoWatermark, 0.6); // Set blend mode for better integration with video content engine.block.setBlendMode(logoWatermark, 'Normal'); // Set the logo watermark duration to match the video engine.block.setDuration(logoWatermark, videoDuration); engine.block.setTimeOffset(logoWatermark, 0); // Add the logo watermark to the page engine.block.appendChild(page, logoWatermark); // Select the page to show the timeline engine.block.setSelected(page, true); // Zoom to fit the page and enable auto-fit for responsive resizing await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); engine.scene.enableZoomAutoFit(page, 'Horizontal', 40, 40); // Start playback automatically try { engine.block.setPlaying(page, true); // eslint-disable-next-line no-console console.log( 'Video watermark guide initialized. Playback started with text and logo watermarks visible.' ); } catch (error) { // eslint-disable-next-line no-console console.log( 'Video watermark guide initialized. Click play button to start playback.' ); } } } export default Example; ``` This guide covers how to create text and image watermarks programmatically, position them on the canvas, style them for visibility, and configure their timeline duration to span the entire video. ## Setting Up Video Mode Before adding watermarks, we configure CE.SDK for video editing. Video mode enables timeline features required for watermark duration control. ```typescript highlight-setup // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); ``` We enable three features: `ly.img.video` for video support, `ly.img.timeline` for timeline controls, and `ly.img.playback` for video playback. These features must be enabled before creating a video scene. ## Creating a Video Scene We create a video scene from a video URL. This automatically sets up the timeline and page dimensions based on the video. ```typescript highlight-create-video-scene // Create a video scene from a sample video const videoUrl = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; await cesdk.engine.scene.createFromVideo(videoUrl); ``` The `createFromVideo` method loads the video, creates a scene, and sets the page dimensions to match the video's aspect ratio. The video becomes a fill block on the timeline with its duration already set. ## Creating a Text Watermark Text watermarks display copyright notices, branding text, or URLs. We create a text block and position it on the canvas. ```typescript highlight-create-text-watermark // Create a text watermark for copyright notice const textWatermark = engine.block.create('text'); // Use Auto sizing so the text block grows to fit its content engine.block.setWidthMode(textWatermark, 'Auto'); engine.block.setHeightMode(textWatermark, 'Auto'); // Set the watermark text content using replaceText engine.block.replaceText(textWatermark, 'All rights reserved © 2025'); // Position in bottom-left corner with padding const textPadding = 20; engine.block.setPositionX(textWatermark, textPadding); engine.block.setPositionY(textWatermark, pageHeight - textPadding - 20); ``` We create a text block with `block.create('text')` and configure it with auto-sizing using `setWidthMode('Auto')` and `setHeightMode('Auto')`. This lets the text block grow to fit its content. We set the text content using `replaceText()` and position the watermark in the bottom-left corner with padding from the edges. ## Styling Text Watermarks Style the text for readability across different video backgrounds. ```typescript highlight-style-text-watermark // Style the text watermark with a subtle font size engine.block.setFloat(textWatermark, 'text/fontSize', 4); engine.block.setTextColor(textWatermark, { r: 1, g: 1, b: 1, a: 1 }); // White text // Set text alignment to left engine.block.setEnum(textWatermark, 'text/horizontalAlignment', 'Left'); // Set watermark opacity for subtle appearance engine.block.setOpacity(textWatermark, 0.7); ``` We set the font size using `setFloat()` with the `'text/fontSize'` property for a subtle watermark appearance. White text color ensures visibility, left alignment positions the text naturally, and 70% opacity creates a semi-transparent appearance that's visible but not distracting. ## Adding Drop Shadow for Visibility Drop shadows ensure text remains readable over both light and dark video backgrounds. ```typescript highlight-text-drop-shadow // Add drop shadow for visibility across different backgrounds engine.block.setDropShadowEnabled(textWatermark, true); engine.block.setDropShadowColor(textWatermark, { r: 0, g: 0, b: 0, a: 0.8 }); engine.block.setDropShadowOffsetX(textWatermark, 2); engine.block.setDropShadowOffsetY(textWatermark, 2); engine.block.setDropShadowBlurRadiusX(textWatermark, 4); engine.block.setDropShadowBlurRadiusY(textWatermark, 4); ``` We enable the drop shadow and configure its appearance. The black shadow color with 80% opacity provides contrast. Offset values (2px in each direction) separate the shadow from the text, while blur radius values (4px) create a soft shadow edge. ## Setting Text Watermark Duration The watermark must persist throughout video playback. We set its duration to match the video duration. ```typescript highlight-text-timeline // Set the text watermark duration to match the video engine.block.setDuration(textWatermark, videoDuration); engine.block.setTimeOffset(textWatermark, 0); // Add the text watermark to the page engine.block.appendChild(page, textWatermark); ``` `setDuration` controls how long the block appears in the timeline. `setTimeOffset` of 0 ensures it starts at the beginning. We then append the watermark to the page, placing it above the video content. ## Creating an Image Watermark Image watermarks display logos or graphics. We create a graphic block with an image fill. ```typescript highlight-create-image-watermark // Create a graphic block for the logo watermark const logoWatermark = engine.block.create('graphic'); // Create a rectangular shape for the logo const rectShape = engine.block.createShape('rect'); engine.block.setShape(logoWatermark, rectShape); // Create an image fill with the logo const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/imgly_logo.jpg' ); engine.block.setFill(logoWatermark, imageFill); // Set content fill mode to contain so the logo fits within bounds engine.block.setContentFillMode(logoWatermark, 'Contain'); ``` We create a graphic block, assign it a rectangular shape, and fill it with an image. The `fill/image/imageFileURI` property specifies the logo URL. We set the content fill mode to 'Contain' so the logo fits within its bounds without cropping. This pattern—graphic block with shape and fill—is standard for displaying images in CE.SDK. ## Positioning Image Watermarks Position the logo in a corner that doesn't obstruct video content. ```typescript highlight-position-image-watermark // Size and position the logo in the top-right corner const logoSize = 80; const logoPadding = 20; engine.block.setWidth(logoWatermark, logoSize); engine.block.setHeight(logoWatermark, logoSize); engine.block.setPositionX(logoWatermark, pageWidth - logoSize - logoPadding); engine.block.setPositionY(logoWatermark, logoPadding); ``` We size the logo at 80x80 pixels—large enough to be recognizable but not dominating. Position values place it in the top-right corner with 20px padding from the edges. ## Configuring Opacity and Blend Mode Control how the watermark integrates with the video. ```typescript highlight-image-opacity-blend // Set opacity for the logo watermark engine.block.setOpacity(logoWatermark, 0.6); // Set blend mode for better integration with video content engine.block.setBlendMode(logoWatermark, 'Normal'); ``` We set 60% opacity for a subtle but visible watermark. The blend mode 'Normal' displays the logo as-is. Other modes like 'Multiply' or 'Screen' create different visual effects depending on the logo and video content. ## Setting Image Watermark Duration Like text watermarks, image watermarks need duration configuration. ```typescript highlight-image-timeline // Set the logo watermark duration to match the video engine.block.setDuration(logoWatermark, videoDuration); engine.block.setTimeOffset(logoWatermark, 0); // Add the logo watermark to the page engine.block.appendChild(page, logoWatermark); ``` We set the same duration and time offset as the text watermark so both appear throughout the video. The `appendChild` call adds the logo to the page above existing content. ## Watermark Positioning Strategies Choose watermark positions based on your use case: - **Bottom-right corner**: Most common for copyright notices. Less intrusive but clearly visible. - **Top-right corner**: Good for logos. Doesn't interfere with typical video framing. - **Bottom-left corner**: Alternative for text when bottom-right conflicts with video content. - **Center**: Strong protection but obstructs content. Use for draft or preview watermarks. Calculate positions dynamically based on page dimensions to handle different video aspect ratios. ## Best Practices ### Visibility - Use drop shadows on text watermarks for contrast against varying backgrounds - Set opacity between 50-70% for subtle but visible branding - Choose appropriate font sizes based on your use case (smaller for subtle branding, larger for prominent notices) - Test watermarks against different scenes in your video ### Timeline Management - Always match watermark duration to video duration - Set time offset to 0 for watermarks that should appear from the start - For time-based watermarks, calculate offsets based on video sections ### Performance - Use appropriately sized logo images (avoid oversized source files) - Limit the number of watermark blocks to minimize rendering overhead - Consider combining multiple watermarks into a single image if they're always used together ## API Reference | Method | Description | |--------|-------------| | `block.create('text')` | Create a text block for text watermarks | | `block.create('graphic')` | Create a graphic block for image watermarks | | `block.setWidthMode(id, mode)` | Set width sizing mode ('Auto', 'Absolute', 'Percent') | | `block.setHeightMode(id, mode)` | Set height sizing mode ('Auto', 'Absolute', 'Percent') | | `block.replaceText(id, text)` | Set text content for text blocks | | `block.setFloat(id, property, value)` | Set numeric properties like font size | | `block.createShape('rect')` | Create a rectangular shape for graphics | | `block.createFill('image')` | Create an image fill for logo watermarks | | `block.setString(id, property, value)` | Set string properties like image URI | | `block.setContentFillMode(id, mode)` | Set content fill mode ('Crop', 'Cover', 'Contain') | | `block.setDuration(id, duration)` | Set watermark timeline duration | | `block.setTimeOffset(id, offset)` | Set watermark start time | | `block.setOpacity(id, opacity)` | Set watermark transparency (0.0-1.0) | | `block.setDropShadowEnabled(id, enabled)` | Enable/disable drop shadow | | `block.setDropShadowColor(id, color)` | Set shadow color | | `block.setDropShadowOffsetX/Y(id, offset)` | Set shadow position | | `block.setDropShadowBlurRadiusX/Y(id, radius)` | Set shadow blur | | `block.setBlendMode(id, mode)` | Set blend mode ('Normal', 'Multiply', etc.) | | `block.appendChild(parent, child)` | Add watermark to page | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Join and Arrange Video Clips" description: "Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/join-and-arrange-3bbc30/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Join and Arrange](https://img.ly/docs/cesdk/angular/edit-video/join-and-arrange-3bbc30/) --- Combine multiple video clips into sequences and organize them on the timeline using CE.SDK's track system and programmatic APIs. ![Join and Arrange Video Clips example showing timeline with video clips organized in tracks](./assets/browser.hero.webp) > **Reading time:** 12 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-join-and-arrange-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-join-and-arrange-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-join-and-arrange-browser/) Video compositions in CE.SDK use a hierarchy: **Scene → Page → Track → Clip**. Tracks organize clips for sequential playback—when you add clips to a track, they play one after another. You can control precise timing using time offsets and create layered compositions by adding multiple tracks to a page. In CE.SDK's block-based architecture, a **clip is a graphic block with a video fill**. This means video clips share the same APIs and capabilities as other blocks—you can position, rotate, scale, and apply effects to video just like images or shapes. The `addVideo()` helper creates this structure automatically and loads the video metadata. ```typescript file=@cesdk_web_examples/guides-create-video-join-and-arrange-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Join and Arrange Video Clips Guide * * Demonstrates combining multiple video clips into sequences: * - Creating video scenes and tracks * - Adding clips to tracks for sequential playback * - Reordering clips within a track * - Controlling clip timing with time offsets * - Creating multi-track compositions */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page duration to accommodate all clips (15 seconds total) engine.block.setDuration(page, 15); // Sample video URL for the demonstration const videoUrl = 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4'; // Create video clips using the addVideo helper method // Each clip is sized to fill the canvas (1920x1080 is standard video resolution) const clipA = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 0 } }); const clipB = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 5 } }); const clipC = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 10 } }); // Create a track and add it to the page // Tracks organize clips for sequential playback on the timeline const track = engine.block.create('track'); engine.block.appendChild(page, track); // Add clips to the track engine.block.appendChild(track, clipA); engine.block.appendChild(track, clipB); engine.block.appendChild(track, clipC); // Resize all track children to fill the page dimensions engine.block.fillParent(track); // Query track children to verify order const trackClips = engine.block.getChildren(track); // eslint-disable-next-line no-console console.log('Track clip count:', trackClips.length, 'clips'); // Set durations for each clip engine.block.setDuration(clipA, 5); engine.block.setDuration(clipB, 5); engine.block.setDuration(clipC, 5); // Set time offsets to position clips sequentially on the timeline engine.block.setTimeOffset(clipA, 0); engine.block.setTimeOffset(clipB, 5); engine.block.setTimeOffset(clipC, 10); // eslint-disable-next-line no-console console.log('Track offsets set: Clip A: 0s, Clip B: 5s, Clip C: 10s'); // Reorder clips: move Clip C to the beginning (index 0) // This demonstrates using insertChild for precise positioning engine.block.insertChild(track, clipC, 0); // After reordering, update time offsets to reflect the new sequence engine.block.setTimeOffset(clipC, 0); engine.block.setTimeOffset(clipA, 5); engine.block.setTimeOffset(clipB, 10); // eslint-disable-next-line no-console console.log('After reorder - updated offsets: C=0s, A=5s, B=10s'); // Get all clips in the track to verify arrangement const finalClips = engine.block.getChildren(track); // eslint-disable-next-line no-console console.log('Final track arrangement:'); finalClips.forEach((clipId, index) => { const offset = engine.block.getTimeOffset(clipId); const duration = engine.block.getDuration(clipId); // eslint-disable-next-line no-console console.log( ` Clip ${index + 1}: offset=${offset}s, duration=${duration}s` ); }); // Create a second track for layered compositions // Track order determines z-index: last track renders on top const overlayTrack = engine.block.create('track'); engine.block.appendChild(page, overlayTrack); // Create an overlay clip for picture-in-picture effect (1/4 size) const overlayClip = await engine.block.addVideo( videoUrl, 1920 / 4, 1080 / 4, { timeline: { duration: 5, timeOffset: 2 } } ); engine.block.appendChild(overlayTrack, overlayClip); // Position overlay in bottom-right corner with padding engine.block.setPositionX(overlayClip, 1920 - 1920 / 4 - 40); engine.block.setPositionY(overlayClip, 1080 - 1080 / 4 - 40); // eslint-disable-next-line no-console console.log('Multi-track composition created with overlay starting at 2s'); // Select the first clip in the main track to show timeline controls engine.block.select(clipC); // Seek to 2.5s to show both main clip and overlay visible // (overlay starts at 2s, so 2.5s shows both elements) engine.block.setPlaybackTime(page, 2.5); // eslint-disable-next-line no-console console.log( 'Join and Arrange guide initialized. Use timeline to view clip arrangement.' ); } } export default Example; ``` This guide covers how to join clips using the built-in timeline UI, how to programmatically add and arrange clips in tracks, and how to create multi-track compositions. ## Joining Clips via UI CE.SDK's timeline UI provides visual tools for arranging video clips. Select the Video mode to access timeline-based editing. ### Adding Clips to Timeline Drag clips from the asset panel directly onto the timeline. When you drop a clip on an existing track, it joins the sequence. Dropping on an empty area creates a new track for that clip. The timeline displays clip duration visually—longer clips take more horizontal space. You can see at a glance how clips relate to each other in time. ### Reordering Clips Drag clips within a track to reorder them. As you drag, CE.SDK shows where the clip will land. Release to confirm the new position. The timeline UI updates time offsets when you reorder clips via drag-and-drop, positioning clips sequentially without gaps. ### Creating Additional Tracks Add multiple tracks to create layered compositions. Tracks stack vertically in the timeline, and clips on upper tracks render on top of clips below. This enables picture-in-picture effects, overlays, and complex multi-layer edits. ## Programmatic Clip Joining ### Prerequisites and Setup For applications that need to join clips programmatically—whether for automation, batch processing, or dynamic compositions—we start by setting up CE.SDK in Video mode. ```typescript highlight=highlight-enable-video-features // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); ``` Video mode enables timeline features and playback controls. The `ly.img.timeline` feature provides the timeline panel, and `ly.img.playback` enables play/pause controls. ### Creating a Video Scene We create a video scene to access timeline-based editing capabilities. Design mode doesn't support tracks and sequential playback. ```typescript highlight=highlight-create-video-scene await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set page duration to accommodate all clips (15 seconds total) engine.block.setDuration(page, 15); ``` The page duration determines how long the composition plays. Set it to accommodate all your clips—in this example, 15 seconds for three 5-second clips. ### Creating Video Clips We create video clips as graphic blocks with video fills. Each clip needs a video fill that references the source media. ```typescript highlight=highlight-create-clips // Create video clips using the addVideo helper method // Each clip is sized to fill the canvas (1920x1080 is standard video resolution) const clipA = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 0 } }); const clipB = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 5 } }); const clipC = await engine.block.addVideo(videoUrl, 1920, 1080, { timeline: { duration: 5, timeOffset: 10 } }); ``` The `addVideo` helper method creates a graphic block with an attached video fill and automatically loads the video resource metadata. We set width and height to control how the clip appears in the composition. The `timeline` options let us set duration and time offset in one call. ### Creating Tracks Tracks organize clips for sequential playback. We create a track and attach it to the page. ```typescript highlight=highlight-create-track // Create a track and add it to the page // Tracks organize clips for sequential playback on the timeline const track = engine.block.create('track'); engine.block.appendChild(page, track); ``` A track acts as a container for clips. When you add clips to a track, they play in the order they were added. ### Adding Clips to Track We add clips to the track using `appendChild`. Clips join the sequence in the order they're added. ```typescript highlight=highlight-add-clips-to-track // Add clips to the track engine.block.appendChild(track, clipA); engine.block.appendChild(track, clipB); engine.block.appendChild(track, clipC); // Resize all track children to fill the page dimensions engine.block.fillParent(track); // Query track children to verify order const trackClips = engine.block.getChildren(track); // eslint-disable-next-line no-console console.log('Track clip count:', trackClips.length, 'clips'); ``` After adding clips, you can query the track's children to verify the order. `getChildren` returns an array of clip IDs in playback order. ### Setting Clip Durations Each clip needs a duration that determines how long it plays in the timeline. ```typescript highlight=highlight-set-clip-durations // Set durations for each clip engine.block.setDuration(clipA, 5); engine.block.setDuration(clipB, 5); engine.block.setDuration(clipC, 5); ``` Duration is measured in seconds. A 5-second duration means the clip occupies 5 seconds of timeline space. ## Arranging Clips ### Time Offsets Time offsets control when each clip starts playing. We set offsets to position clips at specific points in the timeline. ```typescript highlight=highlight-time-offsets // Set time offsets to position clips sequentially on the timeline engine.block.setTimeOffset(clipA, 0); engine.block.setTimeOffset(clipB, 5); engine.block.setTimeOffset(clipC, 10); // eslint-disable-next-line no-console console.log('Track offsets set: Clip A: 0s, Clip B: 5s, Clip C: 10s'); ``` Clip A starts at 0 seconds, Clip B at 5 seconds, and Clip C at 10 seconds. Combined with 5-second durations, this creates a continuous 15-second sequence with no gaps. ### Reordering Clips Use `insertChild` to move clips to specific positions within a track. This moves an existing child to a new index. ```typescript highlight=highlight-reorder-clips // Reorder clips: move Clip C to the beginning (index 0) // This demonstrates using insertChild for precise positioning engine.block.insertChild(track, clipC, 0); // After reordering, update time offsets to reflect the new sequence engine.block.setTimeOffset(clipC, 0); engine.block.setTimeOffset(clipA, 5); engine.block.setTimeOffset(clipB, 10); // eslint-disable-next-line no-console console.log('After reorder - updated offsets: C=0s, A=5s, B=10s'); ``` When we insert Clip C at index 0, it becomes the first clip. The order changes from A-B-C to C-A-B. We update time offsets to match the new sequence. ### Querying Track Children Use `getChildren` to inspect the current clip order and verify arrangements. ```typescript highlight=highlight-get-track-children // Get all clips in the track to verify arrangement const finalClips = engine.block.getChildren(track); // eslint-disable-next-line no-console console.log('Final track arrangement:'); finalClips.forEach((clipId, index) => { const offset = engine.block.getTimeOffset(clipId); const duration = engine.block.getDuration(clipId); // eslint-disable-next-line no-console console.log( ` Clip ${index + 1}: offset=${offset}s, duration=${duration}s` ); }); ``` This loop outputs each clip's position, time offset, and duration—useful for debugging or building custom timeline UIs. ## Multi-Track Compositions ### Adding Multiple Tracks Create layered compositions by adding multiple tracks to a page. Track order determines rendering order—clips in later tracks appear on top. ```typescript highlight=highlight-multi-track // Create a second track for layered compositions // Track order determines z-index: last track renders on top const overlayTrack = engine.block.create('track'); engine.block.appendChild(page, overlayTrack); // Create an overlay clip for picture-in-picture effect (1/4 size) const overlayClip = await engine.block.addVideo( videoUrl, 1920 / 4, 1080 / 4, { timeline: { duration: 5, timeOffset: 2 } } ); engine.block.appendChild(overlayTrack, overlayClip); // Position overlay in bottom-right corner with padding engine.block.setPositionX(overlayClip, 1920 - 1920 / 4 - 40); engine.block.setPositionY(overlayClip, 1080 - 1080 / 4 - 40); // eslint-disable-next-line no-console console.log('Multi-track composition created with overlay starting at 2s'); ``` The overlay track contains a smaller clip positioned in the corner. It starts at 2 seconds and lasts 5 seconds, creating a picture-in-picture effect during that time range. ### Track Rendering Order CE.SDK renders tracks from first to last. The first track added appears at the bottom, and subsequent tracks layer on top. Use this to create: - **Background layers**: Full-screen videos or images on the first track - **Overlays**: Smaller clips positioned on upper tracks - **Titles**: Text or graphics that appear over video content ## Troubleshooting ### Clips Not Appearing If clips don't show on the timeline, verify they're attached to a track that's attached to the page. Use `getParent` and `getChildren` to inspect the hierarchy: ```typescript const parent = engine.block.getParent(clipId); const children = engine.block.getChildren(trackId); ``` ### Wrong Playback Order If clips play in unexpected order, check time offsets. Clips play based on their time offset values, not their order in the children array. Set explicit offsets when precise timing matters. ### Video Not Loading If video content doesn't appear when using `addVideo`, check that the video URL is accessible and the format is supported. The `addVideo` helper automatically loads video metadata. ## API Reference | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `block.addVideo(uri, width, height, options)` | Create video clip with automatic resource loading | `uri: string, width: number, height: number, options?: { timeline: { duration, timeOffset } }` | `Promise` | | `block.create('track')` | Create a new track | `type: 'track'` | `DesignBlockId` | | `block.appendChild(parent, child)` | Add child to parent | `parent: DesignBlockId, child: DesignBlockId` | `void` | | `block.insertChild(parent, child, index)` | Insert child at specific position | `parent: DesignBlockId, child: DesignBlockId, index: number` | `void` | | `block.getChildren(id)` | Get all children of a block | `id: DesignBlockId` | `DesignBlockId[]` | | `block.setTimeOffset(id, offset)` | Set when block starts in timeline | `id: DesignBlockId, offset: number` | `void` | | `block.getTimeOffset(id)` | Get block's time offset | `id: DesignBlockId` | `number` | | `block.setDuration(id, duration)` | Set block's duration | `id: DesignBlockId, duration: number` | `void` | | `block.getDuration(id)` | Get block's duration | `id: DesignBlockId` | `number` | ## Next Steps Now that you understand how to join and arrange clips, explore related video editing features: - [Trim Video Clips](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) - Control which portion of media plays back - [Control Audio and Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) - Master playback timing and audio mixing - [Video Timeline Overview](https://img.ly/docs/cesdk/angular/create-video/timeline-editor-912252/) - Understand the complete timeline editing system --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Redact Sensitive Content in Videos" description: "Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/redaction-cf6d03/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Redaction](https://img.ly/docs/cesdk/angular/edit-video/redaction-cf6d03/) --- Redact sensitive video content using blur, pixelization, or solid overlays for privacy protection. ![Video Redaction example showing video clips with blur, pixelization, and overlay effects](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-redaction-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-redaction-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-redaction-browser/) CE.SDK applies effects to blocks themselves, not as overlays affecting content beneath. This means redaction involves applying effects directly to the block for complete obscuration. Four techniques cover most privacy scenarios: full-block blur, radial blur, pixelization, and solid overlays. ```typescript file=@cesdk_web_examples/guides-create-video-redaction-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; // Video URLs for demonstrating different redaction scenarios const VIDEOS = { surfer: 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4', lifestyle1: 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-taryn-elliott-7108793.mp4', lifestyle2: 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-taryn-elliott-7108801.mp4', nature1: 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-taryn-elliott-8713109.mp4', nature2: 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-taryn-elliott-8713114.mp4' }; // Labels for each redaction technique const LABELS = [ 'Radial Blur', 'Full-Block Blur', 'Pixelization', 'Solid Overlay', 'Time-Based' ]; // Duration for each video segment (in seconds) const SEGMENT_DURATION = 5.0; /** * CE.SDK Plugin: Video Redaction Guide * * Demonstrates video redaction techniques in CE.SDK: * - Full-block blur for complete video obscuration * - Radial blur for circular redaction patterns * - Pixelization for mosaic-style censoring * - Solid overlays for complete blocking * - Time-based redactions */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); cesdk.feature.enable('ly.img.blur'); cesdk.feature.enable('ly.img.effect'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1920, height: 1080, unit: 'Pixel' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Set 16:9 page dimensions (1920x1080) const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Load all videos simultaneously const videoUrls = [ VIDEOS.nature2, VIDEOS.surfer, VIDEOS.lifestyle1, VIDEOS.lifestyle2, VIDEOS.nature1 ]; const videos = await Promise.all( videoUrls.map((url) => engine.block.addVideo(url, pageWidth, pageHeight)) ); const [ radialVideo, fullBlurVideo, pixelVideo, // Base video for overlay segment (overlay is created separately) , timedVideo ] = videos; // Position all videos at origin (they'll play sequentially) videos.forEach((video, index) => { engine.block.setPositionX(video, 0); engine.block.setPositionY(video, 0); engine.block.setDuration(video, SEGMENT_DURATION); engine.block.setTimeOffset(video, index * SEGMENT_DURATION); engine.block.appendChild(page, video); }); // Full-Block Blur: Apply blur to entire video // Use this when the entire video content needs obscuring // Check if the block supports blur const supportsBlur = engine.block.supportsBlur(fullBlurVideo); // eslint-disable-next-line no-console console.log('Video supports blur:', supportsBlur); // Create and apply uniform blur to entire video const uniformBlur = engine.block.createBlur('uniform'); engine.block.setFloat(uniformBlur, 'blur/uniform/intensity', 0.7); engine.block.setBlur(fullBlurVideo, uniformBlur); engine.block.setBlurEnabled(fullBlurVideo, true); // Pixelization: Apply mosaic effect for clearly intentional censoring // Check if the block supports effects if (engine.block.supportsEffects(pixelVideo)) { // Create and apply pixelize effect const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.setInt( pixelizeEffect, 'effect/pixelize/horizontalPixelSize', 24 ); engine.block.setInt( pixelizeEffect, 'effect/pixelize/verticalPixelSize', 24 ); engine.block.appendEffect(pixelVideo, pixelizeEffect); engine.block.setEffectEnabled(pixelizeEffect, true); } // Solid Overlay: Create opaque shape for complete blocking // Best for highly sensitive information like documents or credentials // Create a solid rectangle overlay const overlay = engine.block.create('//ly.img.ubq/graphic'); const rectShape = engine.block.createShape('//ly.img.ubq/shape/rect'); engine.block.setShape(overlay, rectShape); // Create solid black fill const solidFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(solidFill, 'fill/color/value', { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }); engine.block.setFill(overlay, solidFill); // Position and size the overlay engine.block.setWidth(overlay, pageWidth * 0.4); engine.block.setHeight(overlay, pageHeight * 0.3); engine.block.setPositionX(overlay, pageWidth * 0.55); engine.block.setPositionY(overlay, pageHeight * 0.65); engine.block.appendChild(page, overlay); engine.block.setTimeOffset(overlay, 3 * SEGMENT_DURATION); engine.block.setDuration(overlay, SEGMENT_DURATION); // Time-Based Redaction: Redaction appears only during specific time range // Apply blur to the video const timedBlur = engine.block.createBlur('uniform'); engine.block.setFloat(timedBlur, 'blur/uniform/intensity', 0.9); engine.block.setBlur(timedVideo, timedBlur); engine.block.setBlurEnabled(timedVideo, true); // The video is already timed to appear at a specific offset (set earlier) // You can adjust timeOffset and duration to control when redaction is visible engine.block.setTimeOffset(timedVideo, 4 * SEGMENT_DURATION); engine.block.setDuration(timedVideo, SEGMENT_DURATION); // Radial Blur: Use radial blur for face-like regions // Apply radial blur for circular redaction effect const radialBlur = engine.block.createBlur('radial'); engine.block.setFloat(radialBlur, 'blur/radial/blurRadius', 50); engine.block.setFloat(radialBlur, 'blur/radial/radius', 25); engine.block.setFloat(radialBlur, 'blur/radial/gradientRadius', 35); engine.block.setFloat(radialBlur, 'blur/radial/x', 0.5); engine.block.setFloat(radialBlur, 'blur/radial/y', 0.45); engine.block.setBlur(radialVideo, radialBlur); engine.block.setBlurEnabled(radialVideo, true); // Add text labels for each video segment (positioned top-right) const labelWidth = 400; const labelHeight = 60; const labelPadding = 20; const labelMargin = 30; videos.forEach((_, index) => { const label = LABELS[index]; // Add background first (so it's behind text) const labelBg = engine.block.create('//ly.img.ubq/graphic'); const labelBgShape = engine.block.createShape('//ly.img.ubq/shape/rect'); engine.block.setShape(labelBg, labelBgShape); const labelBgFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(labelBgFill, 'fill/color/value', { r: 0.0, g: 0.0, b: 0.0, a: 0.8 }); engine.block.setFill(labelBg, labelBgFill); engine.block.setWidth(labelBg, labelWidth); engine.block.setHeight(labelBg, labelHeight + labelPadding); engine.block.setPositionX(labelBg, pageWidth - labelWidth - labelMargin); engine.block.setPositionY(labelBg, labelMargin); engine.block.setTimeOffset(labelBg, index * SEGMENT_DURATION); engine.block.setDuration(labelBg, SEGMENT_DURATION); engine.block.appendChild(page, labelBg); // Create text block for label const textBlock = engine.block.create('//ly.img.ubq/text'); engine.block.setString(textBlock, 'text/text', label); engine.block.setFloat(textBlock, 'text/fontSize', 48); engine.block.setEnum(textBlock, 'text/horizontalAlignment', 'Center'); engine.block.setEnum(textBlock, 'text/verticalAlignment', 'Center'); // Set white text color engine.block.setTextColor(textBlock, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); // Position label at top right engine.block.setWidth(textBlock, labelWidth); engine.block.setHeight(textBlock, labelHeight); engine.block.setPositionX( textBlock, pageWidth - labelWidth - labelMargin ); engine.block.setPositionY(textBlock, labelMargin + labelPadding / 2); engine.block.setTimeOffset(textBlock, index * SEGMENT_DURATION); engine.block.setDuration(textBlock, SEGMENT_DURATION); engine.block.appendChild(page, textBlock); }); // Enable auto-fit to keep page in view cesdk.engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40); // Select first video to show timeline controls engine.block.select(fullBlurVideo); // eslint-disable-next-line no-console console.log( 'Video redaction guide initialized. Videos play sequentially - press play to see each redaction technique.' ); } } export default Example; ``` This guide covers how to use the built-in UI for blur and pixelization effects, and how to apply redaction programmatically using blur, pixelization, solid overlays, and time-based controls. ## Understanding Redaction in CE.SDK ### How Effects Work Effects in CE.SDK modify the block's appearance directly rather than creating transparent overlays that affect content beneath. When you blur a video block, the entire block becomes blurred—not just a region on top of the video. ### Choosing a Redaction Technique Select your technique based on privacy requirements and visual impact: - **Full-block blur**: Complete obscuration for backgrounds or placeholder content - **Radial blur**: Circular blur patterns ideal for face-like regions - **Pixelization**: Clearly intentional censoring that's faster to render than heavy blur - **Solid overlays**: Complete blocking for highly sensitive information like documents or credentials ## Using the Built-in UI ### Accessing Blur Controls When you select a video block, the inspector panel provides access to blur settings. Navigate to the blur section to find controls for different blur types including uniform, radial, linear, and mirrored. The uniform blur applies consistent intensity across the entire block. Adjust the intensity slider to control how strongly the content is obscured. Higher values create stronger privacy protection but may affect visual quality. ### Accessing Pixelization Controls The effects panel contains pixelization settings. Select your video block, open the effects section, and add a pixelize effect. Configure the horizontal and vertical pixel sizes to control the mosaic block dimensions. Larger pixel sizes create stronger obscuration but are more visually disruptive. Values between 15-30 pixels work well for standard redaction scenarios. ## Programmatic Redaction ### Full-Block Blur When the entire video needs obscuring, apply blur directly to the original block without duplication. This approach works well for background content or privacy placeholders. ```typescript highlight-full-block-blur // Check if the block supports blur const supportsBlur = engine.block.supportsBlur(fullBlurVideo); // eslint-disable-next-line no-console console.log('Video supports blur:', supportsBlur); // Create and apply uniform blur to entire video const uniformBlur = engine.block.createBlur('uniform'); engine.block.setFloat(uniformBlur, 'blur/uniform/intensity', 0.7); engine.block.setBlur(fullBlurVideo, uniformBlur); engine.block.setBlurEnabled(fullBlurVideo, true); ``` We first check that the block supports blur with `supportsBlur()`. Then we create a uniform blur, configure its intensity, attach it to the video block with `setBlur()`, and enable it with `setBlurEnabled()`. The intensity value ranges from 0.0 to 1.0, where higher values create stronger blur. ### Pixelization Pixelization creates a mosaic effect that's clearly intentional and renders faster than heavy blur. We use the effect system rather than the blur system for pixelization. ```typescript highlight-pixelization // Check if the block supports effects if (engine.block.supportsEffects(pixelVideo)) { // Create and apply pixelize effect const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.setInt( pixelizeEffect, 'effect/pixelize/horizontalPixelSize', 24 ); engine.block.setInt( pixelizeEffect, 'effect/pixelize/verticalPixelSize', 24 ); engine.block.appendEffect(pixelVideo, pixelizeEffect); engine.block.setEffectEnabled(pixelizeEffect, true); } ``` We check `supportsEffects()` before creating the pixelize effect. The horizontal and vertical pixel sizes control the mosaic block dimensions—larger values create stronger obscuration. ### Solid Overlays For complete blocking without any visual hint of the underlying content, create an opaque shape overlay. This approach doesn't require block duplication. ```typescript highlight-solid-overlay // Create a solid rectangle overlay const overlay = engine.block.create('//ly.img.ubq/graphic'); const rectShape = engine.block.createShape('//ly.img.ubq/shape/rect'); engine.block.setShape(overlay, rectShape); // Create solid black fill const solidFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(solidFill, 'fill/color/value', { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }); engine.block.setFill(overlay, solidFill); // Position and size the overlay engine.block.setWidth(overlay, pageWidth * 0.4); engine.block.setHeight(overlay, pageHeight * 0.3); engine.block.setPositionX(overlay, pageWidth * 0.55); engine.block.setPositionY(overlay, pageHeight * 0.65); engine.block.appendChild(page, overlay); ``` We create a graphic block with a rectangle shape and solid color fill. The overlay uses absolute page coordinates for positioning. Set the alpha to 1.0 for complete opacity. ### Time-Based Redaction Redactions can appear only during specific portions of the video timeline. We use `setTimeOffset()` and `setDuration()` to control when the redaction is visible. ```typescript highlight-time-based-redaction // Apply blur to the video const timedBlur = engine.block.createBlur('uniform'); engine.block.setFloat(timedBlur, 'blur/uniform/intensity', 0.9); engine.block.setBlur(timedVideo, timedBlur); engine.block.setBlurEnabled(timedVideo, true); // The video is already timed to appear at a specific offset (set earlier) // You can adjust timeOffset and duration to control when redaction is visible engine.block.setTimeOffset(timedVideo, 4 * SEGMENT_DURATION); engine.block.setDuration(timedVideo, SEGMENT_DURATION); ``` The time offset specifies when the redaction appears (in seconds from the start), and the duration controls how long it remains visible. This is useful for redacting faces or information that only appears briefly. ### Radial Blur For face-like regions, radial blur creates a circular blur pattern that works well with rounded subjects. ```typescript highlight-radial-blur // Apply radial blur for circular redaction effect const radialBlur = engine.block.createBlur('radial'); engine.block.setFloat(radialBlur, 'blur/radial/blurRadius', 50); engine.block.setFloat(radialBlur, 'blur/radial/radius', 25); engine.block.setFloat(radialBlur, 'blur/radial/gradientRadius', 35); engine.block.setFloat(radialBlur, 'blur/radial/x', 0.5); engine.block.setFloat(radialBlur, 'blur/radial/y', 0.45); engine.block.setBlur(radialVideo, radialBlur); engine.block.setBlurEnabled(radialVideo, true); ``` Radial blur properties control the blur center (`x`, `y` from 0.0-1.0), the unblurred center area (`radius`), the blur transition zone (`gradientRadius`), and the blur strength (`blurRadius`). ## Performance Considerations Different redaction techniques have different performance impacts: - **Solid overlays**: Minimal impact, can create many without significant overhead - **Pixelization**: Faster than blur, larger pixel sizes have minimal impact - **Blur effects**: Higher intensity values increase rendering time For complex scenes with multiple redactions, consider using solid overlays where blur isn't necessary, or reduce blur intensity to maintain smooth playback. ## Troubleshooting ### Redaction Not Visible If your redaction doesn't appear, verify that: - The overlay is a child of the page with `appendChild()` - Blur is enabled with `setBlurEnabled()` after setting it with `setBlur()` - Effects are enabled with `setEffectEnabled()` after appending with `appendEffect()` ### Performance Issues Reduce blur intensity, use pixelization instead of heavy blur, or switch to solid overlays for some redactions. ## Best Practices - **Preview thoroughly**: Scrub the entire timeline to verify all sensitive content is covered - **Add safety margins**: Make redaction regions slightly larger than the sensitive area - **Test at export resolution**: Higher resolutions may need stronger blur settings - **Archive originals**: Exported redactions are permanent and cannot be reversed - **Document redactions**: For compliance requirements, maintain records of what was redacted ## API Reference | Method | Description | | ------ | ----------- | | `block.supportsBlur(id)` | Check if block supports blur effects | | `block.createBlur(type)` | Create blur instance (uniform, radial, linear, mirrored) | | `block.setBlur(id, blur)` | Apply blur to block | | `block.setBlurEnabled(id, enabled)` | Enable or disable blur | | `block.supportsEffects(id)` | Check if block supports effects | | `block.createEffect(type)` | Create effect instance (pixelize, etc.) | | `block.appendEffect(id, effect)` | Add effect to block | | `block.setEffectEnabled(effect, enabled)` | Enable or disable effect | | `block.setTimeOffset(id, offset)` | Set when block appears in timeline | | `block.setDuration(id, duration)` | Set block duration in timeline | | `block.create(type)` | Create block of specified type | | `block.createShape(type)` | Create shape for graphic blocks | | `block.setShape(id, shape)` | Apply shape to graphic block | | `block.createFill(type)` | Create fill (color, image, video, gradient) | | `block.setFill(id, fill)` | Apply fill to block | | `block.setFloat(id, property, value)` | Set float property value | | `block.setInt(id, property, value)` | Set integer property value | | `block.setColor(id, property, color)` | Set color property value | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Split Video and Audio" description: "Learn how to split video and audio clips at specific time points in CE.SDK, creating two independent segments from a single clip." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/split-464167/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Split](https://img.ly/docs/cesdk/angular/edit-video/split-464167/) --- Split video and audio clips at specific time points using CE.SDK's timeline UI and programmatic split API to create independent segments. ![Video Split example showing timeline with video clips and split controls](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-split-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-split-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-split-browser/) Clip splitting divides one block into two at a specified time. The original block ends at the split point; a new block starts there. Both blocks reference the same source media with independent timing. This differs from trimming, which adjusts a single block's playback range without creating new blocks. ```typescript file=@cesdk_web_examples/guides-create-video-split-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Split Video Guide * * Demonstrates splitting video clips in CE.SDK: * - Basic splitting at specific time points * - Configuring split options (attachToParent, selectNewBlock, createParentTrackIfNeeded) * - Splitting at playhead position * - Understanding split results (trim properties) * - Splitting multiple tracks at timeline position * - Split and delete workflow */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Get a video from demo asset sources const videoAssets = await engine.asset.findAssets('ly.img.video', { page: 0, perPage: 1 }); const videoUri = videoAssets.assets[0]?.payload?.sourceSet?.[0]?.uri ?? 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Create a video block to demonstrate basic splitting const basicSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the video fill and load resource to access duration const basicFill = engine.block.getFill(basicSplitVideo); await engine.block.forceLoadAVResource(basicFill); // Set block duration for the timeline engine.block.setDuration(basicSplitVideo, 10.0); // Split the video block at 5 seconds // Returns the ID of the newly created block (second segment) const splitResultBlock = engine.block.split(basicSplitVideo, 5.0); // eslint-disable-next-line no-console console.log( `Basic split - Original block: ${basicSplitVideo}, New block: ${splitResultBlock}` ); // Create another video block to demonstrate split options const optionsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const optionsFill = engine.block.getFill(optionsSplitVideo); await engine.block.forceLoadAVResource(optionsFill); engine.block.setDuration(optionsSplitVideo, 10.0); // Split with custom options const optionsSplitResult = engine.block.split(optionsSplitVideo, 4.0, { attachToParent: true, // Attach new block to same parent (default: true) createParentTrackIfNeeded: false, // Don't create track if needed (default: false) selectNewBlock: false // Don't select the new block (default: true) }); // eslint-disable-next-line no-console console.log( `Split with options - New block: ${optionsSplitResult}, selectNewBlock: false` ); // Create a video block to demonstrate playhead-based splitting const playheadSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const playheadFill = engine.block.getFill(playheadSplitVideo); await engine.block.forceLoadAVResource(playheadFill); engine.block.setDuration(playheadSplitVideo, 10.0); // Get the clip's start time on the timeline const clipStartTime = engine.block.getTimeOffset(playheadSplitVideo); // Simulate a playhead position (in a real app, use engine.block.getPlaybackTime(page)) const simulatedPlayheadTime = clipStartTime + 3.0; // Calculate split time relative to the clip const splitTime = simulatedPlayheadTime - clipStartTime; // Perform the split at the calculated time const playheadSplitResult = engine.block.split( playheadSplitVideo, splitTime ); // eslint-disable-next-line no-console console.log( `Playhead split - Split at ${splitTime}s into clip, New block: ${playheadSplitResult}` ); // Create a video block to examine split results const resultsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const resultsFill = engine.block.getFill(resultsSplitVideo); await engine.block.forceLoadAVResource(resultsFill); engine.block.setDuration(resultsSplitVideo, 10.0); // Get trim values before split const originalTrimOffset = engine.block.getTrimOffset(resultsFill); const originalTrimLength = engine.block.getTrimLength(resultsFill); // Split at 6 seconds const resultsNewBlock = engine.block.split(resultsSplitVideo, 6.0); const newBlockFill = engine.block.getFill(resultsNewBlock); // Examine trim properties after split const originalAfterSplitOffset = engine.block.getTrimOffset(resultsFill); const originalAfterSplitLength = engine.block.getTrimLength(resultsFill); const newBlockTrimOffset = engine.block.getTrimOffset(newBlockFill); const newBlockTrimLength = engine.block.getTrimLength(newBlockFill); // eslint-disable-next-line no-console console.log('Split results:'); // eslint-disable-next-line no-console console.log( ` Original before: offset=${originalTrimOffset}, length=${originalTrimLength}` ); // eslint-disable-next-line no-console console.log( ` Original after: offset=${originalAfterSplitOffset}, length=${originalAfterSplitLength}` ); // eslint-disable-next-line no-console console.log( ` New block: offset=${newBlockTrimOffset}, length=${newBlockTrimLength}` ); // Create a video block to demonstrate split-and-delete workflow const deleteWorkflowVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const deleteFill = engine.block.getFill(deleteWorkflowVideo); await engine.block.forceLoadAVResource(deleteFill); engine.block.setDuration(deleteWorkflowVideo, 10.0); // Remove middle section: split at start of section to remove (2s) const middleBlock = engine.block.split(deleteWorkflowVideo, 2.0); // Split again at the end of the section to remove (at 3s into middle block = 5s total) const endBlock = engine.block.split(middleBlock, 3.0); // Delete the middle segment engine.block.destroy(middleBlock); // eslint-disable-next-line no-console console.log( `Split and delete - Removed middle 3s section, kept blocks: ${deleteWorkflowVideo}, ${endBlock}` ); // Create a video block to demonstrate split time validation const validateVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const validateFill = engine.block.getFill(validateVideo); await engine.block.forceLoadAVResource(validateFill); engine.block.setDuration(validateVideo, 8.0); // Get block duration to validate split time const blockDuration = engine.block.getDuration(validateVideo); const desiredSplitTime = 4.0; // Validate split time is within bounds (must be > 0 and < duration) if (desiredSplitTime > 0 && desiredSplitTime < blockDuration) { const validatedSplitResult = engine.block.split( validateVideo, desiredSplitTime ); // eslint-disable-next-line no-console console.log( `Validated split - Duration: ${blockDuration}s, Split at: ${desiredSplitTime}s, New block: ${validatedSplitResult}` ); } else { // eslint-disable-next-line no-console console.log('Split time out of range'); } // ===== Position all blocks in grid layout ===== // Note: Some original blocks were modified by splits, position remaining visible blocks const blocks = [ basicSplitVideo, splitResultBlock, optionsSplitVideo, optionsSplitResult, playheadSplitVideo, playheadSplitResult ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Select first block so timeline controls are visible engine.block.setSelected(basicSplitVideo, true); // Set playback time to 8 seconds for hero image engine.block.setPlaybackTime(page, 8.0); // Start playback automatically when the example loads try { engine.block.setPlaying(page, true); // eslint-disable-next-line no-console console.log( 'Video split guide initialized. Playback started. Use timeline to see split results.' ); } catch (error) { // eslint-disable-next-line no-console console.log( 'Video split guide initialized. Click play button to start playback.' ); } } } export default Example; ``` This guide covers how to use the built-in timeline UI for visual splitting and how to split clips programmatically using the Engine API. ## Splitting Clips via the UI When you select a video or audio block in the timeline, CE.SDK provides split functionality through the toolbar. Position the playhead at the desired split point by clicking on the timeline ruler or dragging the playhead indicator. With the clip selected and playhead positioned, click the Split button in the timeline toolbar. CE.SDK divides the clip at the playhead position, creating two independent segments. Visual feedback shows the resulting segments as separate blocks on the timeline. The timeline enforces minimum duration constraints that prevent splitting too close to clip edges. If the playhead is positioned within this minimum boundary, the split operation will not be available. ## Programmatic Splitting For applications that need to split clips programmatically—whether for automation, batch processing, or dynamic editing—CE.SDK provides the `engine.block.split()` method. ### Prerequisites and Setup Before splitting, ensure video features are enabled and a video scene is created. ```typescript highlight-enable-video-features // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); ``` Video mode is required for split operations. Design mode doesn't provide timeline-based editing capabilities, so use `cesdk.actions.run('scene.create', { mode: 'Video' })` to access split functionality. ### Basic Splitting at a Specific Time Split a block by providing the block ID and the split time in seconds. The time parameter is relative to the block's own timeline, accounting for the block's time offset. ```typescript highlight-basic-split // Create a video block to demonstrate basic splitting const basicSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the video fill and load resource to access duration const basicFill = engine.block.getFill(basicSplitVideo); await engine.block.forceLoadAVResource(basicFill); // Set block duration for the timeline engine.block.setDuration(basicSplitVideo, 10.0); // Split the video block at 5 seconds // Returns the ID of the newly created block (second segment) const splitResultBlock = engine.block.split(basicSplitVideo, 5.0); // eslint-disable-next-line no-console console.log( `Basic split - Original block: ${basicSplitVideo}, New block: ${splitResultBlock}` ); ``` The `split()` method returns the ID of the newly created block. The original block becomes the first segment (before the split point), and the returned block is the second segment (after the split point). ### Configuring Split Options The `SplitOptions` object controls split behavior with three optional properties: - **`attachToParent`** (default: `true`): Whether to attach the new block to the same parent as the original - **`createParentTrackIfNeeded`** (default: `false`): Creates a parent track if needed and adds both blocks to it - **`selectNewBlock`** (default: `true`): Whether to select the newly created block after splitting ```typescript highlight-split-options // Create another video block to demonstrate split options const optionsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const optionsFill = engine.block.getFill(optionsSplitVideo); await engine.block.forceLoadAVResource(optionsFill); engine.block.setDuration(optionsSplitVideo, 10.0); // Split with custom options const optionsSplitResult = engine.block.split(optionsSplitVideo, 4.0, { attachToParent: true, // Attach new block to same parent (default: true) createParentTrackIfNeeded: false, // Don't create track if needed (default: false) selectNewBlock: false // Don't select the new block (default: true) }); // eslint-disable-next-line no-console console.log( `Split with options - New block: ${optionsSplitResult}, selectNewBlock: false` ); ``` Use `selectNewBlock: false` when splitting multiple clips programmatically to avoid changing selection state between operations. ### Splitting at the Current Playhead Position To implement playhead-based splitting like the built-in UI, get the current playback time from the page and convert it to block-relative time. ```typescript highlight-split-at-playhead // Create a video block to demonstrate playhead-based splitting const playheadSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const playheadFill = engine.block.getFill(playheadSplitVideo); await engine.block.forceLoadAVResource(playheadFill); engine.block.setDuration(playheadSplitVideo, 10.0); // Get the clip's start time on the timeline const clipStartTime = engine.block.getTimeOffset(playheadSplitVideo); // Simulate a playhead position (in a real app, use engine.block.getPlaybackTime(page)) const simulatedPlayheadTime = clipStartTime + 3.0; // Calculate split time relative to the clip const splitTime = simulatedPlayheadTime - clipStartTime; // Perform the split at the calculated time const playheadSplitResult = engine.block.split( playheadSplitVideo, splitTime ); // eslint-disable-next-line no-console console.log( `Playhead split - Split at ${splitTime}s into clip, New block: ${playheadSplitResult}` ); ``` The playhead position from `getPlaybackTime(page)` is in absolute timeline time. Subtract the clip's `getTimeOffset()` to convert to block-relative time before passing to `split()`. ## Understanding Split Results After a split operation, both the original and new blocks are configured with updated trim properties. ### Trim Properties After Split ```typescript highlight-split-results // Create a video block to examine split results const resultsSplitVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const resultsFill = engine.block.getFill(resultsSplitVideo); await engine.block.forceLoadAVResource(resultsFill); engine.block.setDuration(resultsSplitVideo, 10.0); // Get trim values before split const originalTrimOffset = engine.block.getTrimOffset(resultsFill); const originalTrimLength = engine.block.getTrimLength(resultsFill); // Split at 6 seconds const resultsNewBlock = engine.block.split(resultsSplitVideo, 6.0); const newBlockFill = engine.block.getFill(resultsNewBlock); // Examine trim properties after split const originalAfterSplitOffset = engine.block.getTrimOffset(resultsFill); const originalAfterSplitLength = engine.block.getTrimLength(resultsFill); const newBlockTrimOffset = engine.block.getTrimOffset(newBlockFill); const newBlockTrimLength = engine.block.getTrimLength(newBlockFill); // eslint-disable-next-line no-console console.log('Split results:'); // eslint-disable-next-line no-console console.log( ` Original before: offset=${originalTrimOffset}, length=${originalTrimLength}` ); // eslint-disable-next-line no-console console.log( ` Original after: offset=${originalAfterSplitOffset}, length=${originalAfterSplitLength}` ); // eslint-disable-next-line no-console console.log( ` New block: offset=${newBlockTrimOffset}, length=${newBlockTrimLength}` ); ``` The original block keeps its trim offset unchanged, but its trim length is reduced to the split point. The new block has its trim offset advanced by the split time and trim length set to cover the remaining duration. Both blocks reference the same source media—splitting is non-destructive. ### Timeline Positioning The original block keeps its `getTimeOffset()`. When `attachToParent` is true, the new block is positioned immediately after the original on the same parent. Both blocks stay on the same track unless `createParentTrackIfNeeded` creates a new track structure. ## Split and Delete Workflow Remove a middle section from a clip by splitting at both boundaries and deleting the middle segment. ```typescript highlight-split-and-delete // Create a video block to demonstrate split-and-delete workflow const deleteWorkflowVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const deleteFill = engine.block.getFill(deleteWorkflowVideo); await engine.block.forceLoadAVResource(deleteFill); engine.block.setDuration(deleteWorkflowVideo, 10.0); // Remove middle section: split at start of section to remove (2s) const middleBlock = engine.block.split(deleteWorkflowVideo, 2.0); // Split again at the end of the section to remove (at 3s into middle block = 5s total) const endBlock = engine.block.split(middleBlock, 3.0); // Delete the middle segment engine.block.destroy(middleBlock); // eslint-disable-next-line no-console console.log( `Split and delete - Removed middle 3s section, kept blocks: ${deleteWorkflowVideo}, ${endBlock}` ); ``` This workflow is useful for removing unwanted sections, such as cutting out pauses, mistakes, or irrelevant portions from a recording. ## Validating Split Time Always validate that the split time is within valid bounds before calling `split()`. The split time must be greater than 0 and less than the block's duration. ```typescript highlight-validate-split-time // Create a video block to demonstrate split time validation const validateVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const validateFill = engine.block.getFill(validateVideo); await engine.block.forceLoadAVResource(validateFill); engine.block.setDuration(validateVideo, 8.0); // Get block duration to validate split time const blockDuration = engine.block.getDuration(validateVideo); const desiredSplitTime = 4.0; // Validate split time is within bounds (must be > 0 and < duration) if (desiredSplitTime > 0 && desiredSplitTime < blockDuration) { const validatedSplitResult = engine.block.split( validateVideo, desiredSplitTime ); // eslint-disable-next-line no-console console.log( `Validated split - Duration: ${blockDuration}s, Split at: ${desiredSplitTime}s, New block: ${validatedSplitResult}` ); } else { // eslint-disable-next-line no-console console.log('Split time out of range'); } ``` Attempting to split at an invalid time (at the beginning, end, or outside the block's duration) will fail or produce unexpected results. ## Troubleshooting ### Split Returns Unexpected Block If the returned block ID doesn't behave as expected, remember that the original block becomes the first segment (before split point) and the returned block is the second segment (after split point). ### Split Time Out of Range If split fails or produces unexpected results, verify the split time is within bounds. Use `getDuration()` to check the valid range before splitting. ### Clip Not Splitting If `split()` has no visible effect, check that the block type supports splitting. Verify `supportsTrim()` returns true for the block's fill. For video and audio fills, ensure `forceLoadAVResource()` has been awaited before attempting to split. ## API Reference | Method | Description | Parameters | Returns | | ----------------------------- | ---------------------------------------------- | -------------------------------------------------- | ---------------- | | `split(id, atTime, options?)` | Split a block at the specified time | `id: DesignBlockId, atTime: number, options?: SplitOptions` | `DesignBlockId` | | `getTimeOffset(id)` | Get time offset relative to parent | `id: DesignBlockId` | `number` | | `getDuration(id)` | Get playback duration | `id: DesignBlockId` | `number` | | `getPlaybackTime(id)` | Get current playback time | `id: DesignBlockId` | `number` | | `getTrimOffset(id)` | Get trim offset of media content | `id: DesignBlockId` | `number` | | `getTrimLength(id)` | Get trim length of media content | `id: DesignBlockId` | `number` | | `supportsTrim(id)` | Check if block supports trim properties | `id: DesignBlockId` | `boolean` | | `forceLoadAVResource(id)` | Force load media resource metadata | `id: DesignBlockId` | `Promise` | | `destroy(id)` | Destroy a block | `id: DesignBlockId` | `void` | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Transform" description: "Documentation for Transform" platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) --- --- ## Related Pages - [Move Videos](https://img.ly/docs/cesdk/angular/edit-video/transform/move-aa9d89/) - Position videos on the canvas using absolute or percentage-based coordinates. - [Crop Videos](https://img.ly/docs/cesdk/angular/edit-video/transform/crop-8b1741/) - Cut out specific areas of a video to focus on key content or change aspect ratio. - [Rotate Videos](https://img.ly/docs/cesdk/angular/edit-video/transform/rotate-eaf662/) - Rotate video elements to adjust orientation and create dynamic compositions. - [Resize](https://img.ly/docs/cesdk/angular/edit-video/transform/resize-b1ce14/) - Resize videos in web apps using the CE.SDK - [Scale Videos in Web Apps](https://img.ly/docs/cesdk/angular/edit-video/transform/scale-f75c8a/) - Embed the CE.SDK scaling feature in your web app. - [Flip](https://img.ly/docs/cesdk/angular/edit-video/transform/flip-a603b0/) - Flip videos horizontally or vertically to create mirror effects and symmetrical designs. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Crop Videos" description: "Cut out specific areas of a video to focus on key content or change aspect ratio." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform/crop-8b1741/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) > [Crop](https://img.ly/docs/cesdk/angular/edit-video/transform/crop-8b1741/) --- Crop videos to focus on specific areas, remove unwanted edges, or prepare clips for fixed formats like 9:16 stories using programmatic crop transforms. ![Crop videos example showing cropped video content](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-crop-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-crop-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-transform-crop-browser/) Video cropping in CreativeEditor SDK (CE.SDK) lets you re-frame clips, remove unwanted edges, or adapt footage for platform-specific formats. Unlike resizing or scaling which affects the entire frame uniformly, cropping selects a specific region to display. ```typescript file=@cesdk_web_examples/guides-create-video-transform-crop-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 720, height: 1280, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page from the scene const pages = engine.block.findByType('page'); const page = pages[0]; // Add a video using the convenience API - this handles track creation automatically const videoUri = 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4'; const videoBlock = await engine.block.addVideo(videoUri, 720, 1280); // Append the video block to the page (for video scenes, this adds to the track) engine.block.appendChild(page, videoBlock); // Set video duration on the timeline engine.block.setDuration(videoBlock, 10); // Get the fill to force load the video resource const videoFill = engine.block.getFill(videoBlock); await engine.block.forceLoadAVResource(videoFill); // Verify the block supports cropping before applying crop operations const supportsCrop = engine.block.supportsCrop(videoBlock); console.log('Block supports crop:', supportsCrop); // Set content fill mode to 'Crop' for manual crop control // This enables the crop transform APIs to take effect engine.block.setContentFillMode(videoBlock, 'Crop'); // Scale the video content within its frame using uniform scale ratio // Values greater than 1.0 zoom in, values less than 1.0 zoom out engine.block.setCropScaleRatio(videoBlock, 1.1); // Pan the video content within the crop frame // Translation values are percentages of the crop frame dimensions // Positive X moves content right, positive Y moves content down engine.block.setCropTranslationX(videoBlock, 0.0); engine.block.setCropTranslationY(videoBlock, 0.0); // Rotate the video content within its frame // Rotation is specified in radians (Math.PI = 180 degrees) engine.block.setCropRotation(videoBlock, Math.PI / 90); // 2 degrees // Retrieve the current crop state const scaleRatio = engine.block.getCropScaleRatio(videoBlock); const translationX = engine.block.getCropTranslationX(videoBlock); const translationY = engine.block.getCropTranslationY(videoBlock); const rotation = engine.block.getCropRotation(videoBlock); console.log('Crop scale ratio:', scaleRatio); console.log('Crop translation X:', translationX); console.log('Crop translation Y:', translationY); console.log('Crop rotation (radians):', rotation); // Adjust crop to ensure content fills the frame without letterboxing // The minScaleRatio parameter sets the minimum allowed scale // This corrects any black bars caused by rotation or translation engine.block.adjustCropToFillFrame(videoBlock, 1.1); const finalScale = engine.block.getCropScaleRatio(videoBlock); console.log('Adjusted scale ratio:', finalScale); // Flip the video content within its crop frame // This flips the content, not the entire block engine.block.flipCropHorizontal(videoBlock); // Lock the crop aspect ratio during interactive editing // When locked, crop handles maintain the current aspect ratio engine.block.setCropAspectRatioLocked(videoBlock, true); const isLocked = engine.block.isCropAspectRatioLocked(videoBlock); console.log('Crop aspect ratio locked:', isLocked); // Reset crop to default state (removes all crop transformations) engine.block.resetCrop(videoBlock); // Re-apply a subtle zoom to demonstrate crop is working engine.block.setCropScaleRatio(videoBlock, 1.05); // Select the video block to show it in the UI engine.block.select(videoBlock); // Set playback time to show video content engine.block.setPlaybackTime(page, 2.0); // Zoom to the video block for better visibility of the crop effect cesdk.engine.scene.zoomToBlock(videoBlock, 0.5, 0.5, 0.8); } } export default Example; ``` This guide covers programmatic video cropping using scale, translation, and rotation transforms, checking crop support, adjusting crop to fill frames, flipping content, and locking aspect ratios. ## Check Crop Support Before applying crop operations, verify the block supports cropping using `engine.block.supportsCrop()`. Video blocks with fills support cropping: ```typescript highlight-check-crop-support // Verify the block supports cropping before applying crop operations const supportsCrop = engine.block.supportsCrop(videoBlock); console.log('Block supports crop:', supportsCrop); // Set content fill mode to 'Crop' for manual crop control // This enables the crop transform APIs to take effect engine.block.setContentFillMode(videoBlock, 'Crop'); ``` ## Scale Crop Scale the video content within its frame using `engine.block.setCropScaleRatio()`. Values greater than 1.0 zoom in, values less than 1.0 zoom out. This applies a uniform scale to both axes: ```typescript highlight-scale-crop // Scale the video content within its frame using uniform scale ratio // Values greater than 1.0 zoom in, values less than 1.0 zoom out engine.block.setCropScaleRatio(videoBlock, 1.1); ``` ## Translate Crop Pan the video content within the crop frame using `engine.block.setCropTranslationX()` and `engine.block.setCropTranslationY()`. Translation values are percentages of the crop frame dimensions. Positive X moves content right, positive Y moves content down: ```typescript highlight-translate-crop // Pan the video content within the crop frame // Translation values are percentages of the crop frame dimensions // Positive X moves content right, positive Y moves content down engine.block.setCropTranslationX(videoBlock, 0.0); engine.block.setCropTranslationY(videoBlock, 0.0); ``` ## Rotate Crop Rotate the video content within its frame using `engine.block.setCropRotation()`. Rotation is specified in radians where `Math.PI` equals 180 degrees: ```typescript highlight-rotate-crop // Rotate the video content within its frame // Rotation is specified in radians (Math.PI = 180 degrees) engine.block.setCropRotation(videoBlock, Math.PI / 90); // 2 degrees ``` ## Get Crop Values Retrieve the current crop state to read or restore crop settings using getter methods: ```typescript highlight-get-crop-values // Retrieve the current crop state const scaleRatio = engine.block.getCropScaleRatio(videoBlock); const translationX = engine.block.getCropTranslationX(videoBlock); const translationY = engine.block.getCropTranslationY(videoBlock); const rotation = engine.block.getCropRotation(videoBlock); console.log('Crop scale ratio:', scaleRatio); console.log('Crop translation X:', translationX); console.log('Crop translation Y:', translationY); console.log('Crop rotation (radians):', rotation); ``` ## Fill Frame Adjust the crop to ensure content fills the frame without letterboxing using `engine.block.adjustCropToFillFrame()`. The `minScaleRatio` parameter sets the minimum allowed scale: ```typescript highlight-fill-frame // Adjust crop to ensure content fills the frame without letterboxing // The minScaleRatio parameter sets the minimum allowed scale // This corrects any black bars caused by rotation or translation engine.block.adjustCropToFillFrame(videoBlock, 1.1); const finalScale = engine.block.getCropScaleRatio(videoBlock); console.log('Adjusted scale ratio:', finalScale); ``` This is useful after applying translations or rotations that might reveal empty areas. ## Flip Crop Flip the video content horizontally or vertically within its crop frame using `engine.block.flipCropHorizontal()` or `engine.block.flipCropVertical()`. This flips the content, not the block itself: ```typescript highlight-flip-crop // Flip the video content within its crop frame // This flips the content, not the entire block engine.block.flipCropHorizontal(videoBlock); ``` ## Lock Aspect Ratio Lock the crop aspect ratio during interactive editing using `engine.block.setCropAspectRatioLocked()`. When locked, crop handles maintain the current aspect ratio: ```typescript highlight-lock-aspect-ratio // Lock the crop aspect ratio during interactive editing // When locked, crop handles maintain the current aspect ratio engine.block.setCropAspectRatioLocked(videoBlock, true); const isLocked = engine.block.isCropAspectRatioLocked(videoBlock); console.log('Crop aspect ratio locked:', isLocked); ``` ## Reset Crop Reset all crop transformations to their default state using `engine.block.resetCrop()`: ```typescript highlight-reset-crop // Reset crop to default state (removes all crop transformations) engine.block.resetCrop(videoBlock); // Re-apply a subtle zoom to demonstrate crop is working engine.block.setCropScaleRatio(videoBlock, 1.05); ``` ## Coordinate System Crop transforms use normalized values: | Property | Value Type | Description | | --- | --- | --- | | Scale | Float (0.0+) | 1.0 is original size, 2.0 is double, 0.5 is half | | Translation | Float (-1.0 to 1.0) | Percentage of frame dimensions | | Rotation | Float (radians) | Math.PI = 180°, Math.PI/2 = 90° | All crop values are independent of canvas zoom level or timeline duration. ## Combining with Other Transforms You can combine crop operations with other block transforms like position, rotation, and scale. The crop transforms affect the content within the block, while block transforms affect the block itself on the canvas: ```typescript // Crop the content (scales/pans the video within its frame) engine.block.setCropScaleRatio(videoBlock, 1.5); engine.block.setCropRotation(videoBlock, Math.PI / 12); // Transform the block itself (moves/rotates the entire block on canvas) engine.block.setRotation(videoBlock, Math.PI / 6); engine.block.setWidth(videoBlock, 800); ``` ## Troubleshooting ### Crop Functions Not Working Check if the block supports cropping using `engine.block.supportsCrop()`. Ensure the block has a fill that supports cropping (video, image fills). ### Black Bars After Scaling Call `engine.block.adjustCropToFillFrame()` or increase the scale ratio so content fully covers the block frame. ### Unexpected Crop Behavior Verify you're operating on the correct block ID. Check that the video resource has loaded using `engine.block.forceLoadAVResource()` before applying crop transforms. ## API Reference | Method | Description | | --- | --- | | `engine.block.supportsCrop()` | Check if block supports cropping | | `engine.block.setCropScaleRatio()` | Set uniform scale ratio | | `engine.block.setCropScaleX()` | Set horizontal scale | | `engine.block.setCropScaleY()` | Set vertical scale | | `engine.block.setCropTranslationX()` | Set horizontal pan | | `engine.block.setCropTranslationY()` | Set vertical pan | | `engine.block.setCropRotation()` | Set rotation in radians | | `engine.block.getCropScaleRatio()` | Get current scale ratio | | `engine.block.getCropTranslationX()` | Get horizontal translation | | `engine.block.getCropTranslationY()` | Get vertical translation | | `engine.block.getCropRotation()` | Get rotation value | | `engine.block.adjustCropToFillFrame()` | Auto-adjust to fill frame | | `engine.block.flipCropHorizontal()` | Flip content horizontally | | `engine.block.flipCropVertical()` | Flip content vertically | | `engine.block.setCropAspectRatioLocked()` | Lock/unlock aspect ratio | | `engine.block.isCropAspectRatioLocked()` | Check if aspect ratio locked | | `engine.block.resetCrop()` | Reset all crop transforms | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Flip Videos" description: "Flip videos horizontally or vertically to create mirror effects and symmetrical designs." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform/flip-a603b0/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) > [Flip](https://img.ly/docs/cesdk/angular/edit-video/transform/flip-a603b0/) --- Use **CE.SDK** to **mirror video clips** horizontally or vertically to create symmetrical designs, correct orientation issues, or achieve specific visual effects. ![Flip Videos example showing videos with horizontal, vertical, and combined flip effects](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-flip-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-flip-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-transform-flip-browser/) Flipping mirrors video content along horizontal or vertical axes. Unlike rotation, which changes the angle of content, flipping creates a true mirror reflection. The flip operation affects only the visual track—audio embedded in the video or on separate tracks remains unchanged, preserving lip-sync and sound design. ```typescript file=@cesdk_web_examples/guides-create-video-transform-flip-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Flip Videos Guide * * Demonstrates video flipping in CE.SDK: * - Flip videos horizontally and vertically * - Query flip states * - Toggle flips programmatically * - Flip multiple clips as a group * - Lock flip operations in templates */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1280, height: 720, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages[0]; // Sample video URL const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Block dimensions const blockWidth = 280; const blockHeight = 160; // Centered grid layout (3 columns × 2 rows) const col1X = 180; const col2X = 500; const col3X = 820; const row1Y = 135; const row2Y = 385; const label1Y = 305; const label2Y = 555; const fontSize = 24; // Helper to create centered white label const createLabel = (text: string, x: number, y: number) => { const label = engine.block.create('text'); engine.block.setString(label, 'text/text', text); engine.block.setFloat(label, 'text/fontSize', fontSize); engine.block.setEnum(label, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(label, blockWidth); engine.block.setPositionX(label, x); engine.block.setPositionY(label, y); // Set white text color engine.block.setTextColor(label, { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, label); return label; }; // Demo 1: Original video (no flip) const originalVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, originalVideo); engine.block.setPositionX(originalVideo, col1X); engine.block.setPositionY(originalVideo, row1Y); createLabel('Original', col1X, label1Y); // Demo 2: Horizontal flip const horizontalFlipVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, horizontalFlipVideo); engine.block.setPositionX(horizontalFlipVideo, col2X); engine.block.setPositionY(horizontalFlipVideo, row1Y); // Flip the video horizontally (mirrors left to right) engine.block.setFlipHorizontal(horizontalFlipVideo, true); createLabel('Horizontal', col2X, label1Y); // Demo 3: Vertical flip const verticalFlipVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, verticalFlipVideo); engine.block.setPositionX(verticalFlipVideo, col3X); engine.block.setPositionY(verticalFlipVideo, row1Y); // Flip the video vertically (mirrors top to bottom) engine.block.setFlipVertical(verticalFlipVideo, true); createLabel('Vertical', col3X, label1Y); // Demo 4: Both flips combined const bothFlipVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, bothFlipVideo); engine.block.setPositionX(bothFlipVideo, col1X); engine.block.setPositionY(bothFlipVideo, row2Y); // Combine horizontal and vertical flips engine.block.setFlipHorizontal(bothFlipVideo, true); engine.block.setFlipVertical(bothFlipVideo, true); createLabel('Both', col1X, label2Y); // Query flip states const isFlippedH = engine.block.getFlipHorizontal(horizontalFlipVideo); const isFlippedV = engine.block.getFlipVertical(verticalFlipVideo); console.log(`Horizontal flip state: ${isFlippedH}`); console.log(`Vertical flip state: ${isFlippedV}`); // Toggle flip by reading current state and setting opposite const currentFlip = engine.block.getFlipHorizontal(originalVideo); engine.block.setFlipHorizontal(originalVideo, !currentFlip); console.log(`Toggled original video flip: ${!currentFlip}`); // Toggle back to keep original state for demo engine.block.setFlipHorizontal(originalVideo, currentFlip); // Demo 5: Group flip - flip multiple videos together const smallWidth = blockWidth / 2; const smallHeight = blockHeight / 2; const groupGap = 10; // Center the pair horizontally within column 2 const groupPairWidth = smallWidth * 2 + groupGap; const groupStartX = col2X + (blockWidth - groupPairWidth) / 2; // Center vertically within row 2 (smaller blocks) const groupY = row2Y + (blockHeight - smallHeight) / 2; const groupVideo1 = await engine.block.addVideo( videoUri, smallWidth, smallHeight ); engine.block.appendChild(page, groupVideo1); engine.block.setPositionX(groupVideo1, groupStartX); engine.block.setPositionY(groupVideo1, groupY); const groupVideo2 = await engine.block.addVideo( videoUri, smallWidth, smallHeight ); engine.block.appendChild(page, groupVideo2); engine.block.setPositionX(groupVideo2, groupStartX + smallWidth + groupGap); engine.block.setPositionY(groupVideo2, groupY); // Group the videos and flip them together const groupId = engine.block.group([groupVideo1, groupVideo2]); engine.block.setFlipHorizontal(groupId, true); createLabel('Group Flip', col2X, label2Y); // Demo 6: Lock flip scope const lockedVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, col3X); engine.block.setPositionY(lockedVideo, row2Y); // Disable flip operations for this block engine.block.setScopeEnabled(lockedVideo, 'layer/flip', false); // Verify scope is disabled const canFlip = engine.block.isScopeEnabled(lockedVideo, 'layer/flip'); console.log(`Flip enabled for locked video: ${canFlip}`); createLabel('Flip Locked', col3X, label2Y); // Set playhead to 1 second to show video content in thumbnails engine.block.setPlaybackTime(page, 1.0); console.log( 'Flip Videos guide initialized. Use the timeline to view and edit videos.' ); } } export default Example; ``` This guide covers how to flip videos both through the built-in user interface and programmatically using the Engine API. ## What You'll Learn - Flip a video clip **horizontally or vertically** - **Query** current flip states - **Toggle** flip on or off programmatically - Flip **multiple clips together** as a group - **Lock** flip operations for template enforcement ## When to Use Flipping helps when you need to: - Create **symmetrical** video layouts or animated collages - Align **eyelines** when switching between front and rear cameras - Keep **branded elements** facing inward on split-screen layouts - Enforce **template layouts** that should freeze flip states ## How Flipping Works CE.SDK represents each video clip as a graphic block with transforms, including flip states. The flip API uses boolean setters and getters that apply to video blocks in the same way they apply to image blocks. **Horizontal flipping** mirrors content left-to-right, creating a reflection as if viewed in a vertical mirror. **Vertical flipping** mirrors content top-to-bottom, inverting the image vertically. ## Using the Built-in Flip UI The editor provides flip controls in the transform panel. Select a video block and use the flip buttons to mirror content horizontally or vertically. These controls are part of the layer transform options available for any graphic block. ## Programmatic Video Flipping ### Initialize CE.SDK We initialize CE.SDK with video editing features enabled. This provides access to the timeline, playback controls, and transform operations. ```typescript highlight=highlight-enable-video-features // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); ``` ### Flip a Video Horizontally To flip a video horizontally, call `engine.block.setFlipHorizontal()` with the block ID and `true`. This mirrors the content left-to-right. ```typescript highlight=highlight-flip-horizontal // Demo 1: Original video (no flip) const originalVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, originalVideo); engine.block.setPositionX(originalVideo, col1X); engine.block.setPositionY(originalVideo, row1Y); createLabel('Original', col1X, label1Y); // Demo 2: Horizontal flip const horizontalFlipVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, horizontalFlipVideo); engine.block.setPositionX(horizontalFlipVideo, col2X); engine.block.setPositionY(horizontalFlipVideo, row1Y); // Flip the video horizontally (mirrors left to right) engine.block.setFlipHorizontal(horizontalFlipVideo, true); ``` The flip is applied immediately. To restore the original orientation, call the same method with `false`. ### Flip a Video Vertically Vertical flipping mirrors content top-to-bottom. We use `engine.block.setFlipVertical()` in the same way. ```typescript highlight=highlight-flip-vertical // Demo 3: Vertical flip const verticalFlipVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, verticalFlipVideo); engine.block.setPositionX(verticalFlipVideo, col3X); engine.block.setPositionY(verticalFlipVideo, row1Y); // Flip the video vertically (mirrors top to bottom) engine.block.setFlipVertical(verticalFlipVideo, true); ``` ### Combine Both Flips You can apply both horizontal and vertical flips to the same block. This rotates the content 180 degrees while maintaining mirror properties on both axes. ```typescript highlight=highlight-flip-both // Demo 4: Both flips combined const bothFlipVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, bothFlipVideo); engine.block.setPositionX(bothFlipVideo, col1X); engine.block.setPositionY(bothFlipVideo, row2Y); // Combine horizontal and vertical flips engine.block.setFlipHorizontal(bothFlipVideo, true); engine.block.setFlipVertical(bothFlipVideo, true); ``` ### Query Flip State Before applying changes, you may want to check the current flip state. Use `engine.block.getFlipHorizontal()` and `engine.block.getFlipVertical()` to query the current values. ```typescript highlight=highlight-get-flip-state // Query flip states const isFlippedH = engine.block.getFlipHorizontal(horizontalFlipVideo); const isFlippedV = engine.block.getFlipVertical(verticalFlipVideo); console.log(`Horizontal flip state: ${isFlippedH}`); console.log(`Vertical flip state: ${isFlippedV}`); ``` Both methods return boolean values indicating whether the block is currently flipped on that axis. ### Toggle Flip On or Off Calling `setFlipHorizontal(block, true)` twice doesn't revert the flip—it leaves the block mirrored. To toggle the flip state, read the current value and set the opposite. ```typescript highlight=highlight-toggle-flip // Toggle flip by reading current state and setting opposite const currentFlip = engine.block.getFlipHorizontal(originalVideo); engine.block.setFlipHorizontal(originalVideo, !currentFlip); console.log(`Toggled original video flip: ${!currentFlip}`); // Toggle back to keep original state for demo engine.block.setFlipHorizontal(originalVideo, currentFlip); ``` This pattern is useful for implementing toggle buttons in your application's user interface. ## Flipping Multiple Clips Together ### Group Flipping When you need to flip multiple video blocks as a unit, group them first using `engine.block.group()`. Applying a flip to the group mirrors all elements while preserving their relative positions. ```typescript highlight=highlight-group-flip // Demo 5: Group flip - flip multiple videos together const smallWidth = blockWidth / 2; const smallHeight = blockHeight / 2; const groupGap = 10; // Center the pair horizontally within column 2 const groupPairWidth = smallWidth * 2 + groupGap; const groupStartX = col2X + (blockWidth - groupPairWidth) / 2; // Center vertically within row 2 (smaller blocks) const groupY = row2Y + (blockHeight - smallHeight) / 2; const groupVideo1 = await engine.block.addVideo( videoUri, smallWidth, smallHeight ); engine.block.appendChild(page, groupVideo1); engine.block.setPositionX(groupVideo1, groupStartX); engine.block.setPositionY(groupVideo1, groupY); const groupVideo2 = await engine.block.addVideo( videoUri, smallWidth, smallHeight ); engine.block.appendChild(page, groupVideo2); engine.block.setPositionX(groupVideo2, groupStartX + smallWidth + groupGap); engine.block.setPositionY(groupVideo2, groupY); // Group the videos and flip them together const groupId = engine.block.group([groupVideo1, groupVideo2]); engine.block.setFlipHorizontal(groupId, true); ``` When you flip a group: - All child blocks mirror together - Relative spacing and positioning remain intact - The flip applies to the group as a whole, not individual children ## Locking Flip Operations ### Prevent Flipping in Templates For template-based workflows, you may want to prevent certain blocks from being flipped. Use `engine.block.setScopeEnabled()` with the `layer/flip` scope. ```typescript highlight=highlight-lock-flip // Demo 6: Lock flip scope const lockedVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, col3X); engine.block.setPositionY(lockedVideo, row2Y); // Disable flip operations for this block engine.block.setScopeEnabled(lockedVideo, 'layer/flip', false); // Verify scope is disabled const canFlip = engine.block.isScopeEnabled(lockedVideo, 'layer/flip'); console.log(`Flip enabled for locked video: ${canFlip}`); ``` Setting the scope to `false` disables flip controls for that block. The block can still be flipped programmatically, but UI controls will be disabled. For complete transform prevention, use `engine.block.setTransformLocked()` to lock all transforms including flip, rotation, and scaling. ## Troubleshooting ### Flip Not Applied If setting flip values has no visible effect, verify that: - The block ID is valid and references a video block - The video resource has finished loading - No parent group has an opposing flip applied ### Flip Operations Blocked If flip operations appear blocked, check whether the `layer/flip` scope is disabled for that block. Use `engine.block.isScopeEnabled(block, 'layer/flip')` to verify. ### Parent Group Conflicts When a parent group is flipped, child blocks appear flipped relative to the group. If you flip both the group and a child, the flips combine. To determine the visual orientation, consider the flip state of the entire hierarchy. ## API References in this Guide | Method | Description | | --- | --- | | `engine.block.setFlipHorizontal(blockId, boolean)` | Mirror a block left/right or restore default orientation | | `engine.block.setFlipVertical(blockId, boolean)` | Mirror a block top/bottom or restore default orientation | | `engine.block.getFlipHorizontal(blockId)` | Check whether the block is currently flipped horizontally | | `engine.block.getFlipVertical(blockId)` | Check whether the block is currently flipped vertically | | `engine.block.group(blockIds[])` | Group blocks to flip them together | | `engine.block.setScopeEnabled(blockId, 'layer/flip', boolean)` | Enable or disable flip controls for templates | | `engine.block.setTransformLocked(blockId, boolean)` | Lock all transforms including flip | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Move Videos" description: "Position videos on the canvas using absolute or percentage-based coordinates." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform/move-aa9d89/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) > [Move](https://img.ly/docs/cesdk/angular/edit-video/transform/move-aa9d89/) --- Position videos on the canvas using absolute pixel coordinates or percentage-based positioning for responsive layouts. ![Move videos example showing positioned videos with labels](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-move-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-move-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-transform-move-browser/) Position videos on the canvas using coordinates that start at the top-left corner (0, 0). X increases right, Y increases down. Values are relative to the parent block, simplifying nested layouts. ```typescript file=@cesdk_web_examples/guides-create-video-transform-move-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; class Example implements EditorPlugin { name = 'guides-create-video-transform-move-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Setup: Load assets and create scene cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); // Enable fill and set page fill color to #6686FF engine.block.setFillEnabled(page, true); engine.block.setColor(engine.block.getFill(page), 'fill/color/value', { r: 102 / 255, g: 134 / 255, b: 255 / 255, a: 1 }); // Demo 1: Movable Video - Can be freely repositioned by user const movableVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 200, 150 ); engine.block.appendChild(page, movableVideo); engine.block.setPositionX(movableVideo, 0); engine.block.setPositionY(movableVideo, 100); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Movable'); engine.block.setFloat(text1, 'text/fontSize', 32); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 200); engine.block.setPositionX(text1, 0); engine.block.setPositionY(text1, 310); engine.block.setFillEnabled(text1, true); engine.block.setColor(engine.block.getFill(text1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text1); // Add explanatory text below const explanation1 = engine.block.create('text'); engine.block.setString( explanation1, 'text/text', 'Uses absolute positioning with pixel coordinates' ); engine.block.setFloat(explanation1, 'text/fontSize', 14); engine.block.setEnum(explanation1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation1, 200); engine.block.setPositionX(explanation1, 0); engine.block.setPositionY(explanation1, 345); engine.block.setFillEnabled(explanation1, true); engine.block.setColor( engine.block.getFill(explanation1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation1); // Demo 2: Percentage Positioning - Responsive layout const percentVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 200, 150 ); engine.block.appendChild(page, percentVideo); // Set position mode to percentage (0.0 to 1.0) engine.block.setPositionXMode(percentVideo, 'Percent'); engine.block.setPositionYMode(percentVideo, 'Percent'); // Position at 37.5% from left (300px), 30% from top (150px) engine.block.setPositionX(percentVideo, 0.375); engine.block.setPositionY(percentVideo, 0.3); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', 'Percentage'); engine.block.setFloat(text2, 'text/fontSize', 32); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 200); engine.block.setPositionX(text2, 300); engine.block.setPositionY(text2, 310); engine.block.setFillEnabled(text2, true); engine.block.setColor(engine.block.getFill(text2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text2); // Add explanatory text below const explanation2 = engine.block.create('text'); engine.block.setString( explanation2, 'text/text', 'Uses percentage values (0.0-1.0) for responsive layouts' ); engine.block.setFloat(explanation2, 'text/fontSize', 14); engine.block.setEnum(explanation2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation2, 200); engine.block.setPositionX(explanation2, 300); engine.block.setPositionY(explanation2, 345); engine.block.setFillEnabled(explanation2, true); engine.block.setColor( engine.block.getFill(explanation2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation2); // Demo 3: Locked Video - Cannot be moved, rotated, or scaled const lockedVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 200, 150 ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, 550); engine.block.setPositionY(lockedVideo, 150); // Lock the transform to prevent user interaction engine.block.setBool(lockedVideo, 'transformLocked', true); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Locked'); engine.block.setFloat(text3, 'text/fontSize', 32); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 200); engine.block.setPositionX(text3, 550); engine.block.setPositionY(text3, 310); engine.block.setFillEnabled(text3, true); engine.block.setColor(engine.block.getFill(text3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text3); // Add explanatory text below const explanation3 = engine.block.create('text'); engine.block.setString( explanation3, 'text/text', 'Transform locked - cannot be repositioned' ); engine.block.setFloat(explanation3, 'text/fontSize', 14); engine.block.setEnum(explanation3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation3, 200); engine.block.setPositionX(explanation3, 550); engine.block.setPositionY(explanation3, 345); engine.block.setFillEnabled(explanation3, true); engine.block.setColor( engine.block.getFill(explanation3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation3); // Get current position values const currentX = engine.block.getPositionX(movableVideo); const currentY = engine.block.getPositionY(movableVideo); console.log('Current position:', currentX, currentY); // Move relative to current position const offsetX = engine.block.getPositionX(movableVideo); const offsetY = engine.block.getPositionY(movableVideo); engine.block.setPositionX(movableVideo, offsetX + 50); engine.block.setPositionY(movableVideo, offsetY + 50); // Adjust text positions after relative movement engine.block.setPositionX(text1, 50); engine.block.setPositionY(text1, 310); engine.block.setPositionX(explanation1, 50); engine.block.setPositionY(explanation1, 345); // Set playhead position to 2 seconds engine.block.setPlaybackTime(page, 2); } } export default Example; ``` This guide covers positioning videos with absolute or percentage coordinates, configuring position modes, and locking transforms to prevent repositioning. ## Position Coordinates Coordinates originate at the top-left (0, 0) of the parent container. Use **absolute** mode for fixed pixel values or **percentage** mode (0.0 to 1.0) for responsive layouts that adapt to parent size changes. ## Positioning Videos Position videos using `engine.block.setPositionX()` and `engine.block.setPositionY()` with absolute pixel coordinates: ```typescript highlight-movable-video engine.block.appendChild(page, movableVideo); engine.block.setPositionX(movableVideo, 0); engine.block.setPositionY(movableVideo, 100); ``` ## Getting Current Position Read current position values using `engine.block.getPositionX()` and `engine.block.getPositionY()`. Values are returned in the current position mode (absolute pixels or percentage 0.0-1.0): ```typescript highlight-get-position // Get current position values const currentX = engine.block.getPositionX(movableVideo); const currentY = engine.block.getPositionY(movableVideo); ``` ## Configuring Position Modes Control how position values are interpreted using `engine.block.setPositionXMode()` and `engine.block.setPositionYMode()`. Set to `'Absolute'` for pixels or `'Percent'` for percentage values (0.0 to 1.0). Check the current mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. The Percentage Positioning section below demonstrates setting these modes. ## Percentage Positioning Position videos using percentage values (0.0 to 1.0) for responsive layouts. Set the position mode to `'Percent'`, then use values between 0.0 and 1.0: ```typescript highlight-percentage-positioning // Set position mode to percentage (0.0 to 1.0) engine.block.setPositionXMode(percentVideo, 'Percent'); engine.block.setPositionYMode(percentVideo, 'Percent'); ``` Percentage positioning adapts automatically when the parent block dimensions change, maintaining relative positions in responsive designs. ## Relative Positioning Move videos relative to their current position by getting the current coordinates and adding offset values: ```typescript highlight-relative-positioning // Move relative to current position const offsetX = engine.block.getPositionX(movableVideo); const offsetY = engine.block.getPositionY(movableVideo); engine.block.setPositionX(movableVideo, offsetX + 50); engine.block.setPositionY(movableVideo, offsetY + 50); ``` ## Locking Transforms Lock transforms to prevent repositioning, rotation, and scaling by setting `transformLocked` to true: ```typescript highlight-locked-video // Lock the transform to prevent user interaction engine.block.setBool(lockedVideo, 'transformLocked', true); ``` ## Troubleshooting ### Video Not Moving Check if transforms are locked using `engine.block.getBool(block, 'transformLocked')`. Ensure the video block exists and values are within parent bounds. ### Unexpected Position Values Check position mode using `engine.block.getPositionXMode()` and `engine.block.getPositionYMode()`. Verify if using absolute (pixels) vs percentage (0.0-1.0) values. Review parent block dimensions if using percentage positioning. ### Positioned Outside Visible Area Verify parent block dimensions and boundaries. Check coordinate system: origin is top-left, not center. Review X/Y values for calculation errors. ### Percentage Positioning Not Responsive Ensure position mode is set to `'Percent'` using `engine.block.setPositionXMode(block, 'Percent')`. Verify percentage values are between 0.0 and 1.0. Check that parent block dimensions can change. ## API Reference | Method | Description | | ------------------------------------- | ------------------------------------------ | | `engine.block.addVideo()` | Create and position video in one operation | | `engine.block.setPositionX()` | Set X coordinate value | | `engine.block.setPositionY()` | Set Y coordinate value | | `engine.block.getPositionX()` | Get current X coordinate value | | `engine.block.getPositionY()` | Get current Y coordinate value | | `engine.block.setPositionXMode()` | Set position mode for X coordinate | | `engine.block.setPositionYMode()` | Set position mode for Y coordinate | | `engine.block.getPositionXMode()` | Get position mode for X coordinate | | `engine.block.getPositionYMode()` | Get position mode for Y coordinate | | `engine.block.setBool()` | Set transform lock state | | `engine.block.getBool()` | Get transform lock state | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Resize" description: "Resize videos in web apps using the CE.SDK" platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform/resize-b1ce14/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) > [Resize](https://img.ly/docs/cesdk/angular/edit-video/transform/resize-b1ce14/) --- The **CreativeEditor SDK (CE.SDK)** provides a video resizing feature. This guide offers a complete overview of the resizing feature, from using it in the default UI to locking resizing to safeguard layouts. ![Resize videos example showing videos with different sizing modes](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-edit-video-transform-resize-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-edit-video-transform-resize-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-edit-video-transform-resize-browser/) ```typescript file=@cesdk_web_examples/guides-edit-video-transform-resize-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; class Example implements EditorPlugin { name = 'guides-edit-video-transform-resize-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Setup: Load assets and create scene cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); // Enable fill and set page fill color to #6686FF engine.block.setFillEnabled(page, true); engine.block.setColor(engine.block.getFill(page), 'fill/color/value', { r: 102 / 255, g: 134 / 255, b: 255 / 255, a: 1 }); // Layout constants for centered positioning // Page: 800x500, 3 columns of 200px each with 50px spacing // Total width: 700px, centered start X: 50px // Column centers: 150, 400, 650 const videoWidth = 150; const videoHeight = 100; const columnWidth = 200; const startY = 165; // Vertically centered const labelY = startY + videoHeight + 10; const explanationY = labelY + 35; // Column 1 center: 150 const col1X = 50; const col1VideoX = col1X + (columnWidth - videoWidth) / 2; // 75 // Column 2 center: 400 (percentage video is 200px = 25% of 800) const col2X = 300; const col2VideoX = col2X; // 200px wide video centered at 400 // Column 3 center: 650 const col3X = 550; const col3VideoX = col3X + (columnWidth - videoWidth) / 2; // 575 // Demo 1: Resizable Video - Can be freely resized by user const resizableVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', videoWidth, videoHeight ); engine.block.appendChild(page, resizableVideo); engine.block.setPositionX(resizableVideo, col1VideoX); engine.block.setPositionY(resizableVideo, startY); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Resizable'); engine.block.setFloat(text1, 'text/fontSize', 28); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, columnWidth); engine.block.setPositionX(text1, col1X); engine.block.setPositionY(text1, labelY); engine.block.setFillEnabled(text1, true); engine.block.setColor(engine.block.getFill(text1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text1); // Add explanatory text below const explanation1 = engine.block.create('text'); engine.block.setString( explanation1, 'text/text', 'Absolute pixel dimensions' ); engine.block.setFloat(explanation1, 'text/fontSize', 12); engine.block.setEnum(explanation1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation1, columnWidth); engine.block.setPositionX(explanation1, col1X); engine.block.setPositionY(explanation1, explanationY); engine.block.setFillEnabled(explanation1, true); engine.block.setColor( engine.block.getFill(explanation1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation1); // Demo 2: Percentage Sizing - Responsive layout const percentVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', videoWidth, videoHeight ); engine.block.appendChild(page, percentVideo); // Set size mode to percentage (0.0 to 1.0) engine.block.setWidthMode(percentVideo, 'Percent'); engine.block.setHeightMode(percentVideo, 'Percent'); // Set to 25% width, 20% height of parent engine.block.setWidth(percentVideo, 0.25); engine.block.setHeight(percentVideo, 0.2); engine.block.setPositionX(percentVideo, col2VideoX); engine.block.setPositionY(percentVideo, startY); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', 'Percentage'); engine.block.setFloat(text2, 'text/fontSize', 28); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, columnWidth); engine.block.setPositionX(text2, col2X); engine.block.setPositionY(text2, labelY); engine.block.setFillEnabled(text2, true); engine.block.setColor(engine.block.getFill(text2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text2); // Add explanatory text below const explanation2 = engine.block.create('text'); engine.block.setString(explanation2, 'text/text', '25% width, 20% height'); engine.block.setFloat(explanation2, 'text/fontSize', 12); engine.block.setEnum(explanation2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation2, columnWidth); engine.block.setPositionX(explanation2, col2X); engine.block.setPositionY(explanation2, explanationY); engine.block.setFillEnabled(explanation2, true); engine.block.setColor( engine.block.getFill(explanation2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation2); // Demo 3: Resize-Locked Video - Cannot be resized const lockedVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', videoWidth, videoHeight ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, col3VideoX); engine.block.setPositionY(lockedVideo, startY); // Lock the transform to prevent resizing engine.block.setTransformLocked(lockedVideo, true); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Locked'); engine.block.setFloat(text3, 'text/fontSize', 28); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, columnWidth); engine.block.setPositionX(text3, col3X); engine.block.setPositionY(text3, labelY); engine.block.setFillEnabled(text3, true); engine.block.setColor(engine.block.getFill(text3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text3); // Add explanatory text below const explanation3 = engine.block.create('text'); engine.block.setString(explanation3, 'text/text', 'Transform locked'); engine.block.setFloat(explanation3, 'text/fontSize', 12); engine.block.setEnum(explanation3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation3, columnWidth); engine.block.setPositionX(explanation3, col3X); engine.block.setPositionY(explanation3, explanationY); engine.block.setFillEnabled(explanation3, true); engine.block.setColor( engine.block.getFill(explanation3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation3); // Get current dimensions const currentWidth = engine.block.getWidth(resizableVideo); const currentHeight = engine.block.getHeight(resizableVideo); console.log('Current dimensions:', currentWidth, 'x', currentHeight); // Check size mode const widthMode = engine.block.getWidthMode(percentVideo); const heightMode = engine.block.getHeightMode(percentVideo); console.log('Size modes:', widthMode, heightMode); // Set playhead position to 2 seconds engine.block.setPlaybackTime(page, 2); } } export default Example; ``` ## What You'll Learn - Resize a single video block with the **default UI**. - Resize clips using **JavaScript** and the **CE.SDK API**. - Resize **groups** of video blocks uniformly. - **Lock** or restrict a user's ability to resize. ### When to Use Resize videos to: - Fit **template areas**. - Insert videos into reusable **layouts**. - Keep aspect ratios **consistent** for: - Social media posts (like reels) - Ads - Create **automation workflows** in JavaScript. - Combine with trimming or cropping. ## Resize a Video Block with the UI When using the default CE.SDK UI: 1. Select the video. 2. The resize handles appear. 3. Drag one of the handles to resize the video until you set the desired size. The editor provides two kinds of handles: - **Corner scale handles:** keep the video's aspect ratio. - **Edge handles:** stretch only the width or the height independently. Any embedded audio remains synchronous, because resizing affects only the block's frame, not the timeline. ### Hide the Resize Handles in the UI To prevent manual resizing in the UI, **hide the resize edge handles** using the **EditorAPI**: 1. Call the `setSetting` function. 2. Include the `"controlGizmo/showResizeHandles"` key. 3. Set the value to `false`. ```ts // Hide resize handles if you want to prevent manual resizing engine.editor.setSetting("controlGizmo/showResizeHandles", false); ``` > **Note:** This setting still **displays the corner handles** and helps avoid editing operations that change the size independently in the UI. ## Resize a Video Block with JavaScript When developing a custom UI use the CreativeEngine BlockAPI to edit a video's size. Two methods exist: choose the one that best suits your project. ### Set Absolute Sizing Set the video's absolute size with `setWidth` and `setHeight`. First, create the video block with `addVideo`, then set explicit pixel dimensions: ```typescript highlight-resizable-video const resizableVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', videoWidth, videoHeight ); engine.block.appendChild(page, resizableVideo); engine.block.setPositionX(resizableVideo, col1VideoX); engine.block.setPositionY(resizableVideo, startY); ``` ### Set Relative Sizing Set the video's size relative to its parent (for example, to the page): 1. Set the size mode with `setWidthMode` and `setHeightMode`. 2. Define the mode as `'Percent'`. 3. Set the size in normalized values, with `1` being full-width. ```typescript highlight-percentage-sizing // Set size mode to percentage (0.0 to 1.0) engine.block.setWidthMode(percentVideo, 'Percent'); engine.block.setHeightMode(percentVideo, 'Percent'); // Set to 25% width, 20% height of parent engine.block.setWidth(percentVideo, 0.25); engine.block.setHeight(percentVideo, 0.2); ``` The preceding code: - Sets the clip to 25% width of its parent. - Makes the clip 20% as tall. - Stays responsive to the parent's size changes. This method allows for the clip to follow the parent's size changes while maintaining proportional dimensions. ### Get Current Dimensions Read current size values using `getWidth` and `getHeight`. Values are returned in the current size mode (absolute pixels or percentage 0.0-1.0): ```typescript highlight-get-dimensions // Get current dimensions const currentWidth = engine.block.getWidth(resizableVideo); const currentHeight = engine.block.getHeight(resizableVideo); ``` ### Check Size Mode Query the current size mode using `getWidthMode` and `getHeightMode`: ```typescript highlight-get-size-mode // Check size mode const widthMode = engine.block.getWidthMode(percentVideo); const heightMode = engine.block.getHeightMode(percentVideo); ``` ### Maintain Aspect Ratio If the block's *fill* is a video (`FillType.video`), the engine preserves the footage's native aspect ratio. To force a different ratio, you must: 1. Calculate the ratio 2. Adjust one side, or add constraints (see **Locking** below). ## Resize Videos Together To resize several videos at once, instead of resizing each video individually: 1. Group the videos with `engine.block.group`. 2. Set the size for the entire group. ```ts // Group multiple video clips const group = engine.block.group([clipA, clipB, clipC]); // Resize the entire group - height scales proportionally engine.block.setWidth(group, 400); ``` To ensure consistent layout, groups of videos: - Always scale uniformly on both axes. - Show only *scale* and *rotate* gizmos in the UI, never individual resize handles. ## Lock or Constrain Resizing Use the BlockAPI to define if your app allows resizing for a specific block: 1. Tell the engine that each block has its own resize policy with `setGlobalScope`. 2. Call `setScopeEnabled`. 3. Include the video block ID. 4. Set the boolean's value for `locked`: - `true` enables resizing. - `false` deactivates resizing. ```ts // Disable only resize for this specific block engine.editor.setGlobalScope('layer/resize', 'Defer'); engine.block.setScopeEnabled(videoBlock, 'layer/resize', false); // The block can still be moved and rotated, but not resized via UI ``` Resize actions remain available through code, but not via the UI. Lock all transforms entirely to prevent resizing, repositioning, and rotation: ```typescript highlight-locked-video // Lock the transform to prevent resizing engine.block.setTransformLocked(lockedVideo, true); ``` The preceding code deactivates all transform actions, resize included: - Manual actions from the UI - Automated actions through code. Create templates with finer-grained scope keys to allow rotation or movement while forbidding size changes. ## Notes on Resizing with CE.SDK | Topic | What you want to do | What happens | | --- | --- | --- | | **Timeline length** | Resize the block's on-canvas frame. | No need to retime; duration and trims stay untouched. | | **Content fill** | Switch the block to `.contain` or `.cover`. | Update it with `setContentFillMode` (see *common crop APIs*). | | **Performance** | Work on large canvases such as 4K. | Plan around GPU limits and the Video Editor 4K constraints. | ## Next Steps Now that you understand how to resize with the CE.SDK, take time to dive into other related features: - [Create a template](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) to enforce design rules. - [Use other transforms](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) available with the CreativeEditor. - [Add captions](https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/) to videos. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Rotate Videos" description: "Rotate video elements to adjust orientation and create dynamic compositions." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform/rotate-eaf662/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) > [Rotate](https://img.ly/docs/cesdk/angular/edit-video/transform/rotate-eaf662/) --- Rotate video elements to any angle using radians or degrees, with precise programmatic control and UI rotation handles. ![Rotate videos example showing videos at different rotation angles](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-rotate-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-rotate-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-transform-rotate-browser/) Rotation in CE.SDK occurs around the block's center point. All rotation values use radians, where `Math.PI` equals 180 degrees. Positive values rotate counterclockwise, negative values rotate clockwise. ```typescript file=@cesdk_web_examples/guides-create-video-transform-rotate-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; class Example implements EditorPlugin { name = 'guides-create-video-transform-rotate-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Setup: Load assets and create scene cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); // Enable fill and set page fill color to #6686FF engine.block.setFillEnabled(page, true); engine.block.setColor(engine.block.getFill(page), 'fill/color/value', { r: 102 / 255, g: 134 / 255, b: 255 / 255, a: 1 }); // Demo 1: Rotated Video - Rotated 45 degrees const rotatedVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 200, 150 ); engine.block.appendChild(page, rotatedVideo); engine.block.setPositionX(rotatedVideo, 50); engine.block.setPositionY(rotatedVideo, 100); // Rotate the video 45 degrees (in radians) const toRadians = (degrees: number) => (degrees * Math.PI) / 180; engine.block.setRotation(rotatedVideo, toRadians(45)); // Add label for rotated video const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', '45° Rotation'); engine.block.setFloat(text1, 'text/fontSize', 28); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 200); engine.block.setPositionX(text1, 50); engine.block.setPositionY(text1, 320); engine.block.setFillEnabled(text1, true); engine.block.setColor(engine.block.getFill(text1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text1); // Get current rotation value const currentRotation = engine.block.getRotation(rotatedVideo); const toDegrees = (radians: number) => (radians * 180) / Math.PI; console.log('Current rotation:', toDegrees(currentRotation), 'degrees'); // Demo 2: 90 Degree Rotation const rotatedVideo90 = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 200, 150 ); engine.block.appendChild(page, rotatedVideo90); engine.block.setPositionX(rotatedVideo90, 300); engine.block.setPositionY(rotatedVideo90, 100); // Rotate 90 degrees using Math.PI / 2 engine.block.setRotation(rotatedVideo90, Math.PI / 2); // Add label for 90 degree rotation const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', '90° Rotation'); engine.block.setFloat(text2, 'text/fontSize', 28); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 200); engine.block.setPositionX(text2, 300); engine.block.setPositionY(text2, 320); engine.block.setFillEnabled(text2, true); engine.block.setColor(engine.block.getFill(text2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text2); // Demo 3: Locked Rotation - Rotation is disabled for this block const lockedVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 200, 150 ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, 550); engine.block.setPositionY(lockedVideo, 150); // Disable rotation for this specific block engine.block.setScopeEnabled(lockedVideo, 'layer/rotate', false); // Add label for locked video const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Rotation Locked'); engine.block.setFloat(text3, 'text/fontSize', 28); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 200); engine.block.setPositionX(text3, 550); engine.block.setPositionY(text3, 320); engine.block.setFillEnabled(text3, true); engine.block.setColor(engine.block.getFill(text3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text3); // Check if rotation is enabled for a block const canRotate = engine.block.isScopeEnabled(lockedVideo, 'layer/rotate'); console.log('Rotation enabled:', canRotate); // Set playhead position to 2 seconds engine.block.setPlaybackTime(page, 2); } } export default Example; ``` This guide covers rotating videos programmatically, converting between degrees and radians, grouping blocks for collective rotation, and locking rotation permissions. ## Rotating Videos Rotate videos using `engine.block.setRotation()` with the angle in radians. Convert from degrees using the formula `radians = degrees * Math.PI / 180`: ```typescript highlight-rotate-video // Rotate the video 45 degrees (in radians) const toRadians = (degrees: number) => (degrees * Math.PI) / 180; engine.block.setRotation(rotatedVideo, toRadians(45)); ``` ## Getting Current Rotation Read the current rotation value using `engine.block.getRotation()`. The value is returned in radians. Convert to degrees with `degrees = radians * 180 / Math.PI`: ```typescript highlight-get-rotation // Get current rotation value const currentRotation = engine.block.getRotation(rotatedVideo); const toDegrees = (radians: number) => (radians * 180) / Math.PI; console.log('Current rotation:', toDegrees(currentRotation), 'degrees'); ``` ## Common Rotation Angles For 90-degree rotations, use `Math.PI / 2`. For 180 degrees, use `Math.PI`. For 270 degrees, use `3 * Math.PI / 2`: ```typescript highlight-rotate-90 // Rotate 90 degrees using Math.PI / 2 engine.block.setRotation(rotatedVideo90, Math.PI / 2); ``` ## Locking Rotation Disable rotation for specific blocks using `engine.block.setScopeEnabled()` with the `'layer/rotate'` scope set to false: ```typescript highlight-lock-rotation // Disable rotation for this specific block engine.block.setScopeEnabled(lockedVideo, 'layer/rotate', false); ``` ## Checking Rotation Permissions Check if rotation is enabled for a block using `engine.block.isScopeEnabled()`: ```typescript highlight-check-scope // Check if rotation is enabled for a block const canRotate = engine.block.isScopeEnabled(lockedVideo, 'layer/rotate'); console.log('Rotation enabled:', canRotate); ``` ## Troubleshooting ### Rotation Has No Effect Verify the block exists in the scene and is not a page block. Check if rotation is locked using `engine.block.isScopeEnabled(block, 'layer/rotate')`. ### Rotation Handle Missing Check if rotation handles are hidden globally via `controlGizmo/showRotateHandles` setting. Verify the `'layer/rotate'` scope is enabled for the block. ### Unexpected Rotation Direction Remember that positive values rotate counterclockwise in CE.SDK. To rotate clockwise, use negative radian values. ## API Reference | Method | Description | | ---------------------------------- | ---------------------------------------- | | `engine.block.setRotation()` | Set block rotation in radians | | `engine.block.getRotation()` | Get current rotation in radians | | `engine.block.setScopeEnabled()` | Enable/disable `'layer/rotate'` scope | | `engine.block.isScopeEnabled()` | Check if rotation is allowed | | `engine.block.setTransformLocked()`| Lock all transforms including rotation | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Scale Videos in Web Apps" description: "Embed the CE.SDK scaling feature in your web app." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/transform/scale-f75c8a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/angular/edit-video/transform-369f28/) > [Scale](https://img.ly/docs/cesdk/angular/edit-video/transform/scale-f75c8a/) --- The CreativeEditor provides a scaling feature to edit videos in your web app, to render an intended composition. Explore the different scaling options within CE.SDK, and learn how to embed it both from the UI and the API. ![Scale videos example showing different scaling techniques](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-scale-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-transform-scale-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-transform-scale-browser/) ## What You'll Learn - Scale videos through **JavaScript**. - Scale **proportionally** or non-uniformly. - **Group** elements to scale them together. - Apply or lock scaling **constraints** in templates. ## When to Use Use scaling to: - **Emphasize** or de-emphasize a clip in a composition. - **Fit** footage to a free-form layout without cropping. - Drive zoom gestures or **responsive** designs. ## How Scaling Works Scaling uses the `scale(block, scale, anchorX, anchorY)` function, with the following **parameters**: | Parameter | Description | Values | | ------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------- | | `block` | Handle (ID) of the block to scale. | `number` | | `scale` | Scale factor to apply. | **1.0** keeps the original size. **>1.0** enlarges the block. **\< 1.0** shrinks it. | | `anchorX` `anchorY` | Origin point of scale along the width and height | **Top/Left** = 0, **Center** = 0.5, **Bottom/Right** = 1. **Defaults** = `0` | For example: - A value of `1.0` sets the original block's size. - A value of `2.0` makes the block twice as large. ```typescript file=@cesdk_web_examples/guides-create-video-transform-scale-browser/browser.ts reference-only import CreativeEditorSDK, { type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; class Example implements EditorPlugin { name = 'guides-create-video-transform-scale-browser'; version = CreativeEditorSDK.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Setup: Load assets and create scene cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 800, height: 500, unit: 'Pixel' } }); const engine = cesdk.engine; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); // Enable fill and set page fill color to #6686FF engine.block.setFillEnabled(page, true); engine.block.setColor(engine.block.getFill(page), 'fill/color/value', { r: 102 / 255, g: 134 / 255, b: 255 / 255, a: 1 }); // Centered 2x2 grid layout for 800x500 canvas // Videos: 120x90, scaled to 180x135 // Grid positions are where videos APPEAR after scaling // Left column visual X=200, Right column visual X=420 // Top row visual Y=50, Bottom row visual Y=265 const leftColumnX = 200; const rightColumnX = 420; const topRowY = 50; const bottomRowY = 265; const titleOffsetY = 145; // 135 (video height) + 10 (gap) const subtitleOffsetY = 172; // title + 27 // For center-scaled video, compensate for position shift // Center scaling shifts top-left by (-30, -22.5) for 1.5x scale on 120x90 const centerScaleOffsetX = 30; const centerScaleOffsetY = 22.5; // Demo 1: Uniform scaling from top-left (default anchor) const uniformVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, uniformVideo); engine.block.setPositionX(uniformVideo, leftColumnX); engine.block.setPositionY(uniformVideo, topRowY); // Scale the video to 150% from the default top-left anchor engine.block.scale(uniformVideo, 1.5); const text1 = engine.block.create('text'); engine.block.setString(text1, 'text/text', 'Uniform Scale'); engine.block.setFloat(text1, 'text/fontSize', 24); engine.block.setEnum(text1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text1, 180); engine.block.setPositionX(text1, leftColumnX); engine.block.setPositionY(text1, topRowY + titleOffsetY); engine.block.setFillEnabled(text1, true); engine.block.setColor(engine.block.getFill(text1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text1); const explanation1 = engine.block.create('text'); engine.block.setString( explanation1, 'text/text', '150% from top-left anchor' ); engine.block.setFloat(explanation1, 'text/fontSize', 12); engine.block.setEnum(explanation1, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation1, 180); engine.block.setPositionX(explanation1, leftColumnX); engine.block.setPositionY(explanation1, topRowY + subtitleOffsetY); engine.block.setFillEnabled(explanation1, true); engine.block.setColor( engine.block.getFill(explanation1), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation1); // Demo 2: Scaling from center anchor const centerVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, centerVideo); // Position compensates for center scaling shift so final position aligns with grid engine.block.setPositionX(centerVideo, rightColumnX + centerScaleOffsetX); engine.block.setPositionY(centerVideo, topRowY + centerScaleOffsetY); // Scale from center anchor (0.5, 0.5) engine.block.scale(centerVideo, 1.5, 0.5, 0.5); const text2 = engine.block.create('text'); engine.block.setString(text2, 'text/text', 'Center Scale'); engine.block.setFloat(text2, 'text/fontSize', 24); engine.block.setEnum(text2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text2, 180); engine.block.setPositionX(text2, rightColumnX); engine.block.setPositionY(text2, topRowY + titleOffsetY); engine.block.setFillEnabled(text2, true); engine.block.setColor(engine.block.getFill(text2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text2); const explanation2 = engine.block.create('text'); engine.block.setString( explanation2, 'text/text', '150% from center anchor' ); engine.block.setFloat(explanation2, 'text/fontSize', 12); engine.block.setEnum(explanation2, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation2, 180); engine.block.setPositionX(explanation2, rightColumnX); engine.block.setPositionY(explanation2, topRowY + subtitleOffsetY); engine.block.setFillEnabled(explanation2, true); engine.block.setColor( engine.block.getFill(explanation2), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation2); // Demo 3: Non-uniform scaling (width only) const stretchVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, stretchVideo); engine.block.setPositionX(stretchVideo, leftColumnX); engine.block.setPositionY(stretchVideo, bottomRowY); // Stretch only the width by 1.5x engine.block.setWidthMode(stretchVideo, 'Absolute'); const currentWidth = engine.block.getWidth(stretchVideo); engine.block.setWidth(stretchVideo, currentWidth * 1.5, true); const text3 = engine.block.create('text'); engine.block.setString(text3, 'text/text', 'Width Stretch'); engine.block.setFloat(text3, 'text/fontSize', 24); engine.block.setEnum(text3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text3, 180); engine.block.setPositionX(text3, leftColumnX); engine.block.setPositionY(text3, bottomRowY + titleOffsetY); engine.block.setFillEnabled(text3, true); engine.block.setColor(engine.block.getFill(text3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text3); const explanation3 = engine.block.create('text'); engine.block.setString( explanation3, 'text/text', '150% width, height unchanged' ); engine.block.setFloat(explanation3, 'text/fontSize', 12); engine.block.setEnum(explanation3, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation3, 180); engine.block.setPositionX(explanation3, leftColumnX); engine.block.setPositionY(explanation3, bottomRowY + subtitleOffsetY); engine.block.setFillEnabled(explanation3, true); engine.block.setColor( engine.block.getFill(explanation3), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation3); // Demo 4: Locked scaling const lockedVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, rightColumnX); engine.block.setPositionY(lockedVideo, bottomRowY); // Lock all transforms to prevent scaling engine.block.setTransformLocked(lockedVideo, true); const text4 = engine.block.create('text'); engine.block.setString(text4, 'text/text', 'Scale Locked'); engine.block.setFloat(text4, 'text/fontSize', 24); engine.block.setEnum(text4, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(text4, 180); engine.block.setPositionX(text4, rightColumnX); engine.block.setPositionY(text4, bottomRowY + titleOffsetY); engine.block.setFillEnabled(text4, true); engine.block.setColor(engine.block.getFill(text4), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 }); engine.block.appendChild(page, text4); const explanation4 = engine.block.create('text'); engine.block.setString( explanation4, 'text/text', 'Transform locked - cannot scale' ); engine.block.setFloat(explanation4, 'text/fontSize', 12); engine.block.setEnum(explanation4, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(explanation4, 180); engine.block.setPositionX(explanation4, rightColumnX); engine.block.setPositionY(explanation4, bottomRowY + subtitleOffsetY); engine.block.setFillEnabled(explanation4, true); engine.block.setColor( engine.block.getFill(explanation4), 'fill/color/value', { r: 1, g: 1, b: 1, a: 1 } ); engine.block.appendChild(page, explanation4); // Set playhead position to 2 seconds engine.block.setPlaybackTime(page, 2); } } export default Example; ``` ## Scale a Video Uniformly To change the clip size without distorting its proportions, use uniform scaling. Uniform scaling multiplies both width and height by the **same factor** to keep the frame's aspect ratio intact. Scaling a video uniformly allows you to: - Enlarge or shrink footage without altering the content. - Maintain per-pixel sharpness. - Align with layout constraints. CE.SDK lets you use the same high-level API on all graphic blocks, videos included. To scale any block, use `engine.block.scale()`: ```typescript highlight-uniform-scale const uniformVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, uniformVideo); engine.block.setPositionX(uniformVideo, leftColumnX); engine.block.setPositionY(uniformVideo, topRowY); // Scale the video to 150% from the default top-left anchor engine.block.scale(uniformVideo, 1.5); ``` The preceding code: - Scales the video to 150% of its original size. - Doesn't change the origin anchor point. As a result, the video expands down and to the right. ### Anchor Point The anchor point is the point around which a layer scales. All changes happen around the anchor's point position. By default, any block's anchor point is **the top left**. To **change the anchor point**, the scale function has two optional parameters: - `anchorX` to move the anchor point along the width. - `anchorY` to move the anchor point along the height. Both can have values between 0.0 and 1.0. For example, to scale from the center: ```typescript highlight-center-scale const centerVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, centerVideo); // Position compensates for center scaling shift so final position aligns with grid engine.block.setPositionX(centerVideo, rightColumnX + centerScaleOffsetX); engine.block.setPositionY(centerVideo, topRowY + centerScaleOffsetY); // Scale from center anchor (0.5, 0.5) engine.block.scale(centerVideo, 1.5, 0.5, 0.5); ``` This function: 1. Scales the video to **150%** of its original size. 2. Sets the origin anchor point at the center with `0.5, 0.5`. This way, the video expands from the center equally in all directions. ## Scale Videos Non-Uniformly You might need to stretch a video only horizontally or vertically. To stretch or compress only one axis, thus distorting a video, use the **width** or **height** functions. ```typescript highlight-nonuniform-scale const stretchVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, stretchVideo); engine.block.setPositionX(stretchVideo, leftColumnX); engine.block.setPositionY(stretchVideo, bottomRowY); // Stretch only the width by 1.5x engine.block.setWidthMode(stretchVideo, 'Absolute'); const currentWidth = engine.block.getWidth(stretchVideo); engine.block.setWidth(stretchVideo, currentWidth * 1.5, true); ``` The preceding code: 1. Sets the width mode to `'Absolute'` to edit the video using a fixed pixel value instead of a relative layout mode. 2. Reads the current width. 3. Multiplies it by 1.5 to compute a new width that's 150% of the original. 4. Writes the new width back to the block with `maintainCrop` set to `true`. Use this to: - Create panoramic crops. - Compensate for aspect ratios during automation. ### Respect the Existing Crop The crop defines which part of the clip stays visible. Stretching the block without preserving its crop might: - Reveal unwanted areas. - Cut off the focal point. The `maintainCrop` parameter (third argument to `setWidth`) keeps the visible region intact and avoids distortion. Consider using `maintainCrop` if a **template** already uses cropping to frame a subject or hide a watermark. ## Scale Clips Together Grouping blocks is a useful way of scaling them proportionally. Use `engine.block.group()` to combine blocks into a group, then scale the group as a single unit: ```typescript const groupId = engine.block.group([videoBlockId, textBlockId]); engine.block.scale(groupId, 1.5, 0.5, 0.5); ``` The preceding code scales the entire group to 150% from the center anchor. > **Warning:** You can't group `page` with other blocks. Group elements on the **top** of the page, **not** with the page itself. ## Lock Scaling in Templates To preserve a template's layout, consider locking the scaling option. This is useful for: - Brand assets - Campaign creatives - Collaboration workflows - Fixed dimensions swapping editors ```typescript highlight-locked-scale const lockedVideo = await engine.block.addVideo( 'https://img.ly/static/ubq_video_samples/bbb.mp4', 120, 90 ); engine.block.appendChild(page, lockedVideo); engine.block.setPositionX(lockedVideo, rightColumnX); engine.block.setPositionY(lockedVideo, bottomRowY); // Lock all transforms to prevent scaling engine.block.setTransformLocked(lockedVideo, true); ``` ### Disable Resize Scope Disable the `layer/resize` scope when working with templates to **prevent users from scaling** blocks: ```typescript engine.block.setScopeEnabled(blockId, 'layer/resize', false); ``` ### Lock All Transformations To **lock** all transformations (move, resize, rotate), use `setTransformLocked`: ```typescript engine.block.setTransformLocked(blockId, true); ``` To check if scaling is currently allowed: ```typescript const canResize = engine.block.isScopeEnabled(blockId, 'layer/resize'); console.log('layer/resize scope enabled?', canResize); ``` ## Troubleshooting ### Video Not Scaling Check if transforms are locked using `engine.block.isTransformLocked(block)`. Ensure the block exists and is a valid design block. ### Unexpected Position After Scale Verify the anchor point coordinates. Default anchor (0, 0) causes expansion to the right and down. Use (0.5, 0.5) for center-based scaling. ### Crop Region Shifting When using `setWidth` or `setHeight`, pass `true` as the third parameter to maintain the crop region. ## Recap | Usage | How To | | ------------------ | ---------------------------------------------------------------------------------------------- | | Uniform scaling | `engine.block.scale(blockId, scaleFactor)` + optional anchor | | Stretching an axis | Set width mode to `'Absolute'`, then use `setWidth()` or `setHeight()` | | Group scaling | 1. Group with `engine.block.group([blockId_1, blockId_2])` 2. Scale the group | | Constraints | Adjust scopes or lock transforms to protect templates | ## API Reference | API | Usage | | ---------------------------- | ------------------------------------------------------------------ | | `block.scale` | Performs uniform or anchored scaling on blocks and groups. | | `block.setWidthMode` | Enables absolute sizing before changing a single axis. | | `block.getWidth` | Reads the current width before non-uniform scaling. | | `block.setWidth` | Writes the adjusted width after single-axis scaling. | | `block.setHeightMode` | Enables absolute sizing for height changes. | | `block.getHeight` | Reads the current height before non-uniform scaling. | | `block.setHeight` | Writes the adjusted height after single-axis scaling. | | `block.group` | Group blocks so they scale together. | | `block.setScopeEnabled` | Toggles the `layer/resize` scope to lock scaling in templates. | | `block.setTransformLocked` | Locks all transform scopes when templates must stay fixed. | | `block.isScopeEnabled` | Checks whether scaling is currently permitted on a block. | | `block.isTransformLocked` | Checks whether all transforms are locked on a block. | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Trim Video Clips" description: "Learn how to trim video clips in CE.SDK to control which portion of media plays back." platform: angular url: "https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) > [Trim](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) --- Control video playback timing by trimming clips to specific start points and durations using CE.SDK's timeline UI and programmatic trim API. ![Video Trim example showing timeline with video clips and trim controls](./assets/browser.hero.webp) > **Reading time:** 12 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-trim-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-create-video-trim-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-create-video-trim-browser/) Understanding the difference between **fill-level trimming** and **block-level timing** is essential. Fill-level trimming (`setTrimOffset`, `setTrimLength`) controls which portion of the source media plays, while block-level timing (`setTimeOffset`, `setDuration`) controls when and how long the block appears in your timeline. These two systems work together to give you complete control over video playback. ```typescript file=@cesdk_web_examples/guides-create-video-trim-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Trim Video Guide * * Demonstrates trimming video clips in CE.SDK: * - Loading video resources with forceLoadAVResource * - Basic video trimming with setTrimOffset/setTrimLength * - Getting current trim values * - Coordinating trim with block duration * - Trimming with looping enabled * - Checking trim support * - Frame-accurate trimming * - Batch trimming multiple videos */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; const scene = engine.scene.get(); const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : scene; // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 8); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample video URL from demo assets const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Create a sample video block to demonstrate trim support checking const sampleVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the video fill - trim operations are applied to the fill, not the block const videoFill = engine.block.getFill(sampleVideo); // Check if the fill supports trim operations const supportsTrim = engine.block.supportsTrim(videoFill); // eslint-disable-next-line no-console console.log('Video fill supports trim:', supportsTrim); // true for video fills // Select this block so timeline controls are visible engine.block.setSelected(sampleVideo, true); // Pattern: Always load video resource before accessing trim properties // This ensures metadata (duration, frame rate, etc.) is available await engine.block.forceLoadAVResource(videoFill); // Now we can safely access video metadata const totalDuration = engine.block.getDouble( videoFill, 'fill/video/totalDuration' ); // eslint-disable-next-line no-console console.log('Total video duration:', totalDuration, 'seconds'); // Pattern #1: Demonstrate Individual Before Combined // Create a separate video block for basic trim demonstration const basicTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the fill to apply trim operations const basicTrimFill = engine.block.getFill(basicTrimVideo); // Load resource before trimming await engine.block.forceLoadAVResource(basicTrimFill); // Trim video to start at 2 seconds and play for 5 seconds engine.block.setTrimOffset(basicTrimFill, 2.0); engine.block.setTrimLength(basicTrimFill, 5.0); // Get current trim values to verify or modify const currentOffset = engine.block.getTrimOffset(basicTrimFill); const currentLength = engine.block.getTrimLength(basicTrimFill); // eslint-disable-next-line no-console console.log( `Basic trim - Offset: ${currentOffset}s, Length: ${currentLength}s` ); // Pattern #5: Progressive Complexity - coordinating trim with block duration // Create a video block demonstrating trim + duration coordination const durationTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const durationTrimFill = engine.block.getFill(durationTrimVideo); await engine.block.forceLoadAVResource(durationTrimFill); // Set trim: play portion from 3s to 8s (5 seconds of content) engine.block.setTrimOffset(durationTrimFill, 3.0); engine.block.setTrimLength(durationTrimFill, 5.0); // Set block duration: how long this block appears in the timeline // When duration equals trim length, the entire trimmed portion plays once engine.block.setDuration(durationTrimVideo, 5.0); // eslint-disable-next-line no-console console.log( 'Trim+Duration - Block will play trimmed 5s exactly once in timeline' ); // Create a video block with trim + looping const loopingTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const loopingTrimFill = engine.block.getFill(loopingTrimVideo); await engine.block.forceLoadAVResource(loopingTrimFill); // Trim to a short 3-second segment engine.block.setTrimOffset(loopingTrimFill, 1.0); engine.block.setTrimLength(loopingTrimFill, 3.0); // Enable looping so the 3-second segment repeats engine.block.setLooping(loopingTrimFill, true); // Verify looping is enabled const isLooping = engine.block.isLooping(loopingTrimFill); // eslint-disable-next-line no-console console.log('Looping enabled:', isLooping); // Set duration longer than trim length - the trim will loop to fill it engine.block.setDuration(loopingTrimVideo, 9.0); // eslint-disable-next-line no-console console.log( 'Looping trim - 3s segment will loop 3 times to fill 9s duration' ); // Pattern #6: Descriptive naming - frame-accurate trim demonstration // Create a video block for frame-accurate trimming const frameAccurateTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const frameFill = engine.block.getFill(frameAccurateTrimVideo); await engine.block.forceLoadAVResource(frameFill); // Note: Frame rate is not directly accessible via the API // For this example, we'll assume a common frame rate of 30fps const frameRate = 30; // Calculate trim offset based on specific frame number // Example: Start at frame 60 for a 30fps video = 2.0 seconds const startFrame = 60; const trimOffsetSeconds = startFrame / frameRate; // Trim for exactly 150 frames = 5.0 seconds at 30fps const trimFrames = 150; const trimLengthSeconds = trimFrames / frameRate; engine.block.setTrimOffset(frameFill, trimOffsetSeconds); engine.block.setTrimLength(frameFill, trimLengthSeconds); // eslint-disable-next-line no-console console.log( `Frame-accurate trim - Frame rate: ${frameRate}fps (assumed), Start frame: ${startFrame}, Duration: ${trimFrames} frames` ); // Pattern: Batch processing multiple video clips // Create multiple video blocks to demonstrate batch trimming const batchVideoUris = [ 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4' ]; const batchVideos = []; for (let i = 0; i < batchVideoUris.length; i++) { const batchVideo = await engine.block.addVideo( batchVideoUris[i], blockWidth, blockHeight ); batchVideos.push(batchVideo); // Get the fill for trim operations const batchFill = engine.block.getFill(batchVideo); // Load resource before trimming await engine.block.forceLoadAVResource(batchFill); // Apply consistent trim: first 4 seconds of each video engine.block.setTrimOffset(batchFill, 0.0); engine.block.setTrimLength(batchFill, 4.0); // Set consistent duration engine.block.setDuration(batchVideo, 4.0); } // eslint-disable-next-line no-console console.log('Batch trim - Applied consistent 4s trim to 3 video blocks'); // ===== Position all blocks in grid layout ===== const blocks = [ sampleVideo, // Position 0 basicTrimVideo, // Position 1 durationTrimVideo, // Position 2 loopingTrimVideo, // Position 3 frameAccurateTrimVideo, // Position 4 ...batchVideos // Positions 5-7 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Start playback automatically when the example loads try { engine.block.setPlaying(page, true); // eslint-disable-next-line no-console console.log( 'Video trim guide initialized. Playback started automatically. Use timeline controls to adjust trim handles.' ); } catch (error) { // eslint-disable-next-line no-console console.log( 'Video trim guide initialized. Click play button to start playback.' ); } } } export default Example; ``` This guide covers how to use the built-in timeline UI for visual trimming and how to trim videos programmatically using the Engine API. ## Understanding Trim Concepts ### Fill-Level Trimming When we trim a video, we're adjusting properties of the video's fill, not the block itself. The fill represents the media source—the actual video file. Fill-level trimming determines which portion of that source media will play. `setTrimOffset` specifies where playback starts within the source media. A trim offset of `2.0` skips the first two seconds of the video file. `setTrimLength` defines how much of the source media plays from the trim offset point. A trim length of 5.0 will play 5 seconds of the source. Combined with a trim offset of 2.0, the video plays from 2 seconds to 7 seconds of the original file. This trimming is completely non-destructive—the source video file remains unchanged. You can adjust trim values at any time to show different portions of the same media. > **Note:** Audio blocks use the same trim API (`setTrimOffset`, `setTrimLength`) as video > blocks. The concepts are identical, though this guide focuses on video. ### Block-Level Timing Block-level timing is separate from trimming and controls when and how long a block exists in the timeline. `setTimeOffset` determines when the block becomes active in the composition timeline (useful for track-based layouts). `setDuration` controls how long the block appears in the timeline. The *trim* controls what plays from the source media, while the *duration* controls how long that playback appears in your timeline. If the duration exceeds the trim length and if looping is disabled, the trimmed portion will play once and then hold the last frame for the remaining duration. ### Common Use Cases Trimming enables many video editing workflows: - **Remove unwanted segments** - Cut intro or outro portions to keep videos concise - **Extract key moments** - Isolate specific segments from longer source media - **Sync audio to video** - Trim audio and video independently for perfect alignment - **Create loops** - Trim to a specific length and enable loop mode for seamless repeating content - **Uniform compositions** - Batch trim multiple clips to consistent lengths ## Trimming Video via UI ### Accessing Trim Controls When you select a video block in the timeline, CE.SDK reveals trim handles at the edges of the clip. These visual controls appear as draggable handles on the left and right sides of the video block in the timeline panel. The trimmed portion of your video is visually distinguished from the untrimmed regions on either side that represent portions of the source media that won't play due to trim settings. This visual feedback makes it immediately clear which part of your video will be included in the final composition. ### Using Trim Handles We adjust trimming by dragging the handles. The left handle controls the trim offset—dragging it right increases the offset, skipping more of the beginning. Dragging left decreases the offset, including more from the start of the video. The right handle adjusts the trim length by changing where the video stops playing. Dragging left shortens the trim length, ending playback earlier. Dragging right extends it, playing more of the source media. For frame-accurate control, many CE.SDK interfaces provide numeric input fields where you can type exact time values in seconds. This precision is essential when you need to trim to specific frames or match exact durations. The icon on the trim handle turns into an outward-pointing arrow. ### Preview During Trimming Scrubbing the playhead through your trimmed content shows exactly what will play. This immediate feedback loop makes it easy to find the perfect trim points visually. If your video extends beyond the page duration, out-of-bounds content is indicated with a blue overlay in the timeline and won't be visible in the final output. ### Constraints and Limitations CE.SDK enforces a minimum trim duration to prevent creating zero-length or extremely short clips that could cause playback issues. If you try to drag handles closer than this minimum, the handle will resist further movement. When clips extend beyond page duration boundaries, grey visual indicators show which portions fall outside. While the video block may be longer than the page, only content within the page duration will appear in exports or final compositions. ## Programmatic Video Trimming ### Prerequisites and Setup For applications that need to apply trimming programmatically—whether for automation, batch processing, or dynamic user experiences—we start by setting up CE.SDK in Video mode with the proper configuration. ```typescript highlight-enable-video-features // Enable video editing features in CE.SDK cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.timeline'); cesdk.feature.enable('ly.img.playback'); ``` Video mode is required for trimming operations. Design mode doesn't provide timeline-based editing capabilities, so we must use `cesdk.actions.run('scene.create', { mode: 'Video' })` to access trim functionality. ### Loading Video Resources Before accessing trim properties or setting trim values, we must load the video resource metadata using `forceLoadAVResource`. This critical step ensures CE.SDK has downloaded information about the video's duration, frame rate, and other properties needed for accurate trimming. ```typescript highlight-load-video-resource // Pattern: Always load video resource before accessing trim properties // This ensures metadata (duration, frame rate, etc.) is available await engine.block.forceLoadAVResource(videoFill); // Now we can safely access video metadata const totalDuration = engine.block.getDouble( videoFill, 'fill/video/totalDuration' ); // eslint-disable-next-line no-console console.log('Total video duration:', totalDuration, 'seconds'); ``` Skipping this step is a common source of errors. Without loading the resource first, trim operations may fail silently or produce unexpected results. Always await `forceLoadAVResource` before calling any trim methods. Once loaded, we can access metadata like `totalDuration` and `frameRate` from the video fill. This information helps us calculate valid trim ranges and ensures we don't try to trim beyond the available media. ### Checking Trim Support Before applying trim operations, we verify that a block supports trimming. While video blocks typically support trimming, other block types like pages and scenes do not. ```typescript highlight-check-trim-support // Create a sample video block to demonstrate trim support checking const sampleVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the video fill - trim operations are applied to the fill, not the block const videoFill = engine.block.getFill(sampleVideo); // Check if the fill supports trim operations const supportsTrim = engine.block.supportsTrim(videoFill); // eslint-disable-next-line no-console console.log('Video fill supports trim:', supportsTrim); // true for video fills // Select this block so timeline controls are visible engine.block.setSelected(sampleVideo, true); ``` Checking support prevents runtime errors and allows you to build robust interfaces that only show trim controls for compatible blocks. Graphic blocks with video fills also support trimming, not just top-level video blocks. ### Trimming Video Once we've confirmed trim support and loaded the resource, we can apply trimming. Here we create a video block and trim it to start 2 seconds into the source media and play for 5 seconds. ```typescript highlight-basic-video-trim // Pattern #1: Demonstrate Individual Before Combined // Create a separate video block for basic trim demonstration const basicTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); // Get the fill to apply trim operations const basicTrimFill = engine.block.getFill(basicTrimVideo); // Load resource before trimming await engine.block.forceLoadAVResource(basicTrimFill); // Trim video to start at 2 seconds and play for 5 seconds engine.block.setTrimOffset(basicTrimFill, 2.0); engine.block.setTrimLength(basicTrimFill, 5.0); ``` The trim offset of 2.0 skips the first 2 seconds of the video. The trim length of 5.0 means exactly 5 seconds of video will play, starting from that offset point. So this video plays from the 2-second mark to the 7-second mark of the original file. ### Getting Current Trim Values We can retrieve the current trim settings to verify values, build UI controls, or make relative adjustments based on existing settings. ```typescript highlight-get-trim-values // Get current trim values to verify or modify const currentOffset = engine.block.getTrimOffset(basicTrimFill); const currentLength = engine.block.getTrimLength(basicTrimFill); // eslint-disable-next-line no-console console.log( `Basic trim - Offset: ${currentOffset}s, Length: ${currentLength}s` ); ``` These getter methods return the current trim offset and length in seconds. Use them to populate UI inputs, calculate remaining media duration, or create undo/redo functionality in your application. ## Additional Trimming Techniques ### Trimming with Block Duration Take a look at this example to understand how trim length and block duration interact: ```typescript highlight-trim-with-duration // Pattern #5: Progressive Complexity - coordinating trim with block duration // Create a video block demonstrating trim + duration coordination const durationTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const durationTrimFill = engine.block.getFill(durationTrimVideo); await engine.block.forceLoadAVResource(durationTrimFill); // Set trim: play portion from 3s to 8s (5 seconds of content) engine.block.setTrimOffset(durationTrimFill, 3.0); engine.block.setTrimLength(durationTrimFill, 5.0); // Set block duration: how long this block appears in the timeline // When duration equals trim length, the entire trimmed portion plays once engine.block.setDuration(durationTrimVideo, 5.0); // eslint-disable-next-line no-console console.log( 'Trim+Duration - Block will play trimmed 5s exactly once in timeline' ); ``` In this example, we trim the video to a 5-second segment (from 3s to 8s of the source) and set the block duration to exactly 5 seconds. This means the entire trimmed portion plays once, then stops. The block duration matches the trim length, so there's no looping or holding on the last frame. If the block duration is less than the trim length, only part of the trimmed segment will play. If duration exceeds trim length without looping enabled, the video plays the trimmed portion once and holds on the last frame for the remaining time. ### Trimming with Looping Looping allows a trimmed video segment to repeat seamlessly. We enable looping and set a block duration longer than the trim length to create repeating playback. ```typescript highlight-trim-with-looping // Create a video block with trim + looping const loopingTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const loopingTrimFill = engine.block.getFill(loopingTrimVideo); await engine.block.forceLoadAVResource(loopingTrimFill); // Trim to a short 3-second segment engine.block.setTrimOffset(loopingTrimFill, 1.0); engine.block.setTrimLength(loopingTrimFill, 3.0); // Enable looping so the 3-second segment repeats engine.block.setLooping(loopingTrimFill, true); // Verify looping is enabled const isLooping = engine.block.isLooping(loopingTrimFill); // eslint-disable-next-line no-console console.log('Looping enabled:', isLooping); // Set duration longer than trim length - the trim will loop to fill it engine.block.setDuration(loopingTrimVideo, 9.0); // eslint-disable-next-line no-console console.log( 'Looping trim - 3s segment will loop 3 times to fill 9s duration' ); ``` Here we trim to a 3-second segment and enable looping. The block duration of 9 seconds means this 3-second segment will loop 3 times to fill the entire duration. This technique is perfect for creating background loops, repeated motion graphics, or extending short clips. When looping is enabled, CE.SDK automatically restarts playback from the trim offset when it reaches the end of the trim length. ### Frame-Accurate Trimming For precise editing, we often need to trim to specific frame boundaries rather than arbitrary time values. Using the video's frame rate metadata, we can calculate exact frame-based trim points. ```typescript highlight-frame-accurate-trim // Pattern #6: Descriptive naming - frame-accurate trim demonstration // Create a video block for frame-accurate trimming const frameAccurateTrimVideo = await engine.block.addVideo( videoUri, blockWidth, blockHeight ); const frameFill = engine.block.getFill(frameAccurateTrimVideo); await engine.block.forceLoadAVResource(frameFill); // Note: Frame rate is not directly accessible via the API // For this example, we'll assume a common frame rate of 30fps const frameRate = 30; // Calculate trim offset based on specific frame number // Example: Start at frame 60 for a 30fps video = 2.0 seconds const startFrame = 60; const trimOffsetSeconds = startFrame / frameRate; // Trim for exactly 150 frames = 5.0 seconds at 30fps const trimFrames = 150; const trimLengthSeconds = trimFrames / frameRate; engine.block.setTrimOffset(frameFill, trimOffsetSeconds); engine.block.setTrimLength(frameFill, trimLengthSeconds); // eslint-disable-next-line no-console console.log( `Frame-accurate trim - Frame rate: ${frameRate}fps (assumed), Start frame: ${startFrame}, Duration: ${trimFrames} frames` ); ``` We first retrieve the frame rate from the video fill metadata. Then we convert frame numbers to time offsets by dividing by the frame rate. Starting at frame 60 with a 30fps video gives us exactly 2.0 seconds. Trimming for 150 frames provides exactly 5.0 seconds of playback. This technique ensures frame-accurate edits, which is essential for professional video editing workflows. Remember that codec compression may affect true frame accuracy—for critical applications, test with your target codecs to verify precision. ### Batch Processing Multiple Videos When working with multiple video clips that need consistent trimming, we can iterate through collections and apply the same trim settings programmatically. ```typescript highlight-batch-trim-videos // Pattern: Batch processing multiple video clips // Create multiple video blocks to demonstrate batch trimming const batchVideoUris = [ 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4', 'https://img.ly/static/ubq_video_samples/bbb.mp4' ]; const batchVideos = []; for (let i = 0; i < batchVideoUris.length; i++) { const batchVideo = await engine.block.addVideo( batchVideoUris[i], blockWidth, blockHeight ); batchVideos.push(batchVideo); // Get the fill for trim operations const batchFill = engine.block.getFill(batchVideo); // Load resource before trimming await engine.block.forceLoadAVResource(batchFill); // Apply consistent trim: first 4 seconds of each video engine.block.setTrimOffset(batchFill, 0.0); engine.block.setTrimLength(batchFill, 4.0); // Set consistent duration engine.block.setDuration(batchVideo, 4.0); } // eslint-disable-next-line no-console console.log('Batch trim - Applied consistent 4s trim to 3 video blocks'); ``` We create multiple video blocks and apply identical trim settings to each one. This ensures consistency across clips—perfect for creating video montages, multi-angle compositions, or any scenario where uniform clip lengths are required. When batch processing, always load each video's resources before trimming. Don't assume all videos have the same duration—check total duration to ensure your trim values don't exceed available media. ## Trim vs Duration Interaction ### How setDuration Affects Playback The relationship between trim length and block duration determines playback behavior. When block duration equals trim length, the video plays the trimmed portion exactly once. When duration is less than trim length, playback stops before the trimmed portion finishes. When duration exceeds trim length with looping disabled, the video plays once and holds on the last frame. With looping enabled, exceeding trim length causes the trimmed segment to repeat until the block duration is filled. This creates seamless loops as long as the content loops visually. ### Best Practices For predictable behavior, always consider both trim and duration together. Set trim values first to define the source media segment you want. Then set duration to control timeline length. If you want the entire trimmed segment to play once, match duration to trim length. For looping content, enable looping before setting a longer duration. When building UIs, update both values together when users adjust trim handles. This prevents confusion about why a video isn't playing the full trimmed length (duration too short) or why it's holding on the last frame (duration too long without looping). ## Performance Considerations CE.SDK's video system is optimized for real-time editing, but understanding these performance factors helps you build responsive applications: - **Resource loading**: Use `forceLoadAVResource` judiciously. Loading resources has overhead, so batch loads when possible rather than loading repeatedly. - **Trim adjustments**: Changing trim values is lightweight—CE.SDK updates the playback range without reprocessing the video. You can adjust trim interactively without performance concerns. - **Mobile devices**: Video decoding is more expensive on mobile. Limit the number of simultaneous video blocks and consider lower resolution sources for editing (high resolution for export). - **Long videos**: Very long source videos (30+ minutes) may have slower seeking to trim offsets. Consider pre-cutting extremely long videos into shorter segments. Test your trim operations on target devices early in development to ensure acceptable performance for your users. ## Troubleshooting ### Trim Not Applied If setting trim values has no visible effect, the most common cause is forgetting to await `forceLoadAVResource`. The resource must be loaded before trim values take effect. Always load resources first. Another possibility is confusing time offset with trim offset. `setTimeOffset` controls when the block appears in the timeline, while `setTrimOffset` controls where in the source media playback starts. Make sure you're using the correct method. ### Incorrect Trim Calculation If trim values seem offset or produce unexpected results, verify you're calculating based on the source media duration, not the block duration. Use `getTotalDuration` from the fill metadata to understand the available media length. Also check that you're not exceeding the total available duration. Trim offset plus trim length should never exceed total duration. CE.SDK may clamp values automatically, but it's better to validate before setting. ### Playback Beyond Trim Length If video plays past the intended trim length, check that block duration doesn't exceed trim length. When duration is longer and looping is disabled, the video will hold on the last frame for the excess duration. Ensure looping is set correctly for your use case. If you want playback to stop at the trim length, set duration equal to trim length or enable looping. ### Audio/Video Desync When trimming both audio and video independently, desynchronization can occur if offset and duration values aren't coordinated carefully. Calculate both trim offsets to maintain the original relationship between audio and video timing. Consider the original sync point between audio and video in the source media. If they were perfectly synced at 0 seconds originally, maintaining the same offset difference preserves that sync. ### Frame-Accurate Trim Issues If frame-accurate trimming doesn't land on exact frames, remember that floating-point precision can cause tiny discrepancies. Round your calculated values to a reasonable precision (e.g., 3 decimal places). Also understand codec limitations. Variable frame rate videos don't have perfectly uniform frame timing, so true frame accuracy may not be possible. Use constant frame rate sources for critical frame-accurate applications. ## Best Practices ### Workflow Recommendations 1. Always `await forceLoadAVResource()` before accessing trim properties 2. Check `supportsTrim()` before applying trim operations 3. Coordinate trim length with block duration for predictable behavior 4. Use TypeScript for type safety with CE.SDK API 5. Preview trimmed content before final export 6. Validate trim values don't exceed total media duration ### Code Organization - Separate media loading from trim logic - Create helper functions for common trim patterns (e.g., `trimToFrames`, `trimToPercentage`) - Handle errors gracefully with try-catch blocks around `forceLoadAVResource` - Document complex trim calculations with comments explaining frame math ### Performance Optimization - Avoid redundant `forceLoadAVResource` calls—load once, trim multiple times - Use appropriate preview quality settings during editing to maintain responsiveness - Test on target devices early to identify performance bottlenecks ## API Reference | Method | Description | Parameters | Returns | | -------------------------------- | ---------------------------------- | ------------------------------------- | --------------- | | `getFill(id)` | Get the fill block for a block | `id: DesignBlockId` | `DesignBlockId` | | `forceLoadAVResource(id)` | Force load media resource metadata | `id: DesignBlockId` | `Promise` | | `supportsTrim(id)` | Check if block supports trimming | `id: DesignBlockId` | `boolean` | | `setTrimOffset(id, offset)` | Set start point of media playback | `id: DesignBlockId, offset: number` | `void` | | `getTrimOffset(id)` | Get current trim offset | `id: DesignBlockId` | `number` | | `setTrimLength(id, length)` | Set duration of trimmed media | `id: DesignBlockId, length: number` | `void` | | `getTrimLength(id)` | Get current trim length | `id: DesignBlockId` | `number` | | `getAVResourceTotalDuration(id)` | Get total duration of source media | `id: DesignBlockId` | `number` | | `setLooping(id, enabled)` | Enable/disable media looping | `id: DesignBlockId, enabled: boolean` | `void` | | `isLooping(id)` | Check if media looping is enabled | `id: DesignBlockId` | `boolean` | | `setDuration(id, duration)` | Set block playback duration | `id: DesignBlockId, duration: number` | `void` | | `getDuration(id)` | Get block duration | `id: DesignBlockId` | `number` | | `setTimeOffset(id, offset)` | Set when block becomes active | `id: DesignBlockId, offset: number` | `void` | | `getTimeOffset(id)` | Get block time offset | `id: DesignBlockId` | `number` | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Engine Interface" description: "Understand CE.SDK's architecture and learn when to use direct Engine access for automation workflows" platform: angular url: "https://img.ly/docs/cesdk/angular/engine-interface-6fb7cf/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Engine](https://img.ly/docs/cesdk/angular/engine-interface-6fb7cf/) --- Access CE.SDK's cross-platform C++ engine programmatically for client-side automation, background processing, and custom workflows in the browser. CE.SDK is built on a layered architecture where a cross-platform C++ core engine powers all creative operations. The Editor UI and your programmatic code access identical capabilities through the same underlying engine. ## Web SDK Packages CE.SDK offers three npm packages: **@cesdk/cesdk-js**: Full package with Editor UI and Engine. Initialize with `CreativeEditorSDK.create()` and access the Engine via `cesdk.engine`. Use this when users edit designs visually while your code handles background tasks. ```javascript import CreativeEditorSDK from '@cesdk/cesdk-js'; const cesdk = await CreativeEditorSDK.create('#container', { // license: 'YOUR_CESDK_LICENSE_KEY', }); const engine = cesdk.engine; ``` **@cesdk/engine**: Engine-only package without UI. Smaller bundle size. Initialize with `CreativeEngine.init()`. Use for browser automation, custom UIs, or hidden Engine instances. **@cesdk/node**: Node.js package for server-side processing. Same API, compiled for Node.js runtime. ## Engine API Namespaces The Engine organizes its functionality into six namespaces: - **engine.block**: Create, modify, and export design elements (shapes, text, images, videos) - **engine.scene**: Load, save, and manage scenes and pages - **engine.asset**: Register and query asset sources (images, templates, fonts) - **engine.editor**: Configure editor settings, manage edit modes, handle undo/redo - **engine.variable**: Define and update template variables for data merge - **engine.event**: Subscribe to engine events (selection changes, state updates) ## Combining UI and Engine Access The Editor UI calls Engine APIs internally. When you use `cesdk.engine`, you're accessing the same APIs. Most applications combine both: users interact with the visual editor while your code automates background tasks. Common patterns: - **Template loading**: Load scenes when users select templates - **Validation**: Check for empty placeholders before export - **Auto-save**: Serialize scenes with `engine.scene.saveToString()` - **Thumbnails**: Generate previews with `engine.block.export()` ## Hidden Engine Instances Run a second, invisible Engine alongside your main UI for background processing: ```javascript import CreativeEngine from '@cesdk/engine'; // Main editor with UI const cesdk = await CreativeEditorSDK.create('#container', config); // Hidden engine for background work const backgroundEngine = await CreativeEngine.init({ // license: 'YOUR_CESDK_LICENSE_KEY', }); async function generateThumbnail(sceneData) { await backgroundEngine.scene.loadFromString(sceneData); const page = backgroundEngine.scene.getPages()[0]; return await backgroundEngine.block.export(page, 'image/jpeg', { targetWidth: 200, targetHeight: 200, }); } ``` ## Memory Management Each Engine instance consumes memory. Dispose instances when done: ```javascript backgroundEngine.dispose(); ``` For resource-intensive tasks like high-resolution exports, consider server-side processing with `@cesdk/node`. ## Troubleshooting **Engine not initialized**: Ensure `CreativeEditorSDK.create()` or `CreativeEngine.init()` completes before accessing `engine`. **Hidden instance blocking UI**: Heavy operations can impact browser performance. Move resource-intensive tasks to server-side. **Memory issues**: Dispose unused instances with `engine.dispose()`. ## Next Steps - [Node.js SDK](https://img.ly/docs/cesdk/angular/what-is-cesdk-2e7acd/) for server-side processing - [Automation Overview](https://img.ly/docs/cesdk/angular/automation/overview-34d971/) for workflow examples --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Thumbnail" description: "Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries, file browsers, and design management interfaces." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/create-thumbnail-749be1/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [Create Thumbnail](https://img.ly/docs/cesdk/angular/export-save-publish/create-thumbnail-749be1/) --- Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries and design management. ![Create Thumbnail hero image](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-create-thumbnail-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-create-thumbnail-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-create-thumbnail-browser/) Thumbnails provide visual previews of designs without loading the full editor. Use `engine.block.export()` with `targetWidth` and `targetHeight` options to scale content while maintaining aspect ratio. Supported formats include PNG, JPEG, and WebP. ```typescript file=@cesdk_web_examples/guides-export-save-publish-create-thumbnail-browser/browser.ts reference-only import type CreativeEditorSDK from '@cesdk/cesdk-js'; import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (!page) throw new Error('No page found'); await engine.scene.zoomToBlock(page, { padding: 40 }); // Setup thumbnail export functionality await this.setupThumbnailActions(cesdk, page); } private async setupThumbnailActions( cesdk: CreativeEditorSDK, page: number ): Promise { const engine = cesdk.engine; // Add thumbnail export buttons to navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-small', label: 'Small Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 150, targetHeight: 150, jpegQuality: 0.8 }); await cesdk.utils.downloadFile(blob, 'image/jpeg'); console.log( `✓ Small thumbnail: ${(blob.size / 1024).toFixed(1)} KB` ); } }, { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-medium', label: 'Medium Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 400, targetHeight: 300, jpegQuality: 0.85 }); await cesdk.utils.downloadFile(blob, 'image/jpeg'); console.log( `✓ Medium thumbnail: ${(blob.size / 1024).toFixed(1)} KB` ); } }, { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-png', label: 'PNG Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 400, targetHeight: 300, pngCompressionLevel: 6 }); await cesdk.utils.downloadFile(blob, 'image/png'); console.log(`✓ PNG thumbnail: ${(blob.size / 1024).toFixed(1)} KB`); } }, { id: 'ly.img.action.navigationBar', key: 'export-thumbnail-webp', label: 'WebP Thumbnail', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/webp', targetWidth: 400, targetHeight: 300, webpQuality: 0.8 }); await cesdk.utils.downloadFile(blob, 'image/webp'); console.log( `✓ WebP thumbnail: ${(blob.size / 1024).toFixed(1)} KB` ); } } ] }); } } export default Example; ``` This guide covers exporting thumbnails at specific dimensions, choosing formats, optimizing quality and file size, and generating multiple thumbnail sizes. ## Export a Thumbnail Call `engine.block.export()` with target dimensions to create a scaled thumbnail. Both `targetWidth` and `targetHeight` must be set together for scaling to work. ```typescript highlight=highlight-thumbnail-small const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 150, targetHeight: 150, jpegQuality: 0.8 }); ``` The block renders large enough to fill the target size while maintaining aspect ratio. If aspect ratios differ, the output extends beyond the target on one axis. ## Choose Thumbnail Format Select the format via the `mimeType` option based on your needs: - **`'image/jpeg'`** — Smaller files, good for photos, no transparency - **`'image/png'`** — Lossless quality, supports transparency - **`'image/webp'`** — Best compression, modern browsers only ### JPEG Thumbnails JPEG works well for photographic content. Control file size with `jpegQuality` (0-1, default 0.9). Values between 0.75-0.85 balance quality and size for thumbnails. ```typescript highlight=highlight-thumbnail-medium const blob = await engine.block.export(page, { mimeType: 'image/jpeg', targetWidth: 400, targetHeight: 300, jpegQuality: 0.85 }); ``` ### PNG Thumbnails PNG provides lossless quality with transparency support. Control encoding speed vs. file size with `pngCompressionLevel` (0-9, default 5). ```typescript highlight=highlight-thumbnail-png const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 400, targetHeight: 300, pngCompressionLevel: 6 }); ``` ### WebP Thumbnails WebP offers the best compression for modern browsers. Control quality with `webpQuality` (0-1, default 1.0 for lossless). ```typescript highlight=highlight-thumbnail-webp const blob = await engine.block.export(page, { mimeType: 'image/webp', targetWidth: 400, targetHeight: 300, webpQuality: 0.8 }); ``` ## Common Thumbnail Sizes Standard sizes for different use cases: | Size | Dimensions | Use Case | | ---- | ---------- | -------- | | Small | 150×150 | Grid galleries, file browsers | | Medium | 400×300 | Preview panels, cards | | Large | 800×600 | Full previews, detail views | ## Optimize Thumbnail Quality Balance quality with file size using format-specific options: | Format | Option | Range | Default | Notes | | ------ | ------ | ----- | ------- | ----- | | JPEG | `jpegQuality` | 0-1 | 0.9 | Lower = smaller files, visible artifacts | | PNG | `pngCompressionLevel` | 0-9 | 5 | Higher = smaller files, slower encoding | | WebP | `webpQuality` | 0-1 | 1.0 | 1.0 = lossless, lower = lossy compression | For thumbnails, JPEG quality of 0.8 or WebP quality of 0.75-0.85 typically provides good results with small file sizes. ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.export(blockId, options)` | Export a block as image with format and dimension options | | `engine.scene.getCurrentPage()` | Get the current page block ID | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a blob to the user's device | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export" description: "Explore export options, supported formats, and configuration features for sharing or rendering output." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) --- --- ## Related Pages - [Options](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Explore export options, supported formats, and configuration features for sharing or rendering output. - [Export for Social Media](https://img.ly/docs/cesdk/angular/export-save-publish/for-social-media-0e8a92/) - Export vertical videos with the correct dimensions, formats, and quality settings for Instagram Reels, TikTok, and YouTube Shorts. - [To MP4](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-mp4-c998a8/) - Export video compositions as MP4 files with configurable encoding options, progress tracking, and resolution control. - [For Audio Processing](https://img.ly/docs/cesdk/angular/guides/export-save-publish/export/audio-68de25/) - Learn how to export audio in WAV or MP4 format from any block type in CE.SDK. - [To PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) - Export your designs as PDF documents with options for print compatibility, underlayer generation, and output control. - [To JPEG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-jpeg-6f88e9/) - Export CE.SDK designs to JPEG format with configurable quality settings for photographs, web images, and social media content. - [To PNG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/) - Export your designs as PNG images with transparency support and configurable compression for web graphics, UI elements, and content requiring crisp edges. - [To WebP](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-webp-aef6f4/) - Export your CE.SDK designs to WebP format for optimized web delivery with lossy and lossless compression options. - [Export to Raw Data](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-raw-data-abd7da/) - Export designs to uncompressed RGBA pixel data for custom image processing, GPU uploads, and advanced graphics workflows. - [Compress](https://img.ly/docs/cesdk/angular/export-save-publish/export/compress-29105e/) - Reduce file sizes when exporting images by configuring compression and quality settings for PNG, JPEG, and WebP formats. - [Export with a Color Mask](https://img.ly/docs/cesdk/angular/export-save-publish/export/with-color-mask-4f868f/) - Learn how to export design blocks with color masking in CE.SDK to remove specific colors and generate alpha masks for print workflows and compositing. - [Pre-Export Validation](https://img.ly/docs/cesdk/angular/export-save-publish/pre-export-validation-3a2cba/) - Documentation for Pre-Export Validation - [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) - Documentation for Partial Export - [Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) - Configure and understand CE.SDK's image and video size limits for both input and export to optimize performance and memory usage. - [Create Thumbnail](https://img.ly/docs/cesdk/angular/export-save-publish/create-thumbnail-749be1/) - Generate thumbnail preview images from CE.SDK scenes by exporting with target dimensions for galleries, file browsers, and design management interfaces. - [Export for Printing](https://img.ly/docs/cesdk/angular/export-save-publish/for-printing-bca896/) - Export designs from CE.SDK as print-ready PDFs with professional output options including high compatibility mode, underlayers for special media, and scene DPI configuration. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Compress" description: "Reduce file sizes when exporting images by configuring compression and quality settings for PNG, JPEG, and WebP formats." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/compress-29105e/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [Compress](https://img.ly/docs/cesdk/angular/export-save-publish/export/compress-29105e/) --- Compression reduces file sizes during export while maintaining visual quality. With CE.SDK you can fine-tune compression settings for both images and videos, allowing your app to manage performance, quality, and storage efficiency. ![Compress example showing CE.SDK with export options](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-compress-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-compress-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-compress-browser/) Image compression reduces file sizes while maintaining acceptable visual quality. CE.SDK supports format-specific compression controls: lossless compression for PNG, lossy quality settings for JPEG, and both modes for WebP. The example includes a navigation bar dropdown menu with export options for comparing different formats and compression levels. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-compress-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Compress Guide * * Demonstrates compression during export: * - PNG lossless compression levels * - JPEG lossy quality settings * - WebP quality settings * - Target dimension scaling * - Video compression with bitrate control * - Navigation bar dropdown with export options */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Load a video template scene for demonstration await cesdk.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.video.template/templates/milli-surf-school.scene' ); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; if (page == null) throw new Error('No page found'); // Helper function to download blob const downloadBlob = (blob: Blob, filename: string) => { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; anchor.click(); URL.revokeObjectURL(url); }; // PNG uses lossless compression - level 0-9 // Higher levels = smaller files, slower encoding // Quality is identical at all levels const exportPngLevel9 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9 }); downloadBlob(blob, 'export-png-level9.png'); cesdk.ui.showNotification({ message: `PNG Level 9: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; const exportPngLevel5 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 5 }); downloadBlob(blob, 'export-png-level5.png'); cesdk.ui.showNotification({ message: `PNG Level 5: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; // JPEG uses lossy compression - quality 0-1 // Lower values = smaller files, more artifacts const exportJpeg90 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', jpegQuality: 0.9 }); downloadBlob(blob, 'export-jpeg-90.jpg'); cesdk.ui.showNotification({ message: `JPEG 90%: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; const exportJpeg60 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', jpegQuality: 0.6 }); downloadBlob(blob, 'export-jpeg-60.jpg'); cesdk.ui.showNotification({ message: `JPEG 60%: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; // WebP supports both lossless (1.0) and lossy (<1.0) modes // Typically 20-30% smaller than JPEG at equivalent quality const exportWebp90 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/webp', webpQuality: 0.9 }); downloadBlob(blob, 'export-webp-90.webp'); cesdk.ui.showNotification({ message: `WebP 90%: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; const exportWebp60 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/webp', webpQuality: 0.6 }); downloadBlob(blob, 'export-webp-60.webp'); cesdk.ui.showNotification({ message: `WebP 60%: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; // Combine compression with dimension scaling // Useful for creating thumbnails or social media previews const exportScaled = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 6, targetWidth: 1200, targetHeight: 630 }); downloadBlob(blob, 'export-scaled-1200x630.png'); cesdk.ui.showNotification({ message: `Scaled 1200×630: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; // Video export with web-optimized bitrate (720p, 2 Mbps) const exportVideoWeb = async () => { const blob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', videoBitrate: 2_000_000, audioBitrate: 128_000, framerate: 30, targetWidth: 1280, targetHeight: 720 }); downloadBlob(blob, 'export-web-720p.mp4'); cesdk.ui.showNotification({ message: `Video 720p: ${(blob.size / (1024 * 1024)).toFixed(1)} MB`, type: 'success' }); }; // Video export with HD bitrate (1080p, 8 Mbps) const exportVideoHD = async () => { const blob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', videoBitrate: 8_000_000, audioBitrate: 192_000, framerate: 30, targetWidth: 1920, targetHeight: 1080 }); downloadBlob(blob, 'export-hd-1080p.mp4'); cesdk.ui.showNotification({ message: `Video 1080p: ${(blob.size / (1024 * 1024)).toFixed(1)} MB`, type: 'success' }); }; // Configure navigation bar with export dropdown cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.back.navigationBar', 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.zoom.navigationBar', // Actions dropdown with all export options { id: 'ly.img.actions.navigationBar', children: [ // PNG exports { id: 'ly.img.action.navigationBar', key: 'export-png-9', label: 'PNG (Level 9 - Smallest)', icon: '@imgly/Save', onClick: exportPngLevel9 }, { id: 'ly.img.action.navigationBar', key: 'export-png-5', label: 'PNG (Level 5 - Balanced)', icon: '@imgly/Save', onClick: exportPngLevel5 }, // JPEG exports { id: 'ly.img.action.navigationBar', key: 'export-jpeg-90', label: 'JPEG (90% Quality)', icon: '@imgly/Save', onClick: exportJpeg90 }, { id: 'ly.img.action.navigationBar', key: 'export-jpeg-60', label: 'JPEG (60% Quality)', icon: '@imgly/Save', onClick: exportJpeg60 }, // WebP exports { id: 'ly.img.action.navigationBar', key: 'export-webp-90', label: 'WebP (90% Quality)', icon: '@imgly/Save', onClick: exportWebp90 }, { id: 'ly.img.action.navigationBar', key: 'export-webp-60', label: 'WebP (60% Quality)', icon: '@imgly/Save', onClick: exportWebp60 }, // Scaled export { id: 'ly.img.action.navigationBar', key: 'export-scaled', label: 'Scaled (1200×630)', icon: '@imgly/Save', onClick: exportScaled }, // Video exports { id: 'ly.img.action.navigationBar', key: 'export-video-web', label: 'Video 720p (2 Mbps)', icon: '@imgly/Video', onClick: exportVideoWeb }, { id: 'ly.img.action.navigationBar', key: 'export-video-hd', label: 'Video 1080p (8 Mbps)', icon: '@imgly/Video', onClick: exportVideoHD } ] } ]); // eslint-disable-next-line no-console console.log('Compression guide initialized. Use the dropdown menu to export in different formats.'); } } export default Example; ``` This guide covers exporting with compression settings, configuring quality levels, controlling output dimensions, and video compression options. ## Compression Options by Format To compress assets, use `engine.block.export` with format-specific options. Each format supports different parameters for balancing speed, file size, and quality. | Format | Parameter | Type | Effect | Default | | ------ | --------- | ---- | ------ | ------- | | PNG | `pngCompressionLevel` | 0–9 | Higher = smaller, slower (lossless) | 5 | | JPEG | `jpegQuality` | 0.0–1.0 | Lower = smaller, lower quality | 0.9 | | WebP | `webpQuality` | 0.0–1.0 | 1.0 = lossless, below 1.0 = lossy | 1.0 | | MP4 | `videoBitrate`, `audioBitrate` | bits/sec | Higher = larger, higher quality | 0 (auto) | ## Export with Compression Call `engine.block.export()` with format-specific compression options. Each format uses different parameters to control file size and quality. ### PNG Compression PNG uses lossless compression controlled by `pngCompressionLevel` (0-9). Higher values produce smaller files but take longer to encode. Quality remains identical at all levels. ```typescript highlight=highlight-png-compression // PNG uses lossless compression - level 0-9 // Higher levels = smaller files, slower encoding // Quality is identical at all levels const exportPngLevel9 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9 }); downloadBlob(blob, 'export-png-level9.png'); cesdk.ui.showNotification({ message: `PNG Level 9: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; ``` Use level 5-6 for balanced results, or level 9 when file size is critical and encoding time is acceptable. ### JPEG Quality JPEG uses lossy compression controlled by `jpegQuality` (0-1). Lower values produce smaller files with more visible artifacts. ```typescript highlight=highlight-jpeg-quality // JPEG uses lossy compression - quality 0-1 // Lower values = smaller files, more artifacts const exportJpeg90 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/jpeg', jpegQuality: 0.9 }); downloadBlob(blob, 'export-jpeg-90.jpg'); cesdk.ui.showNotification({ message: `JPEG 90%: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; ``` Quality 0.8 provides a good balance for web delivery. Use 0.9+ for archival or print workflows. ### WebP Quality WebP supports both lossless and lossy modes via `webpQuality` (0-1). At 1.0, WebP uses lossless encoding. Values below 1.0 enable lossy compression. ```typescript highlight=highlight-webp-quality // WebP supports both lossless (1.0) and lossy (<1.0) modes // Typically 20-30% smaller than JPEG at equivalent quality const exportWebp90 = async () => { const blob = await engine.block.export(page, { mimeType: 'image/webp', webpQuality: 0.9 }); downloadBlob(blob, 'export-webp-90.webp'); cesdk.ui.showNotification({ message: `WebP 90%: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; ``` WebP typically produces 20-30% smaller files than JPEG at equivalent quality, with optional transparency support. ## Target Dimensions Use `targetWidth` and `targetHeight` together to export at specific dimensions. The block renders large enough to fill the target size while maintaining aspect ratio. ```typescript highlight=highlight-target-size // Combine compression with dimension scaling // Useful for creating thumbnails or social media previews const exportScaled = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 6, targetWidth: 1200, targetHeight: 630 }); downloadBlob(blob, 'export-scaled-1200x630.png'); cesdk.ui.showNotification({ message: `Scaled 1200×630: ${(blob.size / 1024).toFixed(0)} KB`, type: 'success' }); }; ``` Combining dimension scaling with compression produces smaller files suitable for specific platforms like social media thumbnails. ## Navigation Bar Export Menu The example demonstrates configuring the CE.SDK navigation bar with a dropdown menu containing all export options. This approach integrates naturally with the editor UI. ```typescript highlight=highlight-navigation-bar // Configure navigation bar with export dropdown cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.back.navigationBar', 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', 'ly.img.zoom.navigationBar', // Actions dropdown with all export options { id: 'ly.img.actions.navigationBar', children: [ // PNG exports { id: 'ly.img.action.navigationBar', key: 'export-png-9', label: 'PNG (Level 9 - Smallest)', icon: '@imgly/Save', onClick: exportPngLevel9 }, { id: 'ly.img.action.navigationBar', key: 'export-png-5', label: 'PNG (Level 5 - Balanced)', icon: '@imgly/Save', onClick: exportPngLevel5 }, // JPEG exports { id: 'ly.img.action.navigationBar', key: 'export-jpeg-90', label: 'JPEG (90% Quality)', icon: '@imgly/Save', onClick: exportJpeg90 }, { id: 'ly.img.action.navigationBar', key: 'export-jpeg-60', label: 'JPEG (60% Quality)', icon: '@imgly/Save', onClick: exportJpeg60 }, // WebP exports { id: 'ly.img.action.navigationBar', key: 'export-webp-90', label: 'WebP (90% Quality)', icon: '@imgly/Save', onClick: exportWebp90 }, { id: 'ly.img.action.navigationBar', key: 'export-webp-60', label: 'WebP (60% Quality)', icon: '@imgly/Save', onClick: exportWebp60 }, // Scaled export { id: 'ly.img.action.navigationBar', key: 'export-scaled', label: 'Scaled (1200×630)', icon: '@imgly/Save', onClick: exportScaled }, // Video exports { id: 'ly.img.action.navigationBar', key: 'export-video-web', label: 'Video 720p (2 Mbps)', icon: '@imgly/Video', onClick: exportVideoWeb }, { id: 'ly.img.action.navigationBar', key: 'export-video-hd', label: 'Video 1080p (8 Mbps)', icon: '@imgly/Video', onClick: exportVideoHD } ] } ]); ``` Each menu item triggers an export with specific compression options and displays the resulting file size via a notification. ## Compress Videos To compress video, use the `VideoExportOptions` structure in the export workflow. You can specify: - **Bitrate**: Mbps for video, kbps for audio - **Frame rate**: fps (frames per second) - **H.264 profile**: Compatibility and feature level - **Target resolution**: Output dimensions in pixels The example includes video export options in the dropdown menu. CE.SDK automatically displays a progress modal during video encoding. ```typescript highlight=highlight-video-export // Video export with web-optimized bitrate (720p, 2 Mbps) const exportVideoWeb = async () => { const blob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', videoBitrate: 2_000_000, audioBitrate: 128_000, framerate: 30, targetWidth: 1280, targetHeight: 720 }); downloadBlob(blob, 'export-web-720p.mp4'); cesdk.ui.showNotification({ message: `Video 720p: ${(blob.size / (1024 * 1024)).toFixed(1)} MB`, type: 'success' }); }; // Video export with HD bitrate (1080p, 8 Mbps) const exportVideoHD = async () => { const blob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', videoBitrate: 8_000_000, audioBitrate: 192_000, framerate: 30, targetWidth: 1920, targetHeight: 1080 }); downloadBlob(blob, 'export-hd-1080p.mp4'); cesdk.ui.showNotification({ message: `Video 1080p: ${(blob.size / (1024 * 1024)).toFixed(1)} MB`, type: 'success' }); }; ``` ### Choose Bitrate Values Adjust bitrate according to your use case: - **Web/social media clips**: 1–2 Mbps - **Downloadable HD video**: 8–12 Mbps - **Automatic optimization**: Set `videoBitrate` to `0` to let CE.SDK choose based on resolution and frame rate ### H.264 Profile Selection The H.264 profile and level determine encoder compatibility and features: - **Baseline**: Mobile-friendly playback - **Main**: Standard HD - **High**: Highest quality (desktop/professional workflows) ## Performance and Trade-Offs Higher compression produces smaller files but has trade-offs: - **Slower export speeds** with higher compression levels - **JPEG and WebP** are faster but can introduce visible artifacts (blurring, color banding) - **Video exports** are resource-consuming and depend on device CPU/GPU performance ### Check Export Limits The EditorAPI provides options to check available export limits before encoding: ```typescript const maxSize = engine.editor.getMaxExportSize(); const availableMemory = engine.editor.getAvailableMemory(); console.log("Max export size:", maxSize, "Memory:", availableMemory); ``` ## Real-World Compression Comparison (1080×1080) | Format | Setting | Avg. File Size | Encode Time | PSNR | Notes | | ------ | ------- | -------------- | ----------- | ---- | ----- | | **PNG** | Level 0 | ~1,450 KB | ~44 ms | ∞ (lossless) | Fastest, largest | | **PNG** | Level 5 | ~1,260 KB | ~61 ms | ∞ | Balanced speed and size | | **PNG** | Level 9 | ~1,080 KB | ~88 ms | ∞ | Smallest, slowest | | **JPEG** | Quality 95 | ~640 KB | ~24 ms | 43 dB | Near-lossless | | **JPEG** | Quality 80 | ~420 KB | ~20 ms | 39 dB | Good default for photos | | **JPEG** | Quality 60 | ~290 KB | ~17 ms | 35 dB | Some artifacts visible | | **WebP** | Quality 95 | ~510 KB | ~27 ms | 44 dB | Smaller than JPEG | | **WebP** | Quality 80 | ~350 KB | ~23 ms | 39 dB | Good web balance | | **WebP** | Lossless | ~830 KB | ~33 ms | ∞ | Smaller than PNG, keeps alpha | *PSNR > 40 dB ≈ visually lossless; 30–35 dB shows mild artifacts.* **Key Takeaways**: - **WebP** achieves 70–85% smaller files than PNG with high quality around 0.8 - **JPEG** performs well for photographs; use 0.8–0.9 for web/print, 0.6 for compact exports - **PNG** is essential for transparency; higher levels reduce size modestly at the cost of speed ## Practical Presets | Use Case | Format | Settings | Notes | | -------- | ------ | -------- | ----- | | **Web/Social Sharing** | JPEG/WebP | `jpegQuality: 0.8` | Balanced quality and size | | **Transparent Assets** | PNG/WebP | `pngCompressionLevel: 6` | Maintains transparency | | **Print/Archival** | PNG | `pngCompressionLevel: 9` | Best fidelity, large files | | **Video for Web** | MP4 | `videoBitrate: 2_000_000` | Smooth playback, small file | | **Video HD Download** | MP4 | `videoBitrate: 8_000_000` | Full HD quality | > **Note:** Consider showing users an **estimated file size** before export. It helps them make informed choices about quality vs. performance. ## Troubleshooting | Issue | Solution | | ----- | -------- | | File size not reduced | Use the correct option name (`jpegQuality`, `webpQuality`) | | JPEG quality too low | Raise quality to 0.9 or switch to PNG/WebP lossless | | Slow export | Lower the compression level—PNG level 5–6 is a good target | | Video not compressing | Set `videoBitrate` to a reasonable non-zero value | ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.export(blockId, options)` | Export a block with compression and format options | | `engine.block.exportVideo(blockId, options)` | Export a video with compression settings | | `engine.editor.getMaxExportSize()` | Get maximum export dimensions | | `engine.editor.getAvailableMemory()` | Get available memory for export | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats - [Export to PNG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/) - Full PNG export options and transparency handling - [Export to JPEG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-jpeg-6f88e9/) - JPEG-specific options for photographs - [Export to WebP](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-webp-aef6f4/) - WebP format with lossless and lossy modes - [Batch Processing](https://img.ly/docs/cesdk/angular/automation/batch-processing-ab2d18/) - Apply compression consistently in automated exports --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Options" description: "Explore export options, supported formats, and configuration features for sharing or rendering output." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) --- Export your designs to multiple formats including PNG, JPEG, WebP, PDF, and MP4. CE.SDK handles all export processing entirely on the client side, giving you fine-grained control over format-specific options like compression, quality, and target dimensions. ![Export overview showing different export format options](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-overview-browser/) Whether you're building a design tool, photo editor, or content automation workflow, understanding export options helps you deliver the right output for each use case. This guide covers supported formats, their options, and how to export programmatically or via the UI. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-overview-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Export Overview Guide * * This example demonstrates: * - Exporting designs to different formats (PNG, JPEG, WebP, PDF) * - Configuring export options (compression, quality, target size) * - Exporting with color masks for print workflows * - Downloading exported files to user device */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Load a template scene from a remote URL await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); // Get the page const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Helper function to download blob const downloadBlob = (blob: Blob, filename: string) => { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; anchor.click(); URL.revokeObjectURL(url); }; // Export to PNG with compression const exportToPng = async () => { const pngBlob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 5 // 0-9, higher = smaller file, slower }); downloadBlob(pngBlob, 'design.png'); cesdk.ui.showNotification({ message: `PNG exported (${(pngBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export to JPEG with quality setting const exportToJpeg = async () => { const jpegBlob = await engine.block.export(page, { mimeType: 'image/jpeg', jpegQuality: 0.9 // 0-1, higher = better quality, larger file }); downloadBlob(jpegBlob, 'design.jpg'); cesdk.ui.showNotification({ message: `JPEG exported (${(jpegBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export to WebP with lossless quality const exportToWebp = async () => { const webpBlob = await engine.block.export(page, { mimeType: 'image/webp', webpQuality: 1.0 // 1.0 = lossless, smaller files than PNG }); downloadBlob(webpBlob, 'design.webp'); cesdk.ui.showNotification({ message: `WebP exported (${(webpBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export to PDF const exportToPdf = async () => { const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true // Rasterize for broader viewer support }); downloadBlob(pdfBlob, 'design.pdf'); cesdk.ui.showNotification({ message: `PDF exported (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export with target size const exportWithTargetSize = async () => { const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1920, targetHeight: 1080 }); downloadBlob(blob, 'design-hd.png'); cesdk.ui.showNotification({ message: `HD export complete (${(blob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export with color mask - removes specified RGB color and creates alpha mask const exportWithColorMask = async () => { // Export with color mask - RGB values are in 0.0-1.0 range // Pure magenta (1.0, 0.0, 1.0) is commonly used for registration marks const [maskedImage, alphaMask] = await engine.block.exportWithColorMask( page, 1.0, // maskColorR - red component 0.0, // maskColorG - green component 1.0, // maskColorB - blue component (RGB: pure magenta) { mimeType: 'image/png' } ); downloadBlob(maskedImage, 'design-masked.png'); downloadBlob(alphaMask, 'design-alpha-mask.png'); cesdk.ui.showNotification({ message: `Color mask export: image (${(maskedImage.size / 1024).toFixed(1)} KB) + mask (${(alphaMask.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Configure navigation bar with export buttons cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', onClick: exportToPng, key: 'export-png', label: 'PNG', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportToJpeg, key: 'export-jpeg', label: 'JPEG', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportToWebp, key: 'export-webp', label: 'WebP', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportToPdf, key: 'export-pdf', label: 'PDF', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportWithTargetSize, key: 'export-hd', label: 'HD', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportWithColorMask, key: 'export-mask', label: 'Mask', icon: '@imgly/Save', variant: 'plain', color: 'accent' } ]); cesdk.ui.showNotification({ message: 'Use the export buttons to export in different formats', type: 'info', duration: 'infinite' }); } } export default Example; ``` This guide covers how to export designs in different formats, configure format-specific options, check device limits, and download exports to the user's device. ## Supported Export Formats CE.SDK supports exporting scenes, pages, groups, or individual blocks in these formats: | Format | MIME Type | Transparency | Best For | | ------ | --------- | ------------ | -------- | | PNG | `image/png` | Yes | Web graphics, UI elements, logos | | JPEG | `image/jpeg` | No | Photographs, web images | | WebP | `image/webp` | Yes (lossless) | Web delivery, smaller files | | PDF | `application/pdf` | Partial | Print, documents | | MP4 | `video/mp4` | No | Video content | | Binary | `application/octet-stream` | Yes | Raw data processing | Each format serves different purposes. PNG preserves transparency and works well for graphics with sharp edges or text. JPEG compresses photographs efficiently but drops transparency. WebP provides excellent compression with optional lossless mode. PDF preserves vector information for print workflows. MP4 exports animated content as video. ## Export Images ### Export to PNG PNG export uses lossless compression with a configurable compression level. Higher compression produces smaller files but takes longer to encode. Quality is not affected. ```typescript highlight-export-png const pngBlob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 5 // 0-9, higher = smaller file, slower }); ``` The `pngCompressionLevel` ranges from 0 (no compression, fastest) to 9 (maximum compression, slowest). The default is 5, which balances file size and encoding speed. ### Export to JPEG JPEG export uses lossy compression controlled by the quality setting. Lower quality produces smaller files but introduces visible artifacts. ```typescript highlight-export-jpeg const jpegBlob = await engine.block.export(page, { mimeType: 'image/jpeg', jpegQuality: 0.9 // 0-1, higher = better quality, larger file }); ``` The `jpegQuality` ranges from 0 to 1. Values above 0.9 provide excellent quality for most use cases. The default is 0.9. > **Caution:** JPEG drops transparency from exports. Transparent areas render with a solid background, which may produce unexpected results for designs relying on alpha channels. ### Export to WebP WebP provides better compression than PNG or JPEG for web delivery. A quality of 1.0 enables lossless mode. ```typescript highlight-export-webp const webpBlob = await engine.block.export(page, { mimeType: 'image/webp', webpQuality: 1.0 // 1.0 = lossless, smaller files than PNG }); ``` The `webpQuality` ranges from 0 to 1. At 1.0, WebP uses lossless compression that typically produces smaller files than equivalent PNG exports. ### Image Export Options | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `mimeType` | `string` | - | Output format: `'image/png'`, `'image/jpeg'`, or `'image/webp'` | | `pngCompressionLevel` | `number` | `5` | PNG compression level (0-9). Higher = smaller file, slower encoding | | `jpegQuality` | `number` | `0.9` | JPEG quality (0-1). Higher = better quality, larger file | | `webpQuality` | `number` | `0.8` | WebP quality (0-1). Set to 1.0 for lossless compression | | `targetWidth` | `number` | - | Target output width in pixels | | `targetHeight` | `number` | - | Target output height in pixels | ## Export PDF PDF export preserves vector information and supports print workflows. The high compatibility option rasterizes content for broader viewer support. ```typescript highlight-export-pdf const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true // Rasterize for broader viewer support }); ``` When `exportPdfWithHighCompatibility` is `true` (the default), images and effects are rasterized according to the scene's DPI setting. Set it to `false` for faster exports, though gradients with transparency may not render correctly in Safari or macOS Preview. The underlayer options are useful for print workflows where you need a solid base layer (often white ink) beneath the design elements. The `underlayerSpotColorName` should match a spot color defined in your print workflow. ### PDF Export Options | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `mimeType` | `string` | - | Must be `'application/pdf'` | | `exportPdfWithHighCompatibility` | `boolean` | `true` | Rasterize images and effects (like gradients) according to the scene's DPI setting for broader viewer support | | `exportPdfWithUnderlayer` | `boolean` | `false` | Add an underlayer behind existing elements matching the shape of page content | | `underlayerSpotColorName` | `string` | `''` | Spot color name for the underlayer fill (used with print workflows) | | `underlayerOffset` | `number` | `0` | Size adjustment for the underlayer shape in design units | | `targetWidth` | `number` | - | Target output width in pixels | | `targetHeight` | `number` | - | Target output height in pixels | ## Export with Color Mask Color mask export removes pixels matching a specific RGB color and generates two output files: the masked image with transparency applied, and an alpha mask showing which pixels were removed. ```typescript highlight-export-color-mask // Export with color mask - RGB values are in 0.0-1.0 range // Pure magenta (1.0, 0.0, 1.0) is commonly used for registration marks const [maskedImage, alphaMask] = await engine.block.exportWithColorMask( page, 1.0, // maskColorR - red component 0.0, // maskColorG - green component 1.0, // maskColorB - blue component (RGB: pure magenta) { mimeType: 'image/png' } ); ``` The `exportWithColorMask()` method accepts the block to export, three RGB color components (0.0-1.0 range), and optional export options. RGB values use floating-point notation where 1.0 equals 255 in standard color notation. Common mask colors for print workflows: - Pure red: `(1.0, 0.0, 0.0)` — Registration marks - Pure magenta: `(1.0, 0.0, 1.0)` — Distinctive marker color - Pure cyan: `(0.0, 1.0, 1.0)` — Alternative marker color The method returns a Promise resolving to an array of two Blobs: the masked image (with matched pixels made transparent) and the alpha mask (black pixels for removed areas, white for retained areas). > **Note:** Color matching is exact. Only pixels with RGB values precisely matching the specified color are removed. Anti-aliased edges or color variations will not be affected. ### Color Mask Export Options The `exportWithColorMask()` method accepts the same options as image export: | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `mimeType` | `string` | `'image/png'` | Output format: `'image/png'`, `'image/jpeg'`, or `'image/webp'` | | `pngCompressionLevel` | `number` | `5` | PNG compression level (0-9) | | `jpegQuality` | `number` | `0.9` | JPEG quality (0-1) | | `webpQuality` | `number` | `0.8` | WebP quality (0-1) | | `targetWidth` | `number` | - | Target output width in pixels | | `targetHeight` | `number` | - | Target output height in pixels | ## Export Video Video export uses the H.264 codec and outputs MP4 or QuickTime files. Unlike image exports, video exports accept a progress callback to track encoding status. ```typescript const page = engine.scene.getCurrentPage(); const videoBlob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', onProgress: (rendered, encoded, total) => { console.log(`Progress: ${Math.round((encoded / total) * 100)}%`); } }); ``` ### Video Export Options Configure video encoding with these options: | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `mimeType` | `'video/mp4'` | `'video/quicktime'` | `'video/mp4'` | Output video format | | `h264Profile` | `number` | `77` (Main) | H.264 profile: 66=Baseline, 77=Main, 100=High | | `h264Level` | `number` | `52` | Encoding level (multiply desired level by 10, e.g., 52 = level 5.2) | | `videoBitrate` | `number` | `0` (auto) | Video bitrate in bits/second. Maximum determined by profile and level | | `audioBitrate` | `number` | `0` (auto) | Audio bitrate in bits/second. Default auto-selects 128kbps for stereo AAC | | `framerate` | `number` | `30` | Target framerate in Hz | | `targetWidth` | `number` | - | Output width in pixels | | `targetHeight` | `number` | - | Output height in pixels | | `timeOffset` | `number` | `0` | Start time offset in seconds | | `duration` | `number` | scene duration | Video duration in seconds | | `allowTextOverhang` | `boolean` | `false` | Include text bounding boxes that account for glyph overhangs | | `abortSignal` | `AbortSignal` | - | Signal to cancel export | The `h264Profile` determines encoder quality and compatibility: - **Baseline (66)**: Broadest device compatibility, lowest quality - **Main (77)**: Good balance of quality and compatibility (default) - **High (100)**: Best quality, may not play on older devices > **Caution:** H.264 does not support transparency. Transparent areas render with a black background. ## Export Audio Export audio tracks from pages or audio blocks. Supported formats are WAV (uncompressed) and MP4 (AAC encoded). ```typescript const page = engine.scene.getCurrentPage(); const audioBlob = await engine.block.exportAudio(page, { mimeType: 'audio/mp4', // or 'audio/wav' onProgress: (rendered, encoded, total) => { console.log(`Progress: ${Math.round((encoded / total) * 100)}%`); } }); ``` ### Audio Export Options Configure audio export with these options: | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | `mimeType` | `'audio/wav'` | `'audio/mp4'` | `'audio/wav'` | Output audio format | | `sampleRate` | `number` | `48000` | Sample rate in Hz | | `numberOfChannels` | `number` | `2` | Number of audio channels (1=mono, 2=stereo) | | `timeOffset` | `number` | `0` | Start time offset in seconds | | `duration` | `number` | block duration | Audio duration in seconds | | `skipEncoding` | `boolean` | `false` | Return raw audio data without encoding | | `abortSignal` | `AbortSignal` | - | Signal to cancel export | Use `audio/wav` for lossless quality when file size is not a concern. Use `audio/mp4` (AAC) for compressed output suitable for web delivery. > **Note:** Audio export extracts and processes audio from all audio-capable blocks within the target block, including video fills with audio tracks and standalone audio blocks. ## Target Size Control You can export at specific dimensions regardless of the block's actual size. The `targetWidth` and `targetHeight` options render the block large enough to fill the target size while maintaining aspect ratio. ```typescript highlight-export-target-size const blob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1920, targetHeight: 1080 }); ``` If the target aspect ratio differs from the block's aspect ratio, the output fills the target dimensions completely. The output may extend beyond the target size on one axis to preserve correct proportions. ## Device Export Limits Before exporting large designs, check the device's export capabilities. Memory constraints or GPU limitations may prevent exports that exceed certain dimensions. ```typescript const maxExportSize = engine.editor.getMaxExportSize(); const availableMemory = engine.editor.getAvailableMemory(); console.log(`Max dimension: ${maxExportSize}px`); console.log(`Available memory: ${availableMemory / 1024 / 1024} MB`); ``` `getMaxExportSize()` returns the maximum width or height in pixels. Both dimensions must stay below this limit. `getAvailableMemory()` returns available memory in bytes, helping you assess whether large exports are feasible. > **Note:** The max export size is an upper bound. Exports may still fail due to memory constraints even when within size limits. For high-resolution exports, consider checking available memory first. ## Built-in Export Action CE.SDK provides a built-in `exportDesign` action that handles export with progress dialogs and error handling. Use `cesdk.utils.export()` to export and `cesdk.utils.downloadFile()` to download the result. Export an image: ```typescript const { blobs, options } = await cesdk.utils.export({ mimeType: 'image/png' }); await cesdk.utils.downloadFile(blobs[0], options.mimeType); ``` Export a PDF: ```typescript const { blobs, options } = await cesdk.utils.export({ mimeType: 'application/pdf' }); await cesdk.utils.downloadFile(blobs[0], options.mimeType); ``` Export a video: ```typescript const { blobs, options } = await cesdk.utils.export({ mimeType: 'video/mp4' }); await cesdk.utils.downloadFile(blobs[0], options.mimeType); ``` ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.export()` | Export a block with format and quality options | | `engine.block.exportWithColorMask()` | Export a block with specific RGB color removed, returning masked image and alpha mask | | `engine.block.exportVideo()` | Export a page as video with encoding options | | `engine.block.exportAudio()` | Export audio from a page or audio block | | `engine.editor.getMaxExportSize()` | Get maximum export dimension in pixels | | `engine.editor.getAvailableMemory()` | Get available memory in bytes | | `cesdk.utils.export()` | Export with progress dialog and error handling | | `cesdk.utils.downloadFile()` | Download a blob or string to the user's device | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Partial Export" description: "Documentation for Partial Export" platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) --- Export individual design elements, grouped blocks, or specific pages from your scene instead of exporting everything at once using CE.SDK's flexible export API. ![Partial Export example showing multiple blocks and groups being exported individually](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-partial-export-browser/) Understanding how to export specific parts of your scene gives you fine-grained control over output generation. Instead of exporting an entire scene, you can export individual images, text blocks, shapes, grouped elements, or specific pages. This is essential for creating asset libraries, implementing "export selection" features, or generating multiple outputs from a single design. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-partial-export-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Partial Export Guide * * This example demonstrates: * - Exporting individual design blocks * - Exporting grouped elements * - Exporting with different formats and options * - Understanding block hierarchy in exports */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a design scene using CE.SDK cesdk method await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page background to light gray const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Sample image URI for demonstrations const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Create design elements for demonstration // Create first image block const imageBlock1 = await engine.block.addImage(imageUri, { size: { width: blockWidth, height: blockHeight } }); engine.block.appendChild(page, imageBlock1); // Create second image block with different styling const imageBlock2 = await engine.block.addImage(imageUri, { size: { width: blockWidth, height: blockHeight }, cornerRadius: 20 }); engine.block.appendChild(page, imageBlock2); // Create a shape block const shapeBlock = engine.block.create('//ly.img.ubq/graphic'); const shape = engine.block.createShape('star'); engine.block.setShape(shapeBlock, shape); engine.block.setWidth(shapeBlock, blockWidth); engine.block.setHeight(shapeBlock, blockHeight); // Add a color fill to the shape const shapeFill = engine.block.createFill('color'); engine.block.setFill(shapeBlock, shapeFill); engine.block.setColor(shapeFill, 'fill/color/value', { r: 1.0, g: 0.7, b: 0.0, a: 1.0 }); engine.block.appendChild(page, shapeBlock); // Create two shapes for grouping demonstration const groupShape1 = engine.block.create('//ly.img.ubq/graphic'); const rect = engine.block.createShape('rect'); engine.block.setShape(groupShape1, rect); engine.block.setWidth(groupShape1, blockWidth * 0.4); engine.block.setHeight(groupShape1, blockHeight * 0.4); const groupFill1 = engine.block.createFill('color'); engine.block.setFill(groupShape1, groupFill1); engine.block.setColor(groupFill1, 'fill/color/value', { r: 0.3, g: 0.6, b: 0.9, a: 1.0 }); engine.block.appendChild(page, groupShape1); const groupShape2 = engine.block.create('//ly.img.ubq/graphic'); const ellipse = engine.block.createShape('ellipse'); engine.block.setShape(groupShape2, ellipse); engine.block.setWidth(groupShape2, blockWidth * 0.4); engine.block.setHeight(groupShape2, blockHeight * 0.4); const groupFill2 = engine.block.createFill('color'); engine.block.setFill(groupShape2, groupFill2); engine.block.setColor(groupFill2, 'fill/color/value', { r: 0.9, g: 0.3, b: 0.5, a: 1.0 }); engine.block.appendChild(page, groupShape2); // Group the two shapes together const group = engine.block.group([groupShape1, groupShape2]); // Position all blocks in grid layout for visualization const allBlocks = [ imageBlock1, imageBlock2, shapeBlock, group, groupShape1 // Note: groupShape1 is inside group, positioning group will position children ]; allBlocks.forEach((block, index) => { if (index < 6) { // Only position first 6 blocks (group contains 2) const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); } }); // Position grouped shapes relative to group const groupPos = getPosition(4); engine.block.setPositionX(group, groupPos.x); engine.block.setPositionY(group, groupPos.y); engine.block.setPositionX(groupShape1, 10); engine.block.setPositionY(groupShape1, 10); engine.block.setPositionX(groupShape2, 60); engine.block.setPositionY(groupShape2, 60); // Helper function: Download blob and show notification const downloadWithNotification = async ( blob: Blob, filename: string, mimeType: string, exportType: string ) => { await cesdk.utils.downloadFile(blob, mimeType as any); // Show notification after successful download cesdk.ui.showNotification({ message: `Export "${exportType}" completed`, type: 'info', duration: 'infinite' }); }; // Override exportDesign action to export selected block or page cesdk.actions.register('exportDesign', async () => { // eslint-disable-next-line no-console console.log('🚀 Export action triggered'); const selectedBlocks = engine.block.findAllSelected(); // eslint-disable-next-line no-console console.log(`📦 Selected blocks: ${selectedBlocks.length}`); let blockToExport: number; if (selectedBlocks.length > 0) { // Export first selected block (or group them if multiple) blockToExport = selectedBlocks.length === 1 ? selectedBlocks[0] : engine.block.group(selectedBlocks); // eslint-disable-next-line no-console console.log( `✅ Exporting selected block(s): ${ selectedBlocks.length === 1 ? 'single block' : 'grouped blocks' }` ); } else { // No selection - export current page const pages = engine.block.findByType('page'); blockToExport = pages[0]; // eslint-disable-next-line no-console console.log('📄 No selection - exporting current page'); } // eslint-disable-next-line no-console console.log(`📸 Exporting block ID: ${blockToExport}`); // Export the block with high compression const blob = await engine.block.export(blockToExport, { mimeType: 'image/png', pngCompressionLevel: 9 // Maximum compression for smaller file size }); // eslint-disable-next-line no-console console.log( `✨ Export complete - size: ${(blob.size / 1024).toFixed(2)} KB` ); // Download the blob await downloadWithNotification(blob, 'export.png', 'image/png', 'Design'); // eslint-disable-next-line no-console console.log('💾 Download complete'); }); // Helper function: Export individual block const exportIndividualBlock = async () => { // eslint-disable-next-line no-console console.log('🚀 Starting individual block export...'); // Show loading dialog before export const exportDialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Block', message: 'Processing export...', progress: 'indeterminate' }); // Find a specific block to export const blockToExport = imageBlock1; // Export the block as PNG with high compression and target size const individualBlob = await engine.block.export(blockToExport, { mimeType: 'image/png', pngCompressionLevel: 9, // Maximum compression for smaller file size targetWidth: 800, // Limit export resolution for faster exports targetHeight: 600 }); // eslint-disable-next-line no-console console.log( `✅ Individual block exported - size: ${( individualBlob.size / 1024 ).toFixed(2)} KB` ); // Close the export dialog exportDialog.close(); // Download the exported block await downloadWithNotification( individualBlob, 'block-export.png', 'image/png', 'Block' ); }; // Helper function: Create and export a group const exportGroupExample = async () => { // eslint-disable-next-line no-console console.log('🚀 Starting group export...'); // Show loading dialog before export const exportDialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Group', message: 'Processing export...', progress: 'indeterminate' }); // Group the blocks together (shapes already created above) const exportGroup = engine.block.group([groupShape1, groupShape2]); // Export the group (includes all children) with high compression and target size const groupBlob = await engine.block.export(exportGroup, { mimeType: 'image/png', pngCompressionLevel: 9, // Maximum compression for smaller file size targetWidth: 800, // Limit export resolution for faster exports targetHeight: 600 }); // eslint-disable-next-line no-console console.log( `✅ Group exported - size: ${(groupBlob.size / 1024).toFixed(2)} KB` ); // Close the export dialog exportDialog.close(); // Download the exported group await downloadWithNotification( groupBlob, 'group-export.png', 'image/png', 'Group' ); }; // Helper function: Export current page const exportCurrentPage = async () => { // Check export limits before exporting const maxExportSize = engine.editor.getMaxExportSize(); const availableMemory = engine.editor.getAvailableMemory(); // eslint-disable-next-line no-console console.log('🚀 Starting page export...'); // eslint-disable-next-line no-console console.log( `📊 Export limits - Max size: ${maxExportSize}px, Available memory: ${availableMemory} bytes` ); // Show loading dialog before export const exportDialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Page', message: 'Processing export...', progress: 'indeterminate' }); // Export the entire page with high compression and target size const pageBlob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9, // Maximum compression for smaller file size targetWidth: 800, // Limit export resolution for faster exports targetHeight: 600 }); // eslint-disable-next-line no-console console.log( `✅ Page exported - size: ${(pageBlob.size / 1024).toFixed(2)} KB` ); // Close the export dialog exportDialog.close(); // Download the exported page await downloadWithNotification( pageBlob, 'page-export.png', 'image/png', 'Page' ); }; // Configure navigation bar layout cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', onClick: async () => await exportCurrentPage(), key: 'export-page', label: 'Export Page', icon: '@imgly/Save', variant: 'plain', color: 'accent' }, { id: 'ly.img.action.navigationBar', onClick: async () => await exportGroupExample(), key: 'export-group', label: 'Export Group', icon: '@imgly/Group', color: 'accent' }, { id: 'ly.img.action.navigationBar', onClick: async () => await exportIndividualBlock(), key: 'export-block', label: 'Export Block', icon: '@imgly/Image', variant: 'plain', color: 'accent' } ]); // Show notification to guide users cesdk.ui.showNotification({ message: 'Use the export buttons on the right to try different export options (Export Page, Export Group, Export Block)', type: 'info', duration: 'infinite' }); // eslint-disable-next-line no-console console.log('Partial export examples initialized successfully'); } } export default Example; ``` This guide covers how to export individual blocks, grouped elements, and pages programmatically using the Block API. ## Understanding Block Hierarchy and Export ### How Block Hierarchy Affects Exports CE.SDK organizes content in a tree structure: Scene → Pages → Groups → Individual Blocks. When you export a block, the export automatically includes all child elements in the hierarchy. Exporting a page exports every element on that page. Exporting a group exports all blocks within that group. Exporting an individual block (like an image or text) exports only that specific element. This hierarchical behavior is powerful because you can control export scope by choosing which level of the hierarchy to target. Want just one image? Export the image block. Want a complete layout section? Export the parent group. ### Export Behavior The export API applies several transformations to ensure consistent output. If the exported block itself is rotated, it will be exported without that rotation—the content appears upright in the output file. Any margin set on the block is included in the export bounds. Outside strokes are included for most block types, though pages handle strokes differently. > **Note:** Only blocks that belong to the scene hierarchy can be exported. Orphaned blocks > (created but not added to the page) cannot be exported until they're attached > to the scene tree. ## Exporting Individual Blocks ### Finding Blocks to Export Before exporting, we need to identify which block to export. We can find blocks by type using `findByType`, by name if you've assigned custom names, or by ID if you already have a reference. Once we have a block reference, exporting is straightforward. Pass the block ID to `engine.block.export()` along with export options like format and quality settings. ### Basic Block Export Here we export a single image block as PNG with compression settings. The export returns a Blob containing the image data. ```typescript highlight-export-individual-block // eslint-disable-next-line no-console console.log('🚀 Starting individual block export...'); // Show loading dialog before export const exportDialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Block', message: 'Processing export...', progress: 'indeterminate' }); // Find a specific block to export const blockToExport = imageBlock1; // Export the block as PNG with high compression and target size const individualBlob = await engine.block.export(blockToExport, { mimeType: 'image/png', pngCompressionLevel: 9, // Maximum compression for smaller file size targetWidth: 800, // Limit export resolution for faster exports targetHeight: 600 }); // eslint-disable-next-line no-console console.log( `✅ Individual block exported - size: ${( individualBlob.size / 1024 ).toFixed(2)} KB` ); // Close the export dialog exportDialog.close(); ``` The `mimeType` determines the output format. CE.SDK supports PNG, JPEG, WEBP, and PDF for static exports. Each format has specific options—PNG uses `pngCompressionLevel`, JPEG uses `jpegQuality`, and WEBP uses `webpQuality`. Different formats serve different purposes. PNG is ideal for graphics requiring transparency, such as UI elements, logos, or illustrations with alpha channels. JPEG works well for photographs where file size matters and transparency isn't needed. WEBP provides better compression than PNG or JPEG for web delivery. PDF preserves vector information and is suited for print workflows. JPEG exports drop transparency and replace it with a solid background, which may produce unexpected results if your design relies on transparency. Always consider whether your content needs an alpha channel when choosing export formats. ## Exporting Grouped Elements ### Creating and Exporting Groups Groups let you export multiple elements together as a single output. This is useful for composite graphics like logos with multiple components, complex illustrations made from many shapes, or layout sections that should be exported as a unit. ```typescript highlight-create-and-export-group // eslint-disable-next-line no-console console.log('🚀 Starting group export...'); // Show loading dialog before export const exportDialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Group', message: 'Processing export...', progress: 'indeterminate' }); // Group the blocks together (shapes already created above) const exportGroup = engine.block.group([groupShape1, groupShape2]); // Export the group (includes all children) with high compression and target size const groupBlob = await engine.block.export(exportGroup, { mimeType: 'image/png', pngCompressionLevel: 9, // Maximum compression for smaller file size targetWidth: 800, // Limit export resolution for faster exports targetHeight: 600 }); // eslint-disable-next-line no-console console.log( `✅ Group exported - size: ${(groupBlob.size / 1024).toFixed(2)} KB` ); // Close the export dialog exportDialog.close(); ``` When you export a group, CE.SDK renders all children together into a single image. The group's bounding box determines the export dimensions, and relative positioning between children is preserved exactly as designed. ### Exporting Selected Elements A common workflow is allowing users to select elements and export their selection. Use `findAllSelected()` to get selected blocks, group them temporarily, and export the group. ```typescript // Get currently selected blocks const selectedBlocks = engine.block.findAllSelected(); if (selectedBlocks.length === 0) { console.log('No blocks selected'); } else if (selectedBlocks.length === 1) { // Export single block directly const blob = await engine.block.export(selectedBlocks[0], { mimeType: 'image/png' }); } else { // Group and export multiple selected blocks const group = engine.block.group(selectedBlocks); const blob = await engine.block.export(group, { mimeType: 'image/png' }); } ``` This pattern enables "Export Selection" functionality in design tools, letting users export precisely what they've chosen without exporting the entire canvas. ## Exporting Pages When working with multi-page documents, you often want to export pages individually rather than as a complete scene. Exporting the page block directly gives you output for that specific page. ```typescript highlight-export-current-page // Check export limits before exporting const maxExportSize = engine.editor.getMaxExportSize(); const availableMemory = engine.editor.getAvailableMemory(); // eslint-disable-next-line no-console console.log('🚀 Starting page export...'); // eslint-disable-next-line no-console console.log( `📊 Export limits - Max size: ${maxExportSize}px, Available memory: ${availableMemory} bytes` ); // Show loading dialog before export const exportDialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Page', message: 'Processing export...', progress: 'indeterminate' }); // Export the entire page with high compression and target size const pageBlob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9, // Maximum compression for smaller file size targetWidth: 800, // Limit export resolution for faster exports targetHeight: 600 }); // eslint-disable-next-line no-console console.log( `✅ Page exported - size: ${(pageBlob.size / 1024).toFixed(2)} KB` ); // Close the export dialog exportDialog.close(); ``` Page exports include everything on the page—the background, all content blocks, and any page-level effects. The page dimensions determine the output size unless you specify `targetWidth` and `targetHeight` to override the dimensions. ## Export Options and Configuration ### Target Size Control Sometimes you need exports at specific dimensions regardless of the block's actual size. The `targetWidth` and `targetHeight` options render the block large enough to fill the target size while maintaining aspect ratio. If you specify a target size that doesn't match the block's aspect ratio, CE.SDK ensures the block fills the target dimensions completely. The output may extend beyond the target size on one axis to preserve the correct proportions—no stretching or distortion occurs. ### Quality and Compression Each export format offers quality controls that balance output size against visual fidelity. For PNG, `pngCompressionLevel` ranges from 0 (no compression, fastest) to 9 (maximum compression, slowest). Higher compression takes longer but produces smaller files without affecting image quality—PNG is lossless. JPEG `jpegQuality` ranges from 0 (lowest quality) to 1 (highest quality). Lower values create smaller files but introduce visible artifacts. Values above 0.9 provide excellent quality for most use cases. WEBP `webpQuality` also ranges from 0 to 1. A value of 1.0 triggers a special lossless mode that often produces smaller files than equivalent PNG exports. ### Export Size Limits Before exporting large blocks or requesting high target dimensions, check the platform's export capabilities. `getMaxExportSize()` returns the maximum width or height in pixels that can be exported. Both the width and height of your export must be below or equal to this limit. However, memory constraints may prevent exports that technically fit within size limits—use `getAvailableMemory()` to assess available memory before attempting large exports. ## Export Limitations and Considerations ### Format-Specific Constraints JPEG drops transparency from the final image, replacing transparent pixels with a solid background (usually white or black depending on implementation). This can cause unexpected results when exporting designs that rely on alpha channels. Always use PNG or WEBP if transparency is required. PDF export behavior depends on the `exportPdfWithHighCompatibility` option. When set to `true`, images and effects are rasterized according to the scene's DPI setting for broader viewer compatibility. When `false`, PDFs export faster by embedding images directly, but gradients with transparency may not render correctly in Safari or macOS Preview. See the [PDF export guide](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) for detailed performance guidance. ### Performance Considerations Exporting is a synchronous operation that blocks the main thread while rendering. For large exports or multiple sequential exports, provide user feedback like progress indicators to prevent the interface from appearing frozen. Batch exports can be optimized by processing blocks in parallel where possible, though be mindful of memory constraints. Exporting dozens of high-resolution images simultaneously may exhaust available memory. Consider batching in smaller groups with delays between batches. ### Hierarchy Requirements Only blocks attached to the scene hierarchy can be exported. If you create a block but don't append it to a page, export will fail. Always ensure blocks are children of the page (or nested within groups that are children of the page) before attempting export. ## API Reference | Method | Description | | --- | --- | | `engine.block.export()` | Export a block with specified format and quality options | | `engine.block.findByType()` | Find blocks by type identifier | | `engine.block.group()` | Group multiple blocks into a single logical unit | | `engine.scene.getPages()` | Get all pages in the current scene | | `engine.editor.getMaxExportSize()` | Get maximum export dimension in pixels | | `engine.editor.getAvailableMemory()` | Get available engine memory in bytes | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Size Limits" description: "Configure and understand CE.SDK's image and video size limits for both input and export to optimize performance and memory usage." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) --- Configure size limits to balance quality and performance in CE.SDK applications. ![Size Limits example showing CE.SDK with maxImageSize configuration](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-size-limits-browser/) CE.SDK processes images and videos client-side, which means size limits depend on the user's GPU capabilities and available memory. Understanding and configuring these limits helps you build applications that deliver high-quality results while maintaining smooth performance across different devices. ```typescript file=@cesdk_web_examples/guides-export-save-publish-size-limits-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Add export image action to navigation bar cesdk.ui.insertOrderComponent( { in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: ['ly.img.exportImage.navigationBar'] } ); const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // ===== Section 1: Reading Current maxImageSize ===== // Get the current maxImageSize setting const currentMaxSize = engine.editor.getSetting('maxImageSize'); console.log('Current maxImageSize:', currentMaxSize); // Default is 4096 pixels // ===== Section 2: Setting Different maxImageSize Values ===== // Configure maxImageSize for different use cases // This must be set BEFORE loading images to ensure they're downscaled // Low memory devices (mobile, tablets) - use 2048 for safety engine.editor.setSetting('maxImageSize', 2048); // High quality (professional workflows, desktop) // engine.editor.setSetting('maxImageSize', 8192); console.log( 'Updated maxImageSize:', engine.editor.getSetting('maxImageSize') ); // ===== Section 3: Observing Settings Changes ===== // Subscribe to settings changes to update UI when maxImageSize changes engine.editor.onSettingsChanged(() => { const newMaxSize = engine.editor.getSetting('maxImageSize'); console.log('maxImageSize changed to:', newMaxSize); // In a real app, update UI here to reflect the new setting }); // The subscription returns an unsubscribe function if you need to clean up later // const unsubscribe = engine.editor.onSettingsChanged(() => { ... }); // unsubscribe(); // Call when no longer needed // ===== Section 4: GPU Capability Detection (Web) ===== // Query GPU max texture size to understand export limits const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (gl) { const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); console.log('GPU Max Texture Size:', maxTextureSize); console.log( 'Safe export dimensions: up to', maxTextureSize, '×', maxTextureSize ); // Most modern GPUs support 4096×4096 to 16384×16384 // Safe baseline is 4096×4096 for universal compatibility } // ===== Section 5: Pre-Export Size Validation ===== // Calculate actual export dimensions including all content // Get bounding box of all content to check actual export size const allBlocks = engine.block.findByType('//ly.img.ubq/graphic'); let maxRight = 0; let maxBottom = 0; allBlocks.forEach((blockId) => { const x = engine.block.getPositionX(blockId); const y = engine.block.getPositionY(blockId); const width = engine.block.getWidth(blockId); const height = engine.block.getHeight(blockId); maxRight = Math.max(maxRight, x + width); maxBottom = Math.max(maxBottom, y + height); }); const exportWidth = Math.max(engine.block.getWidth(page), maxRight); const exportHeight = Math.max(engine.block.getHeight(page), maxBottom); console.log('Export dimensions:', exportWidth, '×', exportHeight); if (gl) { const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); // Use conservative limit (50% of max) for actual VRAM availability const safeTextureSize = Math.floor(maxTextureSize * 0.5); if (exportWidth > safeTextureSize || exportHeight > safeTextureSize) { cesdk.ui.showNotification({ type: 'warning', message: `Export dimensions (${Math.round(exportWidth)}×${Math.round( exportHeight )}) exceed safe GPU limit (${safeTextureSize}×${safeTextureSize})` }); } else { cesdk.ui.showNotification({ type: 'success', message: 'Export dimensions are within safe limits' }); } } // ===== Section 6: Handling Export Errors ===== // Demonstrate proper error handling for size-related export failures try { // Example export operation (not actually exporting in this demo) // const blob = await engine.block.export(page, 'image/png'); // If export fails, catch and handle the error console.log('Export would proceed here with proper error handling'); } catch (error) { console.error('Export failed:', error); // Check if error is size-related if ( error instanceof Error && (error.message.includes('texture') || error.message.includes('size') || error.message.includes('memory')) ) { console.error('Size-related export error detected'); console.error('Suggested remediation:'); console.error('1. Reduce output dimensions'); console.error('2. Decrease maxImageSize setting'); console.error('3. Use export compression options'); } } // Add an image to the page for demonstration // Note: NOT specifying size here - let maxImageSize control the texture loading const imageBlock = await engine.block.addImage(imageUri); engine.block.appendChild(page, imageBlock); // Fit image to page dimensions engine.block.setWidth(imageBlock, 800); engine.block.setHeight(imageBlock, 600); // Position image to fill the page (already matches page dimensions) engine.block.setPositionX(imageBlock, 0); engine.block.setPositionY(imageBlock, 0); // Zoom to fit the content engine.scene.zoomToBlock(page, { padding: 40 }); // Display information in console console.log('=== Size Limits Configuration Summary ==='); console.log( 'Current maxImageSize:', engine.editor.getSetting('maxImageSize') ); console.log( 'Page dimensions:', engine.block.getWidth(page), '×', engine.block.getHeight(page) ); console.log( 'Image dimensions:', engine.block.getWidth(imageBlock), '×', engine.block.getHeight(imageBlock) ); if (gl) { const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); console.log('GPU max texture size:', maxTextureSize); } } } export default Example; ``` This guide covers how to configure the `maxImageSize` setting, query GPU capabilities, validate export dimensions, and handle size-related errors gracefully. ## Understanding Size Limits CE.SDK manages size limits at two stages: **input** (when loading images) and **output** (when exporting). The `maxImageSize` setting controls input resolution, automatically downscaling images that exceed the configured limit (default: 4096×4096px). This reduces memory usage and improves performance across devices. Export resolution has no artificial limits—the theoretical maximum is 16,384×16,384 pixels, constrained only by GPU texture size, available VRAM, and platform capabilities (WebGL/WebGPU on web, Metal/OpenGL on native). ## Resolution & Duration Limits ## Configuring maxImageSize You can read and modify the `maxImageSize` setting using the Settings API to match your application's requirements and target hardware capabilities. ### Reading the Current Setting To check what `maxImageSize` value is currently configured: ```typescript highlight-get-max-image-size // Get the current maxImageSize setting const currentMaxSize = engine.editor.getSetting('maxImageSize'); console.log('Current maxImageSize:', currentMaxSize); // Default is 4096 pixels ``` This returns the maximum size in pixels as an integer value (e.g., `4096` for the default 4096×4096 limit). You might display this value in your UI to inform users about the current quality settings, or use it to make runtime decisions about asset loading strategies. ### Setting a New Value Configure `maxImageSize` to minimize memory usage on constrained devices: ```typescript highlight-set-max-image-size // Configure maxImageSize for different use cases // This must be set BEFORE loading images to ensure they're downscaled // Low memory devices (mobile, tablets) - use 2048 for safety engine.editor.setSetting('maxImageSize', 2048); // High quality (professional workflows, desktop) // engine.editor.setSetting('maxImageSize', 8192); console.log( 'Updated maxImageSize:', engine.editor.getSetting('maxImageSize') ); ``` The setting takes effect immediately for newly loaded images. Images already loaded on the canvas retain their current resolution until reloaded. ### Observing Settings Changes Subscribe to setting changes to update your UI when `maxImageSize` is modified: ```typescript highlight-observe-settings-changes // Subscribe to settings changes to update UI when maxImageSize changes engine.editor.onSettingsChanged(() => { const newMaxSize = engine.editor.getSetting('maxImageSize'); console.log('maxImageSize changed to:', newMaxSize); // In a real app, update UI here to reflect the new setting }); // The subscription returns an unsubscribe function if you need to clean up later // const unsubscribe = engine.editor.onSettingsChanged(() => { ... }); // unsubscribe(); // Call when no longer needed ``` This callback fires whenever any setting changes through the Settings API. You can use it to update quality indicators in your interface, recalculate memory estimates, or trigger asset reloading with the new size limit. ## GPU Capability Detection Modern browsers expose GPU capabilities through WebGL, allowing you to determine safe export dimensions for the user's hardware. ```typescript highlight-query-gpu-capabilities // Query GPU max texture size to understand export limits const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl2') || canvas.getContext('webgl'); if (gl) { const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); console.log('GPU Max Texture Size:', maxTextureSize); console.log( 'Safe export dimensions: up to', maxTextureSize, '×', maxTextureSize ); // Most modern GPUs support 4096×4096 to 16384×16384 // Safe baseline is 4096×4096 for universal compatibility } ``` The `MAX_TEXTURE_SIZE` parameter returns the maximum width or height (in pixels) for a texture on the current GPU. Most modern GPUs support 4096×4096 to 16384×16384, while older or integrated GPUs may be limited to smaller dimensions. You can use this information to: - Set conservative `maxImageSize` defaults based on detected capabilities - Show warnings when users attempt exports that may exceed hardware limits - Provide quality presets that match the device's capabilities - Calculate safe export dimensions automatically ## Troubleshooting Common issues and solutions when working with size limits: | Issue | Cause | Solution | | ------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------- | | Images appear blurry/low quality | `maxImageSize` too low | Increase `maxImageSize` if GPU supports it (query `MAX_TEXTURE_SIZE` first) | | Out of memory errors during editing | `maxImageSize` too high | Decrease `maxImageSize` to reduce memory footprint | | Export fails with no error message | Output exceeds GPU texture limit | Reduce export dimensions or query `MAX_TEXTURE_SIZE` to set safe maximums | | Video export fails | Resolution/duration too high | Export at 1080p instead of 4K, or reduce video duration to under 2 minutes | | Inconsistent results across devices | Different GPU capabilities | Set conservative `maxImageSize` (4096) or detect capabilities programmatically | | Images load slowly | High `maxImageSize` on slow hardware | Lower `maxImageSize` to reduce processing time, especially on mobile devices | | Export succeeds but file is too large | No compression applied | Use JPEG format with quality settings, or apply PNG compression options | ## API Reference Core methods for managing size limits and export operations: | Method | Description | | ------------------------------- | --------------------------------------- | | `engine.editor.getSetting()` | Retrieves the current value of a setting | | `engine.editor.setSetting()` | Updates a setting value | | `engine.editor.onSettingsChanged()` | Subscribes to setting change events | | `engine.block.export()` | Exports a block as an image or video | | `engine.block.getWidth()` | Gets the width of a block in pixels | | `engine.block.getHeight()` | Gets the height of a block in pixels | ## Next Steps Explore related guides to build complete export workflows: - [Settings Guide](https://img.ly/docs/cesdk/angular/settings-970c98/) - Complete Settings API reference and configuration options - [File Format Support](https://img.ly/docs/cesdk/angular/file-format-support-3c4b2a/) - Supported image and video formats with capabilities - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Fundamentals of exporting images and videos from CE.SDK - [Export to PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) - PDF export guide with multi-page support and print optimization --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To JPEG" description: "Export CE.SDK designs to JPEG format with configurable quality settings for photographs, web images, and social media content." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/to-jpeg-6f88e9/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [To JPEG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-jpeg-6f88e9/) --- Export CE.SDK designs to JPEG format—ideal for photographs, social media, and web content where file size matters more than transparency. ![Export to JPEG](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-jpeg-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-jpeg-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-to-jpeg-browser/) JPEG uses lossy compression optimized for photographs and smooth color gradients. Unlike PNG, JPEG does not support transparency—transparent areas render with a solid background. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-jpeg-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) throw new Error('CE.SDK instance is required'); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage()!; // Zoom to fit page in view await engine.scene.zoomToBlock(page); cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', onClick: async () => { await cesdk.actions.run('exportDesign', { mimeType: 'image/jpeg', jpegQuality: 0.9 }); }, key: 'export-action', label: 'Export', icon: '@imgly/Download', }, { id: 'ly.img.action.navigationBar', onClick: async () => { const currentPage = engine.scene.getCurrentPage()!; const exported = await engine.block.export(currentPage, { mimeType: 'image/jpeg', jpegQuality: 0.9 }); await cesdk.utils.downloadFile(exported, 'image/jpeg'); cesdk.ui.showNotification({ message: `Standard (${(exported.size / 1024).toFixed(0)} KB)`, type: 'success' }); }, key: 'export-standard', label: 'Standard', icon: '@imgly/Save' }, { id: 'ly.img.action.navigationBar', onClick: async () => { const currentPage = engine.scene.getCurrentPage()!; const exported = await engine.block.export(currentPage, { mimeType: 'image/jpeg', jpegQuality: 1.0 }); await cesdk.utils.downloadFile(exported, 'image/jpeg'); cesdk.ui.showNotification({ message: `High Quality (${(exported.size / 1024).toFixed(0)} KB)`, type: 'success' }); }, key: 'export-high', label: 'High Quality', icon: '@imgly/Save' }, { id: 'ly.img.action.navigationBar', onClick: async () => { const currentPage = engine.scene.getCurrentPage()!; const exported = await engine.block.export(currentPage, { mimeType: 'image/jpeg', targetWidth: 1920, targetHeight: 1080 }); await cesdk.utils.downloadFile(exported, 'image/jpeg'); cesdk.ui.showNotification({ message: `1920×1080 (${(exported.size / 1024).toFixed(0)} KB)`, type: 'success' }); }, key: 'export-hd', label: '1920×1080', icon: '@imgly/Save' } ] }); cesdk.actions.register('exportDesign', async () => { const currentPage = engine.scene.getCurrentPage()!; const jpeg = await engine.block.export(currentPage, { mimeType: 'image/jpeg', jpegQuality: 0.9 }); await cesdk.utils.downloadFile(jpeg, 'image/jpeg'); }); } } export default Example; ``` This guide covers exporting to JPEG, configuring quality and dimensions, and integrating CE.SDK's built-in export action. ## Export to JPEG Export a design block to JPEG by calling `engine.block.export()` with `mimeType: 'image/jpeg'`. Use `cesdk.utils.downloadFile()` to trigger a browser download. ```typescript highlight=highlight-export-jpeg const exported = await engine.block.export(currentPage, { mimeType: 'image/jpeg', jpegQuality: 0.9 }); await cesdk.utils.downloadFile(exported, 'image/jpeg'); ``` The `jpegQuality` parameter accepts values from greater than 0 to 1. Higher values produce better quality at larger file sizes. The default is 0.9. ## Export Options JPEG export supports these configuration options: | Option | Type | Default | Description | |--------|------|---------|-------------| | `mimeType` | `string` | `'image/png'` | Set to `'image/jpeg'` for JPEG | | `jpegQuality` | `number` | `0.9` | Quality from >0 to 1 | | `targetWidth` | `number` | — | Output width in pixels | | `targetHeight` | `number` | — | Output height in pixels | ### Quality Control Set `jpegQuality` to 1.0 for maximum quality with minimal compression artifacts. This is useful for archival or print preparation. ```typescript highlight=highlight-export-quality const exported = await engine.block.export(currentPage, { mimeType: 'image/jpeg', jpegQuality: 1.0 }); await cesdk.utils.downloadFile(exported, 'image/jpeg'); ``` For web delivery, values around 0.8 balance quality and file size effectively. ### Target Dimensions Specify `targetWidth` and `targetHeight` to export at exact dimensions. The output fills the target size while maintaining aspect ratio. ```typescript highlight=highlight-export-size const exported = await engine.block.export(currentPage, { mimeType: 'image/jpeg', targetWidth: 1920, targetHeight: 1080 }); await cesdk.utils.downloadFile(exported, 'image/jpeg'); ``` ## Built-in Export Action CE.SDK includes a built-in export button you can add to the navigation bar. This provides a familiar export experience without additional code. ```typescript highlight=highlight-builtin-action { id: 'ly.img.action.navigationBar', onClick: async () => { await cesdk.actions.run('exportDesign', { mimeType: 'image/jpeg', jpegQuality: 0.9 }); }, key: 'export-action', label: 'Export', icon: '@imgly/Download', }, ``` The button triggers the `exportDesign` action when clicked. You can customize what happens by registering your own handler. ### Customize Export Behavior Override the `exportDesign` action to control the export format, quality, and download behavior. ```typescript highlight=highlight-custom-action cesdk.actions.register('exportDesign', async () => { const currentPage = engine.scene.getCurrentPage()!; const jpeg = await engine.block.export(currentPage, { mimeType: 'image/jpeg', jpegQuality: 0.9 }); await cesdk.utils.downloadFile(jpeg, 'image/jpeg'); }); ``` When you register `exportDesign`, the built-in export button uses your custom handler instead of the default. ## When to Use JPEG JPEG works well for: - Photographs and images with gradual color transitions - Social media posts and web content - Scenarios where file size matters more than perfect quality > **Note:** For graphics with sharp edges, text, or transparency, use PNG instead. For modern web delivery with better compression, consider WebP. ## Troubleshooting **Output looks blurry** — Increase `jpegQuality` toward 1.0, or use PNG for graphics with hard edges. **File size too large** — Decrease `jpegQuality` to 0.7–0.8, or reduce dimensions with `targetWidth` and `targetHeight`. **Unexpected background** — JPEG does not support transparency. Use PNG or WebP for transparent content. ## API Reference | Method | Description | |--------|-------------| | `engine.block.export(block, options)` | Export a block to the specified format | | `cesdk.utils.downloadFile(blob, mimeType)` | Trigger browser download | | `cesdk.actions.register(name, handler)` | Register or override an action | | `cesdk.ui.insertOrderComponent()` | Add components to navigation bar | | `engine.scene.zoomToBlock(block)` | Zoom camera to fit a block | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) — Compare all available export formats - [Export to PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) — Export for print and document workflows - [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) — Export specific regions or elements - [Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) — Handle large exports and memory constraints --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To MP4" description: "Export video compositions as MP4 files with configurable encoding options, progress tracking, and resolution control." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/to-mp4-c998a8/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [To MP4](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-mp4-c998a8/) --- Export your video compositions as MP4 files with H.264 encoding, progress tracking, and configurable quality settings. ![Export to MP4 hero image](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-mp4-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-mp4-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-to-mp4-browser/) MP4 is the most widely supported video format, using H.264 encoding for efficient compression. CE.SDK handles frame rendering, encoding, and audio muxing entirely client-side, giving you control over quality and file size. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-mp4-browser/browser.ts reference-only import type CreativeEditorSDK from '@cesdk/cesdk-js'; import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; cesdk.feature.enable('ly.img.video'); await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.video.template/templates/milli-surf-school.scene' ); const page = engine.scene.getCurrentPage(); if (!page) throw new Error('No page found'); await cesdk.actions.run('zoom.toPage', { autoFit: true }); // Setup export functionality await this.setupExportActions(cesdk, page); } private async setupExportActions( cesdk: CreativeEditorSDK, page: number ): Promise { const engine = cesdk.engine; // Add export buttons to navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-builtin', label: 'Export Video', icon: '@imgly/Save', onClick: () => { cesdk.actions.run('exportDesign', { mimeType: 'video/mp4' }); } }, { id: 'ly.img.action.navigationBar', key: 'export-video', label: 'Export MP4', icon: '@imgly/Save', onClick: async () => { const dialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Video', message: 'Encoding MP4...', progress: 0 }); try { const blob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', onProgress: (_, encoded, total) => { dialog.updateProgress({ value: encoded, max: total }); } }); dialog.close(); await cesdk.utils.downloadFile(blob, 'video/mp4'); } catch (error) { dialog.showError({ message: 'Export failed' }); throw error; } } }, { id: 'ly.img.action.navigationBar', key: 'export-video-progress', label: 'Export (dialog)', icon: '@imgly/Save', onClick: async () => { const dialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Video', message: 'Encoding MP4...', progress: 0 }); try { const blob = await engine.block.exportVideo(page, { onProgress: (_, encoded, total) => { dialog.updateProgress({ value: encoded, max: total }); } }); dialog.showSuccess({ message: 'Export complete!' }); await cesdk.utils.downloadFile(blob, 'video/mp4'); } catch (error) { dialog.showError({ message: 'Export failed' }); throw error; } } }, { id: 'ly.img.action.navigationBar', key: 'export-video-hd', label: 'Export HD', icon: '@imgly/Save', onClick: async () => { const dialog = cesdk.utils.showLoadingDialog({ title: 'Exporting HD Video', message: 'Encoding 1080p...', progress: 0 }); try { const blob = await engine.block.exportVideo(page, { targetWidth: 1920, targetHeight: 1080, framerate: 30, onProgress: (_, encoded, total) => { dialog.updateProgress({ value: encoded, max: total }); } }); dialog.close(); await cesdk.utils.downloadFile(blob, 'video/mp4'); } catch (error) { dialog.showError({ message: 'Export failed' }); throw error; } } }, { id: 'ly.img.action.navigationBar', key: 'export-video-quality', label: 'Export HQ', icon: '@imgly/Save', onClick: async () => { const dialog = cesdk.utils.showLoadingDialog({ title: 'Exporting HQ Video', message: 'Encoding high quality...', progress: 0 }); try { const blob = await engine.block.exportVideo(page, { h264Profile: 100, videoBitrate: 8_000_000, onProgress: (_, encoded, total) => { dialog.updateProgress({ value: encoded, max: total }); } }); dialog.close(); await cesdk.utils.downloadFile(blob, 'video/mp4'); } catch (error) { dialog.showError({ message: 'Export failed' }); throw error; } } } ] }); } } export default Example; ``` This guide covers exporting videos to MP4, tracking progress, configuring resolution and quality, and using built-in export actions. ## Export to MP4 Call `engine.block.exportVideo()` with a page block to export it as an MP4 video. The method returns a Blob containing the encoded video data. ```typescript highlight=highlight-export-video const blob = await engine.block.exportVideo(page, { mimeType: 'video/mp4', ``` Pass the page ID from `engine.scene.getCurrentPage()` to export the current video scene. ## Export Options Video export supports configuration options for progress tracking, resolution, framerate, and encoding quality. ### Progress Dialog Use `cesdk.utils.showLoadingDialog()` to display a progress dialog during export. The `onProgress` callback updates the dialog with rendering progress. ```typescript highlight=highlight-progress-dialog const dialog = cesdk.utils.showLoadingDialog({ title: 'Exporting Video', message: 'Encoding MP4...', progress: 0 }); try { const blob = await engine.block.exportVideo(page, { onProgress: (_, encoded, total) => { dialog.updateProgress({ value: encoded, max: total }); } }); dialog.showSuccess({ message: 'Export complete!' }); await cesdk.utils.downloadFile(blob, 'video/mp4'); } catch (error) { dialog.showError({ message: 'Export failed' }); throw error; } ``` The dialog provides `updateProgress()` to show a progress bar, `showSuccess()` for completion feedback, and `showError()` for failures. The `onProgress` callback receives rendered frames, encoded frames, and total frames. ### Resolution and Framerate Use `targetWidth`, `targetHeight`, and `framerate` to control output dimensions and smoothness. ```typescript highlight=highlight-resolution const blob = await engine.block.exportVideo(page, { targetWidth: 1920, targetHeight: 1080, framerate: 30, ``` If only one dimension is specified, the other is calculated to maintain aspect ratio. Default framerate is 30 fps. ### H.264 Profile and Quality The `h264Profile` option controls encoding quality and device compatibility: - **66 (Baseline)**: Maximum compatibility, lower compression - **77 (Main)**: Balanced quality and compatibility (default) - **100 (High)**: Best compression, modern devices only Set `videoBitrate` in bits per second to control file size. ```typescript highlight=highlight-quality const blob = await engine.block.exportVideo(page, { h264Profile: 100, videoBitrate: 8_000_000, ``` ### All MP4 Export Options | Option | Description | | ------ | ----------- | | `mimeType` | Output format: `'video/mp4'` or `'video/quicktime'`. Defaults to `'video/mp4'`. | | `onProgress` | Callback receiving `(renderedFrames, encodedFrames, totalFrames)` for progress tracking. | | `targetWidth` | Target output width in pixels. | | `targetHeight` | Target output height in pixels. | | `framerate` | Target framerate in Hz. Defaults to `30`. | | `h264Profile` | H.264 profile: 66 (Baseline), 77 (Main), 100 (High). Defaults to `77`. | | `h264Level` | H.264 level multiplied by 10 (e.g., 52 = level 5.2). Defaults to `52`. | | `videoBitrate` | Video bitrate in bits/second. `0` enables automatic selection. | | `audioBitrate` | Audio bitrate in bits/second. `0` enables automatic selection. | | `timeOffset` | Start time in seconds for partial export. Defaults to `0`. | | `duration` | Export duration in seconds. Defaults to scene duration. | | `abortSignal` | Signal to cancel the export operation. | ## Built-in Export Action CE.SDK provides a built-in `exportDesign` action that handles export with a progress dialog and automatic download. Trigger it with `cesdk.actions.run()`: ```typescript highlight=highlight-builtin-action cesdk.actions.run('exportDesign', { mimeType: 'video/mp4' }); ``` The built-in action exports the current page as MP4 and prompts the user to download the result. It displays a progress dialog during encoding. ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.exportVideo(blockId, options)` | Export a page block as MP4 video with encoding options | | `cesdk.actions.run('exportDesign', options)` | Run the built-in export action with progress dialog | | `cesdk.utils.showLoadingDialog(options)` | Show a loading dialog with progress tracking | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a blob to the user's device | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats - [Export Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) - Check device limits before exporting large videos - [Export Audio](https://img.ly/docs/cesdk/angular/guides/export-save-publish/export/audio-68de25/) - Export audio tracks separately - [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) - Export specific blocks or timeline segments --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To PDF" description: "Export your designs as PDF documents with options for print compatibility, underlayer generation, and output control." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [To PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) --- Export your designs as PDF documents with high compatibility mode and underlayer support for special media printing. ![Export to PDF hero image](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-pdf-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-pdf-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-to-pdf-browser/) PDF provides a universal document format for sharing and printing designs. CE.SDK exports PDF files that preserve vector graphics, support multi-page documents, and include options for print compatibility. You can configure high compatibility mode to ensure consistent rendering across different PDF viewers, and generate underlayers for special media printing like fabric, glass, or DTF transfers. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-pdf-browser/browser.ts reference-only import { MimeType, type EditorPlugin, type EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Export to PDF Guide * * This example demonstrates: * - Exporting designs as PDF documents * - Configuring high compatibility mode for consistent rendering * - Generating underlayers for special media printing * - Controlling output dimensions * - Using the built-in export action */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Load a template scene and zoom to fit await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (!page) throw new Error('No page found'); await engine.scene.zoomToBlock(page, { padding: 40 }); // Register a custom export action with PDF-specific options cesdk.actions.register('exportDesign', async () => { // Export the scene block to include all pages in the PDF const scene = engine.scene.get()!; // Merge PDF-specific defaults with provided options const blob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); await cesdk.utils.downloadFile(blob, 'application/pdf'); }); // Configure navigation bar with export buttons using insertOrderComponent cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-pdf', label: 'PDF', icon: '@imgly/Download', onClick: async () => { // Export scene to include all pages in the PDF const scene = engine.scene.get()!; // Export scene as PDF (includes all pages) const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); // Download using CE.SDK utils await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); } }, { id: 'ly.img.action.navigationBar', key: 'export-high-compat', label: 'High Compat', icon: '@imgly/Download', onClick: async () => { // Export scene to include all pages in the PDF const scene = engine.scene.get()!; // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at scene DPI const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); } }, { id: 'ly.img.action.navigationBar', key: 'export-underlayer', label: 'Underlayer', icon: '@imgly/Download', onClick: async () => { // Export scene to include all pages in the PDF const scene = engine.scene.get()!; // Define the underlayer spot color before export // RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); // Export with underlayer for special media printing const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks underlayer to prevent visible edges underlayerOffset: -2.0 }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); } }, { id: 'ly.img.action.navigationBar', key: 'export-a4', label: 'A4 @ 300 DPI', icon: '@imgly/Download', onClick: async () => { // Export scene to include all pages in the PDF const scene = engine.scene.get()!; // Export with specific dimensions for print output const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', targetWidth: 2480, // A4 at 300 DPI (210mm) targetHeight: 3508 // A4 at 300 DPI (297mm) }); await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); } }, { id: 'ly.img.action.navigationBar', key: 'export-action', label: 'Export', icon: '@imgly/Download', onClick: () => { // Run built-in export with PDF format cesdk.actions.run('exportDesign', { mimeType: 'application/pdf' }); } } ] }); } } export default Example; ``` This guide covers exporting designs to PDF format, configuring high compatibility mode, generating underlayers with spot colors, and controlling output dimensions. ## Export to PDF Call `engine.block.export()` with `mimeType: 'application/pdf'` to export any block as a PDF document. The method returns a Blob containing the PDF data. ```typescript highlight=highlight-export-pdf // Export scene as PDF (includes all pages) const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf' }); ``` Pass the scene ID from `engine.scene.get()` to export all pages as a multi-page PDF. You can also pass a single page ID from `engine.scene.getCurrentPage()` if you only need to export one page. ## Configure High Compatibility Mode Enable `exportPdfWithHighCompatibility` to rasterize complex elements like gradients with transparency at the scene's DPI. This ensures consistent rendering across all PDF viewers. ```typescript highlight=highlight-high-compatibility // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at scene DPI const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); ``` Use high compatibility mode when: - Designs contain gradients with transparency - Effects or blend modes render inconsistently across viewers - Maximum compatibility matters more than vector precision High compatibility mode increases file size because complex elements are converted to raster images rather than remaining as vectors. ## Generate Underlayers for Special Media Underlayers provide a base ink layer (typically white) for printing on transparent or non-white substrates like fabric, glass, or acrylic. The underlayer sits behind your design elements and provides opacity on transparent materials. ### Define the Underlayer Spot Color Before exporting, define a spot color that represents the underlayer ink. The RGB values provide a preview representation in PDF viewers. ```typescript highlight=highlight-spot-color // Define the underlayer spot color before export // RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); ``` The spot color name (e.g., `'RDG_WHITE'`) must match your print provider's requirements. Common names include `RDG_WHITE` for Roland DG printers and `White` for other systems. ### Export with Underlayer Options Configure the underlayer spot color name and optional offset. The `underlayerOffset` adjusts the underlayer size in design units—negative values shrink it inward to prevent visible edges from print misalignment (trapping). ```typescript highlight=highlight-underlayer // Export with underlayer for special media printing const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks underlayer to prevent visible edges underlayerOffset: -2.0 }); ``` The underlayer is generated automatically from the contours of all design elements on the page. Elements with transparency will have proportionally reduced underlayer opacity. ## Export at Target Dimensions Use `targetWidth` and `targetHeight` to control the exported PDF dimensions in pixels. The block renders large enough to fill the target size while maintaining aspect ratio. ```typescript highlight=highlight-target-size // Export with specific dimensions for print output const pdfBlob = await engine.block.export(scene, { mimeType: 'application/pdf', targetWidth: 2480, // A4 at 300 DPI (210mm) targetHeight: 3508 // A4 at 300 DPI (297mm) }); ``` For print output, calculate the target dimensions based on your desired DPI: - A4 at 300 DPI: 2480 × 3508 pixels - Letter at 300 DPI: 2550 × 3300 pixels ## Built-in Export Action Run the `exportDesign` action to execute the default export flow programmatically. ```typescript highlight=highlight-trigger-export // Run built-in export with PDF format cesdk.actions.run('exportDesign', { mimeType: 'application/pdf' }); ``` This executes the registered export action, which handles the complete export process including format selection and file download. ## Customize Export Action Register a custom `exportDesign` action to apply PDF-specific options automatically. This lets you set defaults like high compatibility mode that apply whenever the export action runs. ```typescript highlight=highlight-customize-action // Register a custom export action with PDF-specific options cesdk.actions.register('exportDesign', async () => { // Export the scene block to include all pages in the PDF const scene = engine.scene.get()!; // Merge PDF-specific defaults with provided options const blob = await engine.block.export(scene, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); await cesdk.utils.downloadFile(blob, 'application/pdf'); }); ``` The custom action merges your PDF defaults with any options passed when the action runs. This approach centralizes export configuration and ensures consistent behavior across your application. ## Download Export Use `cesdk.utils.downloadFile()` to trigger the browser's download dialog for the exported blob. ```typescript highlight=highlight-download // Download using CE.SDK utils await cesdk.utils.downloadFile(pdfBlob, 'application/pdf'); ``` Pass the blob and MIME type to prompt the user to save the file locally. ## PDF Export Options | Option | Description | | ------ | ----------- | | `mimeType` | Output format. Must be `'application/pdf'`. | | `exportPdfWithHighCompatibility` | Rasterize complex elements at scene DPI for consistent rendering. Defaults to `true`. | | `exportPdfWithUnderlayer` | Generate an underlayer from design contours. Defaults to `false`. | | `underlayerSpotColorName` | Spot color name for the underlayer ink. Required when `exportPdfWithUnderlayer` is true. | | `underlayerOffset` | Size adjustment in design units. Negative values shrink the underlayer inward. | | `targetWidth` | Target output width in pixels. Must be used with `targetHeight`. | | `targetHeight` | Target output height in pixels. Must be used with `targetWidth`. | | `abortSignal` | Signal to cancel the export operation. | ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.export(blockId, options)` | Export a block as PDF with format and compatibility options | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define a spot color for underlayer ink | | `engine.scene.get()` | Get the scene for multi-page PDF export | | `engine.scene.getCurrentPage()` | Get the current page for single-page export | | `cesdk.actions.run()` | Runs a built-in action like `exportDesign` | | `cesdk.actions.register()` | Registers a custom action to override default behavior | | `cesdk.utils.downloadFile()` | Triggers browser download dialog for a blob | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats - [Export for Printing](https://img.ly/docs/cesdk/angular/export-save-publish/for-printing-bca896/) - Print workflows with DPI and color management - [Spot Colors](https://img.ly/docs/cesdk/angular/colors/for-print/spot-c3a150/) - Define and use spot colors in designs - [Export Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) - Check device limits before exporting large designs --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To PNG" description: "Export your designs as PNG images with transparency support and configurable compression for web graphics, UI elements, and content requiring crisp edges." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [To PNG](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-png-f87eaf/) --- Export your designs as PNG images with full transparency support and configurable compression. ![Export to PNG hero image](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-png-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-png-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-to-png-browser/) PNG (Portable Network Graphics) provides lossless compression with full alpha channel support. It's ideal for web graphics, UI elements, and content requiring crisp edges or transparency. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-png-browser/browser.ts reference-only import type CreativeEditorSDK from '@cesdk/cesdk-js'; import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (!page) throw new Error('No page found'); await engine.scene.zoomToBlock(page, { padding: 40 }); // Setup export functionality await this.setupExportActions(cesdk, page); } private async setupExportActions( cesdk: CreativeEditorSDK, page: number ): Promise { const engine = cesdk.engine; // Add export button to navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'export-design', label: 'Export PNG', icon: '@imgly/Save', onClick: async () => { const blob = await engine.block.export(page, { mimeType: 'image/png' }); await cesdk.utils.downloadFile(blob, 'image/png'); } }, { id: 'ly.img.action.navigationBar', key: 'export-design', label: 'Export PNG (default)', icon: '@imgly/Save', onClick: () => cesdk.actions.run('exportDesign') }, { id: 'ly.img.action.navigationBar', key: 'export-design', label: 'Export PNG (compressed)', icon: '@imgly/Save', onClick: async () => { // Export with compression const compressedBlob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9 }); await cesdk.utils.downloadFile(compressedBlob, 'image/png'); } }, { id: 'ly.img.action.navigationBar', key: 'export-design', label: 'Export PNG (hd)', icon: '@imgly/Save', onClick: async () => { const hdBlob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1920, targetHeight: 1080 }); await cesdk.utils.downloadFile(hdBlob, 'image/png'); } } ] }); } } export default Example; ``` This guide covers exporting designs to PNG, configuring compression, controlling output dimensions, and using built-in export actions. ## Export to PNG Call `engine.block.export()` with `mimeType: 'image/png'` to export any block as a PNG image. The method returns a Blob containing the image data. ```typescript highlight=highlight-export-png const blob = await engine.block.export(page, { mimeType: 'image/png' }); ``` Pass the page ID from `engine.scene.getCurrentPage()` or any block ID to export specific elements. ## Export Options PNG export supports several configuration options for compression, dimensions, and text rendering. ### Compression Level The `pngCompressionLevel` option (0-9) controls file size vs. encoding speed. Higher values produce smaller files but take longer to encode. PNG compression is lossless, so quality remains unchanged. ```typescript highlight=highlight-compression const compressedBlob = await engine.block.export(page, { mimeType: 'image/png', pngCompressionLevel: 9 }); ``` - **0**: No compression, fastest encoding - **5**: Balanced (default) - **9**: Maximum compression, slowest encoding ### Target Dimensions Use `targetWidth` and `targetHeight` together to export at specific dimensions. The block renders large enough to fill the target size while maintaining aspect ratio. ```typescript highlight=highlight-target-size const hdBlob = await engine.block.export(page, { mimeType: 'image/png', targetWidth: 1920, targetHeight: 1080 }); ``` If the target aspect ratio differs from the block's aspect ratio, the output extends beyond the target on one axis to preserve proportions. ### All PNG Export Options | Option | Description | | ------ | ----------- | | `mimeType` | Output format. Defaults to `'image/png'`. | | `pngCompressionLevel` | Compression level (0-9). Higher values produce smaller files but take longer to encode. Quality is unaffected. Defaults to `5`. | | `targetWidth` | Target output width in pixels. Must be used with `targetHeight`. | | `targetHeight` | Target output height in pixels. Must be used with `targetWidth`. | | `allowTextOverhang` | When `true`, text blocks with glyphs extending beyond their frame export with full glyph bounds visible. Defaults to `false`. | | `abortSignal` | Signal to cancel the export operation. | ## Built-in Export Action CE.SDK provides a built-in `exportDesign` action that handles export with a progress dialog and automatic download. Trigger it with `cesdk.actions.run()`: ```typescript highlight=highlight-builtin-action onClick: () => cesdk.actions.run('exportDesign') ``` The built-in action exports the current page as PNG and prompts the user to download the result. Add an export button to the navigation bar to let users trigger this action from the UI. ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.export(blockId, options)` | Export a block as PNG with format and quality options | | `cesdk.actions.run('exportDesign')` | Run the built-in export action with progress dialog | | `cesdk.utils.downloadFile(blob, mimeType)` | Download a blob to the user's device | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats - [Export Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) - Check device limits before exporting large designs - [Export with Color Mask](https://img.ly/docs/cesdk/angular/export-save-publish/export/with-color-mask-4f868f/) - Remove specific colors and generate alpha masks - [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) - Export specific blocks or regions --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export to Raw Data" description: "Export designs to uncompressed RGBA pixel data for custom image processing, GPU uploads, and advanced graphics workflows." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/to-raw-data-abd7da/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [To Raw Data](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-raw-data-abd7da/) --- Exporting designs to raw pixel data gives you direct access to uncompressed RGBA bytes, enabling custom image processing, GPU texture uploads, and integration with advanced graphics pipelines. Unlike compressed formats like PNG or JPEG, raw data export provides the pixel buffer without encoding overhead, making it ideal for performance-critical applications and scenarios where you need to manipulate individual pixels programmatically. ![Export to Raw Data example showing grayscale image processing with export button](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-to-raw-data-browser/) ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-raw-data-browser/src/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import packageJson from '../package.json'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TypefaceAssetSource, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Load assets and create scene await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.addPlugin(new DemoAssetSources({ sceneMode: 'Design', withUploadAssetSources: true })); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Set explicit page dimensions const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Create a single image block to demonstrate raw data export const imageBlock = await engine.block.addImage(imageUri, { size: { width: 800, height: 600 } }); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 0); engine.block.setPositionY(imageBlock, 0); // Add export button to navigation bar cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: ['ly.img.exportImage.navigationBar'] }); // Override the built-in exportDesign action cesdk.actions.register('exportDesign', async () => { // Export to raw pixel data const width = Math.floor(engine.block.getWidth(imageBlock)); const height = Math.floor(engine.block.getHeight(imageBlock)); const blob = await engine.block.export(imageBlock, { mimeType: 'application/octet-stream', targetWidth: width, targetHeight: height }); // Convert blob to raw pixel array const arrayBuffer = await blob.arrayBuffer(); const pixelData = new Uint8Array(arrayBuffer); // Apply grayscale processing const processedData = this.toGrayscale(pixelData, width, height); // Download processed image await this.downloadProcessedImage(processedData, width, height); }); } /** * Convert image to grayscale by averaging RGB channels */ private toGrayscale( pixelData: Uint8Array, _width: number, _height: number ): Uint8Array { const result = new Uint8Array(pixelData); for (let i = 0; i < result.length; i += 4) { const avg = Math.round((result[i] + result[i + 1] + result[i + 2]) / 3); result[i] = avg; // R result[i + 1] = avg; // G result[i + 2] = avg; // B // Keep alpha unchanged: result[i + 3] } return result; } /** * Convert processed pixel data to PNG and trigger download */ private async downloadProcessedImage( pixelData: Uint8Array, width: number, height: number ): Promise { // Create canvas and render pixel data const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Failed to get canvas context'); } // Create ImageData from pixel array const imageData = new ImageData( new Uint8ClampedArray(pixelData), width, height ); ctx.putImageData(imageData, 0, 0); // Convert canvas to blob const blob = await new Promise((resolve, reject) => { canvas.toBlob( blob => { if (blob) { resolve(blob); } else { reject(new Error('Failed to convert canvas to blob')); } }, 'image/png', 1.0 ); }); // Trigger download const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'processed-image.png'; link.click(); // Clean up URL.revokeObjectURL(url); } } export default Example; ``` ## When to Use Raw Data Export Raw pixel data export provides direct access to uncompressed RGBA bytes from CE.SDK, giving you complete control over individual pixels for custom processing workflows. Use raw data export when you need pixel-level access to exported designs for custom algorithms or integrations. For standard image delivery, use PNG or JPEG exports instead, as they provide compression and are ready to use without additional processing. ## Understanding Raw Data Format When you export with `mimeType: 'application/octet-stream'`, CE.SDK returns a Blob containing uncompressed RGBA pixel data. The format is straightforward: - **4 bytes per pixel** representing Red, Green, Blue, and Alpha channels - **Values from 0-255** for each channel (8-bit unsigned integers) - **Row-major order** with pixels arranged left-to-right, top-to-bottom - **Total size** equals width × height × 4 bytes ## How to Export Raw Data To export a block as raw pixel data, use the `engine.block.export()` method with `mimeType: 'application/octet-stream'`: ```typescript highlight-basic-export const blob = await engine.block.export(imageBlock, { mimeType: 'application/octet-stream', targetWidth: width, targetHeight: height }); ``` This returns a Blob containing uncompressed RGBA pixel data that you can process with custom algorithms. ## Download Exported Data The example overrides the built-in `exportDesign` action to implement a custom workflow that exports to raw data, processes the pixels, and downloads the result: ```typescript highlight-export-action-body // Export to raw pixel data const width = Math.floor(engine.block.getWidth(imageBlock)); const height = Math.floor(engine.block.getHeight(imageBlock)); const blob = await engine.block.export(imageBlock, { mimeType: 'application/octet-stream', targetWidth: width, targetHeight: height }); // Convert blob to raw pixel array const arrayBuffer = await blob.arrayBuffer(); const pixelData = new Uint8Array(arrayBuffer); // Apply grayscale processing const processedData = this.toGrayscale(pixelData, width, height); // Download processed image await this.downloadProcessedImage(processedData, width, height); ``` This complete workflow demonstrates exporting to raw pixel data, applying grayscale processing, and downloading the processed image as PNG. ## Performance Considerations Raw data export involves trade-offs between flexibility and efficiency: **Memory Usage**: Raw RGBA data requires 4 bytes per pixel. A 1920×1080 CE.SDK export uses approximately 8.3 MB uncompressed, compared to 1-3 MB for PNG. Reduce memory usage with the `targetWidth` and `targetHeight` export options: ```typescript const blob = await engine.block.export(blockId, { mimeType: 'application/octet-stream', targetWidth: 960, targetHeight: 540 }); ``` **Processing Speed**: Operating directly on pixel data from CE.SDK exports is fast because there's no encoding/decoding overhead. However, processing millions of pixels can be time-consuming for complex algorithms. Consider using Web Workers for heavy processing to avoid blocking the main thread. **When to Use Raw vs. Compressed for CE.SDK Exports**: - Use raw data when you need custom post-processing before final delivery - Use PNG or JPEG for direct downloads from CE.SDK - Use raw data for intermediate processing steps in multi-stage pipelines - Use compressed formats for final output or network transfer ## API Reference | Method | Description | | -------------------------------------- | --------------------------------------------------------- | | `engine.block.export()` | Exports a block with `mimeType: 'application/octet-stream'` for raw RGBA data | | `engine.block.getWidth()` | Returns the width of a block in pixels | | `engine.block.getHeight()` | Returns the height of a block in pixels | | `Blob.arrayBuffer()` | Converts the blob to an ArrayBuffer for raw data access | | `ImageData()` | Creates an ImageData object from a Uint8ClampedArray | | `CanvasRenderingContext2D.putImageData()` | Paints pixel data onto a canvas | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To WebP" description: "Export your CE.SDK designs to WebP format for optimized web delivery with lossy and lossless compression options." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/to-webp-aef6f4/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [To WebP](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-webp-aef6f4/) --- Export designs to WebP format for optimized web delivery with smaller file sizes than PNG or JPEG. ![Export to WebP showing the editor with export options](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-webp-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-to-webp-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-to-webp-browser/) WebP delivers smaller file sizes than PNG and JPEG while preserving image quality and transparency support. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-to-webp-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Export to WebP Guide * * Demonstrates exporting designs to WebP format with: * - Built-in export action triggered programmatically * - Three export buttons showcasing different quality presets * - Lossy, lossless, and social media export options */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Load template and zoom to fit await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (!page) throw new Error('No page found'); await engine.scene.zoomToBlock(page, { padding: 40 }); // Three export buttons with different WebP settings cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'webp-lossy', label: 'Lossy', icon: '@imgly/Download', onClick: async () => { const p = engine.scene.getCurrentPage()!; // Export with lossy compression const blob = await engine.block.export(p, { mimeType: 'image/webp', webpQuality: 0.8 }); // Download using CE.SDK utils await cesdk.utils.downloadFile(blob, 'image/webp'); } }, { id: 'ly.img.action.navigationBar', key: 'webp-lossless', label: 'Lossless', icon: '@imgly/Download', onClick: async () => { const p = engine.scene.getCurrentPage()!; const blob = await engine.block.export(p, { mimeType: 'image/webp', webpQuality: 1.0 }); await cesdk.utils.downloadFile(blob, 'image/webp'); } }, { id: 'ly.img.action.navigationBar', key: 'webp-social', label: 'Social', icon: '@imgly/Download', onClick: async () => { const p = engine.scene.getCurrentPage()!; // Export with target dimensions for social media const blob = await engine.block.export(p, { mimeType: 'image/webp', webpQuality: 0.9, targetWidth: 1200, targetHeight: 630 }); await cesdk.utils.downloadFile(blob, 'image/webp'); } }, { id: 'ly.img.action.navigationBar', key: 'export-action', label: 'Export', icon: '@imgly/Download', onClick: () => { // Run built-in export with WebP format cesdk.actions.run('exportDesign', { mimeType: 'image/webp' }); } } ] }); } } export default Example; ``` This guide covers exporting to WebP, configuring quality settings, and triggering downloads. ## Export to WebP Call `engine.block.export()` with `mimeType: 'image/webp'` and a `webpQuality` value between 0 and 1. ```typescript highlight=highlight-export-webp // Export with lossy compression const blob = await engine.block.export(p, { mimeType: 'image/webp', webpQuality: 0.8 }); ``` The `webpQuality` parameter controls compression. A value of 0.8 provides a good balance between file size and visual quality for most use cases. ## Export Options WebP export supports these options: | Option | Type | Description | |--------|------|-------------| | `mimeType` | `'image/webp'` | Required. Specifies WebP format | | `webpQuality` | `number` | Quality from 0 to 1. Default 1.0 (lossless) | | `targetWidth` | `number` | Optional resize width | | `targetHeight` | `number` | Optional resize height | Combine `targetWidth` and `targetHeight` to resize the output, useful for social media or thumbnail generation. ```typescript highlight=highlight-export-options // Export with target dimensions for social media const blob = await engine.block.export(p, { mimeType: 'image/webp', webpQuality: 0.9, targetWidth: 1200, targetHeight: 630 }); ``` Set `webpQuality` to 1.0 for lossless compression when pixel-perfect output is required. ## Built-in Export Action Run the `exportDesign` action to execute the default export flow programmatically. ```typescript highlight=highlight-trigger-export // Run built-in export with WebP format cesdk.actions.run('exportDesign', { mimeType: 'image/webp' }); ``` This executes the registered export action, which handles the complete export process including format selection and file download. ## Download Export Use `cesdk.utils.downloadFile()` to trigger the browser's download dialog for the exported blob. ```typescript highlight=highlight-download // Download using CE.SDK utils await cesdk.utils.downloadFile(blob, 'image/webp'); ``` Pass the blob and MIME type to prompt the user to save the file locally. > **Note:** WebP is supported in all modern browsers. For older browsers, consider PNG or JPEG as fallback formats. ## API Reference | API | Description | |-----|-------------| | `engine.block.export()` | Exports a block to an image blob with format and quality options | | `cesdk.actions.run()` | Runs a built-in action like `exportDesign` | | `cesdk.utils.downloadFile()` | Triggers browser download dialog for a blob | ## Next Steps [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Learn about all supported export formats and their options. [Export to PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) - Generate print-ready PDF documents from your designs. [Size Limits](https://img.ly/docs/cesdk/angular/export-save-publish/export/size-limits-6f0695/) - Understand export size constraints and optimization strategies. [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) - Export specific blocks or regions instead of the full design. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export with a Color Mask" description: "Learn how to export design blocks with color masking in CE.SDK to remove specific colors and generate alpha masks for print workflows and compositing." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/export/with-color-mask-4f868f/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [With a Color Mask](https://img.ly/docs/cesdk/angular/export-save-publish/export/with-color-mask-4f868f/) --- Remove specific colors from exported images and generate alpha masks using CE.SDK's color mask export API for print workflows, transparency creation, and compositing pipelines. ![Export with Color Mask example showing color removal and alpha mask generation](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-with-color-mask-browser/) When exporting, CE.SDK can remove specific RGB colors by replacing matching pixels with transparency. The export generates two files: the masked image with transparent areas and an alpha mask showing removed pixels. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-with-color-mask-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Create a single image with registration marks const imageBlock = await engine.block.addImage(imageUri, { size: { width: pageWidth * 0.8, height: pageHeight * 0.8 } }); engine.block.appendChild(page, imageBlock); // Center the image on the page const imageWidth = engine.block.getWidth(imageBlock); const imageHeight = engine.block.getHeight(imageBlock); engine.block.setPositionX(imageBlock, (pageWidth - imageWidth) / 2); engine.block.setPositionY(imageBlock, (pageHeight - imageHeight) / 2); // Add registration marks at the corners (pure red for demonstration) const markSize = 30; const imageX = engine.block.getPositionX(imageBlock); const imageY = engine.block.getPositionY(imageBlock); const markPositions = [ { x: imageX - markSize - 10, y: imageY - markSize - 10 }, // Top-left { x: imageX + imageWidth + 10, y: imageY - markSize - 10 }, // Top-right { x: imageX - markSize - 10, y: imageY + imageHeight + 10 }, // Bottom-left { x: imageX + imageWidth + 10, y: imageY + imageHeight + 10 } // Bottom-right ]; markPositions.forEach((pos) => { const mark = engine.block.create('//ly.img.ubq/graphic'); engine.block.setShape( mark, engine.block.createShape('//ly.img.ubq/shape/rect') ); const redFill = engine.block.createFill('//ly.img.ubq/fill/color'); engine.block.setColor(redFill, 'fill/color/value', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); engine.block.setFill(mark, redFill); engine.block.setWidth(mark, markSize); engine.block.setHeight(mark, markSize); engine.block.setPositionX(mark, pos.x); engine.block.setPositionY(mark, pos.y); engine.block.appendChild(page, mark); }); // Override the default image export action to use color mask export cesdk.actions.register('exportDesign', async () => { const currentPage = engine.scene.getCurrentPage(); if (!currentPage) return; // Export with color mask - removes pure red pixels (registration marks) const [maskedImage, alphaMask] = await engine.block.exportWithColorMask( currentPage, 1.0, // Red component 0.0, // Green component 0.0, // Blue component (RGB: pure red) { mimeType: 'image/png' } ); // Download masked image using CE.SDK utils await cesdk.utils.downloadFile(maskedImage, 'image/png'); // Download alpha mask await cesdk.utils.downloadFile(alphaMask, 'image/png'); console.log('Color mask export completed:', { maskedSize: maskedImage.size, maskSize: alphaMask.size }); }); // Add export button to navigation bar cesdk.ui.insertOrderComponent( { in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: ['ly.img.exportImage.navigationBar'] } ); // Log completion console.log('Export with Color Mask example loaded successfully'); console.log( 'Click the export button in the navigation bar to export with color mask' ); } } export default Example; ``` Color mask exports work through exact RGB color matching—pixels that precisely match your specified color values (0.0-1.0 range) are removed. This is useful for print workflows (removing registration marks), transparency creation (removing background colors), or generating alpha masks for compositing tools. ## Exporting with Color Masks We export blocks with color masking using the `exportWithColorMask` method. This method removes specific RGB colors from the rendered output and generates both a masked image and an alpha mask. ```typescript highlight-export-with-color-mask // Export with color mask - removes pure red pixels (registration marks) const [maskedImage, alphaMask] = await engine.block.exportWithColorMask( currentPage, 1.0, // Red component 0.0, // Green component 0.0, // Blue component (RGB: pure red) { mimeType: 'image/png' } ); ``` The method accepts the block to export, three RGB color components (0.0-1.0 range), and optional export options like MIME type. This example uses pure red `(1.0, 0.0, 0.0)` to identify and remove registration marks from the design. The export operation returns a Promise that resolves to an array containing two Blobs. The first Blob is the masked image with transparency applied where the specified color was found. The second Blob is the alpha mask—a black and white image showing which pixels were removed (black) and which remained (white). We then download both files using `cesdk.utils.downloadFile()`, which triggers browser downloads with appropriate file naming. ### Specifying RGB Color Values RGB color components in CE.SDK use floating-point values from 0.0 to 1.0, not the 0-255 integer values common in design tools: - Pure red: `(1.0, 0.0, 0.0)` - Common for registration marks - Pure magenta: `(1.0, 0.0, 1.0)` - Distinctive marker color - Pure cyan: `(0.0, 1.0, 1.0)` - Alternative marker color - Pure yellow: `(1.0, 1.0, 0.0)` - Useful for exclusion zones When converting from standard 0-255 RGB values, divide each component by 255. For example, RGB(255, 128, 0) becomes `(1.0, 0.502, 0.0)`. ## How to Export with Color Masks We override the default export action to apply color masking when users click the export button. This integrates color mask functionality into your editor's workflow without requiring additional UI. ```typescript highlight-register-export-action // Override the default image export action to use color mask export cesdk.actions.register('exportDesign', async () => { const currentPage = engine.scene.getCurrentPage(); if (!currentPage) return; // Export with color mask - removes pure red pixels (registration marks) const [maskedImage, alphaMask] = await engine.block.exportWithColorMask( currentPage, 1.0, // Red component 0.0, // Green component 0.0, // Blue component (RGB: pure red) { mimeType: 'image/png' } ); // Download masked image using CE.SDK utils await cesdk.utils.downloadFile(maskedImage, 'image/png'); // Download alpha mask await cesdk.utils.downloadFile(alphaMask, 'image/png'); console.log('Color mask export completed:', { maskedSize: maskedImage.size, maskSize: alphaMask.size }); }); ``` The custom action registers as `'exportDesign'`, replacing the default export behavior. When triggered, it exports the current page with pure red `(1.0, 0.0, 0.0)` as the mask color, then downloads both the masked image and alpha mask files using `cesdk.utils.downloadFile()`. ## API Reference | Method | Description | | ----------------------------------------------- | --------------------------------------------------------- | | `cesdk.actions.register()` | Registers a custom action that can be triggered by UI elements | | `cesdk.ui.insertOrderComponent()` | Adds UI components to the editor's navigation bar | | `cesdk.utils.downloadFile()` | Triggers a browser download for a file blob | | `engine.scene.getCurrentPage()` | Gets the currently active page in the scene | | `engine.block.exportWithColorMask()` | Exports a block with specific RGB color removed, generating masked image and alpha mask | | `engine.block.addImage()` | Adds an image block to the scene | | `engine.block.create()` | Creates a new block of the specified type | | `engine.block.setShape()` | Sets the shape for a graphic block | | `engine.block.createShape()` | Creates a shape definition for graphic blocks | | `engine.block.createFill()` | Creates a fill definition for blocks | | `engine.block.setColor()` | Sets the color value for a fill | | `engine.block.setFill()` | Applies a fill to a block | | `engine.block.appendChild()` | Adds a block as a child of another block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export for Printing" description: "Export designs from CE.SDK as print-ready PDFs with professional output options including high compatibility mode, underlayers for special media, and scene DPI configuration." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/for-printing-bca896/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [For Printing](https://img.ly/docs/cesdk/angular/export-save-publish/for-printing-bca896/) --- Export print-ready PDFs from CE.SDK with options for high compatibility mode, underlayers for special media like fabric or glass, and configurable output resolution. ![Export for Printing example showing PDF export options](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-for-printing-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-for-printing-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-for-printing-browser/) CE.SDK exports designs as PDFs, but professional print workflows require specific configurations beyond standard export. This guide covers PDF export options for print, including high compatibility mode for complex designs, underlayers for printing on special media, and output resolution settings. ```typescript file=@cesdk_web_examples/guides-export-save-publish-for-printing-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Export for Printing Guide * * This example demonstrates: * - Exporting designs as print-ready PDFs * - Configuring high compatibility mode for complex designs * - Generating underlayers for special media (DTF, fabric, glass) * - Setting scene DPI for print resolution */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Load a template scene - this will be our print design await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); // Get the scene and page const scene = engine.scene.get(); if (!scene) { throw new Error('No scene found'); } const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Set print resolution (DPI) on the scene // 300 DPI is standard for high-quality print output engine.block.setFloat(scene, 'scene/dpi', 300); // Helper function to download blob const downloadBlob = (blob: Blob, filename: string) => { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename; anchor.click(); URL.revokeObjectURL(url); }; // Export PDF with high compatibility mode const exportWithHighCompatibility = async () => { // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at the scene's DPI const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); downloadBlob(pdfBlob, 'print-high-compatibility.pdf'); cesdk.ui.showNotification({ message: `PDF exported with high compatibility (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export PDF without high compatibility (faster, smaller files) const exportStandardPdf = async () => { // Disable high compatibility for faster exports when targeting modern PDF viewers // Complex elements remain as vectors but may render differently across viewers const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: false }); downloadBlob(pdfBlob, 'print-standard.pdf'); cesdk.ui.showNotification({ message: `Standard PDF exported (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Define underlayer spot color and export with underlayer const exportWithUnderlayer = async () => { // Define the underlayer spot color before export // This creates a named spot color that will be used for the underlayer ink // The RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); // Export with underlayer enabled for DTF or special media printing // The underlayer generates a shape behind design elements filled with the spot color const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks the underlayer inward to prevent visible edges underlayerOffset: -2.0 }); downloadBlob(pdfBlob, 'print-with-underlayer.pdf'); cesdk.ui.showNotification({ message: `PDF exported with underlayer (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Export with custom target size const exportWithTargetSize = async () => { // Export with specific dimensions for print output // targetWidth and targetHeight control the exported PDF dimensions in pixels const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, targetWidth: 2480, // A4 at 300 DPI (210mm) targetHeight: 3508 // A4 at 300 DPI (297mm) }); downloadBlob(pdfBlob, 'print-a4-300dpi.pdf'); cesdk.ui.showNotification({ message: `A4 PDF exported (${(pdfBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Configure navigation bar with export buttons cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', onClick: exportWithHighCompatibility, key: 'export-high-compat', label: 'High Compat PDF', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportStandardPdf, key: 'export-standard', label: 'Standard PDF', icon: '@imgly/Save', variant: 'plain' }, { id: 'ly.img.action.navigationBar', onClick: exportWithUnderlayer, key: 'export-underlayer', label: 'With Underlayer', icon: '@imgly/Save', variant: 'plain', color: 'accent' }, { id: 'ly.img.action.navigationBar', onClick: exportWithTargetSize, key: 'export-a4', label: 'A4 @ 300 DPI', icon: '@imgly/Save', variant: 'plain' } ]); cesdk.ui.showNotification({ message: 'Use the export buttons to export print-ready PDFs with different options', type: 'info', duration: 'infinite' }); } } export default Example; ``` ## Default PDF Color Behavior CE.SDK exports PDFs in RGB color space. CMYK or spot colors defined in your design convert to RGB during standard export. For CMYK output with ICC profiles, use the **Print Ready PDF plugin**. The base `engine.block.export()` method provides print compatibility options, but full CMYK workflow requires the plugin. ## Setting Up for Print Export Before exporting, configure your scene with appropriate print settings. Set the scene's DPI to control print resolution—300 DPI is standard for high-quality print output. ```typescript highlight-setup // Load a template scene - this will be our print design await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); // Get the scene and page const scene = engine.scene.get(); if (!scene) { throw new Error('No scene found'); } const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Set print resolution (DPI) on the scene // 300 DPI is standard for high-quality print output engine.block.setFloat(scene, 'scene/dpi', 300); ``` ## PDF Export Options for Print Export a page as PDF using `engine.block.export()` with `mimeType: 'application/pdf'`. ### High Compatibility Mode The `exportPdfWithHighCompatibility` option rasterizes complex elements like gradients with transparency at the scene's DPI. Enable this when: - Designs use gradients with transparency - Effects or blend modes render inconsistently across PDF viewers - Maximum compatibility across print RIPs matters more than vector precision ```typescript highlight-export-high-compatibility // Enable high compatibility mode for consistent rendering across PDF viewers // This rasterizes complex elements like gradients with transparency at the scene's DPI const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true }); ``` Disabling high compatibility produces faster exports with smaller file sizes but may cause rendering inconsistencies in some PDF viewers. ### Standard PDF Export When targeting modern PDF viewers where file size and export speed matter more than universal compatibility: ```typescript highlight-export-standard-pdf // Disable high compatibility for faster exports when targeting modern PDF viewers // Complex elements remain as vectors but may render differently across viewers const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: false }); ``` ## Underlayers for Special Media Underlayers provide a base ink layer (typically white) for printing on: - Transparent or non-white substrates - DTF (Direct-to-Film) transfers - Fabric, glass, or dark materials ### Define the Underlayer Spot Color Before exporting with an underlayer, define the spot color that represents the underlayer ink. Use `engine.editor.setSpotColorRGB()` to create a named spot color with RGB preview values. ```typescript highlight-define-spot-color // Define the underlayer spot color before export // This creates a named spot color that will be used for the underlayer ink // The RGB values (0.8, 0.8, 0.8) provide a preview representation engine.editor.setSpotColorRGB('RDG_WHITE', 0.8, 0.8, 0.8); ``` ### Export with Underlayer Enable `exportPdfWithUnderlayer` and specify the `underlayerSpotColorName` to generate an underlayer from design contours. The underlayer offset controls the size adjustment—negative values shrink the underlayer inward to prevent visible edges from print misalignment. ```typescript highlight-export-with-underlayer // Export with underlayer enabled for DTF or special media printing // The underlayer generates a shape behind design elements filled with the spot color const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: 'RDG_WHITE', // Negative offset shrinks the underlayer inward to prevent visible edges underlayerOffset: -2.0 }); ``` ### Underlayer Offset The `underlayerOffset` option adjusts the underlayer size in design units. Negative values shrink the underlayer inward, which prevents visible white edges when the print layers don't align perfectly. Start with values like `-1.0` to `-3.0` and adjust based on your print equipment's alignment accuracy. ## Export with Target Size Control the exported PDF dimensions using `targetWidth` and `targetHeight`. These values are in pixels and work together with the scene's DPI setting to determine physical print size. ```typescript highlight-export-target-size // Export with specific dimensions for print output // targetWidth and targetHeight control the exported PDF dimensions in pixels const pdfBlob = await engine.block.export(page, { mimeType: 'application/pdf', exportPdfWithHighCompatibility: true, targetWidth: 2480, // A4 at 300 DPI (210mm) targetHeight: 3508 // A4 at 300 DPI (297mm) }); ``` ## CMYK PDFs with ICC Profiles For CMYK color space and ICC profile embedding, use the **Print Ready PDF plugin**. This plugin post-processes exports to convert RGB to CMYK with embedded ICC profiles. See the [Print Ready PDF Plugin](https://img.ly/docs/cesdk/angular/plugins/print-ready-pdf-iroalu/) for setup and usage. ## Troubleshooting ### PDF Not Opening Correctly in Print Software Enable `exportPdfWithHighCompatibility: true` to rasterize complex elements that may not render correctly in prepress software. ### Underlayer Not Visible in PDF Viewer Standard PDF viewers may not display spot colors. Use professional print software like Adobe Acrobat Pro or prepress tools to verify the underlayer separation. ### Colors Look Different After Printing Standard export uses RGB. Use the Print Ready PDF plugin with appropriate ICC profiles for accurate CMYK reproduction. ### White Edges on Special Media Increase the negative `underlayerOffset` value to shrink the underlayer further from design edges. Try values like `-2.0` or `-3.0` depending on your equipment's alignment tolerance. ## API Reference | Method/Option | Purpose | |---------------|---------| | `engine.block.export(block, options)` | Export block to PDF | | `mimeType: 'application/pdf'` | Specify PDF output format | | `targetWidth` | Target width for exported PDF in pixels | | `targetHeight` | Target height for exported PDF in pixels | | `exportPdfWithHighCompatibility` | Rasterize bitmap images and gradients at scene DPI (default: `true`) | | `exportPdfWithUnderlayer` | Generate underlayer from contours (default: `false`) | | `underlayerSpotColorName` | Spot color name for underlayer ink | | `underlayerOffset` | Size adjustment in design units (negative shrinks) | | `engine.editor.setSpotColorRGB(name, r, g, b)` | Define spot color for underlayer | | `engine.block.setFloat(scene, 'scene/dpi', value)` | Set scene DPI for print resolution | ## Next Steps - [Print Ready PDF Plugin](https://img.ly/docs/cesdk/angular/plugins/print-ready-pdf-iroalu/) - CMYK PDFs with ICC profiles - [CMYK Colors](https://img.ly/docs/cesdk/angular/colors/for-print/cmyk-8a1334/) - Configure CMYK colors - [Spot Colors](https://img.ly/docs/cesdk/angular/colors/for-print/spot-c3a150/) - Define and use spot colors - [Export to PDF](https://img.ly/docs/cesdk/angular/export-save-publish/export/to-pdf-95e04b/) - General PDF export options --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export for Social Media" description: "Export vertical videos with the correct dimensions, formats, and quality settings for Instagram Reels, TikTok, and YouTube Shorts." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/for-social-media-0e8a92/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [For Social Media](https://img.ly/docs/cesdk/angular/export-save-publish/for-social-media-0e8a92/) --- Export vertical video designs for social media platforms with the correct dimensions, formats, and quality settings. Configure video exports with appropriate resolution, framerate, and bitrate optimized for Instagram Reels, TikTok, and YouTube Shorts. ![Export for Social Media example showing CE.SDK with export buttons for different platforms](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-for-social-media-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-for-social-media-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-for-social-media-browser/) Short-form vertical video has become the dominant format for social media. Instagram Reels, TikTok, and YouTube Shorts all use the 9:16 aspect ratio at 1080×1920 pixels. This guide demonstrates how to create and export vertical video content with the correct settings for these platforms. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-for-social-media-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Export for Social Media Guide * * This example demonstrates: * - Creating a vertical video scene (9:16) for Instagram Reels, TikTok, YouTube Shorts * - Exporting videos with resolution, framerate, and bitrate settings * - Tracking video export progress */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // Create a vertical video scene (9:16) for Instagram Reels, TikTok, YouTube Shorts await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1080, height: 1920, unit: 'Pixel' } }); const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Add a video clip that fills the vertical frame const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const videoBlock = await engine.block.addVideo( 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4', pageWidth, pageHeight ); engine.block.fillParent(videoBlock); // Export video for Instagram Reels / TikTok / YouTube Shorts (9:16) const exportVideo = async () => { cesdk.ui.showNotification({ message: 'Exporting video...', type: 'info' }); const currentPage = engine.scene.getCurrentPage(); if (!currentPage) return; const videoBlob = await engine.block.exportVideo(currentPage, { mimeType: 'video/mp4', targetWidth: 1080, targetHeight: 1920, framerate: 30, videoBitrate: 8_000_000, // 8 Mbps onProgress: (renderedFrames, encodedFrames, totalFrames) => { const percent = Math.round((encodedFrames / totalFrames) * 100); console.log( `Export progress: ${percent}% (${encodedFrames}/${totalFrames} frames)` ); } }); await cesdk.utils.downloadFile(videoBlob, 'video/mp4'); cesdk.ui.showNotification({ message: `Video exported: ${(videoBlob.size / 1024 / 1024).toFixed( 1 )} MB (1080×1920)`, type: 'success' }); }; // Configure navigation bar with export button cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [ 'ly.img.undoRedo.navigationBar', 'ly.img.spacer', { id: 'ly.img.action.navigationBar', onClick: exportVideo, key: 'export-video', label: 'Export Video', icon: '@imgly/Video', variant: 'plain', color: 'accent' } ]); } } export default Example; ``` This guide covers creating a vertical video scene, exporting with resolution, framerate, and bitrate settings, and tracking export progress. ## Creating a Video Scene Create a video scene with the correct dimensions for vertical video. Use `cesdk.actions.run('scene.create', { mode: 'Video' })` with explicit pixel dimensions to ensure your content matches platform requirements. ```typescript highlight-setup // Create a vertical video scene (9:16) for Instagram Reels, TikTok, YouTube Shorts await cesdk.actions.run('scene.create', { mode: 'Video', page: { width: 1080, height: 1920, unit: 'Pixel' } }); const page = engine.scene.getCurrentPage(); if (!page) { throw new Error('No page found'); } // Add a video clip that fills the vertical frame const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const videoBlock = await engine.block.addVideo( 'https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4', pageWidth, pageHeight ); engine.block.fillParent(videoBlock); ``` The scene uses 1080×1920 pixels (9:16 aspect ratio), which is the standard resolution for Instagram Reels, TikTok, and YouTube Shorts. The video block fills the entire page dimensions. ## Exporting Videos Export video content using `engine.block.exportVideo()`. Configure resolution, framerate, and bitrate for optimal quality and file size. ```typescript highlight-export-video const videoBlob = await engine.block.exportVideo(currentPage, { mimeType: 'video/mp4', targetWidth: 1080, targetHeight: 1920, framerate: 30, videoBitrate: 8_000_000, // 8 Mbps onProgress: (renderedFrames, encodedFrames, totalFrames) => { const percent = Math.round((encodedFrames / totalFrames) * 100); console.log( `Export progress: ${percent}% (${encodedFrames}/${totalFrames} frames)` ); } }); ``` Key video export settings: - **mimeType**: `video/mp4` for broad platform compatibility - **targetWidth/targetHeight**: Output resolution (1080×1920 for vertical) - **framerate**: 30 frames per second (standard for social media) - **videoBitrate**: 8 Mbps provides good quality while keeping file sizes reasonable Higher bitrates produce better quality but larger files. For short-form vertical video, 8 Mbps (8,000,000 bits per second) balances quality and upload speed. ## Tracking Export Progress Video exports can take time, especially for longer content. Use the `onProgress` callback to provide users with feedback during export. ```typescript highlight-progress onProgress: (renderedFrames, encodedFrames, totalFrames) => { const percent = Math.round((encodedFrames / totalFrames) * 100); console.log( `Export progress: ${percent}% (${encodedFrames}/${totalFrames} frames)` ); } ``` The callback receives three parameters: - **renderedFrames**: Number of frames rendered so far - **encodedFrames**: Number of frames encoded to the output file - **totalFrames**: Total frames to be exported The encoding stage typically trails rendering slightly. Calculate progress percentage from `encodedFrames / totalFrames` for an accurate completion indicator. ## Downloading Exported Files After export, use `cesdk.utils.downloadFile()` to trigger a browser download: ```typescript const videoBlob = await engine.block.exportVideo(page, { /* options */ }); await cesdk.utils.downloadFile(videoBlob, 'video/mp4'); ``` This utility handles the download process automatically, including memory cleanup. For server uploads, pass the Blob directly to your upload function or FormData. ## API Reference | Method | Purpose | |--------|---------| | `cesdk.actions.run('scene.create', { mode: 'Video' })` | Create a video scene with specified dimensions | | `engine.block.exportVideo()` | Export block as video (MP4) | | `engine.scene.getCurrentPage()` | Get the active page for export | | `cesdk.utils.downloadFile()` | Trigger browser download for exported files | ### Export Options (Videos) | Option | Type | Description | |--------|------|-------------| | `mimeType` | `string` | Output format: `video/mp4` | | `targetWidth` | `number` | Output width in pixels | | `targetHeight` | `number` | Output height in pixels | | `framerate` | `number` | Frames per second | | `videoBitrate` | `number` | Video bitrate in bits per second | | `onProgress` | `function` | Progress callback with frame counts | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Complete export options including H.264 profiles and advanced settings --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Pre-Export Validation" description: "Documentation for Pre-Export Validation" platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/pre-export-validation-3a2cba/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [Pre-Export Validation](https://img.ly/docs/cesdk/angular/export-save-publish/pre-export-validation-3a2cba/) --- Validate your design before export by detecting elements outside the page, protruding content, obscured text, and other issues that could affect the final output quality. ![Pre-Export Validation example showing validation workflow](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-pre-export-validation-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-export-pre-export-validation-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-export-pre-export-validation-browser/) Pre-export validation catches layout and quality issues before export, preventing problems like cropped content, hidden text, and elements missing from the final output. Production-quality designs require elements to be properly positioned within the page boundaries. ```typescript file=@cesdk_web_examples/guides-export-save-publish-export-pre-export-validation-browser/browser.ts reference-only import type { CreativeEngine, EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import type CreativeEditorSDK from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; type ValidationSeverity = 'error' | 'warning'; interface ValidationIssue { type: | 'outside_page' | 'protruding' | 'text_obscured' | 'unfilled_placeholder'; severity: ValidationSeverity; blockId: number; blockName: string; message: string; } interface ValidationResult { errors: ValidationIssue[]; warnings: ValidationIssue[]; } class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Set role to adopter so placeholders can be replaced engine.editor.setRole('Adopter'); const page = engine.block.findByType('page')[0]; await this.createDemoScene(engine, page); this.overrideExportAction(cesdk, engine); } private async createDemoScene( engine: CreativeEngine, page: number ): Promise { const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const centerY = pageHeight / 2; // Row 1: Main validation examples (horizontally aligned) const row1Y = centerY - 120; const elementWidth = 150; const elementHeight = 100; const spacing = 20; // Calculate positions for 4 elements in a row const totalRowWidth = elementWidth * 4 + spacing * 3; const startX = (pageWidth - totalRowWidth) / 2; // Create an image that's outside the page (will trigger error) // Positioned to the left of the page - completely outside const outsideImage = engine.block.create('graphic'); engine.block.setName(outsideImage, 'Outside Image'); engine.block.setShape(outsideImage, engine.block.createShape('rect')); const outsideFill = engine.block.createFill('image'); engine.block.setString( outsideFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); engine.block.setFill(outsideImage, outsideFill); engine.block.setWidth(outsideImage, elementWidth); engine.block.setHeight(outsideImage, elementHeight); engine.block.setPositionX(outsideImage, -elementWidth - 10); // Left of the page engine.block.setPositionY(outsideImage, row1Y); engine.block.appendChild(page, outsideImage); // Create a properly placed image for reference (first in row) const validImage = engine.block.create('graphic'); engine.block.setName(validImage, 'Valid Image'); engine.block.setShape(validImage, engine.block.createShape('rect')); const validFill = engine.block.createFill('image'); engine.block.setString( validFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_3.jpg' ); engine.block.setFill(validImage, validFill); engine.block.setWidth(validImage, elementWidth); engine.block.setHeight(validImage, elementHeight); engine.block.setPositionX(validImage, startX); engine.block.setPositionY(validImage, row1Y); engine.block.appendChild(page, validImage); // Create unfilled placeholder (second in row - triggers error) const placeholder = engine.block.create('graphic'); engine.block.setName(placeholder, 'Unfilled Placeholder'); engine.block.setShape(placeholder, engine.block.createShape('rect')); const placeholderFill = engine.block.createFill('image'); engine.block.setFill(placeholder, placeholderFill); engine.block.setWidth(placeholder, elementWidth); engine.block.setHeight(placeholder, elementHeight); engine.block.setPositionX(placeholder, startX + elementWidth + spacing); engine.block.setPositionY(placeholder, row1Y); engine.block.appendChild(page, placeholder); engine.block.setScopeEnabled(placeholder, 'fill/change', true); engine.block.setPlaceholderBehaviorEnabled(placeholderFill, true); engine.block.setPlaceholderEnabled(placeholder, true); engine.block.setPlaceholderControlsOverlayEnabled(placeholder, true); engine.block.setPlaceholderControlsButtonEnabled(placeholder, true); // Create text that will be partially obscured (third in row) const obscuredText = engine.block.create('text'); engine.block.setName(obscuredText, 'Obscured Text'); engine.block.setPositionX( obscuredText, startX + (elementWidth + spacing) * 2 ); engine.block.setPositionY(obscuredText, row1Y); engine.block.setWidth(obscuredText, elementWidth); engine.block.setHeight(obscuredText, elementHeight); engine.block.replaceText(obscuredText, 'Hidden'); engine.block.setFloat(obscuredText, 'text/fontSize', 48); engine.block.appendChild(page, obscuredText); // Create a shape that overlaps the text (added after text = on top) const overlappingShape = engine.block.create('graphic'); engine.block.setName(overlappingShape, 'Overlapping Shape'); engine.block.setShape(overlappingShape, engine.block.createShape('rect')); const shapeFill = engine.block.createFill('color'); engine.block.setColor(shapeFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 0.8 }); engine.block.setFill(overlappingShape, shapeFill); engine.block.setWidth(overlappingShape, elementWidth); engine.block.setHeight(overlappingShape, elementHeight); engine.block.setPositionX( overlappingShape, startX + (elementWidth + spacing) * 2 ); engine.block.setPositionY(overlappingShape, row1Y); engine.block.appendChild(page, overlappingShape); // Create an image that protrudes from the page (fourth in row - will trigger warning) // Extends past right page boundary const protrudingImage = engine.block.create('graphic'); engine.block.setName(protrudingImage, 'Protruding Image'); engine.block.setShape(protrudingImage, engine.block.createShape('rect')); const protrudingFill = engine.block.createFill('image'); engine.block.setString( protrudingFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); engine.block.setFill(protrudingImage, protrudingFill); engine.block.setWidth(protrudingImage, elementWidth); engine.block.setHeight(protrudingImage, elementHeight); engine.block.setPositionX(protrudingImage, pageWidth - elementWidth / 2); // Extends 75px past right engine.block.setPositionY(protrudingImage, row1Y); engine.block.appendChild(page, protrudingImage); // Add explanatory text below the row const explanationText = engine.block.create('text'); engine.block.setPositionX(explanationText, 50); engine.block.setPositionY(explanationText, row1Y + elementHeight + 40); engine.block.setWidth(explanationText, pageWidth - 100); engine.block.setHeightMode(explanationText, 'Auto'); engine.block.replaceText( explanationText, 'Click Export to run validation. Move elements to fix issues.' ); engine.block.setFloat(explanationText, 'text/fontSize', 48); engine.block.setEnum(explanationText, 'text/horizontalAlignment', 'Center'); engine.block.appendChild(page, explanationText); } private getBoundingBox( engine: CreativeEngine, blockId: number ): [number, number, number, number] { const x = engine.block.getGlobalBoundingBoxX(blockId); const y = engine.block.getGlobalBoundingBoxY(blockId); const width = engine.block.getGlobalBoundingBoxWidth(blockId); const height = engine.block.getGlobalBoundingBoxHeight(blockId); return [x, y, x + width, y + height]; } private calculateOverlap( box1: [number, number, number, number], box2: [number, number, number, number] ): number { const [ax1, ay1, ax2, ay2] = box1; const [bx1, by1, bx2, by2] = box2; const overlapWidth = Math.max(0, Math.min(ax2, bx2) - Math.max(ax1, bx1)); const overlapHeight = Math.max(0, Math.min(ay2, by2) - Math.max(ay1, by1)); const overlapArea = overlapWidth * overlapHeight; const box1Area = (ax2 - ax1) * (ay2 - ay1); if (box1Area === 0) return 0; return overlapArea / box1Area; } private getBlockName(engine: CreativeEngine, blockId: number): string { const name = engine.block.getName(blockId); if (name) return name; const kind = engine.block.getKind(blockId); return kind.charAt(0).toUpperCase() + kind.slice(1); } private getRelevantBlocks(engine: CreativeEngine): number[] { return [ ...engine.block.findByType('text'), ...engine.block.findByType('graphic') ]; } private findOutsideBlocks( engine: CreativeEngine, page: number ): ValidationIssue[] { const issues: ValidationIssue[] = []; const pageBounds = this.getBoundingBox(engine, page); for (const blockId of this.getRelevantBlocks(engine)) { if (!engine.block.isValid(blockId)) continue; const blockBounds = this.getBoundingBox(engine, blockId); const overlap = this.calculateOverlap(blockBounds, pageBounds); if (overlap === 0) { // Element is completely outside the page issues.push({ type: 'outside_page', severity: 'error', blockId, blockName: this.getBlockName(engine, blockId), message: 'Element is completely outside the visible page area' }); } } return issues; } private findProtrudingBlocks( engine: CreativeEngine, page: number ): ValidationIssue[] { const issues: ValidationIssue[] = []; const pageBounds = this.getBoundingBox(engine, page); for (const blockId of this.getRelevantBlocks(engine)) { if (!engine.block.isValid(blockId)) continue; // Compare element bounds against page bounds const blockBounds = this.getBoundingBox(engine, blockId); const overlap = this.calculateOverlap(blockBounds, pageBounds); // Protruding: partially inside (overlap > 0) but not fully inside (overlap < 1) if (overlap > 0 && overlap < 0.99) { issues.push({ type: 'protruding', severity: 'warning', blockId, blockName: this.getBlockName(engine, blockId), message: 'Element extends beyond page boundaries' }); } } return issues; } private findObscuredText( engine: CreativeEngine, page: number ): ValidationIssue[] { const issues: ValidationIssue[] = []; const children = engine.block.getChildren(page); const textBlocks = engine.block.findByType('text'); for (const textId of textBlocks) { if (!engine.block.isValid(textId)) continue; const textIndex = children.indexOf(textId); if (textIndex === -1) continue; // Elements later in children array are rendered on top const blocksAbove = children.slice(textIndex + 1); for (const aboveId of blocksAbove) { // Skip text blocks - they don't typically obscure other text if (engine.block.getType(aboveId) === '//ly.img.ubq/text') continue; const overlap = this.calculateOverlap( this.getBoundingBox(engine, textId), this.getBoundingBox(engine, aboveId) ); if (overlap > 0) { // Text is obscured by element above it issues.push({ type: 'text_obscured', severity: 'warning', blockId: textId, blockName: this.getBlockName(engine, textId), message: 'Text may be partially hidden by overlapping elements' }); break; } } } return issues; } private findUnfilledPlaceholders(engine: CreativeEngine): ValidationIssue[] { const issues: ValidationIssue[] = []; const placeholders = engine.block.findAllPlaceholders(); for (const blockId of placeholders) { if (!engine.block.isValid(blockId)) continue; if (!this.isPlaceholderFilled(engine, blockId)) { issues.push({ type: 'unfilled_placeholder', severity: 'error', blockId, blockName: this.getBlockName(engine, blockId), message: 'Placeholder has not been filled with content' }); } } return issues; } private isPlaceholderFilled( engine: CreativeEngine, blockId: number ): boolean { const fillId = engine.block.getFill(blockId); if (!fillId || !engine.block.isValid(fillId)) return false; const fillType = engine.block.getType(fillId); // Check image fill - empty URI means unfilled placeholder if (fillType === '//ly.img.ubq/fill/image') { const imageUri = engine.block.getString(fillId, 'fill/image/imageFileURI'); return imageUri !== '' && imageUri !== undefined; } // Other fill types are considered filled return true; } private validateDesign(engine: CreativeEngine): ValidationResult { const page = engine.block.findByType('page')[0]; const outsideIssues = this.findOutsideBlocks(engine, page); const protrudingIssues = this.findProtrudingBlocks(engine, page); const obscuredIssues = this.findObscuredText(engine, page); const placeholderIssues = this.findUnfilledPlaceholders(engine); const allIssues = [ ...outsideIssues, ...protrudingIssues, ...obscuredIssues, ...placeholderIssues ]; return { errors: allIssues.filter((i) => i.severity === 'error'), warnings: allIssues.filter((i) => i.severity === 'warning') }; } private overrideExportAction( cesdk: CreativeEditorSDK, engine: CreativeEngine ): void { cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: ['ly.img.exportImage.navigationBar'] }); const exportDesign = cesdk.actions.get('exportDesign'); cesdk.actions.register('exportDesign', async () => { const result = this.validateDesign(engine); const hasErrors = result.errors.length > 0; const hasWarnings = result.warnings.length > 0; // Log all issues to console for debugging if (hasErrors || hasWarnings) { console.log('Validation Results:', result); } // Block export for errors if (hasErrors) { cesdk.ui.showNotification({ message: `${result.errors.length} error(s) found - export blocked`, type: 'error', duration: 'long' }); // Select first problematic block const firstError = result.errors[0]; if (engine.block.isValid(firstError.blockId)) { engine.block.select(firstError.blockId); } return; } // Show warning but allow export if (hasWarnings) { cesdk.ui.showNotification({ message: `${result.warnings.length} warning(s) found - proceeding with export`, type: 'warning', duration: 'medium' }); } // Proceed with export exportDesign(); }); } } export default Example; ``` This guide demonstrates how to detect elements outside the page, find protruding content, identify obscured text, and integrate validation into the export workflow. ## Getting Element Bounds To detect spatial issues, we need to get the bounding box of elements in global coordinates. The `getGlobalBoundingBox*` methods return positions that account for all transformations: ```typescript highlight-get-bounding-box const x = engine.block.getGlobalBoundingBoxX(blockId); const y = engine.block.getGlobalBoundingBoxY(blockId); const width = engine.block.getGlobalBoundingBoxWidth(blockId); const height = engine.block.getGlobalBoundingBoxHeight(blockId); return [x, y, x + width, y + height]; ``` This returns coordinates as `[x1, y1, x2, y2]` representing the top-left and bottom-right corners of the element. The overlap between element and page bounds is calculated as the intersection area divided by the element's total area. An overlap of 0 means completely outside, while 1 (100%) means fully inside. ## Detecting Elements Outside the Page Elements completely outside the page won't appear in the exported output. We find these by checking for blocks with zero overlap with the page bounds: ```typescript highlight-find-outside-blocks const blockBounds = this.getBoundingBox(engine, blockId); const overlap = this.calculateOverlap(blockBounds, pageBounds); if (overlap === 0) { // Element is completely outside the page ``` These issues are categorized as errors because the content is completely missing from the export. ## Detecting Protruding Elements Elements that extend beyond the page boundaries will be partially cropped in the export. For each block, compare its bounds against the page bounds and calculate the overlap ratio: ```typescript highlight-find-protruding-blocks // Compare element bounds against page bounds const blockBounds = this.getBoundingBox(engine, blockId); const overlap = this.calculateOverlap(blockBounds, pageBounds); // Protruding: partially inside (overlap > 0) but not fully inside (overlap < 1) if (overlap > 0 && overlap < 0.99) { ``` An overlap between 0% and 100% indicates the element is partially inside the page. These issues are warnings because the content is partially visible but may not appear as intended. ## Finding Obscured Text Text hidden behind other elements may be unreadable in the final export. First, get the stacking order and all text blocks: ```typescript highlight-find-obscured-text const children = engine.block.getChildren(page); const textBlocks = engine.block.findByType('text'); ``` The `getChildren()` method returns blocks in stacking order - elements later in the array are rendered on top. For each text block, check if any non-text element above it overlaps with its bounds: ```typescript highlight-check-text-obscured // Elements later in children array are rendered on top const blocksAbove = children.slice(textIndex + 1); for (const aboveId of blocksAbove) { // Skip text blocks - they don't typically obscure other text if (engine.block.getType(aboveId) === '//ly.img.ubq/text') continue; const overlap = this.calculateOverlap( this.getBoundingBox(engine, textId), this.getBoundingBox(engine, aboveId) ); if (overlap > 0) { // Text is obscured by element above it ``` We skip text-on-text comparisons since transparent text backgrounds don't typically obscure other text. When overlap is detected, we flag the text as potentially obscured. ## Checking Placeholder Content Placeholders mark areas where users must add content before export. First, find all placeholder blocks in the design: ```typescript highlight-find-placeholders const placeholders = engine.block.findAllPlaceholders(); ``` Then inspect each placeholder's fill to determine if content has been added. Get the fill block and check its type to determine the validation logic: ```typescript highlight-check-placeholder-filled const fillId = engine.block.getFill(blockId); if (!fillId || !engine.block.isValid(fillId)) return false; const fillType = engine.block.getType(fillId); // Check image fill - empty URI means unfilled placeholder if (fillType === '//ly.img.ubq/fill/image') { const imageUri = engine.block.getString(fillId, 'fill/image/imageFileURI'); return imageUri !== '' && imageUri !== undefined; } ``` For image placeholders, check if the `fill/image/imageFileURI` property has a value. An empty or undefined URI indicates the placeholder hasn't been filled. Unfilled placeholders are treated as errors that block export, ensuring users complete all required content before exporting. ## Overriding the Export Action Intercept the navigation bar export action using `cesdk.actions.get()` and `cesdk.actions.register()`. The action ID `'exportDesign'` controls the export button in the CE.SDK interface: ```typescript highlight-override-export-action cesdk.actions.register('exportDesign', async () => { const result = this.validateDesign(engine); const hasErrors = result.errors.length > 0; const hasWarnings = result.warnings.length > 0; // Log all issues to console for debugging if (hasErrors || hasWarnings) { console.log('Validation Results:', result); } // Block export for errors if (hasErrors) { cesdk.ui.showNotification({ message: `${result.errors.length} error(s) found - export blocked`, type: 'error', duration: 'long' }); // Select first problematic block const firstError = result.errors[0]; if (engine.block.isValid(firstError.blockId)) { engine.block.select(firstError.blockId); } return; } // Show warning but allow export if (hasWarnings) { cesdk.ui.showNotification({ message: `${result.warnings.length} warning(s) found - proceeding with export`, type: 'warning', duration: 'medium' }); } // Proceed with export exportDesign(); }); ``` This override runs validation before export, shows notifications for errors or warnings, and selects the first problematic block to help users locate issues. Export proceeds only when validation passes. ## API Reference | Method | Purpose | |--------|---------| | `engine.block.getGlobalBoundingBoxX(id)` | Get element's global X position | | `engine.block.getGlobalBoundingBoxY(id)` | Get element's global Y position | | `engine.block.getGlobalBoundingBoxWidth(id)` | Get element's global width | | `engine.block.getGlobalBoundingBoxHeight(id)` | Get element's global height | | `engine.block.findByType(type)` | Find all blocks of a specific type | | `engine.block.getChildren(id)` | Get child blocks in stacking order | | `engine.block.getType(id)` | Get the block's type string | | `engine.block.getName(id)` | Get the block's display name | | `engine.block.isValid(id)` | Check if block exists | | `engine.block.select(id)` | Select a block in the editor | | `engine.block.findAllPlaceholders()` | Find all placeholder blocks | | `engine.block.getFill(id)` | Get the fill block | | `engine.block.getString(id, property)` | Get a string property value | | `cesdk.actions.get(actionId)` | Get an action function (browser) | | `cesdk.actions.register(actionId, fn)` | Register an action (browser) | | `cesdk.ui.showNotification(options)` | Display notification (browser) | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Save" description: "Save design progress locally or to a backend service to allow for later editing or publishing." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/save-c8b124/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Save](https://img.ly/docs/cesdk/angular/export-save-publish/save-c8b124/) --- Save and serialize designs in CE.SDK for later retrieval, sharing, or storage using string or archive formats. ![Save designs showing different save format options](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-save-browser/) CE.SDK provides two formats for persisting designs. Choose the format based on your storage and portability requirements. ```typescript file=@cesdk_web_examples/guides-export-save-publish-save-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Save Designs Guide * * Demonstrates how to save and serialize designs in CE.SDK: * - Saving scenes to string format for database storage * - Saving scenes to archive format with embedded assets * - Using built-in save actions and customization */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (cesdk == null) { throw new Error('CE.SDK instance is required'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; await engine.scene.loadFromURL( 'https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_1.scene' ); const page = engine.scene.getCurrentPage(); if (page == null) { throw new Error('No page found in scene'); } engine.scene.zoomToBlock(page, { padding: 40 }); cesdk.actions.register('saveScene', async () => { const sceneString = await engine.scene.saveToString(); // Send to your backend API console.log('Custom save:', sceneString.length, 'bytes'); }); // Button: Save Scene & Download const handleSaveScene = async () => { const sceneString = await engine.scene.saveToString(); const sceneBlob = new Blob([sceneString], { type: 'application/octet-stream' }); await cesdk.utils.downloadFile(sceneBlob, 'application/octet-stream'); cesdk.ui.showNotification({ message: `Scene downloaded (${(sceneString.length / 1024).toFixed(1)} KB)`, type: 'success' }); }; // Button: Save to Archive & Download const handleSaveToArchive = async () => { const archiveBlob = await engine.scene.saveToArchive(); await cesdk.utils.downloadFile(archiveBlob, 'application/zip'); cesdk.ui.showNotification({ message: `Archive downloaded (${(archiveBlob.size / 1024).toFixed(1)} KB)`, type: 'success' }); }; const handleLoadScene = async () => { await cesdk.actions.run('importScene', { format: 'scene' }); }; const handleLoadArchive = async () => { await cesdk.actions.run('importScene', { format: 'archive' }); const loadedPage = engine.scene.getCurrentPage(); if (loadedPage != null) { engine.scene.zoomToBlock(loadedPage, { padding: 40 }); } }; cesdk.ui.insertOrderComponent({ in: 'ly.img.navigation.bar', position: 'end' }, { id: 'ly.img.actions.navigationBar', children: [ { id: 'ly.img.action.navigationBar', key: 'save-scene', label: 'Save Scene', icon: '@imgly/Save', onClick: handleSaveScene }, { id: 'ly.img.action.navigationBar', key: 'save-archive', label: 'Save Archive', icon: '@imgly/Download', onClick: handleSaveToArchive }, { id: 'ly.img.action.navigationBar', key: 'load-scene', label: 'Load Scene', icon: '@imgly/Upload', onClick: handleLoadScene }, { id: 'ly.img.action.navigationBar', key: 'load-archive', label: 'Load Archive', icon: '@imgly/Upload', onClick: handleLoadArchive } ] }); } } export default Example; ``` ## Save Format Comparison | Format | Method | Assets | Best For | | ------ | ------ | ------ | -------- | | String | `saveToString()` | Referenced by URL | Database storage, cloud sync | | Archive | `saveToArchive()` | Embedded in ZIP | Offline use, file sharing | **String format** produces a lightweight Base64-encoded string where assets remain as URL references. Use this when asset URLs will remain accessible. **Archive format** creates a self-contained ZIP with all assets embedded. Use this for portable designs that work offline. ## Save to String Serialize the current scene to a Base64-encoded string suitable for database storage. ```typescript highlight=highlight-save-to-string const sceneString = await engine.scene.saveToString(); ``` The string contains the complete scene structure but references assets by their original URLs. ## Save to Archive Create a self-contained ZIP file with the scene and all embedded assets. ```typescript highlight=highlight-save-to-archive const archiveBlob = await engine.scene.saveToArchive(); ``` The archive includes all pages, elements, and asset data in a single portable file. ## Compression Options CE.SDK supports optional compression for saved scenes to reduce file size. Compression is particularly useful for large scenes or when storage space is limited. ```typescript // Save with Zstd compression (recommended) const compressed = await cesdk.engine.scene.saveToString({ compression: { format: 'Zstd', level: 'Default' } }); ``` **Compression Formats:** - `'None'` - No compression (default) - `'Zstd'` - Zstandard compression (recommended for best performance) **Compression Levels:** - `'Fastest'` - Fastest compression, larger output - `'Default'` - Balanced speed and size (recommended) - `'Best'` - Best compression, slower **Performance:** Compression adds minimal overhead (\<50ms) while reducing scene size by approximately 64%. The Default level provides the best balance of speed and compression ratio. ## Download to User Device Use `cesdk.utils.downloadFile()` to trigger a browser download with the correct MIME type. For scene strings, convert to a Blob first: ```typescript highlight=highlight-download-scene const sceneBlob = new Blob([sceneString], { type: 'application/octet-stream' }); await cesdk.utils.downloadFile(sceneBlob, 'application/octet-stream'); ``` For archive blobs, pass directly to the download utility: ```typescript highlight=highlight-download-archive await cesdk.utils.downloadFile(archiveBlob, 'application/zip'); ``` This utility handles creating and revoking object URLs automatically. ## Load Scene from File Use the built-in `importScene` action to open a file picker for `.scene` files. This restores a previously saved design from its serialized string format. ```typescript highlight=highlight-load-scene const handleLoadScene = async () => { await cesdk.actions.run('importScene', { format: 'scene' }); }; ``` Scene files are lightweight but require the original asset URLs to remain accessible. ## Load Archive from File Load a self-contained `.zip` archive that includes all embedded assets. ```typescript highlight=highlight-load-archive const handleLoadArchive = async () => { await cesdk.actions.run('importScene', { format: 'archive' }); ``` Archives are portable and work offline since all assets are bundled within the file. ## Built-in Save Action CE.SDK includes a built-in `saveScene` action that integrates with the navigation bar. ### Running an Action Trigger the default save behavior programmatically using `actions.run()`: ```typescript await cesdk.actions.run('saveScene'); ``` This executes the registered handler for `saveScene`, which by default downloads the scene file. ### Customizing an Action Override the default behavior by registering a custom handler: ```typescript highlight=highlight-register-custom-action cesdk.actions.register('saveScene', async () => { const sceneString = await engine.scene.saveToString(); // Send to your backend API console.log('Custom save:', sceneString.length, 'bytes'); }); ``` The registered handler runs when the built-in save button is clicked or when the action is triggered via `actions.run()`. ## API Reference | Method | Description | | ------ | ----------- | | `engine.scene.saveToString()` | Serialize scene to Base64 string | | `engine.scene.saveToArchive()` | Save scene with assets as ZIP blob | | `engine.scene.loadFromString()` | Load scene from serialized string | | `engine.scene.loadFromURL()` | Load scene from remote URL | | `engine.scene.loadFromArchiveURL()` | Load scene from URL (file://, http://, https://, or object URL) | | `cesdk.utils.downloadFile()` | Download blob or string to user device | | `cesdk.actions.run()` | Execute a registered action with parameters | | `cesdk.actions.register()` | Register or override an action handler | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/angular/export-save-publish/export/overview-9ed3a8/) - Export designs to image, PDF, and video formats - [Load Scene](https://img.ly/docs/cesdk/angular/open-the-editor/load-scene-478833/) - Load scenes from remote URLs and archives - [Store Custom Metadata](https://img.ly/docs/cesdk/angular/export-save-publish/store-custom-metadata-337248/) - Attach metadata like tags or version info to designs - [Partial Export](https://img.ly/docs/cesdk/angular/export-save-publish/export/partial-export-89aaf6/) - Export individual blocks or selections --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Store Custom Metadata" description: "Attach, retrieve, and manage custom key-value metadata on design blocks in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/export-save-publish/store-custom-metadata-337248/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Store Custom Metadata](https://img.ly/docs/cesdk/angular/export-save-publish/store-custom-metadata-337248/) --- Attach custom key-value metadata to design blocks in CE.SDK for tracking asset origins, storing application state, or linking to external systems. ![Store Custom Metadata example showing an image block with metadata](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-store-custom-metadata-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-save-publish-store-custom-metadata-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-save-publish-store-custom-metadata-browser/) Metadata lets you attach arbitrary string key-value pairs to any design block. This data is invisible to end users but persists with the scene through save/load operations. Common use cases include tracking asset origins, storing application-specific state, and linking blocks to external databases or content management systems. ```typescript file=@cesdk_web_examples/guides-export-save-publish-store-custom-metadata-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Store Custom Metadata Guide * * Demonstrates how to attach, retrieve, and manage custom metadata on design blocks: * - Setting metadata key-value pairs * - Getting metadata values by key * - Checking if metadata exists * - Listing all metadata keys * - Removing metadata * - Storing structured data as JSON */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Create an image block to attach metadata to const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: 400, height: 300 } } ); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); // Set metadata key-value pairs on the block engine.block.setMetadata(imageBlock, 'externalId', 'asset-12345'); engine.block.setMetadata(imageBlock, 'source', 'user-upload'); engine.block.setMetadata(imageBlock, 'uploadedBy', 'user@example.com'); console.log('Set metadata: externalId, source, uploadedBy'); // Retrieve a metadata value by key if (engine.block.hasMetadata(imageBlock, 'externalId')) { const externalId = engine.block.getMetadata(imageBlock, 'externalId'); console.log('External ID:', externalId); } // List all metadata keys on the block const allKeys = engine.block.findAllMetadata(imageBlock); console.log('All metadata keys:', allKeys); // Log all key-value pairs for (const key of allKeys) { const value = engine.block.getMetadata(imageBlock, key); console.log(` ${key}: ${value}`); } // Store structured data as JSON const generationInfo = { source: 'ai-generated', model: 'stable-diffusion', timestamp: Date.now() }; engine.block.setMetadata( imageBlock, 'generationInfo', JSON.stringify(generationInfo) ); // Retrieve and parse structured data const retrievedJson = engine.block.getMetadata( imageBlock, 'generationInfo' ); const parsedInfo = JSON.parse(retrievedJson); console.log('Parsed generation info:', parsedInfo); // Remove a metadata key engine.block.removeMetadata(imageBlock, 'uploadedBy'); console.log('Removed metadata key: uploadedBy'); // Verify the key was removed const hasUploadedBy = engine.block.hasMetadata(imageBlock, 'uploadedBy'); console.log('Has uploadedBy after removal:', hasUploadedBy); // List remaining keys const remainingKeys = engine.block.findAllMetadata(imageBlock); console.log('Remaining metadata keys:', remainingKeys); // Select the image block to show it in focus engine.block.select(imageBlock); console.log( 'Metadata guide initialized. Check the console for metadata operations.' ); } } export default Example; ``` This guide covers how to set, retrieve, list, and remove metadata on blocks, as well as how to store structured data as JSON strings. ## Initialize CE.SDK We start by initializing CE.SDK with a basic configuration. The metadata APIs are available on the `engine.block` namespace. ```typescript highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Create an image block to attach metadata to const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_1.jpg', { size: { width: 400, height: 300 } } ); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 200); engine.block.setPositionY(imageBlock, 150); ``` ## Set Metadata Use `engine.block.setMetadata()` to attach a key-value pair to a block. Both the key and value must be strings. If the key already exists, the value is overwritten. ```typescript highlight-set-metadata // Set metadata key-value pairs on the block engine.block.setMetadata(imageBlock, 'externalId', 'asset-12345'); engine.block.setMetadata(imageBlock, 'source', 'user-upload'); engine.block.setMetadata(imageBlock, 'uploadedBy', 'user@example.com'); console.log('Set metadata: externalId, source, uploadedBy'); ``` You can attach multiple metadata entries to a single block. Each entry is independent and can be accessed, modified, or removed separately. ## Get Metadata Use `engine.block.getMetadata()` to retrieve a value by its key. This method throws an error if the key doesn't exist, so always check with `hasMetadata()` first for conditional access. ```typescript highlight-get-metadata // Retrieve a metadata value by key if (engine.block.hasMetadata(imageBlock, 'externalId')) { const externalId = engine.block.getMetadata(imageBlock, 'externalId'); console.log('External ID:', externalId); } ``` The `hasMetadata()` method returns `true` if the block has metadata for the specified key, and `false` otherwise. This pattern prevents runtime errors when accessing metadata that may not be set. ## List All Metadata Keys Use `engine.block.findAllMetadata()` to get an array of all metadata keys stored on a block. ```typescript highlight-find-all-metadata // List all metadata keys on the block const allKeys = engine.block.findAllMetadata(imageBlock); console.log('All metadata keys:', allKeys); // Log all key-value pairs for (const key of allKeys) { const value = engine.block.getMetadata(imageBlock, key); console.log(` ${key}: ${value}`); } ``` This is useful for iterating through all metadata on a block or debugging what metadata is attached. ## Store Structured Data Since metadata values must be strings, you can store structured data by serializing it to JSON. Parse the JSON when retrieving the data. ```typescript highlight-store-structured-data // Store structured data as JSON const generationInfo = { source: 'ai-generated', model: 'stable-diffusion', timestamp: Date.now() }; engine.block.setMetadata( imageBlock, 'generationInfo', JSON.stringify(generationInfo) ); // Retrieve and parse structured data const retrievedJson = engine.block.getMetadata( imageBlock, 'generationInfo' ); const parsedInfo = JSON.parse(retrievedJson); console.log('Parsed generation info:', parsedInfo); ``` This pattern lets you store complex objects like configuration settings, generation parameters, or any structured information that can be serialized to JSON. ## Remove Metadata Use `engine.block.removeMetadata()` to delete a key-value pair from a block. This method does not throw an error if the key doesn't exist. ```typescript highlight-remove-metadata // Remove a metadata key engine.block.removeMetadata(imageBlock, 'uploadedBy'); console.log('Removed metadata key: uploadedBy'); ``` After removal, you can verify the key was deleted by checking with `hasMetadata()`. ```typescript highlight-verify-removal // Verify the key was removed const hasUploadedBy = engine.block.hasMetadata(imageBlock, 'uploadedBy'); console.log('Has uploadedBy after removal:', hasUploadedBy); // List remaining keys const remainingKeys = engine.block.findAllMetadata(imageBlock); console.log('Remaining metadata keys:', remainingKeys); ``` ## Metadata Persistence Metadata is automatically preserved when saving scenes with `engine.scene.saveToString()` or `engine.scene.saveToArchive()`. When loading a saved scene, all metadata is restored to the blocks. > **Note:** Metadata is only preserved when saving the scene data. Exporting to image or > video formats (PNG, JPEG, MP4) does not include metadata since these are final > output formats. ## Troubleshooting ### getMetadata Throws Error If `getMetadata()` throws an error, the key doesn't exist on the block. Always use `hasMetadata()` to check before retrieving: ```typescript if (engine.block.hasMetadata(block, 'myKey')) { const value = engine.block.getMetadata(block, 'myKey'); } ``` ### Metadata Lost After Load Ensure you're saving with `saveToString()` or `saveToArchive()`, not exporting to image/video formats. Only scene saves preserve metadata. ### Large Metadata Values Metadata is designed for small strings. Very large values may impact performance during save/load operations. For large data, consider storing a reference (like a URL or ID) rather than the data itself. ## API Reference | Method | Description | | --------------------------------------------- | ----------------------------------------- | | `engine.block.setMetadata(block, key, value)` | Set a metadata key-value pair on a block | | `engine.block.getMetadata(block, key)` | Get the value for a metadata key | | `engine.block.hasMetadata(block, key)` | Check if a metadata key exists | | `engine.block.findAllMetadata(block)` | List all metadata keys on a block | | `engine.block.removeMetadata(block, key)` | Remove a metadata key-value pair | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "File Format Support" description: "See which image, video, audio, font, and template formats CE.SDK supports for import and export." platform: angular url: "https://img.ly/docs/cesdk/angular/file-format-support-3c4b2a/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/angular/compatibility-fef719/) > [File Format Support](https://img.ly/docs/cesdk/angular/file-format-support-3c4b2a/) --- ## Importing Media ### SVG Limitations ## Exporting Media ## Importing Templates ## Font Formats ## Video & Audio Codecs CE.SDK supports the most widely adopted video and audio codecs to ensure compatibility across platforms: ## Size Limits ### Image Resolution Limits ### Video Resolution & Duration Limits --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Fills" description: "Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements." platform: angular url: "https://img.ly/docs/cesdk/angular/fills-402ddc/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) --- --- ## Related Pages - [Fills](https://img.ly/docs/cesdk/angular/fills/overview-3895ee/) - Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements. - [Color Fills](https://img.ly/docs/cesdk/angular/fills/color-7129cd/) - Learn how to apply solid color fills to design elements using RGB, CMYK, and Spot Colors in CE.SDK - [Gradient Fills](https://img.ly/docs/cesdk/angular/filters-and-effects/gradients-0ff079/) - Learn how to create and apply linear, radial, and conical gradient fills to design elements in CE.SDK - [Image Fills](https://img.ly/docs/cesdk/angular/fills/image-e9cb5c/) - Apply photos, textures, and patterns to design elements using image fills in CE.SDK for web browsers. - [Video Fills](https://img.ly/docs/cesdk/angular/fills/video-ec7f9f/) - Learn how to apply video content as fills to design elements in CE.SDK. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Color Fills" description: "Learn how to apply solid color fills to design elements using RGB, CMYK, and Spot Colors in CE.SDK" platform: angular url: "https://img.ly/docs/cesdk/angular/fills/color-7129cd/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) > [Solid Color](https://img.ly/docs/cesdk/angular/fills/color-7129cd/) --- Apply uniform solid colors to shapes, text, and design blocks using CE.SDK's comprehensive color fill system with support for multiple color spaces. ![Color Fills example showing various colored shapes with RGB, CMYK, and Spot Colors](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-color-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-color-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-fills-color-browser/) Color fills are one of the fundamental fill types in CE.SDK, allowing you to paint design blocks with solid, uniform colors. Unlike gradient fills that transition between colors or image fills that display photo content, color fills apply a single color across the entire block. The color fill system supports multiple color spaces including RGB for screen display, CMYK for print workflows, and Spot Colors for brand consistency. ```typescript file=@cesdk_web_examples/guides-fills-color-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Color Fills Guide * * This example demonstrates: * - Creating and applying color fills * - Working with RGB, CMYK, and Spot Colors * - Managing fill properties * - Enabling/disabling fills * - Sharing fills between blocks */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Create a design scene using CE.SDK cesdk method await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page background to light gray const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.96, g: 0.96, b: 0.96, a: 1.0 }); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 12); const { blockWidth, blockHeight, getPosition } = layout; // Helper function to create a shape with a fill const createShapeWithFill = ( shapeType: 'rect' | 'ellipse' | 'polygon' | 'star' = 'rect' ): { block: number; fill: number } => { const block = engine.block.create('graphic'); const shape = engine.block.createShape(shapeType); engine.block.setShape(block, shape); // Set size engine.block.setWidth(block, blockWidth); engine.block.setHeight(block, blockHeight); // Append to page engine.block.appendChild(page, block); // Check if block supports fills const canHaveFill = engine.block.supportsFill(block); if (!canHaveFill) { throw new Error('Block does not support fills'); } // Create a color fill const colorFill = engine.block.createFill('color'); // Apply the fill to the block engine.block.setFill(block, colorFill); return { block, fill: colorFill }; }; // Example 1: RGB Color Fill (Red) const { block: rgbBlock, fill: rgbFill } = createShapeWithFill(); engine.block.setColor(rgbFill, 'fill/color/value', { r: 1.0, // Red (0.0 to 1.0) g: 0.0, // Green b: 0.0, // Blue a: 1.0 // Alpha (opacity) }); // Example 2: RGB Color Fill (Green with transparency) const { block: transparentBlock, fill: transparentFill } = createShapeWithFill(); engine.block.setColor(transparentFill, 'fill/color/value', { r: 0.0, g: 0.8, b: 0.2, a: 0.5 // 50% opacity }); // Example 3: RGB Color Fill (Blue) const { block: blueBlock, fill: blueFill } = createShapeWithFill(); engine.block.setColor(blueFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); // Get the current fill from a block const currentFill = engine.block.getFill(blueBlock); const fillType = engine.block.getType(currentFill); // eslint-disable-next-line no-console console.log('Fill type:', fillType); // '//ly.img.ubq/fill/color' // Get the current color value const currentColor = engine.block.getColor(blueFill, 'fill/color/value'); // eslint-disable-next-line no-console console.log('Current color:', currentColor); // Example 4: CMYK Color Fill (Magenta) const { block: cmykBlock, fill: cmykFill } = createShapeWithFill('ellipse'); engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, // Cyan (0.0 to 1.0) m: 1.0, // Magenta y: 0.0, // Yellow k: 0.0, // Key/Black tint: 1.0 // Tint value (0.0 to 1.0) }); // Example 5: Print-Ready CMYK Color const { block: printBlock, fill: printFill } = createShapeWithFill('ellipse'); engine.block.setColor(printFill, 'fill/color/value', { c: 0.0, m: 0.85, y: 1.0, k: 0.0, tint: 1.0 }); // Example 6: Spot Color (Brand Color) // First define the spot color globally engine.editor.setSpotColorRGB('BrandRed', 0.9, 0.1, 0.1); engine.editor.setSpotColorRGB('BrandBlue', 0.1, 0.3, 0.9); // Then apply to fill const { block: spotBlock, fill: spotFill } = createShapeWithFill('ellipse'); engine.block.setColor(spotFill, 'fill/color/value', { name: 'BrandRed', tint: 1.0, externalReference: '' // Optional reference system }); // Example 7: Brand Color Application // Apply brand color to multiple elements const { block: headerBlock, fill: headerFill } = createShapeWithFill('star'); const brandColor = { name: 'BrandBlue', tint: 1.0, externalReference: '' }; engine.block.setColor(headerFill, 'fill/color/value', brandColor); // Example 8: Second element with same brand color const { block: buttonBlock, fill: buttonFill } = createShapeWithFill('star'); engine.block.setColor(buttonFill, 'fill/color/value', brandColor); // Example 9: Toggle fill visibility const { block: toggleBlock, fill: toggleFill } = createShapeWithFill('star'); engine.block.setColor(toggleFill, 'fill/color/value', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); // Check fill state const isEnabled = engine.block.isFillEnabled(toggleBlock); // eslint-disable-next-line no-console console.log('Fill enabled:', isEnabled); // true // Example 10: Shared Fill const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, blockWidth); engine.block.setHeight(block1, blockHeight / 2); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, blockWidth); engine.block.setHeight(block2, blockHeight / 2); engine.block.appendChild(page, block2); // Create one fill const sharedFill = engine.block.createFill('color'); engine.block.setColor(sharedFill, 'fill/color/value', { r: 0.5, g: 0.0, b: 0.5, a: 1.0 }); // Apply to both blocks engine.block.setFill(block1, sharedFill); engine.block.setFill(block2, sharedFill); // Example 11: Yellow Star const { block: replaceBlock, fill: replaceFill } = createShapeWithFill('star'); engine.block.setColor(replaceFill, 'fill/color/value', { r: 0.9, g: 0.9, b: 0.1, a: 1.0 }); // Example 12: Color Space Conversion (for demonstration) const rgbColor = { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }; // Convert to CMYK const cmykColor = engine.editor.convertColorToColorSpace(rgbColor, 'CMYK'); // eslint-disable-next-line no-console console.log('Converted CMYK color:', cmykColor); // ===== Position all blocks in grid layout ===== const blocks = [ rgbBlock, // Position 0 transparentBlock, // Position 1 blueBlock, // Position 2 cmykBlock, // Position 3 printBlock, // Position 4 spotBlock, // Position 5 headerBlock, // Position 6 buttonBlock, // Position 7 toggleBlock, // Position 8 block1, // Position 9 block2, // Position 10 replaceBlock // Position 11 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Zoom to fit all content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` This guide demonstrates how to create, apply, and modify color fills programmatically, work with different color spaces, and manage fill properties for various design elements. ## Understanding Color Fills ### What is a Color Fill? A color fill is a fill object identified by the type `'//ly.img.ubq/fill/color'` (or its shorthand `'color'`) that paints a design block with a single, uniform color. Color fills are part of the broader fill system in CE.SDK and contain a `fill/color/value` property that defines the actual color using various color space formats. Color fills differ from other fill types available in CE.SDK: - **Color fills**: Solid, uniform color across the entire block - **Gradient fills**: Color transitions (linear, radial, conical) - **Image fills**: Photo or raster content - **Video fills**: Animated video content ### Supported Color Spaces CE.SDK's color fill system supports multiple color spaces to accommodate different design and production workflows: - **RGB/sRGB**: Red, Green, Blue with alpha channel (standard for screen display) - **CMYK**: Cyan, Magenta, Yellow, Key (black) with tint (for print production) - **Spot Colors**: Named colors with RGB/CMYK approximations (for brand consistency) Each color space serves specific use cases—use RGB for digital designs, CMYK for print-ready content, and Spot Colors to maintain brand standards across projects. ## Checking Color Fill Support ### Verifying Block Compatibility Before applying color fills to a block, verify that the block type supports fills. Not all block types can have fills—for example, scene and page blocks typically don't support fills. ```typescript highlight-check-fill-support // Check if block supports fills const canHaveFill = engine.block.supportsFill(block); if (!canHaveFill) { throw new Error('Block does not support fills'); } ``` Graphic blocks, shapes, and text blocks typically support fills. Always check `supportsFill()` before accessing fill APIs to avoid runtime errors and ensure smooth operation. ## Creating Color Fills ### Creating a New Color Fill Create a new color fill instance using the `createFill()` method with the type `'color'` or the full type name `'//ly.img.ubq/fill/color'`. ```typescript highlight-create-fill // Create a color fill const colorFill = engine.block.createFill('color'); ``` The `createFill()` method returns a numeric fill ID. The fill exists independently until you attach it to a block using `setFill()`. If you create a fill but don't attach it to a block, you must destroy it manually to prevent memory leaks. ### Default Color Fill Properties New color fills have default properties—typically white or transparent. You can discover all available properties using `findAllProperties()`: ```typescript const properties = engine.block.findAllProperties(colorFillId); console.log(properties); // ["fill/color/value", "type"] ``` ## Applying Color Fills ### Setting a Fill on a Block Once you've created a color fill, attach it to a block using `setFill()`: ```typescript highlight-apply-fill // Apply the fill to the block engine.block.setFill(block, colorFill); ``` This example creates a graphic block with a rectangle shape and applies the color fill to it. The block will now render with the fill's color. ### Getting the Current Fill Retrieve the current fill attached to a block using `getFill()` and inspect its type: ```typescript highlight-get-fill // Get the current fill from a block const currentFill = engine.block.getFill(blueBlock); const fillType = engine.block.getType(currentFill); // eslint-disable-next-line no-console console.log('Fill type:', fillType); // '//ly.img.ubq/fill/color' ``` ## Modifying Color Fill Properties ### Setting RGB Colors Set the fill color using RGB values with the `setColor()` method. RGB values are normalized floats from 0.0 to 1.0, and the alpha channel controls opacity. ```typescript highlight-set-rgb const { block: rgbBlock, fill: rgbFill } = createShapeWithFill(); engine.block.setColor(rgbFill, 'fill/color/value', { r: 1.0, // Red (0.0 to 1.0) g: 0.0, // Green b: 0.0, // Blue a: 1.0 // Alpha (opacity) }); ``` The alpha channel (a) controls opacity: 1.0 is fully opaque, 0.0 is fully transparent. This allows you to create semi-transparent overlays and layered color effects. ### Setting CMYK Colors For print workflows, use CMYK color space with the `setColor()` method. CMYK values are also normalized from 0.0 to 1.0, and include a tint value for partial color application. ```typescript highlight-set-cmyk const { block: cmykBlock, fill: cmykFill } = createShapeWithFill('ellipse'); engine.block.setColor(cmykFill, 'fill/color/value', { c: 0.0, // Cyan (0.0 to 1.0) m: 1.0, // Magenta y: 0.0, // Yellow k: 0.0, // Key/Black tint: 1.0 // Tint value (0.0 to 1.0) }); ``` The tint value allows partial application of the color, useful for creating lighter variations without changing the base CMYK values. ### Setting Spot Colors Spot colors are named colors that must be defined before use. They're ideal for maintaining brand consistency and can have both RGB and CMYK approximations for different output scenarios. ```typescript highlight-set-spot // First define the spot color globally engine.editor.setSpotColorRGB('BrandRed', 0.9, 0.1, 0.1); engine.editor.setSpotColorRGB('BrandBlue', 0.1, 0.3, 0.9); // Then apply to fill const { block: spotBlock, fill: spotFill } = createShapeWithFill('ellipse'); engine.block.setColor(spotFill, 'fill/color/value', { name: 'BrandRed', tint: 1.0, externalReference: '' // Optional reference system }); ``` First, define the spot color globally using `setSpotColorRGB()` or `setSpotColorCMYK()`, then apply it to your fill using the color name. The tint value controls intensity from 0.0 to 1.0. ### Getting Current Color Value Retrieve the current color value from a fill using `getColor()`: ```typescript highlight-get-color // Get the current color value const currentColor = engine.block.getColor(blueFill, 'fill/color/value'); // eslint-disable-next-line no-console console.log('Current color:', currentColor); ``` ## Enabling and Disabling Color Fills ### Toggle Fill Visibility You can temporarily disable a fill without removing it from the block. This preserves all fill properties while making the block transparent: ```typescript highlight-toggle-fill const { block: toggleBlock, fill: toggleFill } = createShapeWithFill('star'); engine.block.setColor(toggleFill, 'fill/color/value', { r: 1.0, g: 0.5, b: 0.0, a: 1.0 }); // Check fill state const isEnabled = engine.block.isFillEnabled(toggleBlock); // eslint-disable-next-line no-console console.log('Fill enabled:', isEnabled); // true ``` Disabling fills is useful for creating stroke-only designs or for temporarily hiding fills during interactive editing sessions. The fill properties remain intact and can be re-enabled at any time. ## Additional Techniques ### Sharing Color Fills You can share a single fill instance between multiple blocks. Changes to the shared fill affect all blocks using it: ```typescript highlight-share-fill const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, blockWidth); engine.block.setHeight(block1, blockHeight / 2); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, blockWidth); engine.block.setHeight(block2, blockHeight / 2); engine.block.appendChild(page, block2); // Create one fill const sharedFill = engine.block.createFill('color'); engine.block.setColor(sharedFill, 'fill/color/value', { r: 0.5, g: 0.0, b: 0.5, a: 1.0 }); // Apply to both blocks engine.block.setFill(block1, sharedFill); engine.block.setFill(block2, sharedFill); ``` With shared fills, modifying the fill's color updates all blocks simultaneously. The fill is only destroyed when the last block referencing it is destroyed. ### Color Space Conversion Convert colors between different color spaces using `convertColorToColorSpace()`: ```typescript highlight-convert-color const rgbColor = { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }; // Convert to CMYK const cmykColor = engine.editor.convertColorToColorSpace(rgbColor, 'CMYK'); // eslint-disable-next-line no-console console.log('Converted CMYK color:', cmykColor); ``` This is useful when you need to ensure color consistency across different output mediums (screen vs. print). ## Common Use Cases ### Brand Color Application Define and apply brand colors as spot colors to maintain consistency across all design elements: ```typescript highlight-brand-colors // Apply brand color to multiple elements const { block: headerBlock, fill: headerFill } = createShapeWithFill('star'); const brandColor = { name: 'BrandBlue', tint: 1.0, externalReference: '' }; engine.block.setColor(headerFill, 'fill/color/value', brandColor); ``` Using spot colors ensures brand consistency and makes it easy to update all instances of a brand color by modifying the spot color definition. ### Transparency Effects Create semi-transparent overlays and visual effects by adjusting the alpha channel: ```typescript highlight-transparency const { block: transparentBlock, fill: transparentFill } = createShapeWithFill(); engine.block.setColor(transparentFill, 'fill/color/value', { r: 0.0, g: 0.8, b: 0.2, a: 0.5 // 50% opacity }); ``` ### Print-Ready Colors Use CMYK color space for designs destined for print production: ```typescript highlight-print-colors const { block: printBlock, fill: printFill } = createShapeWithFill('ellipse'); engine.block.setColor(printFill, 'fill/color/value', { c: 0.0, m: 0.85, y: 1.0, k: 0.0, tint: 1.0 }); ``` ## Troubleshooting ### Fill Not Visible If your fill doesn't appear: - Check if fill is enabled: `engine.block.isFillEnabled(block)` - Verify alpha channel is not 0: Check the `a` property in RGBA colors - Ensure block has valid dimensions (width and height > 0) - Confirm block is in the scene hierarchy ### Color Looks Different Than Expected If colors don't match expectations: - Verify you're using the correct color space (RGB vs CMYK) - Check if spot color is properly defined before use - Review tint values (should be 0.0-1.0) - Consider color space conversion for your output medium ### Memory Leaks To prevent memory leaks: - Always destroy replaced fills: `engine.block.destroy(oldFill)` - Don't create fills without attaching them to blocks - Clean up shared fills when they're no longer needed ### Cannot Apply Color to Block If you can't apply a color fill: - Verify block supports fills: `engine.block.supportsFill(block)` - Check if block has a shape: Some blocks require shapes before fills work - Ensure fill object is valid and not already destroyed ## API Reference | Method | Description | | ---------------------------------------- | ----------------------------------------- | | `createFill('color')` | Create a new color fill object | | `setFill(block, fill)` | Assign fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setColor(fill, property, color)` | Set color value (RGB, CMYK, or Spot) | | `getColor(fill, property)` | Get current color value | | `setFillEnabled(block, enabled)` | Enable or disable fill rendering | | `isFillEnabled(block)` | Check if fill is enabled | | `supportsFill(block)` | Check if block supports fills | | `findAllProperties(fill)` | List all properties of the fill | | `convertColorToColorSpace(color, space)` | Convert between color spaces | | `setSpotColorRGB(name, r, g, b)` | Define spot color with RGB approximation | | `setSpotColorCMYK(name, c, m, y, k)` | Define spot color with CMYK approximation | ## Next Steps Now that you understand color fills, explore other fill types and color management features: - Learn about Gradient Fills for color transitions - Explore Image Fills for photo content - Understand Fill Overview for the comprehensive fill system - Review Apply Colors for color management across properties - Study Blocks Concept for understanding the block system --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Image Fills" description: "Apply photos, textures, and patterns to design elements using image fills in CE.SDK for web browsers." platform: angular url: "https://img.ly/docs/cesdk/angular/fills/image-e9cb5c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) > [Image](https://img.ly/docs/cesdk/angular/fills/image-e9cb5c/) --- Fill shapes, text, and design blocks with photos and images from URLs, uploads, or asset libraries using CE.SDK's versatile image fill system. ![Image Fills example showing multiple images applied to design blocks with different fill modes](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-image-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-image-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-fills-image-browser/) Image fills paint design blocks with raster or vector image content, supporting various formats including PNG, JPEG, WebP, and SVG. You can load images from remote URLs, local files, data URIs, and asset libraries, with built-in support for responsive images through source sets and multiple content fill modes for flexible positioning. ```typescript file=@cesdk_web_examples/guides-fills-image-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Fill features are enabled by default in CE.SDK // You can check and control fill feature availability: const isFillEnabled = cesdk.feature.isEnabled('ly.img.fill', { engine: cesdk.engine }); console.log('Fill feature enabled:', isFillEnabled); await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Calculate responsive grid layout for demonstrations const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 7); const { blockWidth, blockHeight, getPosition } = layout; const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const blockSize = { width: blockWidth, height: blockHeight }; // ===== Section 1: Check Fill Support ===== // Check if a block supports fills before accessing fill APIs const testBlock = engine.block.create('graphic'); const canHaveFill = engine.block.supportsFill(testBlock); console.log('Block supports fills:', canHaveFill); engine.block.destroy(testBlock); // ===== Section 2: Create and Apply Image Fill ===== // Create a new image fill using the convenience API const coverImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, coverImageBlock); // Or create manually for more control const manualBlock = engine.block.create('graphic'); engine.block.setShape(manualBlock, engine.block.createShape('rect')); engine.block.setWidth(manualBlock, blockWidth); engine.block.setHeight(manualBlock, blockHeight); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); engine.block.setFill(manualBlock, imageFill); engine.block.appendChild(page, manualBlock); // Get the current fill from a block const currentFill = engine.block.getFill(coverImageBlock); const fillType = engine.block.getType(currentFill); console.log('Fill type:', fillType); // '//ly.img.ubq/fill/image' // ===== Section 3: Content Fill Modes ===== // Cover mode: Fill entire block, may crop image const coverBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: blockSize } ); engine.block.appendChild(page, coverBlock); engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); // Contain mode: Fit entire image, may leave empty space const containBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: blockSize } ); engine.block.appendChild(page, containBlock); engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); // Get current fill mode const currentMode = engine.block.getEnum(containBlock, 'contentFill/mode'); console.log('Current fill mode:', currentMode); // ===== Section 4: Source Sets (Responsive Images) ===== // Use source sets for responsive images const responsiveBlock = engine.block.create('graphic'); engine.block.setShape(responsiveBlock, engine.block.createShape('rect')); engine.block.setWidth(responsiveBlock, blockWidth); engine.block.setHeight(responsiveBlock, blockHeight); const responsiveFill = engine.block.createFill('image'); engine.block.setSourceSet(responsiveFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 2048, height: 1366 } ]); engine.block.setFill(responsiveBlock, responsiveFill); engine.block.appendChild(page, responsiveBlock); // Get current source set const sourceSet = engine.block.getSourceSet( responsiveFill, 'fill/image/sourceSet' ); console.log('Source set entries:', sourceSet.length); // ===== Section 5: Data URI / Base64 Images ===== // Use data URI for embedded images (small SVG example) const svgContent = ` SVG `; const svgDataUri = `data:image/svg+xml;base64,${btoa(svgContent)}`; const dataUriBlock = engine.block.create('graphic'); engine.block.setShape(dataUriBlock, engine.block.createShape('rect')); engine.block.setWidth(dataUriBlock, blockWidth); engine.block.setHeight(dataUriBlock, blockHeight); const dataUriFill = engine.block.createFill('image'); engine.block.setString(dataUriFill, 'fill/image/imageFileURI', svgDataUri); engine.block.setFill(dataUriBlock, dataUriFill); engine.block.appendChild(page, dataUriBlock); // ===== Section 6: Opacity ===== // Control opacity for transparency effects const opacityBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: blockSize } ); engine.block.appendChild(page, opacityBlock); engine.block.setFloat(opacityBlock, 'opacity', 0.6); // ===== Position all blocks in grid layout ===== const blocks = [ coverImageBlock, // Position 0 manualBlock, // Position 1 coverBlock, // Position 2 containBlock, // Position 3 responsiveBlock, // Position 4 dataUriBlock, // Position 5 opacityBlock // Position 6 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Zoom to show all content await engine.scene.zoomToBlock(page); } } export default Example; ``` This guide covers how to create and apply image fills programmatically, configure content fill modes, work with responsive images, and load images from different sources. ## Understanding Image Fills Image fills are one of the fundamental fill types in CE.SDK, identified by the type `'//ly.img.ubq/fill/image'` or simply `'image'`. Unlike color fills that provide solid colors or gradient fills that create color transitions, image fills paint blocks with photographic or graphic content from image files. CE.SDK supports common image formats including PNG, JPEG, JPG, GIF, WebP, SVG, and BMP, with transparency support in formats like PNG, WebP, and SVG. The image fill system handles content scaling, positioning, and optimization automatically while giving you full programmatic control when needed. ## Checking Image Fill Support Before working with fills, we should verify that a block supports fill operations. Not all blocks in CE.SDK can have fills—for example, scenes and pages typically don't support fills, while graphic blocks, shapes, and text blocks do. ```typescript highlight-check-fill-support // Check if a block supports fills before accessing fill APIs const testBlock = engine.block.create('graphic'); const canHaveFill = engine.block.supportsFill(testBlock); console.log('Block supports fills:', canHaveFill); engine.block.destroy(testBlock); ``` The `supportsFill()` method returns `true` if the block can have a fill assigned to it. Always check this before attempting to access fill APIs to avoid errors. ## Creating Image Fills CE.SDK provides two approaches for creating image fills: a convenience API for quick block creation, and manual creation for more control over the fill configuration. ### Using the Convenience API The fastest way to create a block with an image fill is using the `addImage()` method, which creates a graphic block, configures the image fill, and adds it to the scene in one operation: ```typescript highlight-create-image-fill // Create a new image fill using the convenience API const coverImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, coverImageBlock); ``` This convenience method handles all the underlying setup automatically, including creating the graphic block, shape, fill, and positioning. ### Manual Image Fill Creation For more control over the fill configuration or to apply fills to existing blocks, you can create fills manually: ```typescript highlight-manual-fill-creation // Or create manually for more control const manualBlock = engine.block.create('graphic'); engine.block.setShape(manualBlock, engine.block.createShape('rect')); engine.block.setWidth(manualBlock, blockWidth); engine.block.setHeight(manualBlock, blockHeight); const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); engine.block.setFill(manualBlock, imageFill); engine.block.appendChild(page, manualBlock); ``` When creating fills manually, the fill exists independently until you attach it to a block using `setFill()`. If you create a fill but don't attach it to a block, you must destroy it manually to avoid memory leaks. ### Getting the Current Fill You can retrieve the fill from any block and inspect its type to verify it's an image fill: ```typescript highlight-get-current-fill // Get the current fill from a block const currentFill = engine.block.getFill(coverImageBlock); const fillType = engine.block.getType(currentFill); console.log('Fill type:', fillType); // '//ly.img.ubq/fill/image' ``` The `getFill()` method returns the fill's block ID, which you can then use to query the fill's type and properties. ## Configuring Content Fill Modes Content fill modes control how images scale and position within their containing blocks. CE.SDK provides two primary modes: Cover and Contain, each optimized for different use cases. ### Cover Mode Cover mode ensures the image fills the entire block while maintaining its aspect ratio. Parts of the image may be cropped if the aspect ratios don't match, but there will never be empty space in the block: ```typescript highlight-fill-mode-cover // Cover mode: Fill entire block, may crop image const coverBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_3.jpg', { size: blockSize } ); engine.block.appendChild(page, coverBlock); engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); ``` Cover mode is ideal for backgrounds, hero images, and photo frames where you want the block completely filled with image content. The image is scaled to cover the entire area, and any overflow is cropped. ### Contain Mode Contain mode fits the entire image within the block while maintaining its aspect ratio. This may leave empty space if the aspect ratios don't match, but the entire image will always be visible: ```typescript highlight-fill-mode-contain // Contain mode: Fit entire image, may leave empty space const containBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: blockSize } ); engine.block.appendChild(page, containBlock); engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); ``` Contain mode is best for logos, product images, and situations where preserving the complete image visibility is more important than filling the entire block. ### Getting the Current Fill Mode You can query the current fill mode to understand how the image is being displayed: ```typescript highlight-get-fill-mode // Get current fill mode const currentMode = engine.block.getEnum(containBlock, 'contentFill/mode'); console.log('Current fill mode:', currentMode); ``` This returns either `'Cover'` or `'Contain'` depending on the current configuration. ## Working with Source Sets Source sets enable responsive images by providing multiple resolutions of the same image. The engine automatically selects the most appropriate size based on the current display context, optimizing both performance and visual quality. ### Setting Up a Source Set A source set is an array of image sources, each with a URI and dimensions: ```typescript highlight-source-set // Use source sets for responsive images const responsiveBlock = engine.block.create('graphic'); engine.block.setShape(responsiveBlock, engine.block.createShape('rect')); engine.block.setWidth(responsiveBlock, blockWidth); engine.block.setHeight(responsiveBlock, blockHeight); const responsiveFill = engine.block.createFill('image'); engine.block.setSourceSet(responsiveFill, 'fill/image/sourceSet', [ { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 512, height: 341 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 1024, height: 683 }, { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 2048, height: 1366 } ]); engine.block.setFill(responsiveBlock, responsiveFill); engine.block.appendChild(page, responsiveBlock); ``` Each entry in the source set specifies a URI and the image's width and height in pixels. The engine calculates the current drawing size and selects the source with the closest size that exceeds the required dimensions. > **Note:** Source sets are particularly valuable for optimizing bandwidth usage during > preview while ensuring high-resolution output during export. The engine > automatically uses the highest resolution available when exporting. ### Retrieving Source Sets You can get the current source set from a fill to inspect or modify it: ```typescript highlight-get-source-set // Get current source set const sourceSet = engine.block.getSourceSet( responsiveFill, 'fill/image/sourceSet' ); console.log('Source set entries:', sourceSet.length); ``` ## Loading Images from Different Sources CE.SDK's image fills support multiple image source types, giving you flexibility in how you provide image content to your designs. ### Data URIs and Base64 You can embed image data directly using data URIs, which is particularly useful for small images, icons, or dynamically generated graphics: ```typescript highlight-data-uri // Use data URI for embedded images (small SVG example) const svgContent = ` SVG `; const svgDataUri = `data:image/svg+xml;base64,${btoa(svgContent)}`; const dataUriBlock = engine.block.create('graphic'); engine.block.setShape(dataUriBlock, engine.block.createShape('rect')); engine.block.setWidth(dataUriBlock, blockWidth); engine.block.setHeight(dataUriBlock, blockHeight); const dataUriFill = engine.block.createFill('image'); engine.block.setString(dataUriFill, 'fill/image/imageFileURI', svgDataUri); engine.block.setFill(dataUriBlock, dataUriFill); engine.block.appendChild(page, dataUriBlock); ``` Data URIs embed the entire image within the URI string itself, eliminating the need for network requests. However, this increases the scene file size, so it's best reserved for smaller images or cases where you need guaranteed availability without network dependencies. ## Additional Techniques ### Controlling Opacity You can control the overall opacity of blocks with image fills, affecting the entire block including its fill: ```typescript highlight-opacity // Control opacity for transparency effects const opacityBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_6.jpg', { size: blockSize } ); engine.block.appendChild(page, opacityBlock); engine.block.setFloat(opacityBlock, 'opacity', 0.6); ``` The opacity value ranges from 0 (fully transparent) to 1 (fully opaque). This affects the entire block, including the image fill. For transparency within the image itself, use image formats that support alpha channels like PNG, WebP, or SVG. > **Note:** Opacity is a block property, not a fill property. It affects the entire block > including any strokes, effects, or other visual properties applied to the > block. ## API Reference ### Core Methods | Method | Description | | --------------------------------------- | -------------------------------------------------- | | `createFill('image')` | Create a new image fill object | | `setFill(block, fill)` | Assign an image fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setString(fill, property, value)` | Set the image URI | | `getString(fill, property)` | Get the current image URI | | `setSourceSet(fill, property, sources)` | Set responsive image sources | | `getSourceSet(fill, property)` | Get current source set | | `setEnum(block, property, value)` | Set content fill mode | | `getEnum(block, property)` | Get current fill mode | | `supportsFill(block)` | Check if block supports fills | | `addImage(url, options)` | Convenience method to create image block with fill | ### Image Fill Properties | Property | Type | Description | | ------------------------- | ----------- | ------------------------------------------------- | | `fill/image/imageFileURI` | String | Single image URI (URL, data URI, or file path) | | `fill/image/sourceSet` | SourceSet\[] | Array of responsive image sources with dimensions | ### Content Fill Properties | Property | Type | Values | Description | | ------------------ | ---- | ------------------ | ------------------------------------- | | `contentFill/mode` | Enum | 'Cover', 'Contain' | How the image scales within its block | ### SourceSet Interface ```typescript interface SourceSetEntry { uri: string; // Image URI width: number; // Image width in pixels height: number; // Image height in pixels } ``` --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Fills" description: "Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements." platform: angular url: "https://img.ly/docs/cesdk/angular/fills/overview-3895ee/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) > [Overview](https://img.ly/docs/cesdk/angular/fills/overview-3895ee/) --- Some [design blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) 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](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/). We currently support the following fill types: - `'//ly.img.ubq/fill/color'` - `'//ly.img.ubq/fill/gradient/linear'` - `'//ly.img.ubq/fill/gradient/radial'` - `'//ly.img.ubq/fill/gradient/conical'` - `'//ly.img.ubq/fill/image'` - `'//ly.img.ubq/fill/video'` - `'//ly.img.ubq/fill/pixelStream'` Note: short types are also accepted, e.g. 'color' instead of '//ly.img.ubq/fill/color'. ## Accessing Fills Not all types of design blocks support fills, so you should always first call the `supportsFill(id: number): boolean` API before accessing any of the following APIs. ```javascript 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 `getFill(id: number): number` 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 `getType(id: number): ObjectType` API. ```javascript const colorFill = engine.block.getFill(block); const 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 `findAllProperties(id: number): string[]` 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 [design blocks](https://img.ly/docs/cesdk/angular/concepts/blocks-90241e/) for a complete list of all available properties for each type of fill. ```javascript const 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 `setColor(id: number, property: string, value: Color): void` 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. ```javascript engine.block.setColor(colorFill, 'fill/color/value', { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }); ``` ## Disabling Fills You can disable and enable a fill using the `setFillEnabled(id: number, enabled: boolean): void` 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. ```javascript engine.block.setFillEnabled(block, false); engine.block.setFillEnabled(block, !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 `createFill(type: FillType): number`. ```javascript const imageFill = engine.block.createFill('image'); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); ``` In order to assign a fill to a design block, simply call `setFill(id: number, fill: number): void`. 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. ```javascript engine.block.destroy(engine.block.getFill(block)); engine.block.setFill(block, 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. ```javascript const duplicateBlock = engine.block.duplicate(block); engine.block.setPositionX(duplicateBlock, 450); const autoDuplicateFill = engine.block.getFill(duplicateBlock); engine.block.setString( autoDuplicateFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_2.jpg' ); // const manualDuplicateFill = engine.block.duplicate(autoDuplicateFill); // /* We could now assign this fill to another block. */ // 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. ```javascript const sharedFillBlock = engine.block.create('graphic'); engine.block.setShape(sharedFillBlock, engine.block.createShape('rect')); engine.block.setPositionX(sharedFillBlock, 350); engine.block.setPositionY(sharedFillBlock, 400); engine.block.setWidth(sharedFillBlock, 100); engine.block.setHeight(sharedFillBlock, 100); engine.block.appendChild(page, sharedFillBlock); engine.block.setFill(sharedFillBlock, engine.block.getFill(block)); ``` --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Video Fills" description: "Learn how to apply video content as fills to design elements in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/fills/video-ec7f9f/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) > [Video](https://img.ly/docs/cesdk/angular/fills/video-ec7f9f/) --- Apply motion content to design elements by filling shapes, backgrounds, and text with videos using CE.SDK's video fill system. ![CE.SDK video fills example showing a 3x3 grid with video content applied to different blocks including rectangles, ellipse, and opacity variations](./assets/browser.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-video-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-video-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-fills-video-browser/) Understanding the distinction between **video fills** and **video blocks** is essential. Video fills are fill objects that can be applied to any block supporting fills—shapes, text, backgrounds—to paint them with video content. Video blocks, created with `addVideo()`, are dedicated timeline elements with full editing capabilities like trimming and duration control. Video fills focus on applying video as a visual treatment, while video blocks provide complete video editing functionality. ```typescript file=@cesdk_web_examples/guides-fills-video-browser/browser.ts reference-only import type { CreativeEngine, EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import packageJson from './package.json'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Video Fills Guide * * Demonstrates video fills in CE.SDK: * - Creating video fills * - Setting video sources (single URI and source sets) * - Applying video fills to blocks * - Content fill modes (Cover, Contain) * - Loading video resources * - Getting video thumbnails * - Different use cases (backgrounds, shapes, text) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } // Video fills require Video mode and video features enabled cesdk.feature.enable('ly.img.video'); cesdk.feature.enable('ly.img.fill'); await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine as CreativeEngine; const pages = engine.block.findByType('page'); const page = pages.length > 0 ? pages[0] : engine.scene.get(); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 8); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample video URL from demo assets const videoUri = 'https://img.ly/static/ubq_video_samples/bbb.mp4'; // Create a sample block to demonstrate fill support checking const sampleBlock = engine.block.create('graphic'); engine.block.setShape(sampleBlock, engine.block.createShape('rect')); // Check if the block supports fills const supportsFills = engine.block.supportsFill(sampleBlock); // eslint-disable-next-line no-console console.log('Block supports fills:', supportsFills); // true for graphic blocks // Verify we're in Video mode (required for video fills) const sceneMode = engine.scene.getMode(); if (sceneMode !== 'Video') { throw new Error('Video fills require Video mode.'); } // eslint-disable-next-line no-console console.log('Scene mode:', sceneMode); // "Video" // Pattern #1: Demonstrate Individual Before Combined // Create a basic video fill demonstration const basicBlock = engine.block.create('graphic'); engine.block.setShape(basicBlock, engine.block.createShape('rect')); engine.block.setWidth(basicBlock, blockWidth); engine.block.setHeight(basicBlock, blockHeight); engine.block.appendChild(page, basicBlock); // Create a video fill const basicVideoFill = engine.block.createFill('video'); // or using full type name: engine.block.createFill('//ly.img.ubq/fill/video'); // Set the video source URI engine.block.setString(basicVideoFill, 'fill/video/fileURI', videoUri); // Apply the fill to the block engine.block.setFill(basicBlock, basicVideoFill); // Get and verify the current fill const fillId = engine.block.getFill(basicBlock); const fillType = engine.block.getType(fillId); // eslint-disable-next-line no-console console.log('Fill type:', fillType); // '//ly.img.ubq/fill/video' // Pattern #2: Content fill mode - Cover // Cover mode fills entire block, may crop video to fit const coverBlock = engine.block.create('graphic'); engine.block.setShape(coverBlock, engine.block.createShape('rect')); engine.block.setWidth(coverBlock, blockWidth); engine.block.setHeight(coverBlock, blockHeight); engine.block.appendChild(page, coverBlock); const coverVideoFill = engine.block.createFill('video'); engine.block.setString(coverVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(coverBlock, coverVideoFill); // Set content fill mode to Cover engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); // Get current fill mode const coverMode = engine.block.getEnum(coverBlock, 'contentFill/mode'); // eslint-disable-next-line no-console console.log('Cover block fill mode:', coverMode); // 'Cover' // Content fill mode - Contain // Contain mode fits entire video, may leave empty space const containBlock = engine.block.create('graphic'); engine.block.setShape(containBlock, engine.block.createShape('rect')); engine.block.setWidth(containBlock, blockWidth); engine.block.setHeight(containBlock, blockHeight); engine.block.appendChild(page, containBlock); const containVideoFill = engine.block.createFill('video'); engine.block.setString(containVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(containBlock, containVideoFill); // Set content fill mode to Contain engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); // Force load video resource to access metadata const resourceBlock = engine.block.create('graphic'); engine.block.setShape(resourceBlock, engine.block.createShape('rect')); engine.block.setWidth(resourceBlock, blockWidth); engine.block.setHeight(resourceBlock, blockHeight); engine.block.appendChild(page, resourceBlock); const resourceVideoFill = engine.block.createFill('video'); engine.block.setString(resourceVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(resourceBlock, resourceVideoFill); // Force load the video resource before accessing metadata await engine.block.forceLoadAVResource(resourceVideoFill); // Now we can access video metadata const totalDuration = engine.block.getDouble( resourceVideoFill, 'fill/video/totalDuration' ); // eslint-disable-next-line no-console console.log('Video total duration:', totalDuration, 'seconds'); // Use case: Video as shape fill - Ellipse const ellipseBlock = engine.block.create('graphic'); const ellipseShape = engine.block.createShape('//ly.img.ubq/shape/ellipse'); engine.block.setShape(ellipseBlock, ellipseShape); engine.block.setWidth(ellipseBlock, blockWidth); engine.block.setHeight(ellipseBlock, blockHeight); engine.block.appendChild(page, ellipseBlock); const ellipseVideoFill = engine.block.createFill('video'); engine.block.setString(ellipseVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(ellipseBlock, ellipseVideoFill); // Advanced: Video fill with opacity const opacityBlock = engine.block.create('graphic'); engine.block.setShape(opacityBlock, engine.block.createShape('rect')); engine.block.setWidth(opacityBlock, blockWidth); engine.block.setHeight(opacityBlock, blockHeight); engine.block.appendChild(page, opacityBlock); const opacityVideoFill = engine.block.createFill('video'); engine.block.setString(opacityVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(opacityBlock, opacityVideoFill); // Set block opacity to 70% engine.block.setFloat(opacityBlock, 'opacity', 0.7); // Advanced: Share one video fill between multiple blocks const sharedFill = engine.block.createFill('video'); engine.block.setString(sharedFill, 'fill/video/fileURI', videoUri); // First block using shared fill const sharedBlock1 = engine.block.create('graphic'); engine.block.setShape(sharedBlock1, engine.block.createShape('rect')); engine.block.setWidth(sharedBlock1, blockWidth); engine.block.setHeight(sharedBlock1, blockHeight); engine.block.appendChild(page, sharedBlock1); engine.block.setFill(sharedBlock1, sharedFill); // Second block using the same shared fill const sharedBlock2 = engine.block.create('graphic'); engine.block.setShape(sharedBlock2, engine.block.createShape('rect')); engine.block.setWidth(sharedBlock2, blockWidth * 0.8); // Slightly smaller engine.block.setHeight(sharedBlock2, blockHeight * 0.8); engine.block.appendChild(page, sharedBlock2); engine.block.setFill(sharedBlock2, sharedFill); // eslint-disable-next-line no-console console.log( 'Shared fill - Two blocks using the same video fill instance for memory efficiency' ); // ===== Position all blocks in grid layout ===== const blocks = [ basicBlock, // Position 0 coverBlock, // Position 1 containBlock, // Position 2 resourceBlock, // Position 3 ellipseBlock, // Position 4 opacityBlock, // Position 5 sharedBlock1, // Position 6 sharedBlock2 // Position 7 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Select the first block so users can see the fill in action engine.block.setSelected(basicBlock, true); // Set playback time to 2 seconds to show video content engine.block.setPlaybackTime(page, 2); // Start playback automatically try { engine.block.setPlaying(page, true); // eslint-disable-next-line no-console console.log( 'Video fills guide initialized. Playback started. Demonstrating various video fill techniques across the grid.' ); } catch (error) { // eslint-disable-next-line no-console console.log( 'Video fills guide initialized. Click play to start video playback (browser autoplay restriction).' ); } } } export default Example; ``` This guide covers how to create video fills, apply them to blocks, configure fill modes, and work with video resources programmatically. ## Understanding Video Fills ### What is a Video Fill? A video fill is a fill object that paints a design block with video content. Like color and image fills, video fills are part of CE.SDK's broader fill system. Video fills are identified by the type `'//ly.img.ubq/fill/video'` or the short form `'video'`. They contain properties for the video source, positioning, scaling, and playback behavior. ### Video Fill vs Video Blocks **Video fills** are fill objects created with `createFill('video')` and applied to blocks with `setFill()`. You can use them to fill shapes with video content, create video backgrounds, or add video textures to text. **Video blocks** are created with the convenience method `addVideo()` and come pre-configured with timeline integration, trim support, and playback controls. Use video blocks when building video editors or when you need features like trimming, duration adjustment, and precise playback control. For this guide, we focus on video fills—applying video content as a fill to design elements. For video editing workflows, see the [Trim Video guide](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/). ### Video Mode Requirement Video fills can only be created in Video mode scenes. Design mode doesn't support video fills. You must initialize CE.SDK with `cesdk.actions.run('scene.create', { mode: 'Video' })` to enable video capabilities. ```typescript // Create Video mode scene (required for video fills) await cesdk.actions.run('scene.create', { mode: 'Video' }); // Verify scene mode const mode = engine.scene.getMode(); console.log(mode); // "Video" ``` ## Checking Video Fill Support Before applying video fills, verify that blocks support fills and that you're in the correct scene mode. ```typescript highlight-check-fill-support // Create a sample block to demonstrate fill support checking const sampleBlock = engine.block.create('graphic'); engine.block.setShape(sampleBlock, engine.block.createShape('rect')); // Check if the block supports fills const supportsFills = engine.block.supportsFill(sampleBlock); // eslint-disable-next-line no-console console.log('Block supports fills:', supportsFills); // true for graphic blocks // Verify we're in Video mode (required for video fills) const sceneMode = engine.scene.getMode(); if (sceneMode !== 'Video') { throw new Error('Video fills require Video mode.'); } // eslint-disable-next-line no-console console.log('Scene mode:', sceneMode); // "Video" ``` Graphic blocks, shapes, and text blocks typically support fills. Pages and scenes don't. Always check `supportsFill()` before attempting to apply video fills to prevent errors. ## Creating Video Fills ### Creating Video Fills Creating a video fill involves three steps: create the fill object, set the video source, and apply it to a block. ```typescript highlight-create-video-fill // Pattern #1: Demonstrate Individual Before Combined // Create a basic video fill demonstration const basicBlock = engine.block.create('graphic'); engine.block.setShape(basicBlock, engine.block.createShape('rect')); engine.block.setWidth(basicBlock, blockWidth); engine.block.setHeight(basicBlock, blockHeight); engine.block.appendChild(page, basicBlock); // Create a video fill const basicVideoFill = engine.block.createFill('video'); // or using full type name: engine.block.createFill('//ly.img.ubq/fill/video'); // Set the video source URI engine.block.setString(basicVideoFill, 'fill/video/fileURI', videoUri); // Apply the fill to the block engine.block.setFill(basicBlock, basicVideoFill); ``` The video fill exists independently until you attach it to a block. This allows you to configure the fill completely before applying it. Once applied, the fill paints the block with the video content. ### Getting Current Fill Information We can retrieve the current fill from a block and inspect its type to verify it's a video fill. ```typescript highlight-get-current-fill // Get and verify the current fill const fillId = engine.block.getFill(basicBlock); const fillType = engine.block.getType(fillId); // eslint-disable-next-line no-console console.log('Fill type:', fillType); // '//ly.img.ubq/fill/video' ``` This is useful when building UIs that need to adapt based on the current fill type or when implementing undo/redo functionality that tracks fill changes. ## Content Fill Modes Content fill modes control how video scales and positions within blocks. The two primary modes are Cover and Contain, each suited to different use cases. ### Cover Mode Cover mode fills the entire block with video while maintaining the video's aspect ratio. If the aspect ratios don't match, CE.SDK crops portions of the video to ensure no empty space appears in the block. ```typescript highlight-fill-mode-cover // Pattern #2: Content fill mode - Cover // Cover mode fills entire block, may crop video to fit const coverBlock = engine.block.create('graphic'); engine.block.setShape(coverBlock, engine.block.createShape('rect')); engine.block.setWidth(coverBlock, blockWidth); engine.block.setHeight(coverBlock, blockHeight); engine.block.appendChild(page, coverBlock); const coverVideoFill = engine.block.createFill('video'); engine.block.setString(coverVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(coverBlock, coverVideoFill); // Set content fill mode to Cover engine.block.setEnum(coverBlock, 'contentFill/mode', 'Cover'); // Get current fill mode const coverMode = engine.block.getEnum(coverBlock, 'contentFill/mode'); // eslint-disable-next-line no-console console.log('Cover block fill mode:', coverMode); // 'Cover' ``` Use Cover mode for background videos, full-frame video content, and situations where visual consistency matters more than showing the entire video. It guarantees no empty space but may crop content. ### Contain Mode Contain mode fits the entire video within the block while maintaining aspect ratio. If aspect ratios don't match, CE.SDK adds empty space to preserve the full video visibility. ```typescript highlight-fill-mode-contain // Content fill mode - Contain // Contain mode fits entire video, may leave empty space const containBlock = engine.block.create('graphic'); engine.block.setShape(containBlock, engine.block.createShape('rect')); engine.block.setWidth(containBlock, blockWidth); engine.block.setHeight(containBlock, blockHeight); engine.block.appendChild(page, containBlock); const containVideoFill = engine.block.createFill('video'); engine.block.setString(containVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(containBlock, containVideoFill); // Set content fill mode to Contain engine.block.setEnum(containBlock, 'contentFill/mode', 'Contain'); ``` Use Contain mode when the entire video must remain visible—presentations, product demos, or content where cropping would lose important information. Empty space is acceptable to preserve complete visibility. ## Loading Video Resources Before accessing video metadata like duration or dimensions, you must force load the video resource. This ensures CE.SDK has downloaded the necessary information. ```typescript highlight-force-load-resource // Force load video resource to access metadata const resourceBlock = engine.block.create('graphic'); engine.block.setShape(resourceBlock, engine.block.createShape('rect')); engine.block.setWidth(resourceBlock, blockWidth); engine.block.setHeight(resourceBlock, blockHeight); engine.block.appendChild(page, resourceBlock); const resourceVideoFill = engine.block.createFill('video'); engine.block.setString(resourceVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(resourceBlock, resourceVideoFill); // Force load the video resource before accessing metadata await engine.block.forceLoadAVResource(resourceVideoFill); // Now we can access video metadata const totalDuration = engine.block.getDouble( resourceVideoFill, 'fill/video/totalDuration' ); // eslint-disable-next-line no-console console.log('Video total duration:', totalDuration, 'seconds'); ``` Skipping this step causes errors when trying to access metadata. Videos load asynchronously, so `forceLoadAVResource` ensures the metadata is available before you query it. Once loaded, you can access properties like `fill/video/totalDuration` to get the video length in seconds. This information helps you build UI previews or validate user input. ## Common Use Cases ### Video as Shape Fill Video fills aren't limited to rectangles. You can fill any shape with video content. ```typescript highlight-video-shape-fill // Use case: Video as shape fill - Ellipse const ellipseBlock = engine.block.create('graphic'); const ellipseShape = engine.block.createShape('//ly.img.ubq/shape/ellipse'); engine.block.setShape(ellipseBlock, ellipseShape); engine.block.setWidth(ellipseBlock, blockWidth); engine.block.setHeight(ellipseBlock, blockHeight); engine.block.appendChild(page, ellipseBlock); const ellipseVideoFill = engine.block.createFill('video'); engine.block.setString(ellipseVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(ellipseBlock, ellipseVideoFill); ``` Ellipse shapes, polygons, stars, and custom paths all support video fills. The video content fills the shape boundary, masking the video. ### Video with Opacity Control the transparency of video-filled blocks to create overlay effects or blend video content with backgrounds. ```typescript highlight-opacity // Advanced: Video fill with opacity const opacityBlock = engine.block.create('graphic'); engine.block.setShape(opacityBlock, engine.block.createShape('rect')); engine.block.setWidth(opacityBlock, blockWidth); engine.block.setHeight(opacityBlock, blockHeight); engine.block.appendChild(page, opacityBlock); const opacityVideoFill = engine.block.createFill('video'); engine.block.setString(opacityVideoFill, 'fill/video/fileURI', videoUri); engine.block.setFill(opacityBlock, opacityVideoFill); // Set block opacity to 70% engine.block.setFloat(opacityBlock, 'opacity', 0.7); ``` Opacity affects the entire block, including its video fill. This technique creates semi-transparent video overlays, watermarks, or layered compositions where video content blends with other elements. ## Additional Techniques ### Sharing Video Fills Memory efficiency improves when multiple blocks share a single video fill instance. Changes to the shared fill affect all blocks using it. ```typescript highlight-shared-fill // Advanced: Share one video fill between multiple blocks const sharedFill = engine.block.createFill('video'); engine.block.setString(sharedFill, 'fill/video/fileURI', videoUri); // First block using shared fill const sharedBlock1 = engine.block.create('graphic'); engine.block.setShape(sharedBlock1, engine.block.createShape('rect')); engine.block.setWidth(sharedBlock1, blockWidth); engine.block.setHeight(sharedBlock1, blockHeight); engine.block.appendChild(page, sharedBlock1); engine.block.setFill(sharedBlock1, sharedFill); // Second block using the same shared fill const sharedBlock2 = engine.block.create('graphic'); engine.block.setShape(sharedBlock2, engine.block.createShape('rect')); engine.block.setWidth(sharedBlock2, blockWidth * 0.8); // Slightly smaller engine.block.setHeight(sharedBlock2, blockHeight * 0.8); engine.block.appendChild(page, sharedBlock2); engine.block.setFill(sharedBlock2, sharedFill); // eslint-disable-next-line no-console console.log( 'Shared fill - Two blocks using the same video fill instance for memory efficiency' ); ``` This pattern reduces memory usage when the same video appears multiple times in a composition. It's particularly useful for repeated elements like watermarks or background patterns. Shared fills play back synchronized—all blocks display the same frame at the same time during playback. This ensures visual consistency across multiple elements. ## Troubleshooting ### Video Not Visible If your video fill doesn't appear, check several common causes. Verify the fill is enabled with `isFillEnabled(block)`. Ensure the video URL is accessible—CORS restrictions on web platforms can block video loading. Confirm the block has valid dimensions (width and height greater than zero) and exists in the scene hierarchy. Check that the video format is supported on your platform. MP4 with H.264 encoding works reliably across platforms, while other codecs may have limited support. ### Cannot Create Video Fill If creating a video fill throws an error, verify you're in Video mode. Design mode doesn't support video fills. Use `engine.scene.getMode()` to check the current mode. If it returns "Design", you need to create a video scene instead. Call `await cesdk.actions.run('scene.create', { mode: 'Video' })` during initialization to enable video capabilities. ### Video Not Loading When videos fail to load, verify network connectivity for remote URLs. Check CORS headers—web browsers enforce cross-origin restrictions that can block video access. Validate the URI format uses `https://` for remote videos or appropriate schemes for local files. Test with a known working video URL to isolate whether the issue is with your specific video or a broader configuration problem. Check the browser console for detailed error messages. ### Memory Leaks Always destroy replaced fills to prevent memory leaks. When changing a block's fill, retrieve the old fill with `getFill()`, assign the new fill with `setFill()`, then destroy the old fill with `destroy()`. Don't create fills without attaching them to blocks—unattached fills remain in memory indefinitely. Clean up shared fills when no blocks reference them anymore. ### Performance Issues Video playback is resource-intensive. Use appropriately sized videos—avoid massive files that strain decoding hardware. Consider lower resolutions for editing with high-resolution sources reserved for export. Limit the number of simultaneously playing videos, especially on mobile devices. Too many concurrent video decodes overwhelm device capabilities. Compress videos before use to reduce file sizes and improve loading times. ## API Reference | Method | Description | | ----------------------------------------- | -------------------------------- | | `createFill('video')` | Create a new video fill object | | `setFill(block, fill)` | Assign fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setString(fill, property, value)` | Set video URI property | | `getString(fill, property)` | Get current video URI | | `setSourceSet(fill, property, sources)` | Set responsive video sources | | `getSourceSet(fill, property)` | Get current source set | | `setEnum(block, property, value)` | Set content fill mode | | `getEnum(block, property)` | Get current fill mode | | `setFillEnabled(block, enabled)` | Enable or disable fill rendering | | `isFillEnabled(block)` | Check if fill is enabled | | `supportsFill(block)` | Check if block supports fills | | `forceLoadAVResource(fill)` | Force load video metadata | | `getVideoFillThumbnail(fill, height)` | Get single thumbnail frame | | `adjustCropToFillFrame(block, fillIndex)` | Adjust crop to fill frame | ### Video Fill Properties | Property | Type | Description | | -------------------------- | ----------- | ------------------------------------------- | | `fill/video/fileURI` | String | Single video URI (URL, data URI, file path) | | `fill/video/sourceSet` | SourceSet\[] | Array of responsive video sources | | `fill/video/totalDuration` | Double | Total duration of video in seconds | ### Content Fill Properties | Property | Type | Values | Description | | ------------------ | ---- | ------------------ | ----------------------------- | | `contentFill/mode` | Enum | 'Cover', 'Contain' | How video scales within block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Filters and Effects" description: "Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) --- --- ## Related Pages - [Filters & Effects Library](https://img.ly/docs/cesdk/angular/filters-and-effects/overview-299b15/) - Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying. - [Supported Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/support-a666dd/) - View the full list of visual effects and filters available in CE.SDK, including both built-in and custom options. - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) - Apply filters and effects to design elements to enhance images and create visual transformations. - [Create Custom Filters](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-filters-c796ba/) - Extend CE.SDK with custom LUT filter asset sources for brand-specific color grading and filter collections. - [Chroma Key (Green Screen)](https://img.ly/docs/cesdk/angular/filters-and-effects/chroma-key-green-screen-1e3e99/) - Apply the green screen effect to images and videos, replacing specific colors with transparency for compositing workflows. - [Blur Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/) - Apply blur effects to design elements to create depth, focus attention, or soften backgrounds. - [Create a Custom LUT Filter](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-lut-filter-6e3f49/) - Create and apply custom LUT filters to achieve consistent, brand-aligned visual styles. - [Distortion Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/distortion-5b5a66/) - Apply distortion effects to warp, shift, and transform design elements for dynamic artistic visuals in CE.SDK. - [Duotone](https://img.ly/docs/cesdk/angular/filters-and-effects/duotone-831fc5/) - Apply duotone effects to images, mapping tones to two colors for stylized visuals, vintage aesthetics, or brand-specific treatments. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Apply Filters and Effects" description: "Apply filters and effects to design elements to enhance images and create visual transformations." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Apply Filter or Effect](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) --- Apply professional color grading, blur effects, and artistic treatments to design elements using CE.SDK's visual effects system. ![Apply Filters and Effects example showing images with various effects applied](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-apply-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-apply-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-apply-browser/) While CE.SDK uses a unified effect API for both filters and effects, they serve different purposes. **Filters** typically apply color transformations like LUT filters and duotone, while **effects** apply visual modifications such as blur, pixelize, vignette, and image adjustments. You can combine multiple effects on a single element, creating complex visual treatments by stacking them in a customizable order. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-apply-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout, hexToRgba } from './utils'; /** * CE.SDK Plugin: Filters and Effects Guide * * Demonstrates applying various filters and effects to image blocks: * - Checking effect support * - Applying basic effects (blur) * - Configuring effect parameters (adjustments) * - Applying LUT filters * - Combining multiple effects * - Managing effect stacks */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Enable effects and filters in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Enable all effects cesdk.feature.enable('ly.img.filter'); // Enable all filters cesdk.feature.enable('ly.img.blur'); // Enable blur effect cesdk.feature.enable('ly.img.adjustment'); // Enable adjustments // Calculate responsive grid layout based on page dimensions const layout = calculateGridLayout(pageWidth, pageHeight, 9); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample image URL (this will load from demo assets) const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Query available LUT and Duotone filters from asset sources // These filters are provided by the demo asset sources loaded above const lutResults = await engine.asset.findAssets('ly.img.filter', { page: 0, perPage: 10 }); const duotoneResults = await engine.asset.findAssets('ly.img.filter', { page: 0, perPage: 10 }); const lutAssets = lutResults.assets; const duotoneAssets = duotoneResults.assets; // Pattern #2: Use Convenience APIs - addImage() simplifies block creation // Create a sample block to demonstrate effect support checking const blockSize = { width: blockWidth, height: blockHeight }; const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, sampleBlock); // Check if a block supports effects const supportsEffects = engine.block.supportsEffects(sampleBlock); // eslint-disable-next-line no-console console.log('Block supports effects:', supportsEffects); // true for graphics // Page blocks don't support effects const pageSupportsEffects = engine.block.supportsEffects(page); // eslint-disable-next-line no-console console.log('Page supports effects:', pageSupportsEffects); // false // Select this block so effects panel is visible engine.block.setSelected(sampleBlock, true); // Pattern #1: Demonstrate Individual Before Combined // Create a separate image block for blur demonstration const blurImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, blurImageBlock); // Create and apply a blur effect const blurEffect = engine.block.createEffect('extrude_blur'); engine.block.appendEffect(blurImageBlock, blurEffect); // Adjust blur intensity engine.block.setFloat(blurEffect, 'effect/extrude_blur/amount', 0.5); // Create a separate image block for adjustments demonstration const adjustmentsImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, adjustmentsImageBlock); // Create adjustments effect for brightness and contrast const adjustmentsEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(adjustmentsImageBlock, adjustmentsEffect); // Find all available properties for this effect const adjustmentProperties = engine.block.findAllProperties(adjustmentsEffect); // eslint-disable-next-line no-console console.log('Available adjustment properties:', adjustmentProperties); // Set brightness, contrast, and saturation engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.2 ); engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.15 ); engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.1 ); // Demonstrate LUT filters by applying the first 2 from asset library // These filters are fetched from the demo asset sources (Grid positions 3-4) const lutImageBlocks = []; for (let i = 0; i < Math.min(2, lutAssets.length); i++) { const lutAsset = lutAssets[i]; const lutImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, lutImageBlock); lutImageBlocks.push(lutImageBlock); // Create LUT filter effect using the full effect type URI const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Use asset metadata for LUT configuration // The asset provides the LUT file URI and grid dimensions engine.block.setString( lutEffect, 'effect/lut_filter/lutFileURI', lutAsset.meta?.uri as string ); engine.block.setInt( lutEffect, 'effect/lut_filter/horizontalTileCount', parseInt(lutAsset.meta?.horizontalTileCount as string, 10) ); engine.block.setInt( lutEffect, 'effect/lut_filter/verticalTileCount', parseInt(lutAsset.meta?.verticalTileCount as string, 10) ); engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85); engine.block.appendEffect(lutImageBlock, lutEffect); } // Demonstrate Duotone filters by applying the first 2 from asset library // Duotone filters create artistic two-color treatments (Grid positions 5-6) const duotoneImageBlocks = []; for (let i = 0; i < Math.min(2, duotoneAssets.length); i++) { const duotoneAsset = duotoneAssets[i]; const duotoneImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, duotoneImageBlock); duotoneImageBlocks.push(duotoneImageBlock); // Create Duotone filter effect using the full effect type URI const duotoneEffect = engine.block.createEffect( '//ly.img.ubq/effect/duotone_filter' ); // Convert hex colors from asset metadata to RGBA (0-1 range) const darkColor = hexToRgba(duotoneAsset.meta?.darkColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', darkColor ); const lightColor = hexToRgba(duotoneAsset.meta?.lightColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', lightColor ); engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.8 ); engine.block.appendEffect(duotoneImageBlock, duotoneEffect); } // Pattern #5: Progressive Complexity - now combining multiple effects // Create a separate image block to demonstrate combining multiple effects (Grid position 7) const combinedImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, combinedImageBlock); // Apply effects in order - the stack will contain: // 1. adjustments (brightness/contrast) - applied first // 2. blur - applied second // 3. duotone (color tinting) - applied third // 4. pixelize - applied last const combinedAdjustments = engine.block.createEffect('adjustments'); engine.block.appendEffect(combinedImageBlock, combinedAdjustments); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/brightness', 0.2 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/contrast', 0.15 ); const combinedBlur = engine.block.createEffect('extrude_blur'); engine.block.appendEffect(combinedImageBlock, combinedBlur); engine.block.setFloat(combinedBlur, 'effect/extrude_blur/amount', 0.3); const combinedDuotone = engine.block.createEffect('duotone_filter'); engine.block.appendEffect(combinedImageBlock, combinedDuotone); engine.block.setColor(combinedDuotone, 'duotone_filter/darkColor', { r: 0.1, g: 0.2, b: 0.4, a: 1.0 }); engine.block.setColor(combinedDuotone, 'duotone_filter/lightColor', { r: 0.9, g: 0.8, b: 0.6, a: 1.0 }); engine.block.setFloat(combinedDuotone, 'duotone_filter/intensity', 0.6); const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.appendEffect(combinedImageBlock, pixelizeEffect); engine.block.setInt(pixelizeEffect, 'pixelize/horizontalPixelSize', 8); engine.block.setInt(pixelizeEffect, 'pixelize/verticalPixelSize', 8); // Get all effects applied to the combined block const effects = engine.block.getEffects(combinedImageBlock); // eslint-disable-next-line no-console console.log('Applied effects:', effects); // Access properties of specific effects effects.forEach((effect, index) => { const effectType = engine.block.getType(effect); const isEnabled = engine.block.isEffectEnabled(effect); // eslint-disable-next-line no-console console.log(`Effect ${index}: ${effectType}, enabled: ${isEnabled}`); }); // Check if effect is enabled const isBlurEnabled = engine.block.isEffectEnabled(combinedBlur); // eslint-disable-next-line no-console console.log('Blur effect is enabled:', isBlurEnabled); // Create a temporary block to demonstrate effect removal const tempBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, tempBlock); const tempEffect = engine.block.createEffect('pixelize'); engine.block.appendEffect(tempBlock, tempEffect); engine.block.setInt(tempEffect, 'pixelize/horizontalPixelSize', 12); // Remove the effect const tempEffects = engine.block.getEffects(tempBlock); const effectIndex = tempEffects.indexOf(tempEffect); if (effectIndex !== -1) { engine.block.removeEffect(tempBlock, effectIndex); } // Destroy the removed effect to free memory engine.block.destroy(tempEffect); // ===== Position all blocks in grid layout ===== const blocks = [ sampleBlock, // Position 0 blurImageBlock, // Position 1 adjustmentsImageBlock, // Position 2 ...lutImageBlocks, // Positions 3-4 ...duotoneImageBlocks, // Positions 5-6 combinedImageBlock, // Position 7 tempBlock // Position 8 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Apply same effects to multiple blocks const allGraphics = engine.block.findByType('graphic'); allGraphics.forEach((graphic) => { if (engine.block.supportsEffects(graphic)) { // Only apply to blocks that don't already have effects const existingEffects = engine.block.getEffects(graphic); if (existingEffects.length === 0) { const effect = engine.block.createEffect('adjustments'); engine.block.appendEffect(graphic, effect); engine.block.setFloat(effect, 'effect/adjustments/brightness', 0.1); } } }); // eslint-disable-next-line no-console console.log( 'Effects guide initialized. Select any image to see effects panel.' ); } } export default Example; ``` This guide covers how to enable the built-in effects panel for interactive editing and how to apply and manage effects programmatically using the block API. ## Using the Built-in Effects UI ### Enable Effects Features To give users access to effects in the inspector panel, we enable the effects features using CE.SDK's Feature API. Effects and filters appear in the **inspector bar** and **advanced inspector** when a user selects a supported element. ```typescript highlight-enable-effects-features // Enable effects and filters in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Enable all effects cesdk.feature.enable('ly.img.filter'); // Enable all filters cesdk.feature.enable('ly.img.blur'); // Enable blur effect cesdk.feature.enable('ly.img.adjustment'); // Enable adjustments ``` The Feature API controls which capabilities are available to users. By enabling `ly.img.effect` and `ly.img.filter`, the inspector panel displays effect and filter options when users select compatible blocks. You can also enable specific effects individually like `ly.img.blur` or `ly.img.adjustment` for more granular control. Effects are enabled by default for graphic blocks with image or video fills. The Feature API shown above allows you to control which specific effects appear in the inspector panel UI. ### User Workflow With effects features enabled, users can enhance their designs through a visual workflow in the inspector panel: 1. **Select an element** - Click on any image or supported graphic block in the canvas 2. **Access inspector** - The inspector panel shows available options for the selected element 3. **Find effects section** - Scroll to the effects and filters sections within the inspector 4. **Browse and apply** - Click through available effects to apply them 5. **Adjust parameters** - Use sliders and controls to fine-tune intensity and other effect properties 6. **Manage effects** - Toggle effects on/off, switch between effects, or reset effect parameters > **Note:** Effects are applied immediately when selected. CE.SDK does not currently > support live preview mode when browsing effects before application. Effect > reordering is not supported—use toggle on/off, switch, or reset operations to > manage applied effects. This interactive approach is perfect for creative exploration and allows users to see results immediately without any coding knowledge. ## Programmatic Effect Application ### Initialize CE.SDK For applications that need to apply effects programmatically—whether for automation, batch processing, or dynamic user experiences—we start by setting up CE.SDK with the proper configuration. ```typescript highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); ``` This initializes the full CE.SDK interface with the effects panel enabled, giving you both UI and API access to the effects system. ### Check Effect Support Before applying effects to a block, we check whether it supports them. Not all block types can have effects applied—for example, page blocks and scene blocks do not support effects. ```typescript highlight-check-effect-support // Pattern #2: Use Convenience APIs - addImage() simplifies block creation // Create a sample block to demonstrate effect support checking const blockSize = { width: blockWidth, height: blockHeight }; const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, sampleBlock); // Check if a block supports effects const supportsEffects = engine.block.supportsEffects(sampleBlock); // eslint-disable-next-line no-console console.log('Block supports effects:', supportsEffects); // true for graphics // Page blocks don't support effects const pageSupportsEffects = engine.block.supportsEffects(page); // eslint-disable-next-line no-console console.log('Page supports effects:', pageSupportsEffects); // false // Select this block so effects panel is visible engine.block.setSelected(sampleBlock, true); ``` Effect support is available for: - **Graphic blocks** with image fills - **Graphic blocks** with video fills (with performance considerations) - **Shape blocks** with fills - **Text blocks** (with limited effect types) - **Page blocks** (particularly when they have fills applied, such as background fills) Always verify support before creating and applying effects to avoid errors and ensure a smooth user experience. ### Apply Basic Effects Once we've confirmed a block supports effects, we can create and apply effects using the effect API. Here we create a separate image block using the convenience `addImage()` API and apply a blur effect to it. > **Tip:** The example code uses the `engine.block.addImage()` convenience API throughout > this guide. This built-in helper simplifies image block creation compared to > manually constructing graphic blocks with image fills, and provides additional > configuration options like positioning, sizing, corner radius, shadows, and > timeline properties. ```typescript highlight-apply-basic-effects // Pattern #1: Demonstrate Individual Before Combined // Create a separate image block for blur demonstration const blurImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, blurImageBlock); // Create and apply a blur effect const blurEffect = engine.block.createEffect('extrude_blur'); engine.block.appendEffect(blurImageBlock, blurEffect); // Adjust blur intensity engine.block.setFloat(blurEffect, 'effect/extrude_blur/amount', 0.5); ``` CE.SDK provides several built-in effect types: - `extrude_blur` - Gaussian blur with configurable intensity - `adjustments` - Brightness, contrast, saturation, exposure - `pixelize` - Pixelation effect - `vignette` - Darkened corners - `half_tone` - Halftone pattern - `lut_filter` - Color grading with LUT files - `duotone` - Two-color tinting > **Note:** `extrude_blur` is the only blur available as an effect. CE.SDK also provides > additional blur types in a separate blur category. Each effect type has its own set of configurable properties that control its visual appearance. ### Configure Effect Parameters After creating an effect, we can customize its appearance by setting properties. Each effect exposes different parameters depending on its type and capabilities. ```typescript highlight-configure-effect-parameters // Create a separate image block for adjustments demonstration const adjustmentsImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, adjustmentsImageBlock); // Create adjustments effect for brightness and contrast const adjustmentsEffect = engine.block.createEffect('adjustments'); engine.block.appendEffect(adjustmentsImageBlock, adjustmentsEffect); // Find all available properties for this effect const adjustmentProperties = engine.block.findAllProperties(adjustmentsEffect); // eslint-disable-next-line no-console console.log('Available adjustment properties:', adjustmentProperties); // Set brightness, contrast, and saturation engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/brightness', 0.2 ); engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/contrast', 0.15 ); engine.block.setFloat( adjustmentsEffect, 'effect/adjustments/saturation', 0.1 ); ``` CE.SDK provides typed setter methods for different parameter types: - **`setFloat()`** - For intensity, amount, and radius values (typically 0.0 to 1.0) - **`setInt()`** - For discrete values like pixel sizes - **`setString()`** - For file URIs (LUT files, image references) - **`setBool()`** - For enabling or disabling specific features Using the correct setter method ensures type safety and proper value validation. ### Apply LUT Filters LUT (Look-Up Table) filters apply professional color grading by transforming colors through a predefined mapping. These are particularly useful for creating consistent brand aesthetics or applying cinematic color treatments. The example demonstrates querying LUT filters from the asset library using `engine.asset.findAssets('ly.img.filter')`, then applying them using metadata from the asset results. This approach matches how CE.SDK's built-in filter panel works. ```typescript highlight-apply-lut-filter // Demonstrate LUT filters by applying the first 2 from asset library // These filters are fetched from the demo asset sources (Grid positions 3-4) const lutImageBlocks = []; for (let i = 0; i < Math.min(2, lutAssets.length); i++) { const lutAsset = lutAssets[i]; const lutImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, lutImageBlock); lutImageBlocks.push(lutImageBlock); // Create LUT filter effect using the full effect type URI const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Use asset metadata for LUT configuration // The asset provides the LUT file URI and grid dimensions engine.block.setString( lutEffect, 'effect/lut_filter/lutFileURI', lutAsset.meta?.uri as string ); engine.block.setInt( lutEffect, 'effect/lut_filter/horizontalTileCount', parseInt(lutAsset.meta?.horizontalTileCount as string, 10) ); engine.block.setInt( lutEffect, 'effect/lut_filter/verticalTileCount', parseInt(lutAsset.meta?.verticalTileCount as string, 10) ); engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85); engine.block.appendEffect(lutImageBlock, lutEffect); } ``` LUT filters are ideal for: - Creating consistent brand aesthetics across all designs - Applying cinematic or film-style color grading - Matching reference images or maintaining color continuity - Building curated filter collections for users **Asset metadata structure**: Each LUT asset provides `uri` (the LUT file URL), `horizontalTileCount`, and `verticalTileCount` describing the grid layout of color transformation cubes. ### Apply Duotone Filters Duotone filters create artistic two-color effects by mapping image tones to two colors (dark and light). This effect is popular for creating stylized visuals, vintage aesthetics, or brand-specific color treatments. The example queries duotone filters from the asset library, then applies them using color metadata. The `hexToRgba` utility converts hex color values from asset metadata to RGBA format required by the `setColorRGBA` API. ```typescript highlight-apply-duotone-filter // Demonstrate Duotone filters by applying the first 2 from asset library // Duotone filters create artistic two-color treatments (Grid positions 5-6) const duotoneImageBlocks = []; for (let i = 0; i < Math.min(2, duotoneAssets.length); i++) { const duotoneAsset = duotoneAssets[i]; const duotoneImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, duotoneImageBlock); duotoneImageBlocks.push(duotoneImageBlock); // Create Duotone filter effect using the full effect type URI const duotoneEffect = engine.block.createEffect( '//ly.img.ubq/effect/duotone_filter' ); // Convert hex colors from asset metadata to RGBA (0-1 range) const darkColor = hexToRgba(duotoneAsset.meta?.darkColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', darkColor ); const lightColor = hexToRgba(duotoneAsset.meta?.lightColor as string); engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', lightColor ); engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.8 ); engine.block.appendEffect(duotoneImageBlock, duotoneEffect); } ``` Duotone filters work by: - Mapping darker image tones to the **dark color** - Mapping lighter image tones to the **light color** - Blending between the two colors based on pixel brightness - Adjusting intensity to control the effect strength (0.0 to 1.0) **Asset metadata structure**: Each duotone asset provides `darkColor` and `lightColor` as hex strings (e.g., `"#1a2b3c"`) which must be converted to RGBA values for the effect API. ### Combine Multiple Effects One of the most powerful features of CE.SDK's effect system is the ability to stack multiple effects on a single block. Each effect is applied sequentially, allowing you to build complex visual treatments. > **Note:** The example code demonstrates each effect type individually on separate image > blocks before showing them combined. This educational approach helps you > understand what each effect does before seeing them work together. In your > production code, you can apply multiple effects directly to the same block > without this separation. ```typescript highlight-combine-multiple-effects // Pattern #5: Progressive Complexity - now combining multiple effects // Create a separate image block to demonstrate combining multiple effects (Grid position 7) const combinedImageBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, combinedImageBlock); // Apply effects in order - the stack will contain: // 1. adjustments (brightness/contrast) - applied first // 2. blur - applied second // 3. duotone (color tinting) - applied third // 4. pixelize - applied last const combinedAdjustments = engine.block.createEffect('adjustments'); engine.block.appendEffect(combinedImageBlock, combinedAdjustments); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/brightness', 0.2 ); engine.block.setFloat( combinedAdjustments, 'effect/adjustments/contrast', 0.15 ); const combinedBlur = engine.block.createEffect('extrude_blur'); engine.block.appendEffect(combinedImageBlock, combinedBlur); engine.block.setFloat(combinedBlur, 'effect/extrude_blur/amount', 0.3); const combinedDuotone = engine.block.createEffect('duotone_filter'); engine.block.appendEffect(combinedImageBlock, combinedDuotone); engine.block.setColor(combinedDuotone, 'duotone_filter/darkColor', { r: 0.1, g: 0.2, b: 0.4, a: 1.0 }); engine.block.setColor(combinedDuotone, 'duotone_filter/lightColor', { r: 0.9, g: 0.8, b: 0.6, a: 1.0 }); engine.block.setFloat(combinedDuotone, 'duotone_filter/intensity', 0.6); const pixelizeEffect = engine.block.createEffect('pixelize'); engine.block.appendEffect(combinedImageBlock, pixelizeEffect); engine.block.setInt(pixelizeEffect, 'pixelize/horizontalPixelSize', 8); engine.block.setInt(pixelizeEffect, 'pixelize/verticalPixelSize', 8); ``` **Effect ordering matters**: Effects are applied from the bottom of the stack to the top. In this example: 1. First, we adjust brightness and contrast 2. Then, we apply blur 3. Then, we apply color grading with a LUT filter 4. Finally, we add stylization with pixelization Experiment with different orderings to achieve the desired visual result—changing the order can significantly impact the final appearance. ## Managing Applied Effects ### List and Access Effects We can retrieve all effects applied to a block and inspect their properties. This is useful for building effect management interfaces or debugging effect configurations. ```typescript highlight-list-effects // Get all effects applied to the combined block const effects = engine.block.getEffects(combinedImageBlock); // eslint-disable-next-line no-console console.log('Applied effects:', effects); // Access properties of specific effects effects.forEach((effect, index) => { const effectType = engine.block.getType(effect); const isEnabled = engine.block.isEffectEnabled(effect); // eslint-disable-next-line no-console console.log(`Effect ${index}: ${effectType}, enabled: ${isEnabled}`); }); ``` This allows you to iterate through all applied effects, read their properties, and make modifications as needed. ### Enable/Disable Effects CE.SDK allows you to temporarily toggle effects on and off without removing them from the block. This is particularly useful for before/after comparisons or when you need to optimize rendering performance during interactive editing sessions. ```typescript highlight-enable-disable-effects // Check if effect is enabled const isBlurEnabled = engine.block.isEffectEnabled(combinedBlur); // eslint-disable-next-line no-console console.log('Blur effect is enabled:', isBlurEnabled); ``` When you disable an effect, it remains attached to the block but won't be rendered until you enable it again. This preserves all effect parameters while giving you full control over when the effect is applied. You can use this feature to create interactive preview modes, implement undo-like functionality, or conditionally apply effects based on user preferences or device capabilities. ### Remove Effects When you no longer need an effect, you can remove it from the effect stack and free its resources. Always destroy effects that are no longer in use to prevent memory leaks. ```typescript highlight-remove-effects // Create a temporary block to demonstrate effect removal const tempBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, tempBlock); const tempEffect = engine.block.createEffect('pixelize'); engine.block.appendEffect(tempBlock, tempEffect); engine.block.setInt(tempEffect, 'pixelize/horizontalPixelSize', 12); // Remove the effect const tempEffects = engine.block.getEffects(tempBlock); const effectIndex = tempEffects.indexOf(tempEffect); if (effectIndex !== -1) { engine.block.removeEffect(tempBlock, effectIndex); } // Destroy the removed effect to free memory engine.block.destroy(tempEffect); ``` The `removeEffect()` method takes an index position, so you can remove effects selectively from any position in the stack. After removal, destroy the effect instance to ensure proper cleanup. ## Additional Techniques ### Batch Processing For applications that need to apply the same effects to multiple elements, we can iterate through a collection of blocks and apply effects efficiently. ```typescript highlight-batch-processing // Apply same effects to multiple blocks const allGraphics = engine.block.findByType('graphic'); allGraphics.forEach((graphic) => { if (engine.block.supportsEffects(graphic)) { // Only apply to blocks that don't already have effects const existingEffects = engine.block.getEffects(graphic); if (existingEffects.length === 0) { const effect = engine.block.createEffect('adjustments'); engine.block.appendEffect(graphic, effect); engine.block.setFloat(effect, 'effect/adjustments/brightness', 0.1); } } }); ``` When batch processing, check effect support before creating effects to avoid unnecessary work. You can also reuse effect instances when applying the same configuration to multiple blocks, though be careful to destroy them properly when done. ### Dynamic Effects in Video Mode When working in Video mode (not Design mode), you can combine effects with CE.SDK's built-in animation system to create dynamic visual treatments that change over time. > **Caution:** Effect parameters are static properties and cannot be animated using > JavaScript timers like `setInterval` or `requestAnimationFrame`. For animated > content, use CE.SDK's built-in animation blocks (`createAnimation()`, > `setInAnimation()`, etc.) in Video mode or refer to the animation guides. For dynamic visual effects in video projects, explore CE.SDK's animation system which provides professionally designed transitions and effects that integrate seamlessly with the rendering pipeline. ### Custom Effect Combinations Creating reusable effect presets allows you to maintain consistent styling across your application and speed up common effect applications. Here's a pattern for building reusable effect configurations: ```typescript // Create a reusable preset function async function applyVintagePreset(engine: CreativeEngine, imageBlock: number) { // Apply LUT filter const lutEffect = engine.block.createEffect('lut_filter'); engine.block.setString( lutEffect, 'lut_filter/lutFileURI', 'https://img.ly/static/ubq_luts/vintage.png', ); engine.block.appendEffect(imageBlock, lutEffect); // Add vignette const vignetteEffect = engine.block.createEffect('vignette'); engine.block.setFloat(vignetteEffect, 'vignette/intensity', 0.5); engine.block.appendEffect(imageBlock, vignetteEffect); return { lutEffect, vignetteEffect }; } // Use the preset const effects = await applyVintagePreset(engine, myImageBlock); ``` Preset strategies include: - **Brand filters** - Maintain a consistent look across campaigns - **Style templates** - Provide quick application of complex multi-effect treatments - **User favorites** - Allow users to save and recall their preferred settings ## Performance Considerations CE.SDK's effect system is optimized for real-time performance, but understanding these considerations helps you build responsive applications: - **GPU acceleration**: Effects leverage GPU rendering for smooth performance on modern devices - **Mobile optimization**: Limit effects to 2-3 per element on mobile devices to maintain responsiveness - **Effect complexity**: Blur and LUT filters are computationally expensive compared to simple adjustments - **Video effects**: Apply effects sparingly to video blocks to maintain smooth playback - **Real-time editing**: Temporarily disable effects during intensive editing operations for better interactivity Test your effect combinations on target devices early in development to ensure acceptable performance. ## Troubleshooting ### Effect Not Visible If an effect doesn't appear after applying it, check these common issues: - Verify the block type supports effects using `supportsEffects()` - Check that the effect is enabled with `isEffectEnabled()` - Ensure effect parameters are in valid ranges (e.g., intensity values between 0.0 and 1.0) - Confirm the effect is in the effect stack with `getEffects()` ### Performance Degradation If you experience slow rendering or laggy interactions: - Reduce the number of effects per element (aim for 2-3 maximum on mobile) - Lower blur radius values or use smaller LUT files - Temporarily disable effects during editing with `setEffectEnabled()` - Test on target devices early to identify performance bottlenecks ### Effects Not Persisting Effects should save automatically with the scene, but verify: - You're not destroying effects prematurely before saving - Save/load operations complete successfully - Effect URIs (LUT files, images) remain accessible after loading ### Incompatible Block Types If you can't apply an effect: - Remember that graphic blocks (with image or video fills), shape blocks, and text blocks support effects - Page blocks themselves don't support effects directly, but page fills (such as background fills) do support effects - Scene blocks cannot have effects applied - Check the block type with `block.getType()` and use `block.supportsEffects()` before attempting to apply effects ## API Reference | Method | Description | | ------------------------------------------ | ------------------------------------------ | | `block.supportsEffects(block)` | Check if a block supports effects | | `block.createEffect(type)` | Create a new effect instance | | `block.appendEffect(block, effect)` | Add effect to the end of the effect stack | | `block.insertEffect(block, effect, index)` | Insert effect at a specific position | | `block.removeEffect(block, index)` | Remove effect at the specified index | | `block.getEffects(block)` | Get all effects applied to a block | | `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `block.isEffectEnabled(effect)` | Check if an effect is currently enabled | | `block.findAllProperties(effect)` | Get all available properties for an effect | | `block.setFloat(effect, property, value)` | Set a floating-point property value | | `block.setInt(effect, property, value)` | Set an integer property value | | `block.setString(effect, property, value)` | Set a string property value | | `block.setBool(effect, property, value)` | Set a boolean property value | | `block.destroy(effect)` | Destroy an unused effect instance | ## About the Example Code The example code accompanying this guide follows educational design patterns to help you learn effectively: - **Individual demonstrations**: Each effect type is demonstrated on its own image block before showing combinations, making it easier to understand what each effect does - **Convenience API usage**: The code uses `engine.block.addImage()` instead of manual block construction—this is the recommended approach for simplicity and maintainability - **Spatial layout**: Image blocks are positioned in a grid layout (x/y coordinates) so you can visually see the results of each effect when running the example - **Progressive complexity**: The example starts with simple single effects and gradually builds to complex multi-effect combinations In your production code, you can apply multiple effects directly to the same block without creating separate demonstration blocks. The example structure is optimized for learning, not production usage. ## Next Steps Now that you understand how to apply and manage filters and effects, explore specific effect types and advanced techniques: - **LUT Filters** - Create custom color grading filters for cinematic looks - **Blur Effects** - Apply depth of field and motion blur techniques - **Duotone Effects** - Create striking two-color artistic treatments - **Adjustments** - Fine-tune brightness, contrast, and saturation - **Effect Combinations** - Build sophisticated multi-effect visual treatments --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Blur Effects" description: "Apply blur effects to design elements to create depth, focus attention, or soften backgrounds." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Apply Blur](https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/) --- Apply blur effects to design elements using CE.SDK's dedicated blur system for creating depth, focus, and atmospheric effects. ![Blur Effects example showing an image with radial blur applied](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-blur-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-blur-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-blur-browser/) Unlike general effects that stack on elements, blur is a dedicated feature with its own API methods. Each block supports exactly one blur at a time, though the same blur instance can be shared across multiple blocks. CE.SDK provides four blur types: **uniform** for consistent softening, **linear** and **mirrored** for gradient-based effects along axes, and **radial** for circular focal points. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-blur-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; class BlurPlugin implements EditorPlugin { name = 'BlurPlugin'; version = '1.0.0'; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } const engine = cesdk.engine; await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const page = engine.block.findByType('page')[0]; // Get page dimensions to position content correctly const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); if (!engine.block.supportsBlur(page)) { console.log('Block does not support blur'); return; } // Create an image block const imageBlock = engine.block.create('graphic'); engine.block.setShape(imageBlock, engine.block.createShape('rect')); const imageFill = engine.block.createFill('image'); engine.block.setFill(imageBlock, imageFill); engine.block.setString( imageFill, 'fill/image/imageFileURI', 'https://img.ly/static/ubq_samples/sample_1.jpg' ); // Position image to fill the page engine.block.setWidth(imageBlock, pageWidth); engine.block.setHeight(imageBlock, pageHeight); engine.block.setPositionX(imageBlock, 0); engine.block.setPositionY(imageBlock, 0); engine.block.appendChild(page, imageBlock); const blur = engine.block.createBlur('//ly.img.ubq/blur/radial'); engine.block.setFloat(blur, 'blur/radial/blurRadius', 40); engine.block.setFloat(blur, 'blur/radial/radius', 100); engine.block.setFloat(blur, 'blur/radial/gradientRadius', 80); engine.block.setFloat(blur, 'blur/radial/x', 0.5); engine.block.setFloat(blur, 'blur/radial/y', 0.5); engine.block.setBlur(imageBlock, blur); engine.block.setBlurEnabled(imageBlock, true); const appliedBlur = engine.block.getBlur(imageBlock); const isEnabled = engine.block.isBlurEnabled(imageBlock); const blurType = engine.block.getType(appliedBlur); console.log('Blur type:', blurType, 'Enabled:', isEnabled); engine.block.setBlurEnabled(imageBlock, false); const nowEnabled = engine.block.isBlurEnabled(imageBlock); console.log('Blur now enabled:', nowEnabled); engine.block.setBlurEnabled(imageBlock, true); } } export default BlurPlugin; ``` This guide covers how to apply blur effects programmatically using the block API. ## Programmatic Blur Application ### Check Blur Support Before applying blur to a block, verify it supports blur effects. Graphic blocks with shapes and pages support blur. ```typescript highlight-check-blur-support if (!engine.block.supportsBlur(page)) { console.log('Block does not support blur'); return; } ``` Always check support before creating and applying blur to avoid errors. ### Create and Apply Blur Create a blur instance using `createBlur()` with a blur type, then attach it to a block using `setBlur()`. Enable the blur with `setBlurEnabled()`. ```typescript highlight-create-blur const blur = engine.block.createBlur('//ly.img.ubq/blur/radial'); ``` CE.SDK provides four blur types: - **`//ly.img.ubq/blur/uniform`** - Even softening across the entire element - **`//ly.img.ubq/blur/linear`** - Gradient blur along a line defined by two control points - **`//ly.img.ubq/blur/mirrored`** - Band of focus with blur on both sides (tilt-shift style) - **`//ly.img.ubq/blur/radial`** - Circular blur pattern from a center point > **Note:** Omitting the prefix is also accepted, e.g., `'radial'` instead of > `'//ly.img.ubq/blur/radial'`. ### Configure Blur Parameters Each blur type has specific parameters to control its appearance. Configure them using `setFloat()`. ```typescript highlight-configure-blur engine.block.setFloat(blur, 'blur/radial/blurRadius', 40); engine.block.setFloat(blur, 'blur/radial/radius', 100); engine.block.setFloat(blur, 'blur/radial/gradientRadius', 80); engine.block.setFloat(blur, 'blur/radial/x', 0.5); engine.block.setFloat(blur, 'blur/radial/y', 0.5); ``` **Radial blur parameters:** - `blur/radial/blurRadius` - Blur intensity (default: 30) - `blur/radial/radius` - Size of the non-blurred center area (default: 75) - `blur/radial/gradientRadius` - Size of the blur transition zone (default: 50) - `blur/radial/x` - Center point x-value, 0.0 to 1.0 (default: 0.5) - `blur/radial/y` - Center point y-value, 0.0 to 1.0 (default: 0.5) **Uniform blur parameters:** - `blur/uniform/intensity` - Blur strength, 0.0 to 1.0 (default: 0.5) **Linear blur parameters:** - `blur/linear/blurRadius` - Blur intensity (default: 30) - `blur/linear/x1`, `blur/linear/y1` - Control point 1 (default: 0, 0.5) - `blur/linear/x2`, `blur/linear/y2` - Control point 2 (default: 1, 0.5) **Mirrored blur parameters:** - `blur/mirrored/blurRadius` - Blur intensity (default: 30) - `blur/mirrored/gradientSize` - Hardness of gradient transition (default: 50) - `blur/mirrored/size` - Size of the blurred area (default: 75) - `blur/mirrored/x1`, `blur/mirrored/y1` - Control point 1 (default: 0, 0.5) - `blur/mirrored/x2`, `blur/mirrored/y2` - Control point 2 (default: 1, 0.5) ### Apply Blur to Block After configuring the blur, apply it to the target block and enable it. ```typescript highlight-apply-blur engine.block.setBlur(imageBlock, blur); engine.block.setBlurEnabled(imageBlock, true); ``` The blur takes effect immediately once enabled. You can modify parameters at any time and changes apply in real-time. ## Managing Blur ### Access Existing Blur Retrieve the blur applied to a block using `getBlur()`. You can then read or modify its properties. ```typescript highlight-read-blur const appliedBlur = engine.block.getBlur(imageBlock); const isEnabled = engine.block.isBlurEnabled(imageBlock); const blurType = engine.block.getType(appliedBlur); console.log('Blur type:', blurType, 'Enabled:', isEnabled); ``` ### Enable/Disable Blur Toggle blur on and off without removing it using `setBlurEnabled()`. This preserves all blur parameters for quick before/after comparisons. ```typescript highlight-toggle-blur engine.block.setBlurEnabled(imageBlock, false); const nowEnabled = engine.block.isBlurEnabled(imageBlock); console.log('Blur now enabled:', nowEnabled); engine.block.setBlurEnabled(imageBlock, true); ``` When disabled, the blur remains attached to the block but doesn't render until re-enabled. ### Share Blur Across Blocks A single blur instance can be applied to multiple blocks. Create the blur once, then assign it to each block with `setBlur()`. ```typescript const sharedBlur = engine.block.createBlur('//ly.img.ubq/blur/uniform'); engine.block.setFloat(sharedBlur, 'blur/uniform/intensity', 0.4); engine.block.setBlur(block1, sharedBlur); engine.block.setBlur(block2, sharedBlur); engine.block.setBlurEnabled(block1, true); engine.block.setBlurEnabled(block2, true); ``` Changes to the shared blur affect all blocks using it. ### Replace Blur To change the blur type on a block, create a new blur and assign it with `setBlur()`. The previous blur association is automatically removed. ```typescript const newBlur = engine.block.createBlur('//ly.img.ubq/blur/linear'); engine.block.setBlur(block, newBlur); engine.block.setBlurEnabled(block, true); ``` If the old blur isn't used elsewhere, destroy it with `engine.block.destroy(oldBlur)`. ## Troubleshooting ### Blur Not Visible If blur doesn't appear after applying: - Check the block supports blur with `supportsBlur()` - Verify blur is enabled with `isBlurEnabled()` - Ensure the blur instance is valid ### Blur Appears on Wrong Area For radial, linear, and mirrored blurs: - Verify control point coordinates are within 0.0 to 1.0 range - Check that x/y values match your intended focus area ### Blur Too Subtle or Too Strong - Increase or decrease `blurRadius` or `intensity` values - For radial blur, adjust `gradientRadius` to control the transition softness ## API Reference | Method | Description | | --------------------------------------- | ---------------------------- | | `block.createBlur(type)` | Create new blur instance | | `block.supportsBlur(block)` | Check if block supports blur | | `block.setBlur(block, blur)` | Apply blur to block | | `block.getBlur(block)` | Get blur from block | | `block.setBlurEnabled(block, enabled)` | Enable or disable blur | | `block.isBlurEnabled(block)` | Check if blur is enabled | | `block.setFloat(blur, property, value)` | Set blur float property | | `block.getFloat(blur, property)` | Get blur float property | | `block.getType(blur)` | Get blur type identifier | | `block.destroy(blur)` | Destroy unused blur instance | ## Linear Type A blur effect applied along a linear gradient. This section describes the properties available for the **Linear Type** (`//ly.img.ubq/blur/linear`) block type. | Property | Type | Default | Description | | ------------------------ | ------- | ------- | ------------------------ | | `blur/linear/blurRadius` | `Float` | `30` | Blur intensity. | | `blur/linear/x1` | `Float` | `0` | Control point 1 x-value. | | `blur/linear/x2` | `Float` | `1` | Control point 2 x-value. | | `blur/linear/y1` | `Float` | `0.5` | Control point 1 y-value. | | `blur/linear/y2` | `Float` | `0.5` | Control point 2 y-value. | ## Mirrored Type A blur effect applied in a mirrored linear fashion. This section describes the properties available for the **Mirrored Type** (`//ly.img.ubq/blur/mirrored`) block type. | Property | Type | Default | Description | | ---------------------------- | ------- | ------- | ------------------------ | | `blur/mirrored/blurRadius` | `Float` | `30` | Blur intensity. | | `blur/mirrored/gradientSize` | `Float` | `50` | Hardness of gradients. | | `blur/mirrored/size` | `Float` | `75` | Size of blurred area. | | `blur/mirrored/x1` | `Float` | `0` | Control point 1 x-value. | | `blur/mirrored/x2` | `Float` | `1` | Control point 2 x-value. | | `blur/mirrored/y1` | `Float` | `0.5` | Control point 1 y-value. | | `blur/mirrored/y2` | `Float` | `0.5` | Control point 2 y-value. | ## Radial Type A blur effect applied radially from a center point. This section describes the properties available for the **Radial Type** (`//ly.img.ubq/blur/radial`) block type. | Property | Type | Default | Description | | ---------------------------- | ------- | ------- | ------------------------- | | `blur/radial/blurRadius` | `Float` | `30` | Blur intensity. | | `blur/radial/gradientRadius` | `Float` | `50` | Size of blurred area. | | `blur/radial/radius` | `Float` | `75` | Size of non-blurred area. | | `blur/radial/x` | `Float` | `0.5` | Center point x-value. | | `blur/radial/y` | `Float` | `0.5` | Center point y-value. | ## Uniform Type A blur effect with uniform intensity. This section describes the properties available for the **Uniform Type** (`//ly.img.ubq/blur/uniform`) block type. | Property | Type | Default | Description | | ------------------------ | ------- | ------- | ------------------- | | `blur/uniform/intensity` | `Float` | `0.5` | The blur intensity. | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Chroma Key (Green Screen)" description: "Apply the green screen effect to images and videos, replacing specific colors with transparency for compositing workflows." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/chroma-key-green-screen-1e3e99/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Apply Chroma Key (Green Screen)](https://img.ly/docs/cesdk/angular/filters-and-effects/chroma-key-green-screen-1e3e99/) --- Replace specific colors with transparency using CE.SDK's green screen effect for video compositing and virtual background applications. ![Chroma Key (Green Screen) example showing an image with color keying applied](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-chroma-key-green-screen-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-chroma-key-green-screen-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-chroma-key-green-screen-browser/) The green screen effect (chroma key) replaces a specified color with transparency, enabling compositing workflows where foreground subjects appear over different backgrounds. While green is the most common key color due to its contrast with skin tones, the effect works with any solid color—blue screens, white backgrounds, or custom colors. CE.SDK processes chroma keying in real-time using GPU-accelerated shaders. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-chroma-key-green-screen-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Chroma Key (Green Screen) Guide * * Demonstrates the green screen effect for color keying: * - Applying the green screen effect to an image * - Configuring the target color to key out * - Adjusting colorMatch, smoothness, and spill parameters * - Compositing with background layers * - Managing and toggling effects */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Create an image block to apply the green screen effect const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: { width: 600, height: 450 } } ); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 100); engine.block.setPositionY(imageBlock, 75); // Create the green screen effect const greenScreenEffect = engine.block.createEffect('green_screen'); // Apply the effect to the image block engine.block.appendEffect(imageBlock, greenScreenEffect); // Set the target color to key out // Use off-white to remove the bright sky background // For traditional green screen footage, use { r: 0.0, g: 1.0, b: 0.0, a: 1.0 } engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.98, g: 0.98, b: 0.98, a: 1.0 }); // Adjust color matching tolerance // Higher values (closer to 1.0) key out more color variations // Lower values create more precise keying engine.block.setFloat( greenScreenEffect, 'effect/green_screen/colorMatch', 0.26 ); // Control edge smoothness for natural transitions // Higher values create softer edges that blend with backgrounds engine.block.setFloat( greenScreenEffect, 'effect/green_screen/smoothness', 1.0 ); // Remove color spill from reflective surfaces // Reduces color tint on edges near the keyed background engine.block.setFloat(greenScreenEffect, 'effect/green_screen/spill', 1.0); // Create a background layer for compositing const backgroundBlock = engine.block.create('graphic'); const backgroundShape = engine.block.createShape('rect'); engine.block.setShape(backgroundBlock, backgroundShape); // Create a solid color fill for the background const backgroundFill = engine.block.createFill('color'); engine.block.setColor(backgroundFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }); engine.block.setFill(backgroundBlock, backgroundFill); // Size and position the background to cover the page engine.block.setWidth(backgroundBlock, 800); engine.block.setHeight(backgroundBlock, 600); engine.block.setPositionX(backgroundBlock, 0); engine.block.setPositionY(backgroundBlock, 0); // Add to page and send to back engine.block.appendChild(page, backgroundBlock); engine.block.sendToBack(backgroundBlock); // Bring the keyed image to front engine.block.bringToFront(imageBlock); // Check if the effect is currently enabled const isEnabled = engine.block.isEffectEnabled(greenScreenEffect); console.log('Green screen effect enabled:', isEnabled); // Toggle the effect on or off engine.block.setEffectEnabled(greenScreenEffect, !isEnabled); console.log( 'Effect toggled:', engine.block.isEffectEnabled(greenScreenEffect) ); // Re-enable for demonstration engine.block.setEffectEnabled(greenScreenEffect, true); // Check if the block supports effects const supportsEffects = engine.block.supportsEffects(imageBlock); console.log('Block supports effects:', supportsEffects); // Get all effects applied to the block const effects = engine.block.getEffects(imageBlock); console.log('Number of effects:', effects.length); // Remove the effect from the block (by index) engine.block.removeEffect(imageBlock, 0); console.log('Effect removed from block'); // Destroy the effect instance to free resources engine.block.destroy(greenScreenEffect); console.log('Effect destroyed'); // Re-apply the effect for final display const finalEffect = engine.block.createEffect('green_screen'); engine.block.appendEffect(imageBlock, finalEffect); engine.block.setColor(finalEffect, 'effect/green_screen/fromColor', { r: 0.98, g: 0.98, b: 0.98, a: 1.0 }); engine.block.setFloat(finalEffect, 'effect/green_screen/colorMatch', 0.26); engine.block.setFloat(finalEffect, 'effect/green_screen/smoothness', 1.0); engine.block.setFloat(finalEffect, 'effect/green_screen/spill', 1.0); // Select the image block so the inspector panel shows it engine.block.setSelected(imageBlock, true); console.log( 'Chroma key guide initialized. Select the image to see effect parameters.' ); } } export default Example; ``` This guide covers how to apply the green screen effect programmatically, configure color selection and keying parameters, composite with background layers, and manage effects on blocks. ## Apply the Green Screen Effect We start by creating an image block and applying the green screen effect to it. The effect immediately processes the target color, making matching pixels transparent. ```typescript highlight-create-effect // Create an image block to apply the green screen effect const imageBlock = await engine.block.addImage( 'https://img.ly/static/ubq_samples/sample_4.jpg', { size: { width: 600, height: 450 } } ); engine.block.appendChild(page, imageBlock); engine.block.setPositionX(imageBlock, 100); engine.block.setPositionY(imageBlock, 75); // Create the green screen effect const greenScreenEffect = engine.block.createEffect('green_screen'); // Apply the effect to the image block engine.block.appendEffect(imageBlock, greenScreenEffect); ``` The `createEffect('green_screen')` method creates a new green screen effect instance. We then attach it to the image block using `appendEffect()`, which adds the effect to the block's effect stack. ## Configure Color Selection The green screen effect targets a specific color to key out. We set this color using `setColor()` with the `effect/green_screen/fromColor` property. ```typescript highlight-configure-color // Set the target color to key out // Use off-white to remove the bright sky background // For traditional green screen footage, use { r: 0.0, g: 1.0, b: 0.0, a: 1.0 } engine.block.setColor(greenScreenEffect, 'effect/green_screen/fromColor', { r: 0.98, g: 0.98, b: 0.98, a: 1.0 }); ``` The example uses off-white (`r: 0.98, g: 0.98, b: 0.98`) to key out a bright sky background. For traditional green screen footage, use pure green (`r: 0.0, g: 1.0, b: 0.0`). For blue screen footage, set the color to pure blue. Match the exact color you want to remove for best results. ## Adjust Color Matching Tolerance The `colorMatch` parameter controls how closely pixels must match the target color to be keyed out. We adjust this using `setFloat()`. ```typescript highlight-adjust-color-match // Adjust color matching tolerance // Higher values (closer to 1.0) key out more color variations // Lower values create more precise keying engine.block.setFloat( greenScreenEffect, 'effect/green_screen/colorMatch', 0.26 ); ``` Higher values (closer to 1.0) key out a wider range of similar colors, which is useful for footage with uneven lighting or color variations in the background. Lower values create more precise keying for well-lit footage with uniform backgrounds. ## Control Edge Smoothness The `smoothness` parameter controls the transition between opaque and transparent areas. This affects how sharp or soft the edges appear around keyed subjects. ```typescript highlight-adjust-smoothness // Control edge smoothness for natural transitions // Higher values create softer edges that blend with backgrounds engine.block.setFloat( greenScreenEffect, 'effect/green_screen/smoothness', 1.0 ); ``` Higher smoothness values create softer edges that blend naturally with new backgrounds, reducing harsh outlines. Lower values produce sharper edges, which may be preferable for high-contrast composites or when preserving fine detail. ## Remove Color Spill Color spill occurs when the key color reflects onto the foreground subject, creating a green or blue tint on edges. The `spill` parameter reduces this color cast. ```typescript highlight-adjust-spill // Remove color spill from reflective surfaces // Reduces color tint on edges near the keyed background engine.block.setFloat(greenScreenEffect, 'effect/green_screen/spill', 1.0); ``` Increase the spill value when you notice the key color appearing on subject edges or reflective surfaces. This is common with shiny hair, glasses, or metallic objects near the screen. ## Composite with Background Layers After keying, we can layer the transparent content over backgrounds using block ordering. We create a background block and use `sendToBack()` to place it behind the keyed image. ```typescript highlight-composite-background // Create a background layer for compositing const backgroundBlock = engine.block.create('graphic'); const backgroundShape = engine.block.createShape('rect'); engine.block.setShape(backgroundBlock, backgroundShape); // Create a solid color fill for the background const backgroundFill = engine.block.createFill('color'); engine.block.setColor(backgroundFill, 'fill/color/value', { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }); engine.block.setFill(backgroundBlock, backgroundFill); // Size and position the background to cover the page engine.block.setWidth(backgroundBlock, 800); engine.block.setHeight(backgroundBlock, 600); engine.block.setPositionX(backgroundBlock, 0); engine.block.setPositionY(backgroundBlock, 0); // Add to page and send to back engine.block.appendChild(page, backgroundBlock); engine.block.sendToBack(backgroundBlock); // Bring the keyed image to front engine.block.bringToFront(imageBlock); ``` The background appears through the transparent areas where the key color was removed. You can use image or video fills instead of solid colors for more dynamic backgrounds. ## Toggle the Effect We can check whether an effect is enabled using `isEffectEnabled()`. ```typescript highlight-check-enabled // Check if the effect is currently enabled const isEnabled = engine.block.isEffectEnabled(greenScreenEffect); ``` To toggle the effect on or off, use `setEffectEnabled()`. This preserves the effect configuration while temporarily removing its visual impact. ```typescript highlight-set-enabled // Toggle the effect on or off engine.block.setEffectEnabled(greenScreenEffect, !isEnabled); ``` Toggling effects is useful for before/after comparisons or conditional processing without removing and recreating the effect. ## Manage the Effect Beyond toggling, you can query, remove, and clean up effects. Use `supportsEffects()` to check if a block can have effects, `getEffects()` to list all applied effects, `removeEffect()` to detach an effect from a block, and `destroy()` to free the effect's resources. ```typescript highlight-manage-effects // Check if the block supports effects const supportsEffects = engine.block.supportsEffects(imageBlock); console.log('Block supports effects:', supportsEffects); // Get all effects applied to the block const effects = engine.block.getEffects(imageBlock); console.log('Number of effects:', effects.length); // Remove the effect from the block (by index) engine.block.removeEffect(imageBlock, 0); console.log('Effect removed from block'); // Destroy the effect instance to free resources engine.block.destroy(greenScreenEffect); console.log('Effect destroyed'); ``` When removing effects, use the index from `getEffects()` to specify which effect to remove. After removing an effect from a block, call `destroy()` on the effect instance to release its resources. This is important for memory management in long-running applications. ## Troubleshooting ### Keying Results Appear Rough or Incomplete - Increase `colorMatch` value to capture more color variations - Ensure source footage has even lighting on the screen - Check that the target color accurately matches the screen color ### Edges Have Color Fringing - Increase `spill` value to remove color cast - Adjust `smoothness` to soften hard edges - Consider using a higher `colorMatch` for gradual color transitions ### Transparent Areas Appear in Wrong Places - Decrease `colorMatch` to be more selective about which colors are keyed - Verify the `fromColor` matches only the intended background color - Check that foreground subjects don't contain colors similar to the key color ## API Reference | Method | Description | | ------------------------------------------------------------------- | -------------------------------------- | | `block.createEffect('green_screen')` | Create a green screen effect instance | | `block.appendEffect(block, effect)` | Add effect to a block's effect stack | | `block.setColor(effect, 'effect/green_screen/fromColor', color)` | Set the color to key out | | `block.setFloat(effect, 'effect/green_screen/colorMatch', value)` | Set color matching tolerance (0.0-1.0) | | `block.setFloat(effect, 'effect/green_screen/smoothness', value)` | Set edge smoothness (0.0-1.0) | | `block.setFloat(effect, 'effect/green_screen/spill', value)` | Set spill removal intensity (0.0-1.0) | | `block.isEffectEnabled(effect)` | Check if an effect is enabled | | `block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `block.supportsEffects(block)` | Check if a block supports effects | | `block.getEffects(block)` | Get all effects applied to a block | | `block.removeEffect(block, index)` | Remove effect at specified index | | `block.destroy(effect)` | Destroy an effect instance | ## Next Steps - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) - Learn the fundamentals of the effect system - [Blur](https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/) - Add blur effects for depth of field - [Duotone](https://img.ly/docs/cesdk/angular/filters-and-effects/duotone-831fc5/) - Create two-color artistic treatments --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Custom Filters" description: "Extend CE.SDK with custom LUT filter asset sources for brand-specific color grading and filter collections." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-filters-c796ba/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Create Custom Filters](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-filters-c796ba/) --- Extend CE.SDK with your own LUT filters by creating and registering custom filter asset sources for brand-specific color grading. ![Create Custom Filters example showing images with custom filters applied](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-add-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-add-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-add-browser/) CE.SDK provides built-in LUT filters, but many applications need brand-specific color grading or custom filter collections. Custom filter asset sources let you register your own LUT filters that appear alongside or replace the defaults. Once registered, custom filters integrate seamlessly with the built-in UI and can be applied programmatically. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-add-browser/browser.ts reference-only import type { AssetQueryData, AssetResult, AssetSource, AssetsQueryResult, EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import packageJson from './package.json'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; /** * CE.SDK Plugin: Create Custom Filters Guide * * Demonstrates creating and registering custom LUT filter asset sources: * - Creating a filter source with addSource() * - Defining filter assets with LUT metadata * - Loading filters from JSON configuration * - Querying custom filters * - Applying filters from custom sources */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Enable filters in the inspector panel using the Feature API cesdk.feature.enable('ly.img.filter'); // Add a custom filter to the built-in LUT filter source // The ID must follow the format //ly.img.cesdk.filters.lut/{name} // for the UI to display the label correctly engine.asset.addAssetToSource('ly.img.filter', { id: '//ly.img.cesdk.filters.lut/mycustomfilter', label: { en: 'MY CUSTOM FILTER' }, tags: { en: ['custom', 'brand'] }, meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }); // Add translation for the custom filter label cesdk.i18n.setTranslations({ en: { 'property.lutFilter.mycustomfilter': 'MY CUSTOM FILTER' } }); // Create a custom filter asset source for organizing multiple filters const customFilterSource: AssetSource = { id: 'my-custom-filters', async findAssets( queryData: AssetQueryData ): Promise { // Define custom LUT filter assets const filters: AssetResult[] = [ { id: 'vintage-warm', label: 'Vintage Warm', tags: ['vintage', 'warm', 'retro'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'cool-cinema', label: 'Cool Cinema', tags: ['cinema', 'cool', 'film'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'bw-classic', label: 'B&W Classic', tags: ['black and white', 'classic', 'monochrome'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ]; // Filter by query if provided let filteredAssets = filters; if (queryData.query) { const searchTerm = queryData.query.toLowerCase(); filteredAssets = filters.filter( (asset) => asset.label?.toLowerCase().includes(searchTerm) || asset.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ); } // Filter by groups if provided if (queryData.groups && queryData.groups.length > 0) { filteredAssets = filteredAssets.filter((asset) => asset.tags?.some((tag) => queryData.groups?.includes(tag)) ); } // Handle pagination const page = queryData.page ?? 0; const perPage = queryData.perPage ?? 10; const startIndex = page * perPage; const paginatedAssets = filteredAssets.slice( startIndex, startIndex + perPage ); return { assets: paginatedAssets, total: filteredAssets.length, currentPage: page, nextPage: startIndex + perPage < filteredAssets.length ? page + 1 : undefined }; }, // Return available filter categories async getGroups(): Promise { return ['vintage', 'cinema', 'black and white']; } }; // Register the custom filter source for programmatic access engine.asset.addSource(customFilterSource); // Load filters from a JSON configuration string const filterConfigJSON = JSON.stringify({ version: '2.0.0', id: 'my-json-filters', assets: [ { id: 'sunset-glow', label: { en: 'Sunset Glow' }, tags: { en: ['warm', 'sunset', 'golden'] }, groups: ['Warm Tones'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'ocean-breeze', label: { en: 'Ocean Breeze' }, tags: { en: ['cool', 'blue', 'ocean'] }, groups: ['Cool Tones'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ] }); // Create asset source from JSON string const jsonSourceId = await engine.asset.addLocalAssetSourceFromJSONString( filterConfigJSON ); // eslint-disable-next-line no-console console.log('Created JSON-based filter source:', jsonSourceId); // Query filters from our custom source for programmatic use const customFilterResults = await engine.asset.findAssets( 'my-custom-filters', { page: 0, perPage: 10 } ); // Create an image block to demonstrate applying a custom filter const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { x: 50, y: 150, size: { width: 300, height: 200 } }); engine.block.appendChild(page, imageBlock); // Get a filter from our custom source const filterAsset = customFilterResults.assets[0]; if (filterAsset && filterAsset.meta) { // Create and configure the LUT filter effect const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Set LUT file URI from asset metadata engine.block.setString( lutEffect, 'effect/lut_filter/lutFileURI', filterAsset.meta.uri as string ); // Configure LUT grid dimensions engine.block.setInt( lutEffect, 'effect/lut_filter/horizontalTileCount', parseInt(filterAsset.meta.horizontalTileCount as string, 10) ); engine.block.setInt( lutEffect, 'effect/lut_filter/verticalTileCount', parseInt(filterAsset.meta.verticalTileCount as string, 10) ); // Set filter intensity (0.0 to 1.0) engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.85); // Apply the effect to the image block engine.block.appendEffect(imageBlock, lutEffect); } // Create a second image without a filter for comparison const imageBlock2 = await engine.block.addImage(imageUri, { x: 450, y: 150, size: { width: 300, height: 200 } }); engine.block.appendChild(page, imageBlock2); // Select the filtered image to show the filter in the inspector engine.block.select(imageBlock); // eslint-disable-next-line no-console console.log( 'Custom filters guide initialized. Select an image to see filters in the inspector panel.' ); } } export default Example; ``` This guide covers how to create filter asset sources, define filter metadata, load filters from JSON configuration, and apply custom filters to design elements. ## Filter Asset Metadata LUT filters need these properties in the `meta` object: - **`uri`** - URL to the LUT image file (PNG format) - **`thumbUri`** - URL to the thumbnail preview image - **`horizontalTileCount`** - Number of horizontal tiles in the LUT grid (typically 5 or 8) - **`verticalTileCount`** - Number of vertical tiles in the LUT grid (typically 5 or 8) - **`blockType`** - Must be `//ly.img.ubq/effect/lut_filter` for LUT filters ## Adding a Custom Filter We register a custom filter source using `engine.asset.addSource()` with a `findAssets` callback. This callback returns filter assets matching the query parameters. After registering the source, we use `cesdk.ui.updateAssetLibraryEntry()` to add our custom source to the filter inspector panel. ```typescript highlight-create-custom-source // Add a custom filter to the built-in LUT filter source // The ID must follow the format //ly.img.cesdk.filters.lut/{name} // for the UI to display the label correctly engine.asset.addAssetToSource('ly.img.filter', { id: '//ly.img.cesdk.filters.lut/mycustomfilter', label: { en: 'MY CUSTOM FILTER' }, tags: { en: ['custom', 'brand'] }, meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }); // Add translation for the custom filter label cesdk.i18n.setTranslations({ en: { 'property.lutFilter.mycustomfilter': 'MY CUSTOM FILTER' } }); // Create a custom filter asset source for organizing multiple filters const customFilterSource: AssetSource = { id: 'my-custom-filters', async findAssets( queryData: AssetQueryData ): Promise { // Define custom LUT filter assets const filters: AssetResult[] = [ { id: 'vintage-warm', label: 'Vintage Warm', tags: ['vintage', 'warm', 'retro'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'cool-cinema', label: 'Cool Cinema', tags: ['cinema', 'cool', 'film'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'bw-classic', label: 'B&W Classic', tags: ['black and white', 'classic', 'monochrome'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ]; // Filter by query if provided let filteredAssets = filters; if (queryData.query) { const searchTerm = queryData.query.toLowerCase(); filteredAssets = filters.filter( (asset) => asset.label?.toLowerCase().includes(searchTerm) || asset.tags?.some((tag) => tag.toLowerCase().includes(searchTerm)) ); } // Filter by groups if provided if (queryData.groups && queryData.groups.length > 0) { filteredAssets = filteredAssets.filter((asset) => asset.tags?.some((tag) => queryData.groups?.includes(tag)) ); } // Handle pagination const page = queryData.page ?? 0; const perPage = queryData.perPage ?? 10; const startIndex = page * perPage; const paginatedAssets = filteredAssets.slice( startIndex, startIndex + perPage ); return { assets: paginatedAssets, total: filteredAssets.length, currentPage: page, nextPage: startIndex + perPage < filteredAssets.length ? page + 1 : undefined }; }, // Return available filter categories async getGroups(): Promise { return ['vintage', 'cinema', 'black and white']; } }; // Register the custom filter source for programmatic access engine.asset.addSource(customFilterSource); ``` The `findAssets` callback receives query parameters including pagination (`page`, `perPage`), search terms (`query`), and category filters (`groups`). We filter and paginate the results accordingly. The `updateAssetLibraryEntry()` call connects our custom source to the `ly.img.filter` panel, making our filters appear alongside the built-in LUT filters when a user selects an image. ### Filter Asset Structure Each filter asset returned by `findAssets` needs: - **`id`** - Unique identifier for the filter - **`label`** - Display name shown in the UI - **`tags`** - Keywords for search filtering - **`meta`** - Object containing LUT configuration (uri, thumbUri, tile counts, blockType) The optional `getGroups()` method returns available filter categories for the UI. ## Loading Filters from JSON Configuration For larger filter collections, we load definitions from JSON using `engine.asset.addLocalAssetSourceFromJSONString()`. This approach simplifies management of filter libraries. ```typescript highlight-load-from-json-string // Load filters from a JSON configuration string const filterConfigJSON = JSON.stringify({ version: '2.0.0', id: 'my-json-filters', assets: [ { id: 'sunset-glow', label: { en: 'Sunset Glow' }, tags: { en: ['warm', 'sunset', 'golden'] }, groups: ['Warm Tones'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } }, { id: 'ocean-breeze', label: { en: 'Ocean Breeze' }, tags: { en: ['cool', 'blue', 'ocean'] }, groups: ['Cool Tones'], meta: { uri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', thumbUri: 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png', horizontalTileCount: '5', verticalTileCount: '5', blockType: '//ly.img.ubq/effect/lut_filter' } } ] }); // Create asset source from JSON string const jsonSourceId = await engine.asset.addLocalAssetSourceFromJSONString( filterConfigJSON ); // eslint-disable-next-line no-console console.log('Created JSON-based filter source:', jsonSourceId); ``` ### JSON Structure for Filter Assets The JSON format includes: - **`version`** - Schema version (use "2.0.0") - **`id`** - Unique source identifier - **`assets`** - Array of filter definitions Each asset in the array contains: - **`id`** - Unique filter identifier - **`label`** - Localized label object (e.g., `{ "en": "Filter Name" }`) - **`tags`** - Localized tags for search - **`groups`** - Category assignments for UI organization - **`meta`** - LUT configuration properties For filters hosted on a CDN, use `engine.asset.addLocalAssetSourceFromJSONURI()` instead, which resolves relative URLs against the JSON file's parent directory. ## Troubleshooting ### Filters Not Appearing in UI - Verify the asset source is registered before loading the scene - Check that filter metadata includes all required fields (`uri`, `thumbUri`, tile counts) - Ensure the `blockType` is set to `//ly.img.ubq/effect/lut_filter` - Confirm thumbnails are accessible URLs ### LUT Not Rendering Correctly - Verify tile count values match the actual LUT image grid dimensions - Check that the LUT image URL is CORS-enabled for cross-origin requests - Confirm the LUT image uses PNG format ### Filter Thumbnails Missing - Verify `thumbUri` points to an accessible image - Check that thumbnail URLs don't have CORS restrictions - Ensure thumbnail dimensions are appropriate for UI display (typically 100-200px) ## API Reference | Method | Description | | --- | --- | | `engine.asset.addSource(source)` | Register a custom asset source with findAssets callback | | `engine.asset.addLocalAssetSourceFromJSONString(json, basePath)` | Create asset source from inline JSON configuration | | `engine.asset.addLocalAssetSourceFromJSONURI(uri)` | Load asset source from remote JSON file | | `engine.asset.findAssets(sourceId, query)` | Query assets from a registered source | | `engine.asset.findAllSources()` | Get all registered asset source IDs | | `engine.asset.removeSource(id)` | Remove a registered asset source | | `cesdk.ui.updateAssetLibraryEntry(entryId, config)` | Add custom sources to filter inspector panel | | `engine.block.createEffect(type)` | Create effect instance (use `//ly.img.ubq/effect/lut_filter` for LUT filters) | | `engine.block.setString(effect, property, value)` | Set string property (LUT file URI) | | `engine.block.setInt(effect, property, value)` | Set integer property (tile counts) | | `engine.block.setFloat(effect, property, value)` | Set float property (intensity) | | `engine.block.appendEffect(block, effect)` | Add effect to block's effect stack | ## Next Steps Now that you understand how to create and register custom filter sources, explore related topics: - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) - Learn to apply filters to design elements and manage effect stacks - [Create a Custom LUT Filter](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-lut-filter-6e3f49/) - Understand LUT image format and create your own color grading filters - [Blur Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/) - Add blur effects to images and videos --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create a Custom LUT Filter" description: "Create and apply custom LUT filters to achieve consistent, brand-aligned visual styles." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-lut-filter-6e3f49/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Apply Custom LUT Filter](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-lut-filter-6e3f49/) --- Apply custom LUT (Look-Up Table) filters to achieve brand-consistent color grading directly through CE.SDK's effect API. ![Create Custom LUT Filter example showing an image with a custom LUT color grade applied](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-create-custom-lut-filter-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-create-custom-lut-filter-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-create-custom-lut-filter-browser/) LUT filters remap colors through a predefined transformation table, enabling cinematic color grading and consistent brand aesthetics. This guide shows how to apply your own LUT files directly to design elements using the effect API. For organizing collections of filters through asset sources, see [Create Custom Filters](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-filters-c796ba/). ```typescript file=@cesdk_web_examples/guides-filters-and-effects-create-custom-lut-filter-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Create Custom LUT Filter Guide * * Demonstrates applying custom LUT filters directly using the effect API: * - Creating a lut_filter effect * - Configuring the LUT file URI and tile dimensions * - Setting filter intensity * - Toggling the effect on and off */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Create a gradient background for the page const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.15, g: 0.1, b: 0.25, a: 1 }, stop: 0 }, { color: { r: 0.3, g: 0.15, b: 0.4, a: 1 }, stop: 0.5 }, { color: { r: 0.2, g: 0.1, b: 0.35, a: 1 }, stop: 1 } ]); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointY', 1); engine.block.setFill(page, gradientFill); // Create a centered title text const titleText = engine.block.create('text'); engine.block.setString(titleText, 'text/text', 'Custom LUT Filter'); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.setTextFontSize(titleText, 96); engine.block.setTextColor(titleText, { r: 1, g: 1, b: 1, a: 1 }); engine.block.setWidthMode(titleText, 'Auto'); engine.block.setHeightMode(titleText, 'Auto'); engine.block.appendChild(page, titleText); // Create a subtext below the title const subText = engine.block.create('text'); engine.block.setString(subText, 'text/text', 'img.ly'); engine.block.setEnum(subText, 'text/horizontalAlignment', 'Center'); engine.block.setTextFontSize(subText, 64); engine.block.setTextColor(subText, { r: 0.8, g: 0.8, b: 0.8, a: 1 }); engine.block.setWidthMode(subText, 'Auto'); engine.block.setHeightMode(subText, 'Auto'); engine.block.appendChild(page, subText); // Get text dimensions for centering calculations const titleWidth = engine.block.getFrameWidth(titleText); const titleHeight = engine.block.getFrameHeight(titleText); const subTextWidth = engine.block.getFrameWidth(subText); const subTextHeight = engine.block.getFrameHeight(subText); // Image dimensions (smaller) const imageWidth = 200; const imageHeight = 150; // Calculate total content height and vertical centering const textGap = 8; const imagePadding = 60; const totalContentHeight = titleHeight + textGap + subTextHeight + imagePadding + imageHeight; const startY = (pageHeight - totalContentHeight) / 2; // Position title centered engine.block.setPositionX(titleText, (pageWidth - titleWidth) / 2); engine.block.setPositionY(titleText, startY); // Position subtext below title engine.block.setPositionX(subText, (pageWidth - subTextWidth) / 2); engine.block.setPositionY(subText, startY + titleHeight + textGap); // Add an image block to apply the LUT filter const imageY = startY + titleHeight + textGap + subTextHeight + imagePadding; const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { x: (pageWidth - imageWidth) / 2, y: imageY, size: { width: imageWidth, height: imageHeight } }); engine.block.appendChild(page, imageBlock); // Create a LUT filter effect const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); // Configure the LUT file URI - this is a tiled PNG containing the color lookup table const lutUrl = 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png'; engine.block.setString(lutEffect, 'effect/lut_filter/lutFileURI', lutUrl); // Set the tile grid dimensions - must match the LUT image structure engine.block.setInt(lutEffect, 'effect/lut_filter/horizontalTileCount', 5); engine.block.setInt(lutEffect, 'effect/lut_filter/verticalTileCount', 5); // Set filter intensity (0.0 = no effect, 1.0 = full effect) engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.8); // Apply the effect to the image block engine.block.appendEffect(imageBlock, lutEffect); // Register a custom button component to toggle the LUT filter cesdk.ui.registerComponent('lut.toggle', ({ builder }) => { const isEnabled = engine.block.isEffectEnabled(lutEffect); builder.Button('toggle-lut', { label: 'LUT Filter', icon: isEnabled ? '@imgly/ToggleIconOn' : '@imgly/ToggleIconOff', isActive: isEnabled, onClick: () => { engine.block.setEffectEnabled(lutEffect, !isEnabled); } }); }); // Add the toggle button to the navigation bar cesdk.ui.insertOrderComponent( { in: 'ly.img.navigation.bar', position: 'end' }, 'lut.toggle' ); // Retrieve all effects on the block const effects = engine.block.getEffects(imageBlock); // eslint-disable-next-line no-console console.log('Number of effects:', effects.length); // 1 // Check if block supports effects const supportsEffects = engine.block.supportsEffects(imageBlock); // eslint-disable-next-line no-console console.log('Supports effects:', supportsEffects); // true // Select the image to show it in the editor engine.block.select(imageBlock); // eslint-disable-next-line no-console console.log('Custom LUT filter applied successfully.'); } } export default Example; ``` ## Understanding LUT Image Format CE.SDK uses a tiled PNG format where a 3D color cube is laid out as a 2D grid. Each tile represents a slice of the color cube along the blue axis. The LUT image requires two configuration values: - **`horizontalTileCount`** - Number of tiles across the image width - **`verticalTileCount`** - Number of tiles down the image height CE.SDK supports these tile configurations: - 5×5 tiles with 128px cube size - 8×8 tiles with 512px cube size Standard `.cube` files must be converted to this tiled PNG format using image processing tools. ## Creating LUT PNG Images ### Obtaining LUT Files LUT files are available from multiple sources: - **Color grading software** - Adobe Photoshop, DaVinci Resolve, and Affinity Photo can export 3D LUT files in `.cube` format - **Online LUT libraries** - Many free and commercial LUT packs are available for download - **LUT generators** - Tools that create custom color transformations from reference images ### Converting .cube to Tiled PNG CE.SDK requires LUTs in a specific tiled PNG format where each tile represents a slice of the 3D color cube along the blue axis. To convert a standard `.cube` file: 1. **Parse the .cube file** - Read the 3D color lookup table data 2. **Arrange slices as tiles** - Each blue channel value becomes a separate tile containing the red-green color plane 3. **Export as PNG** - Save the grid as a PNG image CE.SDK's built-in LUTs follow a naming convention: `imgly_lut_{name}_{h}_{v}_{cubeSize}.png` where `h` and `v` are tile counts and `cubeSize` indicates the LUT precision. ### Using Python for Conversion You can write a Python script using PIL/Pillow and NumPy to convert `.cube` files: ```python # Pseudocode for .cube to tiled PNG conversion # 1. Parse the .cube file to extract the 3D LUT data # 2. Reshape data into (blue_slices, height, width, 3) array # 3. Arrange slices in a grid matching tile configuration # 4. Save as PNG with Image.fromarray() ``` ### Using CE.SDK's Built-in LUTs The simplest approach is to use CE.SDK's existing LUT assets as a starting point. The built-in filters use pre-generated tiled PNGs that you can reference for format verification. Check the filter extension at `ly.img.cesdk.filters.lut` for examples of properly formatted LUT images. ## Hosting LUT Files LUT images must be served from an accessible URL. For production deployments, use HTTPS and enable CORS headers for cross-origin requests in browser environments. ## Creating the LUT Effect Create a `lut_filter` effect instance using the effect API: ```typescript highlight-create-effect // Create a LUT filter effect const lutEffect = engine.block.createEffect( '//ly.img.ubq/effect/lut_filter' ); ``` This creates an effect that can be configured and applied to image blocks. ## Configuring LUT Properties Set the LUT file URL and tile dimensions to match your LUT image: ```typescript highlight-configure-lut // Configure the LUT file URI - this is a tiled PNG containing the color lookup table const lutUrl = 'https://cdn.img.ly/assets/v4/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png'; engine.block.setString(lutEffect, 'effect/lut_filter/lutFileURI', lutUrl); // Set the tile grid dimensions - must match the LUT image structure engine.block.setInt(lutEffect, 'effect/lut_filter/horizontalTileCount', 5); engine.block.setInt(lutEffect, 'effect/lut_filter/verticalTileCount', 5); ``` The tile counts must match the actual LUT image grid structure. Using incorrect values produces distorted colors. ## Setting Filter Intensity Control the strength of the color transformation with intensity: ```typescript highlight-set-intensity // Set filter intensity (0.0 = no effect, 1.0 = full effect) engine.block.setFloat(lutEffect, 'effect/lut_filter/intensity', 0.8); ``` Values range from 0.0 (no effect) to 1.0 (full effect). Use intermediate values for subtle color grading. ## Applying the Effect Attach the configured effect to an image block: ```typescript highlight-apply-effect // Apply the effect to the image block engine.block.appendEffect(imageBlock, lutEffect); ``` The effect renders immediately after being applied. ## Toggling the Effect Add a toggle button to the navigation bar for enabling and disabling the filter: ```typescript highlight-toggle-effect // Register a custom button component to toggle the LUT filter cesdk.ui.registerComponent('lut.toggle', ({ builder }) => { const isEnabled = engine.block.isEffectEnabled(lutEffect); builder.Button('toggle-lut', { label: 'LUT Filter', icon: isEnabled ? '@imgly/ToggleIconOn' : '@imgly/ToggleIconOff', isActive: isEnabled, onClick: () => { engine.block.setEffectEnabled(lutEffect, !isEnabled); } }); }); // Add the toggle button to the navigation bar cesdk.ui.insertOrderComponent( { in: 'ly.img.navigation.bar', position: 'end' }, 'lut.toggle' ); ``` The `registerComponent` function creates a custom UI component that tracks the effect's enabled state. The `insertOrderComponent` method adds it to the navigation bar. Clicking the button toggles the effect while preserving all settings. ## Managing Effects Retrieve and inspect effects applied to a block: ```typescript highlight-manage-effects // Retrieve all effects on the block const effects = engine.block.getEffects(imageBlock); // eslint-disable-next-line no-console console.log('Number of effects:', effects.length); // 1 // Check if block supports effects const supportsEffects = engine.block.supportsEffects(imageBlock); // eslint-disable-next-line no-console console.log('Supports effects:', supportsEffects); // true ``` Use `getEffects()` to access all effects on a block and `supportsEffects()` to verify compatibility before applying. ## Troubleshooting ### LUT Not Rendering - Verify the LUT image URL is accessible and CORS-enabled - Confirm the image uses PNG format - Check that tile count values match the actual image grid ### Colors Look Wrong - Verify tile counts match the LUT image structure - Ensure the LUT was generated with sRGB color space ## API Reference | Method | Description | | --- | --- | | `engine.block.createEffect('//ly.img.ubq/effect/lut_filter')` | Create a LUT filter effect instance | | `engine.block.setString(effect, 'effect/lut_filter/lutFileURI', uri)` | Set the LUT image URL | | `engine.block.setInt(effect, 'effect/lut_filter/horizontalTileCount', count)` | Set horizontal tile count | | `engine.block.setInt(effect, 'effect/lut_filter/verticalTileCount', count)` | Set vertical tile count | | `engine.block.setFloat(effect, 'effect/lut_filter/intensity', value)` | Set filter intensity (0.0-1.0) | | `engine.block.appendEffect(block, effect)` | Apply effect to a block | | `engine.block.getEffects(block)` | Get all effects on a block | | `engine.block.setEffectEnabled(effect, enabled)` | Enable or disable an effect | | `engine.block.isEffectEnabled(effect)` | Check if effect is enabled | | `engine.block.removeEffect(block, index)` | Remove effect at index | | `engine.block.destroy(effect)` | Destroy an effect instance | | `engine.block.supportsEffects(block)` | Check if block supports effects | | `cesdk.ui.registerComponent(id, renderFn)` | Register a custom UI component | | `cesdk.ui.insertOrderComponent(options, component)` | Add a component to the navigation bar | ## Next Steps - [Create Custom Filters](https://img.ly/docs/cesdk/angular/filters-and-effects/create-custom-filters-c796ba/) - Register custom LUT filters as asset sources - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) - Learn more about the effects system - [Duotone Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/duotone-831fc5/) - Create two-color artistic treatments --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Distortion Effects" description: "Apply distortion effects to warp, shift, and transform design elements for dynamic artistic visuals in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/distortion-5b5a66/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Distortion](https://img.ly/docs/cesdk/angular/filters-and-effects/distortion-5b5a66/) --- Apply distortion effects to warp, shift, and transform images and videos for dynamic artistic visuals. ![Distortion Effects](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-distortion-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-distortion-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-distortion-browser/) Distortion effects differ from color filters in that they modify the geometry and spatial arrangement of pixels rather than their color values. CE.SDK provides several distortion effect types: liquid warping, mirror reflections, color channel shifting, radial pixelation, and TV glitch. Each effect offers configurable parameters to control the intensity and style of the distortion. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-distortion-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Distortion Effects Guide * * Demonstrates applying various distortion effects to image blocks: * - Checking effect support * - Applying liquid distortion * - Applying mirror effect * - Applying shifter (chromatic aberration) * - Applying radial pixel effect * - Applying TV glitch effect * - Combining multiple distortion effects * - Managing effects (enable/disable/remove) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Enable effects in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); // Calculate responsive grid layout based on page dimensions const layout = calculateGridLayout(pageWidth, pageHeight, 6); const { blockWidth, blockHeight, getPosition } = layout; // Use a sample image URL const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const blockSize = { width: blockWidth, height: blockHeight }; // Create a sample block to demonstrate effect support checking const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, sampleBlock); // Check if a block supports effects before applying them const supportsEffects = engine.block.supportsEffects(sampleBlock); console.log('Block supports effects:', supportsEffects); // Create an image block for liquid distortion demonstration const liquidBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, liquidBlock); // Create and apply liquid effect - creates flowing, organic warping const liquidEffect = engine.block.createEffect('liquid'); engine.block.setFloat(liquidEffect, 'effect/liquid/amount', 0.5); engine.block.setFloat(liquidEffect, 'effect/liquid/scale', 1.0); engine.block.setFloat(liquidEffect, 'effect/liquid/time', 0.0); engine.block.appendEffect(liquidBlock, liquidEffect); // Create an image block for mirror effect demonstration const mirrorBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, mirrorBlock); // Create and apply mirror effect - reflects image along a side const mirrorEffect = engine.block.createEffect('mirror'); // Side values: 0 = Left, 1 = Right, 2 = Top, 3 = Bottom engine.block.setInt(mirrorEffect, 'effect/mirror/side', 0); engine.block.appendEffect(mirrorBlock, mirrorEffect); // Create an image block for shifter effect demonstration const shifterBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, shifterBlock); // Create and apply shifter effect - displaces color channels const shifterEffect = engine.block.createEffect('shifter'); engine.block.setFloat(shifterEffect, 'effect/shifter/amount', 0.3); engine.block.setFloat(shifterEffect, 'effect/shifter/angle', 0.785); engine.block.appendEffect(shifterBlock, shifterEffect); // Create an image block for radial pixel effect demonstration const radialPixelBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, radialPixelBlock); // Create and apply radial pixel effect - pixelates in circular pattern const radialPixelEffect = engine.block.createEffect('radial_pixel'); engine.block.setFloat(radialPixelEffect, 'effect/radial_pixel/radius', 0.5); engine.block.setFloat( radialPixelEffect, 'effect/radial_pixel/segments', 0.5 ); engine.block.appendEffect(radialPixelBlock, radialPixelEffect); // Create an image block for TV glitch effect demonstration const tvGlitchBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, tvGlitchBlock); // Create and apply TV glitch effect - simulates analog TV interference const tvGlitchEffect = engine.block.createEffect('tv_glitch'); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion', 0.4); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion2', 0.2); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/speed', 0.5); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/rollSpeed', 0.1); engine.block.appendEffect(tvGlitchBlock, tvGlitchEffect); // Get all effects applied to a block const effects = engine.block.getEffects(tvGlitchBlock); console.log('Applied effects:', effects); // Get the type of each effect effects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`); }); // Check if an effect is enabled const isEnabled = engine.block.isEffectEnabled(liquidEffect); console.log('Liquid effect enabled:', isEnabled); // Disable an effect without removing it engine.block.setEffectEnabled(liquidEffect, false); console.log( 'Liquid effect now disabled:', !engine.block.isEffectEnabled(liquidEffect) ); // Re-enable the effect engine.block.setEffectEnabled(liquidEffect, true); // To remove an effect, get its index and use removeEffect const shifterEffects = engine.block.getEffects(shifterBlock); const effectIndex = shifterEffects.indexOf(shifterEffect); if (effectIndex !== -1) { // Remove effect at the specified index engine.block.removeEffect(shifterBlock, effectIndex); // Destroy the removed effect to free memory engine.block.destroy(shifterEffect); } // Re-add the effect for display purposes const newShifterEffect = engine.block.createEffect('shifter'); engine.block.setFloat(newShifterEffect, 'effect/shifter/amount', 0.3); engine.block.setFloat(newShifterEffect, 'effect/shifter/angle', 0.785); engine.block.appendEffect(shifterBlock, newShifterEffect); // Find all available properties for an effect const tvGlitchProperties = engine.block.findAllProperties(tvGlitchEffect); console.log('TV glitch properties:', tvGlitchProperties); // Position all blocks in grid layout const blocks = [ sampleBlock, liquidBlock, mirrorBlock, shifterBlock, radialPixelBlock, tvGlitchBlock ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Select the liquid effect block (second block) and open the effects panel engine.block.select(liquidBlock); cesdk.ui.openPanel('//ly.img.panel/inspector/effects'); console.log('Distortion effects guide initialized.'); } } export default Example; ``` This guide covers how to enable distortion effects in the built-in UI and how to apply and configure them programmatically using the block API. ## Using the Built-in Distortion UI To enable distortion effects in the inspector panel, use the Feature API: ```typescript highlight-enable-effects // Enable effects in the inspector panel using the Feature API cesdk.feature.enable('ly.img.effect'); ``` Once enabled, users can access distortion effects from the inspector when selecting an image or video block. The effects panel displays available distortions with real-time preview as parameters are adjusted. ## Check Effect Support Before applying distortion effects, verify the block supports them. Graphic blocks with image or video fills support effects, while scene blocks do not. ```typescript highlight-check-support // Create a sample block to demonstrate effect support checking const sampleBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, sampleBlock); // Check if a block supports effects before applying them const supportsEffects = engine.block.supportsEffects(sampleBlock); console.log('Block supports effects:', supportsEffects); ``` ## Apply Liquid Effect The liquid effect creates organic, flowing distortions that warp the image as if viewed through water. We can configure the intensity and scale of the warping. ```typescript highlight-liquid-effect // Create an image block for liquid distortion demonstration const liquidBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, liquidBlock); // Create and apply liquid effect - creates flowing, organic warping const liquidEffect = engine.block.createEffect('liquid'); engine.block.setFloat(liquidEffect, 'effect/liquid/amount', 0.5); engine.block.setFloat(liquidEffect, 'effect/liquid/scale', 1.0); engine.block.setFloat(liquidEffect, 'effect/liquid/time', 0.0); engine.block.appendEffect(liquidBlock, liquidEffect); ``` The liquid effect parameters: - **amount** (0.0 to 1.0) - Controls the intensity of the warping - **scale** - Adjusts the size of the liquid pattern - **time** - Animation time offset for animated liquid distortions ## Apply Mirror Effect The mirror effect reflects the image along a configurable side, creating symmetrical compositions. ```typescript highlight-mirror-effect // Create an image block for mirror effect demonstration const mirrorBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, mirrorBlock); // Create and apply mirror effect - reflects image along a side const mirrorEffect = engine.block.createEffect('mirror'); // Side values: 0 = Left, 1 = Right, 2 = Top, 3 = Bottom engine.block.setInt(mirrorEffect, 'effect/mirror/side', 0); engine.block.appendEffect(mirrorBlock, mirrorEffect); ``` The `side` parameter uses integer values: `0` (Left), `1` (Right), `2` (Top), or `3` (Bottom) to specify the reflection axis. ## Apply Shifter Effect The shifter effect displaces color channels at an angle, creating chromatic aberration commonly seen in glitch art and retro visuals. ```typescript highlight-shifter-effect // Create an image block for shifter effect demonstration const shifterBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, shifterBlock); // Create and apply shifter effect - displaces color channels const shifterEffect = engine.block.createEffect('shifter'); engine.block.setFloat(shifterEffect, 'effect/shifter/amount', 0.3); engine.block.setFloat(shifterEffect, 'effect/shifter/angle', 0.785); engine.block.appendEffect(shifterBlock, shifterEffect); ``` The shifter effect parameters: - **amount** (0.0 to 1.0) - Controls the displacement distance - **angle** - Sets the direction of the shift in radians ## Apply Radial Pixel Effect The radial pixel effect pixelates the image in a circular pattern emanating from the center, useful for focus effects or stylized treatments. ```typescript highlight-radial-pixel-effect // Create an image block for radial pixel effect demonstration const radialPixelBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, radialPixelBlock); // Create and apply radial pixel effect - pixelates in circular pattern const radialPixelEffect = engine.block.createEffect('radial_pixel'); engine.block.setFloat(radialPixelEffect, 'effect/radial_pixel/radius', 0.5); engine.block.setFloat( radialPixelEffect, 'effect/radial_pixel/segments', 0.5 ); engine.block.appendEffect(radialPixelBlock, radialPixelEffect); ``` The radial pixel effect parameters: - **radius** (0.0 to 1.0) - Controls the size of the pixelation effect - **segments** (0.0 to 1.0) - Controls the angular segmentation intensity ## Apply TV Glitch Effect The TV glitch effect simulates analog television interference with horizontal distortion and rolling effects, popular for retro and digital aesthetics. ```typescript highlight-tv-glitch-effect // Create an image block for TV glitch effect demonstration const tvGlitchBlock = await engine.block.addImage(imageUri, { size: blockSize }); engine.block.appendChild(page, tvGlitchBlock); // Create and apply TV glitch effect - simulates analog TV interference const tvGlitchEffect = engine.block.createEffect('tv_glitch'); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion', 0.4); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/distortion2', 0.2); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/speed', 0.5); engine.block.setFloat(tvGlitchEffect, 'effect/tv_glitch/rollSpeed', 0.1); engine.block.appendEffect(tvGlitchBlock, tvGlitchEffect); ``` The TV glitch effect parameters: - **distortion** - Primary horizontal distortion intensity - **distortion2** - Secondary distortion layer - **speed** - Animation speed for the glitch effect - **rollSpeed** - Vertical roll speed simulating signal sync issues ## List Applied Effects Retrieve all effects applied to a block to inspect or iterate over them. ```typescript highlight-get-effects // Get all effects applied to a block const effects = engine.block.getEffects(tvGlitchBlock); console.log('Applied effects:', effects); // Get the type of each effect effects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`); }); ``` This returns an array of effect IDs in the order they were applied. ## Enable and Disable Effects Toggle effects on and off without removing them from the block. This preserves all effect parameters while controlling visibility. ```typescript highlight-toggle-effect // Check if an effect is enabled const isEnabled = engine.block.isEffectEnabled(liquidEffect); console.log('Liquid effect enabled:', isEnabled); // Disable an effect without removing it engine.block.setEffectEnabled(liquidEffect, false); console.log( 'Liquid effect now disabled:', !engine.block.isEffectEnabled(liquidEffect) ); // Re-enable the effect engine.block.setEffectEnabled(liquidEffect, true); ``` Disabled effects remain attached to the block but won't be rendered until re-enabled. This is useful for before/after comparisons or performance optimization. ## Remove Effects Remove effects from a block when they're no longer needed. Always destroy removed effects to free memory. ```typescript highlight-remove-effect // To remove an effect, get its index and use removeEffect const shifterEffects = engine.block.getEffects(shifterBlock); const effectIndex = shifterEffects.indexOf(shifterEffect); if (effectIndex !== -1) { // Remove effect at the specified index engine.block.removeEffect(shifterBlock, effectIndex); // Destroy the removed effect to free memory engine.block.destroy(shifterEffect); } // Re-add the effect for display purposes const newShifterEffect = engine.block.createEffect('shifter'); engine.block.setFloat(newShifterEffect, 'effect/shifter/amount', 0.3); engine.block.setFloat(newShifterEffect, 'effect/shifter/angle', 0.785); engine.block.appendEffect(shifterBlock, newShifterEffect); ``` ## Discover Effect Properties Use `findAllProperties()` to discover all available properties for any effect type. ```typescript highlight-effect-properties // Find all available properties for an effect const tvGlitchProperties = engine.block.findAllProperties(tvGlitchEffect); console.log('TV glitch properties:', tvGlitchProperties); ``` This returns an array of property paths that can be used with `setFloat()`, `setInt()`, or `setEnum()`. ## API Reference | Method | Description | |--------|-------------| | `engine.block.supportsEffects(id)` | Check if a block supports effects | | `engine.block.createEffect(type)` | Create a new effect instance | | `engine.block.appendEffect(id, effectId)` | Add an effect to a block | | `engine.block.getEffects(id)` | Get all effects applied to a block | | `engine.block.setEffectEnabled(effectId, enabled)` | Enable or disable an effect | | `engine.block.isEffectEnabled(effectId)` | Check if an effect is enabled | | `engine.block.removeEffect(id, index)` | Remove an effect at a specific index | | `engine.block.findAllProperties(id)` | Discover all properties of an effect | | `engine.block.setFloat(id, property, value)` | Set a float property value | | `engine.block.setInt(id, property, value)` | Set an integer property value | | `engine.block.destroy(id)` | Destroy a block to free memory | | `engine.block.getType(id)` | Get the type of a block | ## Available Distortion Effects | Effect Type | Description | Key Properties | |-------------|-------------|----------------| | `liquid` | Flowing, organic warping | `amount`, `scale`, `time` | | `mirror` | Reflection along a side | `side` (0=Left, 1=Right, 2=Top, 3=Bottom) | | `shifter` | Chromatic aberration | `amount`, `angle` | | `radial_pixel` | Circular pixelation | `radius`, `segments` | | `tv_glitch` | Analog TV interference | `distortion`, `distortion2`, `speed`, `rollSpeed` | ## Next Steps - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) - Learn the foundational effect APIs - [Blur Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/blur-71d642/) - Apply blur techniques for depth and focus effects --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Duotone" description: "Apply duotone effects to images, mapping tones to two colors for stylized visuals, vintage aesthetics, or brand-specific treatments." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/duotone-831fc5/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Duotone](https://img.ly/docs/cesdk/angular/filters-and-effects/duotone-831fc5/) --- Apply duotone effects to images, mapping tones to two colors for stylized visuals and brand-specific treatments. ![CE.SDK Duotone](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-duotone-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-duotone-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-duotone-browser/) Duotone is a color effect that maps image brightness to two colors: a dark color for shadows and a light color for highlights. The result is a striking two-tone image where all original colors are replaced by gradations between your chosen pair. This technique creates bold, cohesive visuals that are particularly effective for brand consistency, vintage aesthetics, and artistic treatments. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-duotone-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { hexToRgba } from './utils'; /** * CE.SDK Plugin: Duotone Guide * * Demonstrates applying duotone effects to image blocks: * - Querying duotone presets from the asset library * - Applying duotone with preset colors * - Creating custom duotone color combinations * - Managing and removing effects */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Enable duotone filters in the inspector panel cesdk.feature.enable('ly.img.filter'); // Use a sample image URL const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; // Create image block for preset demonstration const presetImageBlock = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.appendChild(page, presetImageBlock); engine.block.setPositionX(presetImageBlock, 25); engine.block.setPositionY(presetImageBlock, 25); // Verify a block supports effects before applying them const canApplyEffects = engine.block.supportsEffects(presetImageBlock); if (!canApplyEffects) { console.warn('Block does not support effects'); return; } // Query duotone presets from the asset library const duotoneResults = await engine.asset.findAssets( 'ly.img.filter', { page: 0, perPage: 10 } ); const duotonePresets = duotoneResults.assets; // Apply a preset to the first image if (duotonePresets.length > 0) { const preset = duotonePresets[0]; // Create a new duotone effect block const duotoneEffect = engine.block.createEffect('duotone_filter'); // Configure effect with preset colors (convert hex to RGBA) if (preset.meta?.darkColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', hexToRgba(preset.meta.darkColor as string) ); } if (preset.meta?.lightColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', hexToRgba(preset.meta.lightColor as string) ); } engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.9 ); // Attach the configured effect to the image block engine.block.appendEffect(presetImageBlock, duotoneEffect); } // Create image block for custom duotone demonstration const customImageBlock = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.appendChild(page, customImageBlock); engine.block.setPositionX(customImageBlock, 425); engine.block.setPositionY(customImageBlock, 25); // Create duotone with custom brand colors const customDuotone = engine.block.createEffect('duotone_filter'); // Dark color: deep navy blue (shadows) engine.block.setColor(customDuotone, 'effect/duotone_filter/darkColor', { r: 0.1, g: 0.15, b: 0.3, a: 1.0 }); // Light color: warm cream (highlights) engine.block.setColor(customDuotone, 'effect/duotone_filter/lightColor', { r: 0.95, g: 0.9, b: 0.8, a: 1.0 }); // Control effect strength (0.0 = original, 1.0 = full duotone) engine.block.setFloat( customDuotone, 'effect/duotone_filter/intensity', 0.85 ); engine.block.appendEffect(customImageBlock, customDuotone); // Create image block for combined effects demonstration const combinedImageBlock = await engine.block.addImage(imageUri, { size: { width: 350, height: 250 } }); engine.block.appendChild(page, combinedImageBlock); engine.block.setPositionX(combinedImageBlock, 225); engine.block.setPositionY(combinedImageBlock, 325); // Combine duotone with other effects // First, add adjustments for brightness and contrast const adjustments = engine.block.createEffect('adjustments'); engine.block.setFloat(adjustments, 'effect/adjustments/brightness', 0.1); engine.block.setFloat(adjustments, 'effect/adjustments/contrast', 0.15); engine.block.appendEffect(combinedImageBlock, adjustments); // Then add duotone on top const combinedDuotone = engine.block.createEffect('duotone_filter'); engine.block.setColor( combinedDuotone, 'effect/duotone_filter/darkColor', { r: 0.2, g: 0.1, b: 0.3, a: 1.0 } // Deep purple ); engine.block.setColor( combinedDuotone, 'effect/duotone_filter/lightColor', { r: 1.0, g: 0.85, b: 0.7, a: 1.0 } // Warm peach ); engine.block.setFloat( combinedDuotone, 'effect/duotone_filter/intensity', 0.75 ); engine.block.appendEffect(combinedImageBlock, combinedDuotone); // Get all effects currently applied to a block const appliedEffects = engine.block.getEffects(presetImageBlock); console.log(`Block has ${appliedEffects.length} effect(s) applied`); // Disable an effect without removing it if (appliedEffects.length > 0) { engine.block.setEffectEnabled(appliedEffects[0], false); // Check if an effect is currently enabled const isEnabled = engine.block.isEffectEnabled(appliedEffects[0]); console.log(`Effect enabled: ${isEnabled}`); // Re-enable the effect engine.block.setEffectEnabled(appliedEffects[0], true); } // Remove an effect at a specific index from a block const effectsOnCustom = engine.block.getEffects(customImageBlock); if (effectsOnCustom.length > 0) { engine.block.removeEffect(customImageBlock, 0); } // Destroy removed effect blocks to free memory if (effectsOnCustom.length > 0) { engine.block.destroy(effectsOnCustom[0]); } // Re-apply custom duotone after demonstration const newCustomDuotone = engine.block.createEffect('duotone_filter'); engine.block.setColor(newCustomDuotone, 'effect/duotone_filter/darkColor', { r: 0.1, g: 0.15, b: 0.3, a: 1.0 }); engine.block.setColor( newCustomDuotone, 'effect/duotone_filter/lightColor', { r: 0.95, g: 0.9, b: 0.8, a: 1.0 } ); engine.block.setFloat( newCustomDuotone, 'effect/duotone_filter/intensity', 0.85 ); engine.block.appendEffect(customImageBlock, newCustomDuotone); // Select the first image block to show the effects panel engine.block.select(presetImageBlock); console.log( 'Duotone guide initialized. Select any image to see the filters panel.' ); } } export default Example; ``` ## Understanding Duotone Unlike filters that simply tint or shift colors, duotone completely remaps the tonal range of an image. The effect analyzes each pixel's luminosity and assigns a color based on where it falls between pure black and pure white: - **Dark tones** (shadows, blacks) adopt the dark color - **Light tones** (highlights, whites) adopt the light color - **Midtones** blend between the two colors This creates images with a consistent color palette regardless of the original colors, making duotone ideal for: - **Brand consistency** - Apply your brand's color palette across diverse imagery - **Visual cohesion** - Unify photos from different sources in a design - **Vintage aesthetics** - Recreate classic print techniques like cyanotype or sepia - **Bold statements** - Create eye-catching visuals for social media or marketing The intensity property lets you blend between the original image and the full duotone effect, giving you creative control over how strongly the effect is applied. ## Using the Built-in Duotone UI CE.SDK provides a filters panel where users can browse and apply duotone presets interactively. To enable this, use the Feature API: ```typescript highlight=highlight-enable-filters // Enable duotone filters in the inspector panel cesdk.feature.enable('ly.img.filter'); ``` With filters enabled, users can: 1. Select any image block in the canvas 2. Open the inspector panel 3. Navigate to the Filters section 4. Browse available duotone presets and apply with a single click 5. Adjust the intensity slider to control effect strength ## Check Effect Support Before applying effects programmatically, verify the block supports them. Only graphic blocks with image or video fills can have effects applied: ```typescript highlight=highlight-check-support // Verify a block supports effects before applying them const canApplyEffects = engine.block.supportsEffects(presetImageBlock); if (!canApplyEffects) { console.warn('Block does not support effects'); return; } ``` Attempting to apply effects to unsupported blocks (like text or shapes without fills) will result in an error. ## Applying Duotone Presets CE.SDK includes a library of professionally designed duotone presets. Each preset defines a dark/light color pair optimized for visual appeal. ### Query Built-in Presets Use the Asset API to retrieve available duotone presets from the asset library: ```typescript highlight=highlight-query-presets // Query duotone presets from the asset library const duotoneResults = await engine.asset.findAssets( 'ly.img.filter', { page: 0, perPage: 10 } ); const duotonePresets = duotoneResults.assets; ``` Preset metadata contains `darkColor` and `lightColor` as hex strings. Convert these to RGBA format (values 0-1) before passing to the effect API. ### Create Effect Block Create a new duotone effect block that can be configured and attached to an image: ```typescript highlight=highlight-create-effect // Create a new duotone effect block const duotoneEffect = engine.block.createEffect('duotone_filter'); ``` ### Configure Preset Colors Apply the preset's color values to the effect using `setColor()` for colors and `setFloat()` for intensity: ```typescript highlight=highlight-apply-preset // Configure effect with preset colors (convert hex to RGBA) if (preset.meta?.darkColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/darkColor', hexToRgba(preset.meta.darkColor as string) ); } if (preset.meta?.lightColor) { engine.block.setColor( duotoneEffect, 'effect/duotone_filter/lightColor', hexToRgba(preset.meta.lightColor as string) ); } engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.9 ); ``` ### Append Effect to Block Attach the fully configured effect to an image block: ```typescript highlight=highlight-append-effect // Attach the configured effect to the image block engine.block.appendEffect(presetImageBlock, duotoneEffect); ``` ## Creating Custom Colors For brand-specific treatments or unique creative effects, define your own color combinations using `engine.block.setColor()`: ```typescript highlight=highlight-custom-colors // Create duotone with custom brand colors const customDuotone = engine.block.createEffect('duotone_filter'); // Dark color: deep navy blue (shadows) engine.block.setColor(customDuotone, 'effect/duotone_filter/darkColor', { r: 0.1, g: 0.15, b: 0.3, a: 1.0 }); // Light color: warm cream (highlights) engine.block.setColor(customDuotone, 'effect/duotone_filter/lightColor', { r: 0.95, g: 0.9, b: 0.8, a: 1.0 }); // Control effect strength (0.0 = original, 1.0 = full duotone) engine.block.setFloat( customDuotone, 'effect/duotone_filter/intensity', 0.85 ); engine.block.appendEffect(customImageBlock, customDuotone); ``` ### Choosing Effective Color Pairs The relationship between your dark and light colors determines the final aesthetic: | Color Relationship | Visual Effect | Example Use Case | | --- | --- | --- | | **High contrast** | Bold, graphic look | Social media, posters | | **Low contrast** | Subtle, sophisticated | Editorial, luxury brands | | **Warm to cool** | Dynamic temperature shift | Lifestyle, fashion | | **Monochromatic** | Tinted photography style | Vintage, noir aesthetic | **Classic combinations to try:** - **Cyanotype**: Deep blue (`#1a365d`) to light cyan (`#e0f7fa`) - **Sepia**: Dark brown (`#3e2723`) to cream (`#fff8e1`) - **Neon**: Deep purple (`#1a1a2e`) to hot pink (`#ff1493`) - **Corporate**: Navy (`#0d47a1`) to silver (`#eceff1`) > **Tip:** Start with colors from your brand palette. Use your primary brand color as the light color for highlights, paired with a darker complementary shade for shadows. ## Combining with Other Effects Duotone can be stacked with other effects like brightness adjustments, contrast, or blur. Effects are applied in stack order, so the sequence affects the final result: ```typescript highlight=highlight-combine-effects // Combine duotone with other effects // First, add adjustments for brightness and contrast const adjustments = engine.block.createEffect('adjustments'); engine.block.setFloat(adjustments, 'effect/adjustments/brightness', 0.1); engine.block.setFloat(adjustments, 'effect/adjustments/contrast', 0.15); engine.block.appendEffect(combinedImageBlock, adjustments); // Then add duotone on top const combinedDuotone = engine.block.createEffect('duotone_filter'); engine.block.setColor( combinedDuotone, 'effect/duotone_filter/darkColor', { r: 0.2, g: 0.1, b: 0.3, a: 1.0 } // Deep purple ); engine.block.setColor( combinedDuotone, 'effect/duotone_filter/lightColor', { r: 1.0, g: 0.85, b: 0.7, a: 1.0 } // Warm peach ); engine.block.setFloat( combinedDuotone, 'effect/duotone_filter/intensity', 0.75 ); engine.block.appendEffect(combinedImageBlock, combinedDuotone); ``` **Effect order matters**: In this example, brightness and contrast are applied first, then duotone maps the adjusted tones. Reversing the order would apply duotone first, then adjust the duotone colors' brightness—producing a different result. Common combinations: - **Adjustments → Duotone**: Optimize image contrast before applying duotone for better tonal separation - **Duotone → Vignette**: Add depth with darkened edges after the color treatment - **Blur → Duotone**: Create dreamy, soft-focus duotone backgrounds ## Managing Duotone Effects Once effects are applied to a block, you can list, toggle, and remove them programmatically. ### List Applied Effects Retrieve all effect block IDs currently attached to a block: ```typescript highlight=highlight-list-effects // Get all effects currently applied to a block const appliedEffects = engine.block.getEffects(presetImageBlock); ``` ### Toggle Effect Visibility Disable an effect temporarily without removing it from the block: ```typescript highlight=highlight-toggle-effects // Disable an effect without removing it if (appliedEffects.length > 0) { engine.block.setEffectEnabled(appliedEffects[0], false); // Check if an effect is currently enabled const isEnabled = engine.block.isEffectEnabled(appliedEffects[0]); console.log(`Effect enabled: ${isEnabled}`); // Re-enable the effect engine.block.setEffectEnabled(appliedEffects[0], true); } ``` ### Remove Effects Detach an effect from a block by specifying its index in the effect stack: ```typescript highlight=highlight-remove-effect // Remove an effect at a specific index from a block const effectsOnCustom = engine.block.getEffects(customImageBlock); if (effectsOnCustom.length > 0) { engine.block.removeEffect(customImageBlock, 0); } ``` ### Clean Up Resources After removing an effect, destroy it to free memory: ```typescript highlight=highlight-cleanup-effect // Destroy removed effect blocks to free memory if (effectsOnCustom.length > 0) { engine.block.destroy(effectsOnCustom[0]); } ``` > **Caution:** When removing effects, always destroy the effect block afterward to prevent memory leaks. Effects are independent blocks that persist until explicitly destroyed. ### Duotone Properties | Property | Type | Range | Description | | --- | --- | --- | --- | | `effect/duotone_filter/darkColor` | Color | RGBA (0-1) | Color applied to shadows and dark tones | | `effect/duotone_filter/lightColor` | Color | RGBA (0-1) | Color applied to highlights and light tones | | `effect/duotone_filter/intensity` | Float | 0.0 - 1.0 | Effect strength (0 = original, 1 = full duotone) | **Intensity guidelines:** - `0.5 - 0.7`: Subtle tint, original image still recognizable - `0.8 - 0.9`: Strong duotone effect, ideal for most use cases - `1.0`: Full duotone, no original colors remain ## Best Practices ### Performance Considerations - **Batch effect creation**: When applying the same duotone to multiple images, create the effect once and clone it rather than creating new effects for each block - **Limit effect stacking**: Each additional effect increases render time; keep stacks minimal for real-time editing - **Clean up unused effects**: Always destroy effect blocks when they're no longer needed to free GPU resources - **Disable rather than remove**: For temporary changes, use `setEffectEnabled(false)` instead of removing and re-creating effects ### Common Issues **Duotone not visible**: Verify the block supports effects with `engine.block.supportsEffects(block)`. Only graphic blocks with image or video fills support effects. **Colors look wrong**: Ensure RGBA values are in the 0-1 range, not 0-255. For example, use `{ r: 0.5, g: 0.5, b: 0.5, a: 1.0 }` instead of `{ r: 128, g: 128, b: 128, a: 255 }`. **Effect too subtle or overwhelming**: Adjust the intensity property. Start at 0.85 and tune based on your image content and color choices. **Muddy midtones**: If midtones look flat, increase contrast between your dark and light colors, or add an adjustments effect before duotone to improve tonal separation. ## API Reference ### Effect Methods | Method | Description | | --- | --- | | `engine.asset.findAssets(sourceId, query)` | Queries assets from an asset source | | `engine.block.supportsEffects(block)` | Returns `true` if the block can have effects applied | | `engine.block.createEffect(type)` | Creates a new effect block of the specified type | | `engine.block.setColor(block, property, color)` | Sets a color property on a block | | `engine.block.setFloat(block, property, value)` | Sets a float property on a block | | `engine.block.appendEffect(block, effect)` | Appends an effect to a block's effect stack | | `engine.block.getEffects(block)` | Returns array of effect block IDs applied to a block | | `engine.block.setEffectEnabled(effect, enabled)` | Enables or disables an effect | | `engine.block.isEffectEnabled(effect)` | Returns `true` if the effect is enabled | | `engine.block.removeEffect(block, index)` | Removes an effect at the given index from a block | | `engine.block.destroy(block)` | Destroys a block and frees resources | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Gradient Fills" description: "Learn how to create and apply linear, radial, and conical gradient fills to design elements in CE.SDK" platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/gradients-0ff079/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) > [Gradient](https://img.ly/docs/cesdk/angular/filters-and-effects/gradients-0ff079/) --- Create smooth color transitions in shapes, text, and design blocks using CE.SDK's gradient fill system with support for linear, radial, and conical gradients. ![Gradient Fills example showing linear, radial, and conical gradient transitions](./assets/browser.hero.webp) > **Reading time:** 20 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-fills-gradient-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-fills-gradient-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-fills-gradient-browser/) Gradient fills are one of the fundamental fill types in CE.SDK, allowing you to paint design blocks with smooth color transitions. Unlike solid color fills that apply a uniform color or image fills that display photo content, gradient fills create dynamic visual effects with depth and visual interest. The gradient fill system supports three types: linear gradients that transition along a straight line, radial gradients that emanate from a center point, and conical gradients that rotate around a center point like a color wheel. ```typescript file=@cesdk_web_examples/guides-fills-gradient-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; import { calculateGridLayout } from './utils'; /** * CE.SDK Plugin: Gradient Fills Guide * * This example demonstrates: * - Creating linear, radial, and conical gradient fills * - Configuring gradient color stops * - Positioning gradients * - Using different color spaces in gradients * - Advanced gradient techniques */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); // Fill features are enabled by default in CE.SDK // You can check and control fill feature availability: const engine = cesdk.engine; const isFillEnabled = cesdk.feature.isEnabled('ly.img.fill', { engine }); console.log('Fill feature enabled:', isFillEnabled); // Create a design scene using CE.SDK cesdk method await cesdk.actions.run('scene.create', { page: { width: 1200, height: 900, unit: 'Pixel' } }); // Get the page const pages = engine.block.findByType('page'); const page = pages[0]; if (!page) { throw new Error('No page found'); } // Set page background to light gray const pageFill = engine.block.getFill(page); engine.block.setColor(pageFill, 'fill/color/value', { r: 0.95, g: 0.95, b: 0.95, a: 1.0 }); // Calculate responsive grid layout based on page dimensions const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); const layout = calculateGridLayout(pageWidth, pageHeight, 15); const { blockWidth, blockHeight, getPosition } = layout; // Helper function to create a shape with a fill const createShapeWithFill = ( fillType: 'gradient/linear' | 'gradient/radial' | 'gradient/conical' ): { block: number; fill: number } => { const block = engine.block.create('graphic'); const shape = engine.block.createShape('rect'); engine.block.setShape(block, shape); // Set size engine.block.setWidth(block, blockWidth); engine.block.setHeight(block, blockHeight); // Append to page engine.block.appendChild(page, block); // Check if block supports fills const canHaveFill = engine.block.supportsFill(block); if (!canHaveFill) { throw new Error('Block does not support fills'); } // Create gradient fill const gradientFill = engine.block.createFill(fillType); // Apply the fill to the block engine.block.setFill(block, gradientFill); return { block, fill: gradientFill }; }; // ============================================================================= // Example 1: Linear Gradient (Vertical) // ============================================================================= const { block: linearVerticalBlock, fill: linearVertical } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(linearVertical, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 } ]); // Set vertical gradient (top to bottom) engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointX', 0.5 ); engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat( linearVertical, 'fill/gradient/linear/endPointX', 0.5 ); engine.block.setFloat(linearVertical, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 2: Linear Gradient (Horizontal) // ============================================================================= const { block: linearHorizontalBlock, fill: linearHorizontal } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( linearHorizontal, 'fill/gradient/colors', [ { color: { r: 0.8, g: 0.2, b: 0.4, a: 1.0 }, stop: 0 }, { color: { r: 0.2, g: 0.8, b: 0.6, a: 1.0 }, stop: 1 } ] ); // Set horizontal gradient (left to right) engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/endPointX', 1 ); engine.block.setFloat( linearHorizontal, 'fill/gradient/linear/endPointY', 0.5 ); // ============================================================================= // Example 3: Linear Gradient (Diagonal) // ============================================================================= const { block: linearDiagonalBlock, fill: linearDiagonal } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(linearDiagonal, 'fill/gradient/colors', [ { color: { r: 0.5, g: 0.2, b: 0.8, a: 1.0 }, stop: 0 }, { color: { r: 0.9, g: 0.6, b: 0.2, a: 1.0 }, stop: 1 } ]); // Set diagonal gradient (top-left to bottom-right) engine.block.setFloat( linearDiagonal, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( linearDiagonal, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat(linearDiagonal, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(linearDiagonal, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 4: Multi-Stop Linear Gradient (Aurora Effect) // ============================================================================= const { block: auroraGradientBlock, fill: auroraGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(auroraGradient, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.1, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.8, g: 0.2, b: 0.6, a: 1 }, stop: 0.3 }, { color: { r: 1.0, g: 0.5, b: 0.3, a: 1 }, stop: 0.6 }, { color: { r: 1.0, g: 0.8, b: 0.2, a: 1 }, stop: 1 } ]); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(auroraGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/endPointY', 0.5 ); // ============================================================================= // Example 5: Radial Gradient (Centered) // ============================================================================= const { block: radialCenteredBlock, fill: radialCentered } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops(radialCentered, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ]); // Set center point (middle of block) engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointX', 0.5 ); engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointY', 0.5 ); engine.block.setFloat(radialCentered, 'fill/gradient/radial/radius', 0.8); // ============================================================================= // Example 6: Radial Gradient (Top-Left Highlight) // ============================================================================= const { block: radialHighlightBlock, fill: radialHighlight } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops( radialHighlight, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ] ); // Set top-left highlight engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointX', 0 ); engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointY', 0 ); engine.block.setFloat(radialHighlight, 'fill/gradient/radial/radius', 1.0); // ============================================================================= // Example 7: Radial Gradient (Vignette Effect) // ============================================================================= const { block: radialVignetteBlock, fill: radialVignette } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops(radialVignette, 'fill/gradient/colors', [ { color: { r: 0.9, g: 0.9, b: 0.9, a: 1.0 }, stop: 0 }, { color: { r: 0.1, g: 0.1, b: 0.1, a: 1.0 }, stop: 1 } ]); // Centered vignette engine.block.setFloat( radialVignette, 'fill/gradient/radial/centerPointX', 0.5 ); engine.block.setFloat( radialVignette, 'fill/gradient/radial/centerPointY', 0.5 ); engine.block.setFloat(radialVignette, 'fill/gradient/radial/radius', 0.6); // ============================================================================= // Example 8: Conical Gradient (Color Wheel) // ============================================================================= const { block: conicalColorWheelBlock, fill: conicalColorWheel } = createShapeWithFill('gradient/conical'); engine.block.setGradientColorStops( conicalColorWheel, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 0 }, { color: { r: 1.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.25 }, { color: { r: 0.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.5 }, { color: { r: 0.0, g: 0.0, b: 1.0, a: 1 }, stop: 0.75 }, { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 1 } ] ); // Set center point (middle of block) engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointX', 0.5 ); engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointY', 0.5 ); // ============================================================================= // Example 9: Conical Gradient (Loading Spinner) // ============================================================================= const { block: conicalSpinnerBlock, fill: conicalSpinner } = createShapeWithFill('gradient/conical'); engine.block.setGradientColorStops(conicalSpinner, 'fill/gradient/colors', [ { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 0 }, stop: 0.75 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 1 } ]); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointX', 0.5 ); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointY', 0.5 ); // ============================================================================= // Example 10: Gradient with CMYK Colors // ============================================================================= const { block: cmykGradientBlock, fill: cmykGradient } = createShapeWithFill('gradient/linear'); // CMYK color stops for print engine.block.setGradientColorStops(cmykGradient, 'fill/gradient/colors', [ { color: { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 0 }, { color: { c: 1.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 } ]); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat( cmykGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointY', 0.5); // ============================================================================= // Example 11: Gradient with Spot Colors // ============================================================================= // First define spot colors engine.editor.setSpotColorRGB('BrandPrimary', 0.2, 0.4, 0.8); engine.editor.setSpotColorRGB('BrandSecondary', 1.0, 0.6, 0.0); const { block: spotGradientBlock, fill: spotGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(spotGradient, 'fill/gradient/colors', [ { color: { name: 'BrandPrimary', tint: 1.0, externalReference: '' }, stop: 0 }, { color: { name: 'BrandSecondary', tint: 1.0, externalReference: '' }, stop: 1 } ]); engine.block.setFloat(spotGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(spotGradient, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(spotGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(spotGradient, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 12: Transparency Overlay Gradient // ============================================================================= const { block: overlayGradientBlock, fill: overlayGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( overlayGradient, 'fill/gradient/colors', [ { color: { r: 0.0, g: 0.0, b: 0.0, a: 0 }, stop: 0 }, { color: { r: 0.0, g: 0.0, b: 0.0, a: 0.7 }, stop: 1 } ] ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointX', 0.5 ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/endPointX', 0.5 ); engine.block.setFloat(overlayGradient, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 13: Duotone Gradient // ============================================================================= const { block: duotoneGradientBlock, fill: duotoneGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( duotoneGradient, 'fill/gradient/colors', [ { color: { r: 0.8, g: 0.2, b: 0.9, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.9, b: 0.8, a: 1 }, stop: 1 } ] ); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointY', 1); // ============================================================================= // Example 14: Shared Gradient Fill // ============================================================================= const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, blockWidth); engine.block.setHeight(block1, blockHeight / 2 - 5); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, blockWidth); engine.block.setHeight(block2, blockHeight / 2 - 5); engine.block.appendChild(page, block2); // Create one gradient fill const sharedGradient = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(sharedGradient, 'fill/gradient/colors', [ { color: { r: 1, g: 0, b: 0, a: 1 }, stop: 0 }, { color: { r: 0, g: 0, b: 1, a: 1 }, stop: 1 } ]); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(sharedGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/endPointY', 0.5 ); // Apply to both blocks engine.block.setFill(block1, sharedGradient); engine.block.setFill(block2, sharedGradient); // Change gradient after a delay to show it affects both setTimeout(() => { engine.block.setGradientColorStops( sharedGradient, 'fill/gradient/colors', [ { color: { r: 0, g: 1, b: 0, a: 1 }, stop: 0 }, { color: { r: 1, g: 1, b: 0, a: 1 }, stop: 1 } ] ); }, 2000); // ============================================================================= // Example 15: Get Gradient Properties // ============================================================================= const { block: inspectGradientBlock, fill: inspectGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( inspectGradient, 'fill/gradient/colors', [ { color: { r: 0.6, g: 0.3, b: 0.7, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.7, b: 0.6, a: 1.0 }, stop: 1 } ] ); // Get current fill from block const fillId = engine.block.getFill(block1); const fillType = engine.block.getType(fillId); // eslint-disable-next-line no-console console.log('Fill type:', fillType); // '//ly.img.ubq/fill/gradient/linear' // Get gradient color stops const colorStops = engine.block.getGradientColorStops( inspectGradient, 'fill/gradient/colors' ); // eslint-disable-next-line no-console console.log('Color stops:', colorStops); // Get linear gradient position const startX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointX' ); const startY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointY' ); const endX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointX' ); const endY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointY' ); // eslint-disable-next-line no-console console.log('Linear gradient position:', { startX, startY, endX, endY }); // ===== Position all blocks in grid layout ===== const blocks = [ linearVerticalBlock, // Position 0 linearHorizontalBlock, // Position 1 linearDiagonalBlock, // Position 2 auroraGradientBlock, // Position 3 radialCenteredBlock, // Position 4 radialHighlightBlock, // Position 5 radialVignetteBlock, // Position 6 conicalColorWheelBlock, // Position 7 conicalSpinnerBlock, // Position 8 cmykGradientBlock, // Position 9 spotGradientBlock, // Position 10 overlayGradientBlock, // Position 11 duotoneGradientBlock, // Position 12 block1, // Position 13 (top half) inspectGradientBlock // Position 14 ]; blocks.forEach((block, index) => { const pos = getPosition(index); engine.block.setPositionX(block, pos.x); engine.block.setPositionY(block, pos.y); }); // Position block2 below block1 in the same grid cell const block1Pos = getPosition(13); engine.block.setPositionX(block2, block1Pos.x); engine.block.setPositionY(block2, block1Pos.y + blockHeight / 2 + 5); // Zoom to fit all content await engine.scene.zoomToBlock(page, { padding: { left: 40, top: 40, right: 40, bottom: 40 } }); } } export default Example; ``` This guide demonstrates how to create, apply, and configure gradient fills programmatically, work with color stops, position gradients, and create modern visual effects like aurora gradients and button highlights. ## Understanding Gradient Fills ### What is a Gradient Fill? A gradient fill is a fill object that paints a design block with smooth color transitions. Gradient fills are part of the broader fill system in CE.SDK and come in three types, each identified by a unique type string: - **Linear**: `'//ly.img.ubq/fill/gradient/linear'` or `'gradient/linear'` - **Radial**: `'//ly.img.ubq/fill/gradient/radial'` or `'gradient/radial'` - **Conical**: `'//ly.img.ubq/fill/gradient/conical'` or `'gradient/conical'` Each gradient type contains color stops that define colors at specific positions and positioning properties that control the gradient's direction and coverage. ### Gradient Types Comparison #### Linear Gradients Linear gradients transition colors along a straight line defined by start and end points. They're the most common gradient type and create clean, modern looks. Common use cases include hero sections, call-to-action buttons, headers, and banners. ```typescript highlight-linear-gradient const { block: linearVerticalBlock, fill: linearVertical } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(linearVertical, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 } ]); ``` #### Radial Gradients Radial gradients emanate from a central point outward, creating circular or elliptical color transitions. They add depth and create focal points or spotlight effects. Common use cases include button highlights, card shadows, vignettes, and circular badges. ```typescript highlight-radial-gradient engine.block.setGradientColorStops(radialCentered, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ]); ``` #### Conical Gradients Conical gradients transition colors around a center point like a color wheel, starting at the top (12 o'clock) and rotating clockwise. Colors are specified by position rather than angle. Common use cases include pie charts, loading spinners, circular progress indicators, and color picker wheels. ```typescript highlight-conical-gradient engine.block.setGradientColorStops( conicalColorWheel, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 0 }, { color: { r: 1.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.25 }, { color: { r: 0.0, g: 1.0, b: 0.0, a: 1 }, stop: 0.5 }, { color: { r: 0.0, g: 0.0, b: 1.0, a: 1 }, stop: 0.75 }, { color: { r: 1.0, g: 0.0, b: 0.0, a: 1 }, stop: 1 } ] ); ``` ### Gradient vs Other Fill Types Understanding how gradients differ from other fill types helps you choose the right fill for your design: - **Gradient fills**: Smooth color transitions (linear, radial, conical) - **Color fills**: Solid, uniform color - **Image fills**: Photo or raster content - **Video fills**: Animated video content ### Color Stops Explained Color stops define the colors at specific positions in the gradient. Each stop consists of: - `color`: An RGB, CMYK, or Spot color value - `stop`: Position value between 0.0 and 1.0 (0% to 100%) A gradient requires a minimum of two color stops. You can add multiple stops to create complex color transitions. Color stops can use any color space supported by CE.SDK, including RGB for screen display, CMYK for print, and Spot Colors for brand consistency. ```typescript highlight-color-stops engine.block.setGradientColorStops(auroraGradient, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.1, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.8, g: 0.2, b: 0.6, a: 1 }, stop: 0.3 }, { color: { r: 1.0, g: 0.5, b: 0.3, a: 1 }, stop: 0.6 }, { color: { r: 1.0, g: 0.8, b: 0.2, a: 1 }, stop: 1 } ]); ``` ## Using the Built-in Gradient UI CE.SDK provides built-in UI controls for working with gradients through the **inspector bar** and **advanced inspector**. Users can switch between solid color and gradient fills, select gradient type (linear, radial, conical), add and remove color stops visually, adjust individual stop colors, and drag gradient control points for positioning. **Note**: Currently, only **linear gradients** are fully supported in the built-in UI. Radial and conical gradients are available programmatically. ### Enabling Fill Features Gradient controls are part of the fill feature system. You can check if the fill feature is enabled and control it programmatically: ```typescript highlight-enable-fill-feature // Fill features are enabled by default in CE.SDK // You can check and control fill feature availability: const engine = cesdk.engine; const isFillEnabled = cesdk.feature.isEnabled('ly.img.fill', { engine }); console.log('Fill feature enabled:', isFillEnabled); ``` ## Checking Gradient Fill Support ### Verifying Block Compatibility Before applying gradient fills, verify that the block type supports fills. Not all blocks support fills—for example, scenes and pages typically don't. ```typescript highlight-check-fill-support // Check if block supports fills const canHaveFill = engine.block.supportsFill(block); if (!canHaveFill) { throw new Error('Block does not support fills'); } ``` Always check `supportsFill()` before accessing fill APIs. Graphic blocks, shapes, and text typically support fills. ## Creating Gradient Fills ### Creating a New Linear Gradient Create a new linear gradient fill using the `createFill()` method with the type `'gradient/linear'`: ```typescript highlight-create-linear const { block: linearVerticalBlock, fill: linearVertical } = createShapeWithFill('gradient/linear'); ``` ### Creating a Radial Gradient Create a radial gradient using the type `'gradient/radial'`: ```typescript highlight-create-radial const { block: radialCenteredBlock, fill: radialCentered } = createShapeWithFill('gradient/radial'); ``` ### Creating a Conical Gradient Create a conical gradient using the type `'gradient/conical'`: ```typescript highlight-create-conical const { block: conicalColorWheelBlock, fill: conicalColorWheel } = createShapeWithFill('gradient/conical'); ``` The `createFill()` method returns a numeric fill ID. The fill exists independently until you attach it to a block. If you create a fill but don't attach it to a block, you must destroy it manually to prevent memory leaks. ## Applying Gradient Fills ### Setting a Gradient Fill on a Block Once you've created a gradient fill, attach it to a block using `setFill()`: ```typescript highlight-apply-gradient // Create gradient fill const gradientFill = engine.block.createFill(fillType); // Apply the fill to the block engine.block.setFill(block, gradientFill); ``` ### Getting the Current Fill Retrieve the current fill attached to a block and inspect its type: ```typescript highlight-get-fill const { block: inspectGradientBlock, fill: inspectGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( inspectGradient, 'fill/gradient/colors', [ { color: { r: 0.6, g: 0.3, b: 0.7, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.7, b: 0.6, a: 1.0 }, stop: 1 } ] ); // Get current fill from block const fillId = engine.block.getFill(block1); const fillType = engine.block.getType(fillId); // eslint-disable-next-line no-console console.log('Fill type:', fillType); // '//ly.img.ubq/fill/gradient/linear' ``` ## Configuring Gradient Color Stops ### Setting Color Stops Set color stops using the `setGradientColorStops()` method with an array of color and position pairs: ```typescript highlight-set-color-stops engine.block.setGradientColorStops(linearVertical, 'fill/gradient/colors', [ { color: { r: 1.0, g: 0.8, b: 0.2, a: 1.0 }, stop: 0 }, { color: { r: 0.3, g: 0.4, b: 0.7, a: 1.0 }, stop: 1 } ]); ``` RGB values are normalized floats from 0.0 to 1.0. Stop positions are normalized where 0.0 represents the start and 1.0 represents the end. The alpha channel controls opacity per color stop. ### Getting Color Stops Retrieve the current color stops from a gradient fill: ```typescript highlight-get-color-stops // Get gradient color stops const colorStops = engine.block.getGradientColorStops( inspectGradient, 'fill/gradient/colors' ); // eslint-disable-next-line no-console console.log('Color stops:', colorStops); ``` ### Using Different Color Spaces Gradient color stops support multiple color spaces: ```typescript highlight-color-spaces const { block: cmykGradientBlock, fill: cmykGradient } = createShapeWithFill('gradient/linear'); // CMYK color stops for print engine.block.setGradientColorStops(cmykGradient, 'fill/gradient/colors', [ { color: { c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 0 }, { color: { c: 1.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0 }, stop: 1 } ]); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat( cmykGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(cmykGradient, 'fill/gradient/linear/endPointY', 0.5); ``` ## Positioning Linear Gradients ### Setting Start and End Points Linear gradients are positioned using start and end points with normalized coordinates (0.0 to 1.0) relative to block dimensions: ```typescript highlight-linear-position // Set vertical gradient (top to bottom) engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointX', 0.5 ); engine.block.setFloat( linearVertical, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat( linearVertical, 'fill/gradient/linear/endPointX', 0.5 ); engine.block.setFloat(linearVertical, 'fill/gradient/linear/endPointY', 1); ``` Coordinates are normalized where (0, 0) represents the top-left corner and (1, 1) represents the bottom-right corner. ### Common Linear Gradient Directions **Horizontal (Left to Right):** ```typescript engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointY', 0.5); engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointY', 0.5); ``` **Diagonal (Top-Left to Bottom-Right):** ```typescript engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(linearGradient, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(linearGradient, 'fill/gradient/linear/endPointY', 1); ``` ### Getting Current Position Retrieve the current position values: ```typescript highlight-get-linear-position // Get linear gradient position const startX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointX' ); const startY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/startPointY' ); const endX = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointX' ); const endY = engine.block.getFloat( inspectGradient, 'fill/gradient/linear/endPointY' ); // eslint-disable-next-line no-console console.log('Linear gradient position:', { startX, startY, endX, endY }); ``` ## Positioning Radial Gradients ### Setting Center Point and Radius Radial gradients are positioned using a center point and radius: ```typescript highlight-radial-position // Set center point (middle of block) engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointX', 0.5 ); engine.block.setFloat( radialCentered, 'fill/gradient/radial/centerPointY', 0.5 ); engine.block.setFloat(radialCentered, 'fill/gradient/radial/radius', 0.8); ``` The `centerPointX/Y` properties use normalized coordinates (0.0 to 1.0) relative to block dimensions. The `radius` property is relative to the smaller side of the block frame, where 1.0 equals full coverage. Default values are centerX = 0.0, centerY = 0.0, and radius = 1.0. ### Common Radial Patterns **Centered Circle:** ```typescript engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointX', 0.5); engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointY', 0.5); engine.block.setFloat(radialGradient, 'fill/gradient/radial/radius', 0.7); ``` **Top-Left Highlight:** ```typescript engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointX', 0); engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointY', 0); engine.block.setFloat(radialGradient, 'fill/gradient/radial/radius', 1.0); ``` **Bottom-Right Vignette:** ```typescript engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointX', 1); engine.block.setFloat(radialGradient, 'fill/gradient/radial/centerPointY', 1); engine.block.setFloat(radialGradient, 'fill/gradient/radial/radius', 1.5); ``` ## Positioning Conical Gradients ### Setting Center Point Conical gradients are positioned using a center point. The rotation starts at the top (12 o'clock) and proceeds clockwise: ```typescript highlight-conical-position // Set center point (middle of block) engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointX', 0.5 ); engine.block.setFloat( conicalColorWheel, 'fill/gradient/conical/centerPointY', 0.5 ); ``` The `centerPointX/Y` properties use normalized coordinates (0.0 to 1.0) relative to block dimensions. There is no separate rotation or angle property—the gradient always starts at the top. Default values are centerX = 0.0 and centerY = 0.0. ## Additional Techniques ### Sharing Gradient Fills You can share a single gradient fill between multiple blocks. Changes to the shared gradient affect all blocks using it: ```typescript highlight-share-gradient const block1 = engine.block.create('graphic'); const shape1 = engine.block.createShape('rect'); engine.block.setShape(block1, shape1); engine.block.setWidth(block1, blockWidth); engine.block.setHeight(block1, blockHeight / 2 - 5); engine.block.appendChild(page, block1); const block2 = engine.block.create('graphic'); const shape2 = engine.block.createShape('rect'); engine.block.setShape(block2, shape2); engine.block.setWidth(block2, blockWidth); engine.block.setHeight(block2, blockHeight / 2 - 5); engine.block.appendChild(page, block2); // Create one gradient fill const sharedGradient = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(sharedGradient, 'fill/gradient/colors', [ { color: { r: 1, g: 0, b: 0, a: 1 }, stop: 0 }, { color: { r: 0, g: 0, b: 1, a: 1 }, stop: 1 } ]); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(sharedGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat( sharedGradient, 'fill/gradient/linear/endPointY', 0.5 ); // Apply to both blocks engine.block.setFill(block1, sharedGradient); engine.block.setFill(block2, sharedGradient); // Change gradient after a delay to show it affects both setTimeout(() => { engine.block.setGradientColorStops( sharedGradient, 'fill/gradient/colors', [ { color: { r: 0, g: 1, b: 0, a: 1 }, stop: 0 }, { color: { r: 1, g: 1, b: 0, a: 1 }, stop: 1 } ] ); }, 2000); ``` ### Duplicating Gradient Fills When you duplicate a block, its gradient fill is automatically duplicated, creating an independent copy. Each duplicate has its own fill instance that can be modified independently without affecting the original. ## Common Use Cases ### Modern Hero Background (Aurora Effect) Create dreamy multi-color gradient backgrounds for hero sections: ```typescript highlight-aurora-gradient const { block: auroraGradientBlock, fill: auroraGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops(auroraGradient, 'fill/gradient/colors', [ { color: { r: 0.4, g: 0.1, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.8, g: 0.2, b: 0.6, a: 1 }, stop: 0.3 }, { color: { r: 1.0, g: 0.5, b: 0.3, a: 1 }, stop: 0.6 }, { color: { r: 1.0, g: 0.8, b: 0.2, a: 1 }, stop: 1 } ]); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/startPointY', 0.5 ); engine.block.setFloat(auroraGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat( auroraGradient, 'fill/gradient/linear/endPointY', 0.5 ); ``` ### Button Highlight Effect Use radial gradients to add depth and highlight effects to buttons: ```typescript highlight-button-gradient const { block: radialHighlightBlock, fill: radialHighlight } = createShapeWithFill('gradient/radial'); engine.block.setGradientColorStops( radialHighlight, 'fill/gradient/colors', [ { color: { r: 1.0, g: 1.0, b: 1.0, a: 0.3 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1.0 }, stop: 1 } ] ); // Set top-left highlight engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointX', 0 ); engine.block.setFloat( radialHighlight, 'fill/gradient/radial/centerPointY', 0 ); engine.block.setFloat(radialHighlight, 'fill/gradient/radial/radius', 1.0); ``` ### Loading Spinner (Conical) Create circular progress indicators and loading animations with conical gradients: ```typescript highlight-spinner-gradient const { block: conicalSpinnerBlock, fill: conicalSpinner } = createShapeWithFill('gradient/conical'); engine.block.setGradientColorStops(conicalSpinner, 'fill/gradient/colors', [ { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 0 }, stop: 0.75 }, { color: { r: 0.2, g: 0.4, b: 0.8, a: 1 }, stop: 1 } ]); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointX', 0.5 ); engine.block.setFloat( conicalSpinner, 'fill/gradient/conical/centerPointY', 0.5 ); ``` ### Transparency Overlay Create smooth transparency effects with alpha channel transitions: ```typescript highlight-overlay-gradient const { block: overlayGradientBlock, fill: overlayGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( overlayGradient, 'fill/gradient/colors', [ { color: { r: 0.0, g: 0.0, b: 0.0, a: 0 }, stop: 0 }, { color: { r: 0.0, g: 0.0, b: 0.0, a: 0.7 }, stop: 1 } ] ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointX', 0.5 ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat( overlayGradient, 'fill/gradient/linear/endPointX', 0.5 ); engine.block.setFloat(overlayGradient, 'fill/gradient/linear/endPointY', 1); ``` ### Duotone Effect Create modern two-color gradient overlays: ```typescript highlight-duotone-gradient const { block: duotoneGradientBlock, fill: duotoneGradient } = createShapeWithFill('gradient/linear'); engine.block.setGradientColorStops( duotoneGradient, 'fill/gradient/colors', [ { color: { r: 0.8, g: 0.2, b: 0.9, a: 1 }, stop: 0 }, { color: { r: 0.2, g: 0.9, b: 0.8, a: 1 }, stop: 1 } ] ); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointX', 0 ); engine.block.setFloat( duotoneGradient, 'fill/gradient/linear/startPointY', 0 ); engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(duotoneGradient, 'fill/gradient/linear/endPointY', 1); ``` ## Troubleshooting ### Gradient Not Visible If your gradient doesn't appear: - Check if fill is enabled: `engine.block.isFillEnabled(block)` - Verify color stops have visible colors (check alpha channels) - Ensure block has valid dimensions (width and height > 0) - Confirm block is in the scene hierarchy - Check if color stops are properly ordered by stop position ### Gradient Looks Different Than Expected If the gradient doesn't look right: - Verify color stop positions are between 0.0 and 1.0 - Check gradient direction and positioning properties - Ensure correct gradient type is used (linear vs radial vs conical) - Review color space (RGB vs CMYK) for output medium - Confirm alpha values for transparency effects ### Gradient Direction Wrong If the gradient direction is incorrect: - For linear gradients, check `startPointX/Y` and `endPointX/Y` values - Remember coordinates are normalized (0.0 to 1.0), not pixels - Verify the block's coordinate system and transformations - Test with simple horizontal or vertical gradients first ### Memory Leaks To prevent memory leaks: - Always destroy replaced gradients: `engine.block.destroy(oldFill)` - Don't create gradient fills without attaching them to blocks - Clean up shared gradients when no longer needed ### Cannot Apply Gradient to Block If you can't apply a gradient fill: - Verify block supports fills: `engine.block.supportsFill(block)` - Check if block has a shape: Some blocks require shapes - Ensure gradient fill object is valid and not already destroyed ### Color Stops Not Updating If color stops don't update: - Verify you're calling `setGradientColorStops()` not `setColor()` - Ensure property name is exactly `'fill/gradient/colors'` - Check that color stop array is properly formatted - Confirm fill ID is correct and still valid ## API Reference ### Core Methods | Method | Description | | ---------------------------------------------- | --------------------------------------- | | `createFill('gradient/linear')` | Create a new linear gradient fill | | `createFill('gradient/radial')` | Create a new radial gradient fill | | `createFill('gradient/conical')` | Create a new conical gradient fill | | `setFill(block, fill)` | Assign gradient fill to a block | | `getFill(block)` | Get the fill ID from a block | | `setGradientColorStops(fill, property, stops)` | Set gradient color stops array | | `getGradientColorStops(fill, property)` | Get current gradient color stops | | `setFloat(fill, property, value)` | Set gradient position/radius properties | | `getFloat(fill, property)` | Get gradient position/radius values | | `setFillEnabled(block, enabled)` | Enable or disable fill rendering | | `isFillEnabled(block)` | Check if fill is enabled | | `supportsFill(block)` | Check if block supports fills | ### Linear Gradient Properties | Property | Type | Default | Description | | ---------------------------------- | ------------------- | ------- | ------------------------- | | `fill/gradient/colors` | GradientColorStop\[] | - | Array of color stops | | `fill/gradient/linear/startPointX` | Float (0.0-1.0) | 0.5 | Horizontal start position | | `fill/gradient/linear/startPointY` | Float (0.0-1.0) | 0.0 | Vertical start position | | `fill/gradient/linear/endPointX` | Float (0.0-1.0) | 0.5 | Horizontal end position | | `fill/gradient/linear/endPointY` | Float (0.0-1.0) | 1.0 | Vertical end position | ### Radial Gradient Properties | Property | Type | Default | Description | | ----------------------------------- | ------------------- | ------- | ------------------------------- | | `fill/gradient/colors` | GradientColorStop\[] | - | Array of color stops | | `fill/gradient/radial/centerPointX` | Float (0.0-1.0) | 0.0 | Horizontal center position | | `fill/gradient/radial/centerPointY` | Float (0.0-1.0) | 0.0 | Vertical center position | | `fill/gradient/radial/radius` | Float | 1.0 | Radius relative to smaller side | ### Conical Gradient Properties | Property | Type | Default | Description | | ------------------------------------ | ------------------- | ------- | -------------------------- | | `fill/gradient/colors` | GradientColorStop\[] | - | Array of color stops | | `fill/gradient/conical/centerPointX` | Float (0.0-1.0) | 0.0 | Horizontal center position | | `fill/gradient/conical/centerPointY` | Float (0.0-1.0) | 0.0 | Vertical center position | **Note**: Conical gradients rotate clockwise starting from the top (12 o'clock). There is no rotation or angle property. ### GradientColorStop Interface ```typescript interface GradientColorStop { color: Color; // RGB, CMYK, or Spot color stop: number; // Position (0.0 to 1.0) } // Color formats supported: type Color = | { r: number; g: number; b: number; a: number } // RGB | { c: number; m: number; y: number; k: number; tint: number } // CMYK | { name: string; tint: number; externalReference: string }; // Spot ``` ## Next Steps Now that you understand gradient fills, explore other fill types and color management features: - Learn about Color Fills for solid colors - Explore Image Fills for photo content - Understand Fill Overview for the comprehensive fill system - Review Apply Colors for color management across properties - Study Blocks Concept for understanding the block system --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Filters & Effects Library" description: "Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/overview-299b15/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Overview](https://img.ly/docs/cesdk/angular/filters-and-effects/overview-299b15/) --- In CreativeEditor SDK (CE.SDK), *filters* and *effects* refer to visual modifications that enhance or transform the appearance of design elements. Filters typically adjust an element’s overall color or tone, while effects add specific visual treatments like blur, sharpness, or distortion. You can apply both filters and effects through the user interface or programmatically using the CE.SDK API. They allow you to refine the look of images, videos, and graphic elements in your designs with precision and flexibility. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Supported Filters and Effects" description: "View the full list of visual effects and filters available in CE.SDK, including both built-in and custom options." platform: angular url: "https://img.ly/docs/cesdk/angular/filters-and-effects/support-a666dd/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) > [Supported Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/support-a666dd/) --- Discover all available filters and effects in CE.SDK and learn how to check if a block supports them. ![Supported Filters and Effects example showing a gradient background with glow and vignette effects](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-support-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-filters-and-effects-support-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-filters-and-effects-support-browser/) CE.SDK provides 22 built-in effect types for visual transformations including color adjustments, blur effects, artistic filters, and distortion effects. This reference guide shows how to check effect support and add effects programmatically, followed by detailed property tables for each effect type. ```typescript file=@cesdk_web_examples/guides-filters-and-effects-support-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Supported Filters and Effects Reference * * Demonstrates how to check effect support and add effects to blocks: * - Checking if a block supports effects * - Creating and appending effects * - Configuring effect properties */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { width: 800, height: 600, unit: 'Pixel' } }); const engine = cesdk.engine; const page = engine.block.findByType('page')[0]; // Enable effects and filters in the inspector panel cesdk.feature.enable('ly.img.effect'); cesdk.feature.enable('ly.img.filter'); // Create a beautiful gradient background const gradientFill = engine.block.createFill('gradient/linear'); engine.block.setGradientColorStops(gradientFill, 'fill/gradient/colors', [ { color: { r: 0.02, g: 0.02, b: 0.08, a: 1.0 }, stop: 0 }, // Near black { color: { r: 0.04, g: 0.06, b: 0.18, a: 1.0 }, stop: 0.4 }, // Dark navy { color: { r: 0.08, g: 0.12, b: 0.28, a: 1.0 }, stop: 0.7 }, // Deep blue { color: { r: 0.1, g: 0.15, b: 0.35, a: 1.0 }, stop: 1 } // Dark blue ]); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointX', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/startPointY', 0); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointX', 1); engine.block.setFloat(gradientFill, 'fill/gradient/linear/endPointY', 1); engine.block.setFill(page, gradientFill); // Define font for text const fontUri = 'https://cdn.img.ly/packages/imgly/cesdk-js/latest/assets/extensions/ly.img.cesdk.fonts/fonts/Roboto/Roboto-Bold.ttf'; const typeface = { name: 'Roboto', fonts: [ { uri: fontUri, subFamily: 'Bold', weight: 'bold' as const, style: 'normal' as const } ] }; // Create title text: "Supported Filters and Effects" at 80pt (centered) const titleText = engine.block.create('text'); engine.block.appendChild(page, titleText); engine.block.replaceText(titleText, 'Supported Filters and Effects'); engine.block.setFont(titleText, fontUri, typeface); engine.block.setTextFontSize(titleText, 80); engine.block.setTextColor(titleText, { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }); engine.block.setEnum(titleText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(titleText, 780); engine.block.setWidthMode(titleText, 'Absolute'); engine.block.setHeightMode(titleText, 'Auto'); engine.block.setPositionX(titleText, 10); engine.block.setPositionY(titleText, 160); // Create subtext: "img.ly" at 64pt (closer to title) const subtitleText = engine.block.create('text'); engine.block.appendChild(page, subtitleText); engine.block.replaceText(subtitleText, 'img.ly'); engine.block.setFont(subtitleText, fontUri, typeface); engine.block.setTextFontSize(subtitleText, 64); engine.block.setTextColor(subtitleText, { r: 0.75, g: 0.82, b: 1.0, a: 0.85 }); engine.block.setEnum(subtitleText, 'text/horizontalAlignment', 'Center'); engine.block.setWidth(subtitleText, 780); engine.block.setWidthMode(subtitleText, 'Absolute'); engine.block.setHeightMode(subtitleText, 'Auto'); engine.block.setPositionX(subtitleText, 10); engine.block.setPositionY(subtitleText, 210); // Check if a block supports effects before applying them // Not all block types support effects - verify first to avoid errors // Add an image to demonstrate effects (centered below text) const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { size: { width: 300, height: 210 } }); engine.block.appendChild(page, imageBlock); // Center the image below the subtext engine.block.setPositionX(imageBlock, (800 - 300) / 2); // 250 engine.block.setPositionY(imageBlock, 310); // Image blocks support effects const imageSupportsEffects = engine.block.supportsEffects(imageBlock); console.log('Image supports effects:', imageSupportsEffects); // true // Create an effect using the effect type identifier // CE.SDK provides 22 built-in effect types (see property tables below) const duotoneEffect = engine.block.createEffect('duotone_filter'); // Append the effect to the image's effect stack engine.block.appendEffect(imageBlock, duotoneEffect); // Configure effect properties using the property path format: // effect/{effect-type}/{property-name} // Set duotone colors to match the dark blue gradient background engine.block.setColor(duotoneEffect, 'effect/duotone_filter/darkColor', { r: 0.02, g: 0.04, b: 0.12, a: 1.0 }); // Near black blue engine.block.setColor(duotoneEffect, 'effect/duotone_filter/lightColor', { r: 0.5, g: 0.7, b: 1.0, a: 1.0 }); // Light blue engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.8 ); // Retrieve all effects applied to a block const appliedEffects = engine.block.getEffects(imageBlock); console.log('Number of applied effects:', appliedEffects.length); // Log each effect's type appliedEffects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`); }); // Select the image to show effects in inspector engine.block.select(imageBlock); console.log( 'Support guide initialized. Select the image to see effects in the inspector.' ); } } export default Example; ``` This guide covers checking effect support on blocks, adding effects programmatically, and the complete list of available effect types with their properties. For detailed tutorials on configuring and combining multiple effects, see the [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) guide. ## Check Effect Support Before applying effects to a block, verify whether it supports them using `supportsEffects()`. Not all block types can have effects applied. ```typescript highlight-check-effect-support // Check if a block supports effects before applying them // Not all block types support effects - verify first to avoid errors // Add an image to demonstrate effects (centered below text) const imageUri = 'https://img.ly/static/ubq_samples/sample_1.jpg'; const imageBlock = await engine.block.addImage(imageUri, { size: { width: 300, height: 210 } }); engine.block.appendChild(page, imageBlock); // Center the image below the subtext engine.block.setPositionX(imageBlock, (800 - 300) / 2); // 250 engine.block.setPositionY(imageBlock, 310); // Image blocks support effects const imageSupportsEffects = engine.block.supportsEffects(imageBlock); console.log('Image supports effects:', imageSupportsEffects); // true ``` Effect support is available for: - **Graphic blocks** with image or video fills - **Shape blocks** with fills - **Text blocks** (with limited effect types) - **Page blocks** (particularly when they have background fills) ## Add an Effect Create an effect using `createEffect()` with the effect type identifier, then attach it to a block with `appendEffect()`. ```typescript highlight-add-effect // Create an effect using the effect type identifier // CE.SDK provides 22 built-in effect types (see property tables below) const duotoneEffect = engine.block.createEffect('duotone_filter'); // Append the effect to the image's effect stack engine.block.appendEffect(imageBlock, duotoneEffect); ``` ## Configure Effect Properties Configure effect parameters using setter methods. Property paths follow the format `effect/{effect-type}/{property-name}`. ```typescript highlight-configure-effect // Configure effect properties using the property path format: // effect/{effect-type}/{property-name} // Set duotone colors to match the dark blue gradient background engine.block.setColor(duotoneEffect, 'effect/duotone_filter/darkColor', { r: 0.02, g: 0.04, b: 0.12, a: 1.0 }); // Near black blue engine.block.setColor(duotoneEffect, 'effect/duotone_filter/lightColor', { r: 0.5, g: 0.7, b: 1.0, a: 1.0 }); // Light blue engine.block.setFloat( duotoneEffect, 'effect/duotone_filter/intensity', 0.8 ); ``` CE.SDK provides typed setter methods for different parameter types: - **`setFloat()`** - For intensity, amount, and decimal values - **`setInt()`** - For discrete values like pixel sizes - **`setString()`** - For file URIs (LUT files) - **`setBool()`** - For enabling or disabling features - **`setColor()`** - For color values (RGBA format) ## Retrieve Applied Effects Use `getEffects()` to retrieve all effects applied to a block. ```typescript highlight-get-effects // Retrieve all effects applied to a block const appliedEffects = engine.block.getEffects(imageBlock); console.log('Number of applied effects:', appliedEffects.length); // Log each effect's type appliedEffects.forEach((effect, index) => { const effectType = engine.block.getType(effect); console.log(`Effect ${index}: ${effectType}`); }); ``` ## Effects The following tables document all available effect types and their configurable properties. ## Adjustments Type An effect block for basic image adjustments. This section describes the properties available for the **Adjustments Type** (`//ly.img.ubq/effect/adjustments`) block type. | Property | Type | Default | Description | | -------------------------------- | ------- | ------- | ------------------------------------ | | `effect/adjustments/blacks` | `Float` | `0` | Adjustment of only the blacks. | | `effect/adjustments/brightness` | `Float` | `0` | Adjustment of the brightness. | | `effect/adjustments/clarity` | `Float` | `0` | Adjustment of the detail. | | `effect/adjustments/contrast` | `Float` | `0` | Adjustment of the contrast. | | `effect/adjustments/exposure` | `Float` | `0` | Adjustment of the exposure. | | `effect/adjustments/gamma` | `Float` | `0` | Gamma correction, non-linear. | | `effect/adjustments/highlights` | `Float` | `0` | Adjustment of only the highlights. | | `effect/adjustments/saturation` | `Float` | `0` | Adjustment of the saturation. | | `effect/adjustments/shadows` | `Float` | `0` | Adjustment of only the shadows. | | `effect/adjustments/sharpness` | `Float` | `0` | Adjustment of the sharpness. | | `effect/adjustments/temperature` | `Float` | `0` | Adjustment of the color temperature. | | `effect/adjustments/whites` | `Float` | `0` | Adjustment of only the whites. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Cross Cut Type An effect that distorts the image with horizontal slices. This section describes the properties available for the **Cross Cut Type** (`//ly.img.ubq/effect/cross_cut`) block type. | Property | Type | Default | Description | | ------------------------- | ------- | ------- | ------------------------------ | | `effect/cross_cut/offset` | `Float` | `0.07` | Horizontal offset per slice. | | `effect/cross_cut/slices` | `Float` | `5` | Number of horizontal slices. | | `effect/cross_cut/speedV` | `Float` | `0.5` | Vertical slice position. | | `effect/cross_cut/time` | `Float` | `1` | Randomness input. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Dot Pattern Type An effect that displays the image using a dot matrix. This section describes the properties available for the **Dot Pattern Type** (`//ly.img.ubq/effect/dot_pattern`) block type. | Property | Type | Default | Description | | ------------------------- | ------- | ------- | ------------------------------ | | `effect/dot_pattern/blur` | `Float` | `0.3` | Global blur. | | `effect/dot_pattern/dots` | `Float` | `30` | Number of dots. | | `effect/dot_pattern/size` | `Float` | `0.5` | Size of an individual dot. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Duotone Filter Type An effect that applies a two-tone color mapping. This section describes the properties available for the **Duotone Filter Type** (`//ly.img.ubq/effect/duotone_filter`) block type. | Property | Type | Default | Description | | ---------------------------------- | ------- | --------------------------- | --------------------------------------------------------------------------------- | | `effect/duotone_filter/darkColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | The darker of the two colors. Negative filter intensities emphasize this color. | | `effect/duotone_filter/intensity` | `Float` | `0` | The mixing weight of the two colors in the range \[-1, 1]. | | `effect/duotone_filter/lightColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | The brighter of the two colors. Positive filter intensities emphasize this color. | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Extrude Blur Type An effect that applies a radial extrude blur. This section describes the properties available for the **Extrude Blur Type** (`//ly.img.ubq/effect/extrude_blur`) block type. | Property | Type | Default | Description | | ---------------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/extrude_blur/amount` | `Float` | `0.2` | Blur intensity. | ## Glow Type An effect that applies an artificial glow. This section describes the properties available for the **Glow Type** (`//ly.img.ubq/effect/glow`) block type. | Property | Type | Default | Description | | ---------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/glow/amount` | `Float` | `0.5` | Glow brightness. | | `effect/glow/darkness` | `Float` | `0.3` | Glow darkness. | | `effect/glow/size` | `Float` | `4` | Intensity of the glow. | ## Green Screen Type An effect that replaces a specific color with transparency. This section describes the properties available for the **Green Screen Type** (`//ly.img.ubq/effect/green_screen`) block type. | Property | Type | Default | Description | | -------------------------------- | ------- | --------------------------- | ---------------------------------------------------------------------------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/green_screen/colorMatch` | `Float` | `0.4` | Threshold between the source color and the from color. | | `effect/green_screen/fromColor` | `Color` | `{"r":0,"g":1,"b":0,"a":1}` | The color to be replaced. | | `effect/green_screen/smoothness` | `Float` | `0.08` | Controls the rate at which the color transition increases when the similarity threshold is exceeded. | | `effect/green_screen/spill` | `Float` | `0` | Controls the desaturation of the source color to reduce color spill. | ## Half Tone Type An effect that overlays a halftone pattern. This section describes the properties available for the **Half Tone Type** (`//ly.img.ubq/effect/half_tone`) block type. | Property | Type | Default | Description | | ------------------------ | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/half_tone/angle` | `Float` | `0` | Angle of pattern. | | `effect/half_tone/scale` | `Float` | `0.5` | Scale of pattern. | ## Linocut Type An effect that overlays a linocut pattern. This section describes the properties available for the **Linocut Type** (`//ly.img.ubq/effect/linocut`) block type. | Property | Type | Default | Description | | ---------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/linocut/scale` | `Float` | `0.5` | Scale of pattern. | ## Liquid Type An effect that applies a liquefy distortion. This section describes the properties available for the **Liquid Type** (`//ly.img.ubq/effect/liquid`) block type. | Property | Type | Default | Description | | ---------------------- | ------- | ------- | ------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/liquid/amount` | `Float` | `0.06` | Severity of the applied effect. | | `effect/liquid/scale` | `Float` | `0.62` | Global scale. | | `effect/liquid/time` | `Float` | `0.5` | Continuous randomness input. | ## Lut Filter Type An effect that applies a color lookup table (LUT). This section describes the properties available for the **Lut Filter Type** (`//ly.img.ubq/effect/lut_filter`) block type. | Property | Type | Default | Description | | --------------------------------------- | -------- | ------- | ---------------------------------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/lut_filter/horizontalTileCount` | `Int` | `5` | The horizontal number of tiles contained in the LUT image. | | `effect/lut_filter/intensity` | `Float` | `1` | A value in the range of \[0, 1]. Defaults to 1.0. | | `effect/lut_filter/lutFileURI` | `String` | `""` | The URI to a LUT PNG file. | | `effect/lut_filter/verticalTileCount` | `Int` | `5` | The vertical number of tiles contained in the LUT image. | ## Mirror Type An effect that mirrors the image along a central axis. This section describes the properties available for the **Mirror Type** (`//ly.img.ubq/effect/mirror`) block type. | Property | Type | Default | Description | | -------------------- | ------ | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/mirror/side` | `Int` | `1` | Axis to mirror along. | ## Outliner Type An effect that highlights the outlines in an image. This section describes the properties available for the **Outliner Type** (`//ly.img.ubq/effect/outliner`) block type. | Property | Type | Default | Description | | ----------------------------- | ------- | ------- | -------------------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/outliner/amount` | `Float` | `0.5` | Intensity of edge highlighting. | | `effect/outliner/passthrough` | `Float` | `0.5` | Visibility of input image in non-edge areas. | ## Pixelize Type An effect that pixelizes the image. This section describes the properties available for the **Pixelize Type** (`//ly.img.ubq/effect/pixelize`) block type. | Property | Type | Default | Description | | ------------------------------------- | ------ | ------- | ----------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/pixelize/horizontalPixelSize` | `Int` | `20` | The number of pixels on the x-axis. | | `effect/pixelize/verticalPixelSize` | `Int` | `20` | The number of pixels on the y-axis. | ## Posterize Type An effect that reduces the number of colors in the image. This section describes the properties available for the **Posterize Type** (`//ly.img.ubq/effect/posterize`) block type. | Property | Type | Default | Description | | ------------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/posterize/levels` | `Float` | `3` | Number of color levels. | ## Radial Pixel Type An effect that reduces the image into radial pixel rows. This section describes the properties available for the **Radial Pixel Type** (`//ly.img.ubq/effect/radial_pixel`) block type. | Property | Type | Default | Description | | ------------------------------ | ------- | ------- | ------------------------------------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/radial_pixel/radius` | `Float` | `0.1` | Radius of an individual row of pixels, relative to the image. | | `effect/radial_pixel/segments` | `Float` | `0.01` | Proportional size of a pixel in each row. | ## Recolor Type An effect that replaces one color with another. This section describes the properties available for the **Recolor Type** (`//ly.img.ubq/effect/recolor`) block type. | Property | Type | Default | Description | | -------------------------------- | ------- | --------------------------- | ---------------------------------------------------------------------------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/recolor/brightnessMatch` | `Float` | `1` | Affects the weight of brightness when calculating color similarity. | | `effect/recolor/colorMatch` | `Float` | `0.4` | Threshold between the source color and the from color. | | `effect/recolor/fromColor` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | The color to be replaced. | | `effect/recolor/smoothness` | `Float` | `0.08` | Controls the rate at which the color transition increases when the similarity threshold is exceeded. | | `effect/recolor/toColor` | `Color` | `{"r":0,"g":0,"b":1,"a":1}` | The color to replace with. | ## Sharpie Type Cartoon-like effect. This section describes the properties available for the **Sharpie Type** (`//ly.img.ubq/effect/sharpie`) block type. | Property | Type | Default | Description | | ---------------- | ------ | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | ## Shifter Type An effect that shifts individual color channels. This section describes the properties available for the **Shifter Type** (`//ly.img.ubq/effect/shifter`) block type. | Property | Type | Default | Description | | ----------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/shifter/amount` | `Float` | `0.05` | Intensity of the shift. | | `effect/shifter/angle` | `Float` | `0.3` | Shift direction. | ## Tilt Shift Type An effect that applies a tilt-shift blur. This section describes the properties available for the **Tilt Shift Type** (`//ly.img.ubq/effect/tilt_shift`) block type. | Property | Type | Default | Description | | ---------------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/tilt_shift/amount` | `Float` | `0.016` | Blur intensity. | | `effect/tilt_shift/position` | `Float` | `0.4` | Horizontal position in image. | ## Tv Glitch Type An effect that mimics TV banding and distortion. This section describes the properties available for the **Tv Glitch Type** (`//ly.img.ubq/effect/tv_glitch`) block type. | Property | Type | Default | Description | | ------------------------------ | ------- | ------- | ---------------------------------- | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/tv_glitch/distortion` | `Float` | `3` | Rough horizontal distortion. | | `effect/tv_glitch/distortion2` | `Float` | `1` | Fine horizontal distortion. | | `effect/tv_glitch/rollSpeed` | `Float` | `1` | Vertical offset. | | `effect/tv_glitch/speed` | `Float` | `2` | Number of changes per time change. | ## Vignette Type An effect that adds a vignette (darkened corners). This section describes the properties available for the **Vignette Type** (`//ly.img.ubq/effect/vignette`) block type. | Property | Type | Default | Description | | -------------------------- | ------- | ------- | ------------------------------ | | `effect/enabled` | `Bool` | `true` | Whether the effect is enabled. | | `effect/vignette/darkness` | `Float` | `1` | Brightness of vignette. | | `effect/vignette/offset` | `Float` | `1` | Radial offset. | ## Next Steps - [Apply Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects/apply-2764e4/) - Learn how to configure, combine, and manage multiple effects --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Agent Skills" description: "Install CE.SDK documentation and code generation skills for AI coding assistants." platform: angular url: "https://img.ly/docs/cesdk/angular/get-started/agent-skills-f7g8h9/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) > [Agent Skills](https://img.ly/docs/cesdk/angular/get-started/agent-skills-f7g8h9/) --- The CE.SDK Agent Skills plugin gives AI coding assistants bundled documentation, guided code generation, and autonomous project scaffolding for building applications with CreativeEditor SDK across 10 Web frameworks. ## What Are Agent Skills? [Agent Skills](https://agentskills.io) are portable knowledge packs that plug into AI coding assistants. By installing the CE.SDK skills, you get: - **Offline documentation**: All guides, API references, and best practices bundled locally — no external API calls - **Guided code generation**: Build and explain skills that walk through CE.SDK implementation step by step - **Autonomous scaffolding**: A builder agent that creates complete CE.SDK projects from scratch ## Available Skills | Skill | Description | |-------|-------------| | `docs-react` | Look up CE.SDK React reference guides and documentation | | `docs-vue` | Look up CE.SDK Vue.js reference guides and documentation | | `docs-svelte` | Look up CE.SDK Svelte reference guides and documentation | | `docs-sveltekit` | Look up CE.SDK SvelteKit reference guides and documentation | | `docs-angular` | Look up CE.SDK Angular reference guides and documentation | | `docs-nextjs` | Look up CE.SDK Next.js reference guides and documentation | | `docs-nuxtjs` | Look up CE.SDK Nuxt.js reference guides and documentation | | `docs-electron` | Look up CE.SDK Electron reference guides and documentation | | `docs-js` | Look up CE.SDK Vanilla JavaScript reference guides and documentation | | `docs-node` | Look up CE.SDK Node.js reference guides and documentation | | `build` | Implement features, write code, and set up CE.SDK Web projects | | `explain` | Explain how CE.SDK Web features work — concepts, architecture, workflows | The plugin also includes a **builder** agent that autonomously scaffolds complete CE.SDK web applications — detecting your framework, applying starter kit templates, and implementing features end-to-end. ## Setup Instructions ### Claude Code Plugin Add the marketplace and install the plugin: ```bash # Add the marketplace (one-time setup) claude plugin marketplace add imgly/agent-skills # Install the plugin claude plugin install cesdk@imgly ``` ### Vercel Skills CLI Install using the [Vercel Skills CLI](https://github.com/vercel-labs/skills): ```bash # Install all skills for Claude Code npx skills add imgly/agent-skills -a claude-code # Install a specific skill only npx skills add imgly/agent-skills --skill docs-react -a claude-code # List available skills first npx skills add imgly/agent-skills --list ``` ### Manual Copy For any skills-compatible agent, copy skill folders directly from the [GitHub repository](https://github.com/imgly/agent-skills): ```bash # Clone the repo git clone https://github.com/imgly/agent-skills.git # Copy a specific skill into your Claude Code project cp -r agent-skills/plugins/cesdk/skills/docs-react .claude/skills/cesdk-docs-react # Or copy the builder agent cp agent-skills/plugins/cesdk/agents/builder.md .claude/agents/cesdk-builder.md ``` ## Usage Once installed, invoke skills with slash commands in your AI coding assistant: ### Look up documentation ``` /cesdk:docs-react configuration /cesdk:docs-vue getting started /cesdk:docs-nextjs server-side rendering ``` ### Build a feature ``` /cesdk:build add text overlays to images /cesdk:build create a photo editor with filters ``` ### Explain a concept ``` /cesdk:explain how the block hierarchy works /cesdk:explain export pipeline and output formats ``` ## How It Works Each documentation skill bundles the complete CE.SDK guides and API references for its framework in a compressed index. Skills read directly from these local files — no external services or MCP servers are required. The build skill includes starter kit templates for common use cases like design editors, video editors, and photo editors. It detects your project's framework and generates code accordingly. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Quickstart" description: "Get started with CE.SDK by choosing a starter kit" platform: angular url: "https://img.ly/docs/cesdk/angular/get-started/angular/quickstart-a7u8i9/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) > [Quickstart Angular](https://img.ly/docs/cesdk/angular/get-started/angular/quickstart-a7u8i9/) --- Get started with CE.SDK. Choose a starter kit below to see it in action, then follow the integration guide. ## Starter Kits Try the live demos below to see each editor in action, then get started with the one that fits your use case. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "MCP Server" description: "Connect AI assistants to CE.SDK documentation using the Model Context Protocol (MCP) server." platform: angular url: "https://img.ly/docs/cesdk/angular/get-started/mcp-server-fde71c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) > [MCP Server](https://img.ly/docs/cesdk/angular/get-started/mcp-server-fde71c/) --- The CE.SDK MCP server provides a standardized interface that allows any compatible AI assistant to search and access our documentation. This enables AI tools like Claude, Cursor, and VS Code Copilot to provide more accurate, context-aware help when working with CE.SDK. ## What is MCP? The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI assistants to securely connect to external data sources. By connecting your AI tools to our MCP server, you get: - **Accurate answers**: AI assistants can search and retrieve the latest CE.SDK documentation - **Context-aware help**: Get platform-specific guidance for your development environment - **Up-to-date information**: Always access current documentation without relying on training data ## Available Tools The MCP server exposes two tools: | Tool | Description | |------|-------------| | `search` | Search documentation by query string | | `fetch` | Retrieve the full content of a document by ID | ## Server Endpoint | URL | Transport | |-----|-----------| | `https://mcp.img.ly/mcp` | Streamable HTTP | No authentication is required. ## Setup Instructions ### Claude Code Add the MCP server with a single command: ```bash claude mcp add --transport http imgly_docs https://mcp.img.ly/mcp ``` ### Claude Desktop 1. Open Claude Desktop and go to **Settings** (click your profile icon) 2. Navigate to **Connectors** in the sidebar 3. Click **Add custom connector** 4. Enter the URL: `https://mcp.img.ly/mcp` 5. Click **Add** to connect ### Cursor Add the following to your Cursor MCP configuration. You can use either: - **Project-specific**: `.cursor/mcp.json` in your project root - **Global**: `~/.cursor/mcp.json` ```json { "mcpServers": { "imgly_docs": { "url": "https://mcp.img.ly/mcp" } } } ``` ### VS Code Add to your workspace configuration at `.vscode/mcp.json`: ```json { "servers": { "imgly_docs": { "type": "http", "url": "https://mcp.img.ly/mcp" } } } ``` ### Windsurf Add the following to your Windsurf MCP configuration at `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "imgly_docs": { "serverUrl": "https://mcp.img.ly/mcp" } } } ``` ### Other Clients For other MCP-compatible clients, use the endpoint `https://mcp.img.ly/mcp` with HTTP transport. Refer to your client's documentation for the specific configuration format. ## Usage Once configured, your AI assistant will automatically have access to CE.SDK documentation. You can ask questions like: - "How do I add a text block in CE.SDK?" - "Show me how to export a design as PNG" - "What are the available blend modes?" The AI will search our documentation and provide answers based on the latest CE.SDK guides and API references. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Get Started" description: "Start integrating CE.SDK into your application—from understanding the SDK to running your first editor." platform: angular url: "https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/angular/get-started/overview-e18f40/) --- Everything you need to integrate CE.SDK into your application. Learn what the SDK offers, get up and running with starter kits, explore AI-powered workflows, and understand our licensing model. ## Next Steps - [What is CE.SDK?](props.paths['2e7acd']) - [Quickstart](props.paths['r1q2w3e']) - [Vibecoding](props.paths['c3014f']) - [Licensing](props.paths['8aa063']) --- ## Related Pages - [Angular Creative Editor](https://img.ly/docs/cesdk/angular/what-is-cesdk-2e7acd/) - The CreativeEditor SDK offers a robust Angular library designed for crafting and editing rich visual designs directly within the browser. - [Quickstart](https://img.ly/docs/cesdk/angular/get-started/angular/quickstart-a7u8i9/) - Get started with CE.SDK by choosing a starter kit - [MCP Server](https://img.ly/docs/cesdk/angular/get-started/mcp-server-fde71c/) - Connect AI assistants to CE.SDK documentation using the Model Context Protocol (MCP) server. - [Agent Skills](https://img.ly/docs/cesdk/angular/get-started/agent-skills-f7g8h9/) - Install CE.SDK documentation and code generation skills for AI coding assistants. - [LLMs.txt](https://img.ly/docs/cesdk/angular/llms-txt-eb9cc5/) - Our documentation is available in LLMs.txt format - [Licensing](https://img.ly/docs/cesdk/angular/licensing-8aa063/) - Understand CE.SDK’s flexible licensing, trial options, and how keys work across dev, staging, and production. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Guides" description: "Documentation for Guides" platform: angular url: "https://img.ly/docs/cesdk/angular/guides-8d8b00/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) --- --- ## Related Pages - [Configuration](https://img.ly/docs/cesdk/angular/configuration-2c1c3d/) - Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements. - [Actions API](https://img.ly/docs/cesdk/angular/actions-6ch24x/) - Learn how to use the Actions API to register and customize action handlers in CE.SDK - [Settings](https://img.ly/docs/cesdk/angular/settings-970c98/) - Explore all configurable editor settings and learn how to read, update, and observe them via the Settings API. - [Serve Assets](https://img.ly/docs/cesdk/angular/serve-assets-b0827c/) - Configure CE.SDK to load engine and content assets from your own servers instead of the IMG.LY CDN for production deployments. - [Engine Interface](https://img.ly/docs/cesdk/angular/engine-interface-6fb7cf/) - Understand CE.SDK's architecture and learn when to use direct Engine access for automation workflows - [Automate Workflows](https://img.ly/docs/cesdk/angular/automation-715209/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale. - [AI Integration](https://img.ly/docs/cesdk/angular/user-interface/ai-integration-5aa356/) - Integrate external AI services into CE.SDK to enhance creative workflows and power advanced features. - [User Interface](https://img.ly/docs/cesdk/angular/user-interface-5a089a/) - Use CE.SDK’s customizable, production-ready UI or replace it entirely with your own interface. - [Open the Editor](https://img.ly/docs/cesdk/angular/open-the-editor-23a1db/) - Learn how to load and create scenes, set the zoom level, and configure file proxies or URI resolvers. - [Insert Media Into Scenes](https://img.ly/docs/cesdk/angular/insert-media-a217f5/) - Understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code. - [Import Media](https://img.ly/docs/cesdk/angular/import-media-4e3703/) - Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK. - [Export](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) - Explore export options, supported formats, and configuration features for sharing or rendering output. - [Save](https://img.ly/docs/cesdk/angular/export-save-publish/save-c8b124/) - Save design progress locally or to a backend service to allow for later editing or publishing. - [Store Custom Metadata](https://img.ly/docs/cesdk/angular/export-save-publish/store-custom-metadata-337248/) - Attach, retrieve, and manage custom key-value metadata on design blocks in CE.SDK. - [Edit Image](https://img.ly/docs/cesdk/angular/edit-image-c64912/) - Use CE.SDK to crop, transform, annotate, or enhance images with editing tools and programmatic APIs. - [Create Videos](https://img.ly/docs/cesdk/angular/create-video-c41a08/) - Learn how to create and customize videos in CE.SDK using scenes, assets, and timeline-based editing. - [Audio](https://img.ly/docs/cesdk/angular/create-audio/audio-2f700b/) - Create audio in videos - [Text](https://img.ly/docs/cesdk/angular/text-8a993a/) - Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools. - [Create and Edit Shapes](https://img.ly/docs/cesdk/angular/shapes-9f1b2c/) - Draw custom vector shapes, combine them with boolean operations, and insert QR codes into your designs. - [Create and Edit Stickers](https://img.ly/docs/cesdk/angular/stickers-3d4e5f/) - Create and customize stickers using image fills for icons, logos, emoji, and multi-color graphics. - [Create Compositions](https://img.ly/docs/cesdk/angular/create-composition-db709c/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions. - [Create Templates](https://img.ly/docs/cesdk/angular/create-templates-3aef79/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK. - [Colors](https://img.ly/docs/cesdk/angular/colors-a9b79c/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats. - [Fills](https://img.ly/docs/cesdk/angular/fills-402ddc/) - Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements. - [Outlines](https://img.ly/docs/cesdk/angular/outlines-b7820c/) - Enhance design elements with strokes, shadows, and glow effects to improve contrast and visual appeal. - [Filters and Effects](https://img.ly/docs/cesdk/angular/filters-and-effects-6f88ac/) - Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying. - [Animation](https://img.ly/docs/cesdk/angular/animation-ce900c/) - Add motion to designs with support for keyframes, timeline editing, and programmatic animation control. - [Rules](https://img.ly/docs/cesdk/angular/rules-1427c0/) - Define and enforce layout, branding, and safety rules to ensure consistent and compliant designs. - [Conversion](https://img.ly/docs/cesdk/angular/conversion-c3fbb3/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools. - [Improve Performance](https://img.ly/docs/cesdk/angular/performance-3c12eb/) - Optimize CE.SDK browser integration with code splitting, source sets for large assets, export workers, and lifecycle best practices. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "For Audio Processing" description: "Learn how to export audio in WAV or MP4 format from any block type in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/guides/export-save-publish/export/audio-68de25/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/angular/export-save-publish/export-82f968/) > [For Audio Processing](https://img.ly/docs/cesdk/angular/guides/export-save-publish/export/audio-68de25/) --- Export audio from pages, video blocks, audio blocks, and tracks to WAV or MP4 format for external processing, transcription, or analysis. ![Audio Export example showing audio export interface in CE.SDK](./assets/browser.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-export-audio-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-export-audio-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-export-audio-browser/) The `exportAudio` API allows you to extract audio from any block that contains audio content. This is particularly useful when integrating with external audio processing services like speech-to-text transcription, audio enhancement, or music analysis platforms. Audio can be exported from multiple block types: - **Page blocks** - Export the complete mixed audio timeline - **Video blocks** - Extract audio tracks from videos - **Audio blocks** - Export standalone audio content - **Track blocks** - Export audio from specific timeline tracks ## Export Audio Export audio from any block using the `exportAudio` API: ```javascript const page = cesdk.engine.scene.getCurrentPage(); const audioBlob = await cesdk.engine.block.exportAudio(page, { mimeType: 'audio/wav', sampleRate: 48000, numberOfChannels: 2 }); ``` ### Export Options Configure your audio export with these options: - **`mimeType`** - `'audio/wav'` (uncompressed) or `'audio/mp4'` (compressed AAC) - **`sampleRate`** - Audio quality in Hz (default: 48000) - **`numberOfChannels`** - 1 for mono or 2 for stereo - **`timeOffset`** - Start time in seconds (default: 0) - **`duration`** - Length to export in seconds (0 = entire duration) - **`onProgress`** - Callback function receiving `(rendered, encoded, total)` for progress tracking ## Find Audio Sources To find blocks with audio in your scene: ```javascript // Find audio blocks const audioBlocks = cesdk.engine.block.findByType('audio'); // Find video blocks with audio const videoFills = cesdk.engine.block.findByType('//ly.img.ubq/fill/video'); const videosWithAudio = videoFills.filter(block => { try { return cesdk.engine.block.getAudioInfoFromVideo(block).length > 0; } catch { return false; } }); ``` ## Working with Multi-Track Video Audio Videos can contain multiple audio tracks (e.g., different languages). CE.SDK provides APIs to inspect and extract specific tracks. ### Check audio track count ```javascript const videoFillId = cesdk.engine.block.findByType('//ly.img.ubq/fill/video')[0]; const trackCount = cesdk.engine.block.getAudioTrackCountFromVideo(videoFillId); console.log(`Video has ${trackCount} audio track(s)`); ``` ### Get track information ```javascript const audioTracks = cesdk.engine.block.getAudioInfoFromVideo(videoFillId); audioTracks.forEach((track, index) => { console.log(`Track ${index}:`, { channels: track.channels, // 1=mono, 2=stereo sampleRate: track.sampleRate, // Sample rate in Hz language: track.language, // e.g., "en", "es" label: track.label // Track description }); }); ``` ### Extract a specific track ```javascript // Create audio block from track 0 (first track) const audioBlockId = cesdk.engine.block.createAudioFromVideo(videoFillId, 0); // Export just this track's audio const trackAudioBlob = await cesdk.engine.block.exportAudio(audioBlockId, { mimeType: 'audio/wav' }); ``` ### Extract all tracks ```javascript // Create audio blocks for all tracks const audioBlockIds = cesdk.engine.block.createAudiosFromVideo(videoFillId); // Export each track for (let i = 0; i < audioBlockIds.length; i++) { const trackBlob = await cesdk.engine.block.exportAudio(audioBlockIds[i]); console.log(`Track ${i}: ${trackBlob.size} bytes`); } ``` ## Complete Workflow: Audio to Captions A common workflow is to export audio, send it to a transcription service, and use the returned captions in your scene. ### Step 1: Export Audio ```javascript const page = cesdk.engine.scene.getCurrentPage(); const audioBlob = await cesdk.engine.block.exportAudio(page, { mimeType: 'audio/wav', sampleRate: 48000, numberOfChannels: 2 }); ``` ### Step 2: Send to Transcription Service Send the audio to a service that returns SubRip (SRT) format captions: ```javascript async function transcribeAudio(audioBlob) { const formData = new FormData(); formData.append('audio', audioBlob, 'audio.wav'); formData.append('format', 'srt'); const response = await fetch('https://api.transcription-service.com/transcribe', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' }, body: formData }); // Returns SRT format text return await response.text(); } const srtContent = await transcribeAudio(audioBlob); ``` ### Step 3: Import Captions from SRT Use the built-in API to create caption blocks from the SRT response: ```javascript // Create a file from the SRT text const srtFile = new File([srtContent], 'captions.srt', { type: 'application/x-subrip' }); // Create object URL and import captions const uri = URL.createObjectURL(srtFile); const captions = await cesdk.engine.block.createCaptionsFromURI(uri); URL.revokeObjectURL(uri); // Add captions to page const page = cesdk.engine.scene.getCurrentPage(); const captionTrack = cesdk.engine.block.create('//ly.img.ubq/captionTrack'); captions.forEach(caption => { cesdk.engine.block.appendChild(captionTrack, caption); }); cesdk.engine.block.appendChild(page, captionTrack); // Center the first caption as a reference point cesdk.engine.block.alignHorizontally([captions[0]], 'Center'); cesdk.engine.block.alignVertically([captions[0]], 'Center'); ``` ### Other Processing Services Audio export also supports these workflows: - **Audio enhancement** - Noise removal, normalization - **Music analysis** - Tempo, key, beat detection - **Language detection** - Identify spoken language - **Speaker diarization** - Identify who spoke when ## Next Steps Now that you understand audio export, explore related audio and video features: - [Add Captions](https://img.ly/docs/cesdk/angular/edit-video/add-captions-f67565/) - Learn how to create and sync caption blocks with audio content - [Control Audio and Video](https://img.ly/docs/cesdk/angular/create-video/control-daba54/) - Master time offset, duration, and playback controls for audio blocks - [Trim Video Clips](https://img.ly/docs/cesdk/angular/edit-video/trim-4f688b/) - Apply the same trim concepts to isolate audio segments --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Import Media" description: "Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media-4e3703/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/angular/import-media/overview-84bb23/) - Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK. - [Asset Concepts](https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/) - This guide explains the foundational architecture of the CE.SDK asset system, including what asset sources are, how they organize content, and how they connect to the user interface. - [Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/) - Manage how users browse, preview, and insert media assets into their designs with a customizable asset library. - [Import From Local Source](https://img.ly/docs/cesdk/angular/import-media/from-local-source-39b2a9/) - Enable users to upload files from their device for use as design assets in the editor. - [Import From Remote Source](https://img.ly/docs/cesdk/angular/import-media/from-remote-source-b65faf/) - Connect CE.SDK to external sources like servers or third-party platforms to import assets remotely. - [Capture From Camera](https://img.ly/docs/cesdk/angular/import-media/capture-from-camera-92f388/) - Capture photos or videos directly from a connected camera for immediate use in your design. - [Edit or Remove Assets](https://img.ly/docs/cesdk/angular/import-media/edit-or-remove-assets-ce072c/) - Manage assets in local asset sources by updating metadata, removing individual assets, or deleting entire sources in CE.SDK. - [Source Sets](https://img.ly/docs/cesdk/angular/import-media/source-sets-5679c8/) - Provide multiple versions of images and videos at different resolutions for optimal performance and quality across editing and export workflows. - [Using Default Assets](https://img.ly/docs/cesdk/angular/import-media/default-assets-d2763d/) - Load shapes, stickers, images, and other built-in assets from IMG.LY's CDN to populate your CE.SDK editor using the Asset API. - [Retrieve MIME Type](https://img.ly/docs/cesdk/angular/import-media/retrieve-mimetype-ed13bf/) - Detect the MIME type of resources loaded in the engine to determine file formats for processing, export, or display. - [Asset Content JSON Schema](https://img.ly/docs/cesdk/angular/import-media/content-json-schema-a7b3d2/) - Understand the JSON schema structure for defining asset source content including version, metadata, and payload properties for images, videos, fonts, and templates. - [Supported File Formats for Import](https://img.ly/docs/cesdk/angular/import-media/file-format-support-8cdc84/) - Review the supported image, video, audio, and template formats for importing assets into CE.SDK on the web. - [Size Limits](https://img.ly/docs/cesdk/angular/import-media/size-limits-c32275/) - Learn about file size restrictions and how to optimize large assets for use in CE.SDK. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Asset Library" description: "Manage how users browse, preview, and insert media assets into their designs with a customizable asset library." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/) --- --- ## Related Pages - [Basics](https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/) - Explore the core functionality of the asset library and how users browse, search, and insert media. - [Customize](https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/) - Adapt the asset library UI and behavior to suit your application’s structure and user needs. - [Thumbnails](https://img.ly/docs/cesdk/angular/import-media/asset-panel/thumbnails-c23949/) - Configure thumbnail images for assets in CE.SDK's asset library with proper sizing, preview URIs for audio, and customized UI display. - [Refresh Assets](https://img.ly/docs/cesdk/angular/import-media/asset-panel/refresh-assets-382060/) - Trigger asset reloads to ensure the library reflects newly uploaded or updated items. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Basics" description: "Explore the core functionality of the asset library and how users browse, search, and insert media." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/) > [Basics](https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/) --- CE.SDK treats all insertable content as assets—images, videos, audio, stickers, shapes, templates, and text presets flow through a unified asset system. ![Asset Library Basics example showing the CE.SDK editor with a custom asset library entry in the dock](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-basics-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-basics-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-import-media-asset-library-basics-browser/) The asset library connects the engine to the user interface through three layers: ``` ┌─────────────────────────────────────────────────────────────┐ │ User Interface │ │ ┌─────────────┐ references ┌──────────────────────┐ │ │ │ Dock Entry │ ───────────────▶ │ Asset Library Entry │ │ │ │ (Button) │ │ (Display Config) │ │ │ └─────────────┘ └──────────┬───────────┘ │ │ │ queries │ ├──────────────────────────────────────────────│──────────────┤ │ Engine ▼ │ │ ┌──────────────────────┐ │ │ │ Asset Source │ │ │ │ (Data Provider) │ │ │ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` The following example demonstrates all three layers working together: ```typescript file=@cesdk_web_examples/guides-import-media-asset-library-basics-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Asset Library Basics Guide * * Demonstrates the asset library architecture: * - Creating a custom asset source (engine layer) * - Creating an asset library entry (UI configuration layer) * - Connecting the entry to the dock (UI interaction layer) */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); // Layer 1: Asset Source - provides assets to the UI cesdk.engine.asset.addSource({ id: 'my-custom-images', findAssets: async () => ({ assets: [ { id: 'sample-1', meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 1920, height: 1280 } }, { id: 'sample-2', meta: { uri: 'https://img.ly/static/ubq_samples/sample_2.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_2.jpg', width: 1920, height: 1280 } }, { id: 'sample-3', meta: { uri: 'https://img.ly/static/ubq_samples/sample_3.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_3.jpg', width: 1920, height: 1280 } } ], total: 3, currentPage: 1, nextPage: undefined }), applyAsset: async (assetResult) => cesdk.engine.asset.defaultApplyAsset(assetResult) }); // Layer 2: Asset Library Entry - connects sources to display settings cesdk.ui.addAssetLibraryEntry({ id: 'my-images-entry', sourceIds: ['my-custom-images'], previewLength: 3, gridColumns: 3, gridItemHeight: 'square' }); // Layer 3: Dock - adds button to access the entry cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ { id: 'ly.img.assetLibrary.dock', key: 'my-images-entry', label: 'libraries.my-images-entry.label', entries: ['my-images-entry'] }, ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }) ]); cesdk.i18n.setTranslations({ en: { 'libraries.my-images-entry.label': 'My Images' } }); // Query registered entries const allEntries = cesdk.ui.findAllAssetLibraryEntries(); console.log('Registered entries:', allEntries); const myEntry = cesdk.ui.getAssetLibraryEntry('my-images-entry'); console.log('My entry:', myEntry); // Open the panel to show the custom assets immediately cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['my-images-entry'] } }); } } export default Example; ``` This guide covers: - Registering asset sources with the engine - Creating asset library entries to configure display settings - Adding entries to the dock for user access ## Layer 1: Asset Source Asset sources provide data through `findAssets` and handle insertion through `applyAsset`. Register them with `engine.asset.addSource()`. ```typescript highlight=highlight-asset-source // Layer 1: Asset Source - provides assets to the UI cesdk.engine.asset.addSource({ id: 'my-custom-images', findAssets: async () => ({ assets: [ { id: 'sample-1', meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', width: 1920, height: 1280 } }, { id: 'sample-2', meta: { uri: 'https://img.ly/static/ubq_samples/sample_2.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_2.jpg', width: 1920, height: 1280 } }, { id: 'sample-3', meta: { uri: 'https://img.ly/static/ubq_samples/sample_3.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_3.jpg', width: 1920, height: 1280 } } ], total: 3, currentPage: 1, nextPage: undefined }), applyAsset: async (assetResult) => cesdk.engine.asset.defaultApplyAsset(assetResult) }); ``` For details on asset source configuration, see the [Asset Sources concept](https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/). ## Layer 2: Asset Library Entry Entries connect asset sources to display settings. Create them with `cesdk.ui.addAssetLibraryEntry()`. ```typescript highlight=highlight-asset-entry // Layer 2: Asset Library Entry - connects sources to display settings cesdk.ui.addAssetLibraryEntry({ id: 'my-images-entry', sourceIds: ['my-custom-images'], previewLength: 3, gridColumns: 3, gridItemHeight: 'square' }); ``` For display properties like `gridColumns` and `previewLength`, see the [Thumbnails](https://img.ly/docs/cesdk/angular/import-media/asset-panel/thumbnails-c23949/) guide. ## Layer 3: Dock Add entries to the dock with `cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, order)` using the `ly.img.assetLibrary.dock` component type. ```typescript highlight=highlight-dock-entry // Layer 3: Dock - adds button to access the entry cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ { id: 'ly.img.assetLibrary.dock', key: 'my-images-entry', label: 'libraries.my-images-entry.label', entries: ['my-images-entry'] }, ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }) ]); cesdk.i18n.setTranslations({ en: { 'libraries.my-images-entry.label': 'My Images' } }); ``` ## Managing Entries Query and inspect registered entries: ```typescript highlight=highlight-list-entries // Query registered entries const allEntries = cesdk.ui.findAllAssetLibraryEntries(); console.log('Registered entries:', allEntries); const myEntry = cesdk.ui.getAssetLibraryEntry('my-images-entry'); console.log('My entry:', myEntry); ``` ## API Reference | Method | Description | |--------|-------------| | `findAllAssetLibraryEntries()` | List all registered entry IDs | | `getAssetLibraryEntry(id)` | Get entry configuration | | `addAssetLibraryEntry(entry)` | Register a new entry | | `removeAssetLibraryEntry(id)` | Remove an entry | ## Next Steps - [Customize](https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/) — Icons, i18n, replace libraries - [Thumbnails](https://img.ly/docs/cesdk/angular/import-media/asset-panel/thumbnails-c23949/) — Thumbnail URIs, display settings - [Refresh Assets](https://img.ly/docs/cesdk/angular/import-media/asset-panel/refresh-assets-382060/) — Trigger asset reloads - [Asset Sources](https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/) — Creating asset sources --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Customize" description: "Adapt the asset library UI and behavior to suit your application’s structure and user needs." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/) > [Customize](https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/) --- Adapt the asset library to match your application's structure and user needs. ![Customize Asset Library](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-customize-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-customize-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-import-media-asset-library-customize-browser/) The asset library displays assets from registered asset sources. While sources define the data, asset library entries control how that data is presented in the UI. CE.SDK provides default entries for common asset types (images, videos, stickers, etc.), but you can create custom entries or modify existing ones to match your application's needs. ```typescript file=@cesdk_web_examples/guides-import-media-asset-library-customize-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); const engine = cesdk.engine; // ===== Section 1: Localizing Entry Labels ===== // Provide translations for custom entries before creating them // Labels appear at different navigation levels: // - Entry label: shown in dock and as panel header (sources overview) // - Source label: shown as section header and when navigating into a source // - Group label: shown when a source contains grouped assets cesdk.i18n.setTranslations({ en: { // Entry-level labels (sources overview) 'libraries.my-custom-assets.label': 'My Assets (Entry Level)', 'libraries.my-replace-assets.label': 'Replace Options', // Source-level labels within entry (overrides default source labels) 'libraries.my-custom-assets.ly.img.image.label': 'Images (Source Level)', 'libraries.my-custom-assets.ly.img.sticker.label': 'Stickers (Source Level)', // Group-level labels within sticker source (all 8 sticker categories) // Format: libraries....label 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.doodle/category/doodle.label': 'Doodle (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.emoji/category/emoji.label': 'Emoji (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.emoticons/category/emoticons.label': 'Emoticons (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.hand/category/hand.label': 'Hands (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.sketches/category/sketches.label': 'Sketches (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.3Dstickers/category/3Dstickers.label': '3D Grain (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.craft/category/craft.label': 'Craft (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.marker/category/marker.label': 'Markers (Group Level)' } }); // ===== Section 2: Creating Custom Entries with Theme-Aware Icons ===== // Create a custom asset library entry with theme-aware icons // Use existing demo sources (ly.img.image, ly.img.sticker) to populate the entry cesdk.ui.addAssetLibraryEntry({ id: 'my-custom-assets', sourceIds: ['ly.img.image', 'ly.img.sticker'], // Preview settings control the overview showing all sources previewLength: 4, previewBackgroundType: 'contain', // Grid settings control the detailed view when navigating into a source // Using 2 columns creates a distinct layout from the preview row gridColumns: 2, gridItemHeight: 'square', gridBackgroundType: 'cover', // Theme-aware icon function receives theme and iconSize parameters icon: ({ theme, iconSize }) => { if (theme === 'dark') { return iconSize === 'large' ? 'https://img.ly/static/cesdk/guides/icon-large-dark.svg' : 'https://img.ly/static/cesdk/guides/icon-normal-dark.svg'; } return iconSize === 'large' ? 'https://img.ly/static/cesdk/guides/icon-large-light.svg' : 'https://img.ly/static/cesdk/guides/icon-normal-light.svg'; } }); // ===== Section 3: Creating Entry for Replace Operations ===== // Create a separate entry for replace operations cesdk.ui.addAssetLibraryEntry({ id: 'my-replace-assets', sourceIds: ['ly.img.image'], previewLength: 3, gridColumns: 2, gridItemHeight: 'square', previewBackgroundType: 'contain', gridBackgroundType: 'contain' }); // ===== Section 4: Modifying Default Entries ===== // Update the default images entry with different grid columns cesdk.ui.updateAssetLibraryEntry('ly.img.image', { gridColumns: 4 }); // ===== Section 5: Extending Source IDs ===== // Use a callback pattern with currentIds to extend sourceIds cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ({ currentIds }) => [...currentIds, 'ly.img.upload'] }); // ===== Section 6: Configuring Replace Entries ===== // Configure which entries appear for replace operations based on block type cesdk.ui.setReplaceAssetLibraryEntries( ({ selectedBlocks, defaultEntryIds }) => { // Only show replace options when exactly one block is selected if (selectedBlocks.length !== 1) { return []; } const { fillType } = selectedBlocks[0]; // Show custom replace entry for image fills if (fillType === '//ly.img.ubq/fill/image') { return [...defaultEntryIds, 'my-replace-assets']; } // Return empty array to hide replace button for other fill types return []; } ); // ===== Section 7: Adding Entries to the Dock ===== // Add custom entry to the top of the dock with a separator cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ { id: 'ly.img.assetLibrary.dock', key: 'my-custom-assets', // Dock icons use a static URL string icon: 'https://img.ly/static/cesdk/guides/icon-normal-light.svg', label: 'libraries.my-custom-assets.label', entries: ['my-custom-assets'] }, { id: 'ly.img.separator' }, ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }) ]); // Create a design scene to display the editor await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); // Add explanatory text to the canvas const page = engine.scene.getCurrentPage(); if (page) { // Get page dimensions to constrain text within boundaries const pageWidth = engine.block.getWidth(page); const margin = 20; const textWidth = pageWidth - margin * 2; // Title text const titleBlock = engine.block.create('text'); engine.block.replaceText(titleBlock, 'Customize Asset Library'); engine.block.setFloat(titleBlock, 'text/fontSize', 28); engine.block.setWidth(titleBlock, textWidth); engine.block.setHeightMode(titleBlock, 'Auto'); engine.block.setPositionX(titleBlock, margin); engine.block.setPositionY(titleBlock, margin); engine.block.appendChild(page, titleBlock); // Instructions text const instructionsBlock = engine.block.create('text'); engine.block.replaceText( instructionsBlock, '← Click "My Assets (Entry Level)" in the dock.\n\n' + 'Labels show navigation hierarchy:\n' + 'Entry → Source → Group Level' ); engine.block.setFloat(instructionsBlock, 'text/fontSize', 13); engine.block.setWidth(instructionsBlock, textWidth); engine.block.setHeightMode(instructionsBlock, 'Auto'); engine.block.setPositionX(instructionsBlock, margin); engine.block.setPositionY(instructionsBlock, 55); engine.block.appendChild(page, instructionsBlock); } // Open the asset library panel to show the custom entry cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['my-custom-assets'] } }); } } export default Example; ``` This guide covers creating custom entries with themed icons, modifying default entries, configuring context-aware replacement behavior, and adding custom entries to the dock. ## Creating Custom Entries We create custom asset library entries using `cesdk.ui.addAssetLibraryEntry()`. Each entry needs a unique `id`, an array of `sourceIds` referencing registered asset sources, and display configuration options. In this example, we reference the built-in demo sources (`ly.img.image` and `ly.img.sticker`) to populate our custom entry. You can reference any registered source, including custom sources you create. ### Theme-Aware Icons We can provide different icons for light and dark themes using a function for the `icon` property. The function receives `theme` ('light' | 'dark') and `iconSize` ('normal' | 'large') parameters: ```typescript highlight=highlight-custom-entry-themed-icon // Create a custom asset library entry with theme-aware icons // Use existing demo sources (ly.img.image, ly.img.sticker) to populate the entry cesdk.ui.addAssetLibraryEntry({ id: 'my-custom-assets', sourceIds: ['ly.img.image', 'ly.img.sticker'], // Preview settings control the overview showing all sources previewLength: 4, previewBackgroundType: 'contain', // Grid settings control the detailed view when navigating into a source // Using 2 columns creates a distinct layout from the preview row gridColumns: 2, gridItemHeight: 'square', gridBackgroundType: 'cover', // Theme-aware icon function receives theme and iconSize parameters icon: ({ theme, iconSize }) => { if (theme === 'dark') { return iconSize === 'large' ? 'https://img.ly/static/cesdk/guides/icon-large-dark.svg' : 'https://img.ly/static/cesdk/guides/icon-normal-dark.svg'; } return iconSize === 'large' ? 'https://img.ly/static/cesdk/guides/icon-large-light.svg' : 'https://img.ly/static/cesdk/guides/icon-normal-light.svg'; } }); ``` The icon function is called each time the theme changes, ensuring the correct icon is displayed automatically. > **Note:** #### Preview vs Grid ViewWhen an entry has multiple sources, users first see a **preview** showing assets in a horizontal row. The `previewLength` and `previewBackgroundType` settings control this overview. When users click a source header to navigate into it, they see the full **grid view** with all assets arranged according to `gridColumns`, `gridItemHeight`, and `gridBackgroundType`. For example, a preview row transitioning to a 2-column grid creates a distinct visual change. ### Entry for Replace Operations We can create separate entries specifically for replace operations. These entries appear when users click "Replace" on a selected block: ```typescript highlight=highlight-replace-entry // Create a separate entry for replace operations cesdk.ui.addAssetLibraryEntry({ id: 'my-replace-assets', sourceIds: ['ly.img.image'], previewLength: 3, gridColumns: 2, gridItemHeight: 'square', previewBackgroundType: 'contain', gridBackgroundType: 'contain' }); ``` ## Modifying Default Entries We update existing entries using `cesdk.ui.updateAssetLibraryEntry()`. The second parameter can be a partial entry object: ```typescript highlight=highlight-modify-default-entry // Update the default images entry with different grid columns cesdk.ui.updateAssetLibraryEntry('ly.img.image', { gridColumns: 4 }); ``` ### Extending Source IDs To extend `sourceIds` while preserving existing sources, we use a callback pattern with `currentIds`: ```typescript highlight=highlight-extend-source-ids // Use a callback pattern with currentIds to extend sourceIds cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ({ currentIds }) => [...currentIds, 'ly.img.upload'] }); ``` The callback receives `currentIds` containing the entry's existing source IDs, allowing us to append additional sources (like `ly.img.upload` for user uploads) without replacing the defaults. ## Configuring Replace Entries We control which asset library entries appear when users click "Replace" on a selected block using `cesdk.ui.setReplaceAssetLibraryEntries()`. The callback receives context with `selectedBlocks` (array of block info including `id`, `blockType`, and `fillType`) and `defaultEntryIds`. ```typescript highlight=highlight-configure-replace-entries // Configure which entries appear for replace operations based on block type cesdk.ui.setReplaceAssetLibraryEntries( ({ selectedBlocks, defaultEntryIds }) => { // Only show replace options when exactly one block is selected if (selectedBlocks.length !== 1) { return []; } const { fillType } = selectedBlocks[0]; // Show custom replace entry for image fills if (fillType === '//ly.img.ubq/fill/image') { return [...defaultEntryIds, 'my-replace-assets']; } // Return empty array to hide replace button for other fill types return []; } ); ``` Return an empty array to hide the replace button for specific block types. This gives you complete control over which assets can replace which blocks. ## Adding Entries to the Dock We add custom entries to the dock using `cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, order)`. Use `cesdk.ui.getComponentOrder({ in: 'ly.img.dock' })` to get the current order and append your entry: ```typescript highlight=highlight-add-to-dock // Add custom entry to the top of the dock with a separator cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [ { id: 'ly.img.assetLibrary.dock', key: 'my-custom-assets', // Dock icons use a static URL string icon: 'https://img.ly/static/cesdk/guides/icon-normal-light.svg', label: 'libraries.my-custom-assets.label', entries: ['my-custom-assets'] }, { id: 'ly.img.separator' }, ...cesdk.ui.getComponentOrder({ in: 'ly.img.dock' }) ]); ``` Each dock item uses `id: 'ly.img.assetLibrary.dock'` with a unique `key`, `entries` array, and optional `icon` and `label` properties. The `label` property references a translation key. ## Localizing Entry Labels We provide translations for custom entries using `cesdk.i18n.setTranslations()`. Labels appear at three navigation levels: - `libraries..label` — Entry label shown in the dock and panel header (entry level) - `libraries...label` — Source label within an entry (source level) - `libraries....label` — Group label within a source (group level) ```typescript highlight=highlight-i18n-translations // Provide translations for custom entries before creating them // Labels appear at different navigation levels: // - Entry label: shown in dock and as panel header (sources overview) // - Source label: shown as section header and when navigating into a source // - Group label: shown when a source contains grouped assets cesdk.i18n.setTranslations({ en: { // Entry-level labels (sources overview) 'libraries.my-custom-assets.label': 'My Assets (Entry Level)', 'libraries.my-replace-assets.label': 'Replace Options', // Source-level labels within entry (overrides default source labels) 'libraries.my-custom-assets.ly.img.image.label': 'Images (Source Level)', 'libraries.my-custom-assets.ly.img.sticker.label': 'Stickers (Source Level)', // Group-level labels within sticker source (all 8 sticker categories) // Format: libraries....label 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.doodle/category/doodle.label': 'Doodle (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.emoji/category/emoji.label': 'Emoji (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.emoticons/category/emoticons.label': 'Emoticons (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.hand/category/hand.label': 'Hands (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.sketches/category/sketches.label': 'Sketches (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.3Dstickers/category/3Dstickers.label': '3D Grain (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.craft/category/craft.label': 'Craft (Group Level)', 'libraries.my-custom-assets.ly.img.sticker.//ly.img.cesdk.stickers.marker/category/marker.label': 'Markers (Group Level)' } }); ``` Set translations before adding entries to ensure labels are available when the UI renders. The entry label appears when viewing sources, source labels appear as section headers, and group labels appear when navigating into sources that contain grouped assets (like sticker categories). ## Troubleshooting **Entry not appearing in dock**: Ensure you've added the entry to the dock order using `setComponentOrder()`. Creating an entry with `addAssetLibraryEntry()` only registers it—it won't appear until added to the dock. **Replace button not showing**: Verify your `setReplaceAssetLibraryEntries()` callback returns entry IDs (not an empty array) for the selected block type. Check the `blockType` and `fillType` values in the context. **Icon not changing with theme**: Ensure your `icon` function returns different URLs for different `theme` values. The function is called each time the theme changes. **Missing labels**: Check that translation keys match the pattern `libraries..label`. Use `cesdk.i18n.setTranslations()` before adding entries to ensure labels are available. ## API Reference | Method | Category | Purpose | |--------|----------|---------| | `cesdk.ui.addAssetLibraryEntry()` | UI | Register a new asset library entry | | `cesdk.ui.updateAssetLibraryEntry()` | UI | Modify an existing entry's properties | | `cesdk.ui.removeAssetLibraryEntry()` | UI | Remove an asset library entry | | `cesdk.ui.getAssetLibraryEntry()` | UI | Retrieve an entry's configuration | | `cesdk.ui.findAllAssetLibraryEntries()` | UI | List all registered entry IDs | | `cesdk.ui.setReplaceAssetLibraryEntries()` | UI | Configure context-aware replace entries | | `cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, order)` | UI | Set the dock component order | | `cesdk.ui.getComponentOrder({ in: 'ly.img.dock' })` | UI | Get the current dock component order | | `cesdk.i18n.setTranslations()` | i18n | Add translation strings | ## Next Steps - [Asset Library Basics](https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/) — Full reference for entry properties - [Thumbnails](https://img.ly/docs/cesdk/angular/import-media/asset-panel/thumbnails-c23949/) — Configure thumbnail display and grid layout - [Refresh Assets](https://img.ly/docs/cesdk/angular/import-media/asset-panel/refresh-assets-382060/) — Update the library when external changes occur - [Your Server](https://img.ly/docs/cesdk/angular/import-media/from-remote-source/your-server-b91910/) — Create custom asset sources from your own backend --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Refresh Assets" description: "Trigger asset reloads to ensure the library reflects newly uploaded or updated items." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/asset-panel/refresh-assets-382060/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/) > [Refresh Assets](https://img.ly/docs/cesdk/angular/import-media/asset-panel/refresh-assets-382060/) --- Learn how to refresh asset sources when external changes occur outside CE.SDK. ![Refresh Assets](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-refresh-assets-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-refresh-assets-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-import-media-asset-library-refresh-assets-browser/) CE.SDK automatically refreshes the asset library for built-in operations like uploads and deletions. However, when assets are modified outside of CE.SDK—through a custom CMS, cloud storage, or third-party upload widget—the asset panel won't reflect these changes automatically. Use `engine.asset.assetSourceContentsChanged()` to notify the engine and trigger a refresh. ```typescript file=@cesdk_web_examples/guides-import-media-asset-library-refresh-assets-browser/browser.ts reference-only import type { AssetsQueryResult, EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import packageJson from './package.json'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; // Simulated external data store (represents Cloudinary, S3, or external CMS) const externalAssets = [ { id: 'cloud-1', url: 'https://img.ly/static/ubq_samples/sample_1.jpg', name: 'Mountain Landscape' }, { id: 'cloud-2', url: 'https://img.ly/static/ubq_samples/sample_2.jpg', name: 'Ocean Sunset' } ]; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const engine = cesdk.engine; // ===== Section 1: Register a Custom Asset Source ===== // Register a custom asset source that fetches from an external system // This source will need manual refresh when external changes occur engine.asset.addSource({ id: 'cloudinary-images', async findAssets(queryData): Promise { // Fetch current assets from external data store const filteredAssets = externalAssets.filter( (asset) => !queryData.query || asset.name.toLowerCase().includes(queryData.query.toLowerCase()) ); return { assets: filteredAssets.map((asset) => ({ id: asset.id, label: asset.name, meta: { uri: asset.url, thumbUri: asset.url, blockType: '//ly.img.ubq/graphic' } })), total: filteredAssets.length, currentPage: queryData.page, nextPage: undefined }; } }); // Add the custom source to the asset library cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ['cloudinary-images'], gridColumns: 2, gridItemHeight: 'square', previewLength: 4 }); // ===== Section 2: Simulate External Upload ===== // Simulate an external upload widget (e.g., Cloudinary upload widget) // In a real application, this would be triggered by the widget's success callback const simulateExternalUpload = () => { // Add a new asset to the external store const newAsset = { id: `cloud-${Date.now()}`, url: 'https://img.ly/static/ubq_samples/sample_3.jpg', name: `Uploaded Image ${externalAssets.length + 1}` }; externalAssets.push(newAsset); // Notify CE.SDK that the source contents have changed engine.asset.assetSourceContentsChanged('cloudinary-images'); console.log('External upload complete, asset library refreshed'); }; // ===== Section 3: Simulate External Modification ===== // Simulate backend modifications (e.g., CMS updates, API changes) const simulateExternalModification = () => { // Modify assets in the external store if (externalAssets.length > 0) { externalAssets[0] = { ...externalAssets[0], name: `Modified: ${externalAssets[0].name}` }; } // Refresh the asset library to reflect changes engine.asset.assetSourceContentsChanged('cloudinary-images'); console.log('External modification complete, asset library refreshed'); }; // ===== Section 4: Simulate External Deletion ===== // Simulate asset deletion from external system const simulateExternalDeletion = () => { // Remove the last asset from the external store if (externalAssets.length > 2) { const removed = externalAssets.pop(); console.log(`Removed asset: ${removed?.name}`); // Refresh the asset library to reflect the deletion engine.asset.assetSourceContentsChanged('cloudinary-images'); console.log('External deletion complete, asset library refreshed'); } }; // Expose functions for demo purposes (window as any).simulateExternalUpload = simulateExternalUpload; (window as any).simulateExternalModification = simulateExternalModification; (window as any).simulateExternalDeletion = simulateExternalDeletion; // Automatically trigger an upload to demonstrate the refresh setTimeout(() => { simulateExternalUpload(); }, 2000); // Open the asset library to show the custom source cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['ly.img.image'] } }); } } export default Example; ``` This guide covers when manual refresh is needed, how to trigger refreshes programmatically, and integration patterns for external upload widgets. ## When to Use Asset Refresh CE.SDK handles asset refresh automatically for built-in operations. Manual refresh is required when external systems modify asset source content. **Automatic refresh** (no action needed): - Uploads using built-in sources like `ly.img.upload.*` - Deletions through default upload handlers - Modifications made through CE.SDK's asset APIs **Manual refresh required**: - External uploads via third-party widgets (Cloudinary, Dropzone) - Backend modifications through CMS or API updates - Sync with external storage (S3, Azure Blob) - Real-time collaboration when another user adds assets ## Registering a Custom Asset Source Before refreshing assets, you need a custom asset source that fetches from your external system. The `findAssets` method queries your external data store each time the panel needs to display assets. ```typescript highlight-custom-source // Register a custom asset source that fetches from an external system // This source will need manual refresh when external changes occur engine.asset.addSource({ id: 'cloudinary-images', async findAssets(queryData): Promise { // Fetch current assets from external data store const filteredAssets = externalAssets.filter( (asset) => !queryData.query || asset.name.toLowerCase().includes(queryData.query.toLowerCase()) ); return { assets: filteredAssets.map((asset) => ({ id: asset.id, label: asset.name, meta: { uri: asset.url, thumbUri: asset.url, blockType: '//ly.img.ubq/graphic' } })), total: filteredAssets.length, currentPage: queryData.page, nextPage: undefined }; } }); ``` This custom source fetches assets from an external data store (simulating Cloudinary, S3, or a CMS). When the external store changes, the asset panel won't update until you call `assetSourceContentsChanged()`. ## Refreshing After External Uploads When users upload files through a third-party widget, call `assetSourceContentsChanged()` in the success callback. This notifies CE.SDK that the source contents have changed and triggers a re-fetch. ```typescript highlight-external-upload // Simulate an external upload widget (e.g., Cloudinary upload widget) // In a real application, this would be triggered by the widget's success callback const simulateExternalUpload = () => { // Add a new asset to the external store const newAsset = { id: `cloud-${Date.now()}`, url: 'https://img.ly/static/ubq_samples/sample_3.jpg', name: `Uploaded Image ${externalAssets.length + 1}` }; externalAssets.push(newAsset); // Notify CE.SDK that the source contents have changed engine.asset.assetSourceContentsChanged('cloudinary-images'); console.log('External upload complete, asset library refreshed'); }; ``` The key is calling `assetSourceContentsChanged('cloudinary-images')` after the external upload completes. This tells CE.SDK to call `findAssets()` again, which fetches the updated asset list from your external store. ## Refreshing After External Modifications When your backend modifies asset metadata—renaming files, updating tags, or changing thumbnails—call `assetSourceContentsChanged()` to sync the asset panel. ```typescript highlight-external-modification // Simulate backend modifications (e.g., CMS updates, API changes) const simulateExternalModification = () => { // Modify assets in the external store if (externalAssets.length > 0) { externalAssets[0] = { ...externalAssets[0], name: `Modified: ${externalAssets[0].name}` }; } // Refresh the asset library to reflect changes engine.asset.assetSourceContentsChanged('cloudinary-images'); console.log('External modification complete, asset library refreshed'); }; ``` Any modification to assets in your external store requires a refresh. Without calling `assetSourceContentsChanged()`, the asset panel displays stale data until the user navigates away and returns. ## Refreshing After External Deletions When assets are deleted from your external system, call `assetSourceContentsChanged()` to remove them from the asset panel. ```typescript highlight-external-deletion // Simulate asset deletion from external system const simulateExternalDeletion = () => { // Remove the last asset from the external store if (externalAssets.length > 2) { const removed = externalAssets.pop(); console.log(`Removed asset: ${removed?.name}`); // Refresh the asset library to reflect the deletion engine.asset.assetSourceContentsChanged('cloudinary-images'); console.log('External deletion complete, asset library refreshed'); } }; ``` The refresh ensures deleted assets no longer appear in the panel. If you skip this step, users may try to use assets that no longer exist. ## Integration Patterns ### Third-Party Upload Widget Integrate with upload widgets like Cloudinary by calling `assetSourceContentsChanged()` in the success callback: ```typescript const widget = cloudinary.createUploadWidget( { cloudName: 'my-cloud' }, (error, result) => { if (result.event === 'success') { engine.asset.assetSourceContentsChanged('cloudinary-images'); } } ); ``` ### WebSocket Updates For real-time sync with backend changes: ```typescript socket.on('assets:changed', (sourceId) => { engine.asset.assetSourceContentsChanged(sourceId); }); ``` ### Polling for Changes If your backend doesn't support real-time notifications: ```typescript setInterval(() => { engine.asset.assetSourceContentsChanged('my-source'); }, 30000); // Refresh every 30 seconds ``` ## Troubleshooting **Assets not updating**: Verify the source ID passed to `assetSourceContentsChanged()` matches the ID used when registering the source with `addSource()`. Source IDs are case-sensitive. **Refresh not triggering**: Ensure you call `assetSourceContentsChanged()` after the external operation completes. If called before the upload finishes, `findAssets()` may return stale data. **Stale assets displayed**: Check that your `findAssets` implementation fetches fresh data on each call. Avoid caching responses unless you invalidate the cache when calling `assetSourceContentsChanged()`. **Asset panel not visible**: The refresh only affects visible panels. If the asset panel is closed, the refresh queues until the user reopens it. ## API Reference | Method | Category | Purpose | |--------|----------|---------| | `engine.asset.assetSourceContentsChanged(sourceID)` | Asset API | Notify engine that asset source contents changed | | `engine.asset.addSource(source)` | Asset API | Register custom asset source | | `engine.asset.findAssets(sourceID, query)` | Asset API | Query assets from a source | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Thumbnails" description: "Configure thumbnail images for assets in CE.SDK's asset library with proper sizing, preview URIs for audio, and customized UI display." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/asset-panel/thumbnails-c23949/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-library-65d6c4/) > [Thumbnails](https://img.ly/docs/cesdk/angular/import-media/asset-panel/thumbnails-c23949/) --- Learn how to configure thumbnail images for assets in CE.SDK's asset library. ![Asset Library Thumbnails](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-thumbnails-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-asset-library-thumbnails-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-import-media-asset-library-thumbnails-browser/) Thumbnails provide visual previews of assets in the asset library, improving the user experience when browsing images, videos, audio files, and other media. We recommend using **512px width for thumbUri** to ensure quality across platforms. ```typescript file=@cesdk_web_examples/guides-import-media-asset-library-thumbnails-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, CaptionPresetsAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { VideoEditorConfig } from './video-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new VideoEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new CaptionPresetsAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin( new UploadAssetSources({ include: ['ly.img.image.upload', 'ly.img.video.upload', 'ly.img.audio.upload'] }) ); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.video.*', 'ly.img.image.*', 'ly.img.audio.*', 'ly.img.video.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin( new PagePresetsAssetSource({ include: [ 'ly.img.page.presets.instagram.*', 'ly.img.page.presets.facebook.*', 'ly.img.page.presets.x.*', 'ly.img.page.presets.linkedin.*', 'ly.img.page.presets.pinterest.*', 'ly.img.page.presets.tiktok.*', 'ly.img.page.presets.youtube.*', 'ly.img.page.presets.video.*' ] }) ); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { mode: 'Video', page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.instagram.story' } }); const engine = cesdk.engine; // ===== Section 1: Basic Thumbnails ===== // Add a local asset source with basic thumbnails engine.asset.addLocalSource('custom-images'); // Add an image with 512px width thumbnail (recommended size) engine.asset.addAssetToSource('custom-images', { id: 'sample-1', label: { en: 'Landscape Photo' }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', // 512px recommended blockType: '//ly.img.ubq/graphic' } }); // Additional images for the asset library (not shown in highlight) engine.asset.addAssetToSource('custom-images', { id: 'sample-2', label: { en: 'Portrait Photo' }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_2.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_2.jpg', blockType: '//ly.img.ubq/graphic' } }); engine.asset.addAssetToSource('custom-images', { id: 'sample-3', label: { en: 'Nature Scene' }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_3.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_3.jpg', blockType: '//ly.img.ubq/graphic' } }); // ===== Section 2: Preview URIs for Audio ===== // Add audio assets with preview URIs for playback in the asset library engine.asset.addLocalSource('custom-audio'); // Audio with full URIs and preview clips engine.asset.addAssetToSource('custom-audio', { id: 'dance-harder', label: { en: 'Dance Harder' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a', // Full audio file thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/thumbnails/dance_harder.jpg', // Waveform visualization (image, UI-only) previewUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a', // Preview clip - set as block property on canvas mimeType: 'audio/x-m4a', // Required for audio preview to work blockType: '//ly.img.ubq/audio', duration: '212.531995' } }); engine.asset.addAssetToSource('custom-audio', { id: 'far-from-home', label: { en: 'Far From Home' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/thumbnails/audio-wave.png', previewUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a', mimeType: 'audio/x-m4a', blockType: '//ly.img.ubq/audio', duration: '98.716009' } }); engine.asset.addAssetToSource('custom-audio', { id: 'elsewhere', label: { en: 'Elsewhere' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/elsewhere.m4a', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/thumbnails/elsewhere.jpg', previewUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/elsewhere.m4a', mimeType: 'audio/x-m4a', blockType: '//ly.img.ubq/audio', duration: '121.2' } }); // ===== Section 3: Custom Asset Source with Thumbnail Mapping ===== // Create a custom asset source that maps external API responses to CE.SDK format // This example mimics how Unsplash thumbnails would be mapped engine.asset.addSource({ id: 'custom-api-source', async findAssets(queryData) { // Simulate external API response (e.g., from Unsplash) const mockApiResponse = { results: [ { id: 'photo-1', urls: { full: 'https://img.ly/static/ubq_samples/sample_4.jpg', // High-res small: 'https://img.ly/static/ubq_samples/sample_4.jpg' // 512px thumbnail }, alt_description: 'Mountain landscape' }, { id: 'photo-2', urls: { full: 'https://img.ly/static/ubq_samples/sample_5.jpg', small: 'https://img.ly/static/ubq_samples/sample_5.jpg' }, alt_description: 'Ocean waves' }, { id: 'photo-3', urls: { full: 'https://img.ly/static/ubq_samples/sample_6.jpg', small: 'https://img.ly/static/ubq_samples/sample_6.jpg' }, alt_description: 'Forest path' } ], total: 3 }; // Map external API format to CE.SDK AssetResult format return { assets: mockApiResponse.results.map((photo) => ({ id: photo.id, label: photo.alt_description, meta: { uri: photo.urls.full, // High-res image for canvas thumbUri: photo.urls.small, // Thumbnail for asset library (512px recommended) blockType: '//ly.img.ubq/graphic' } })), total: mockApiResponse.total, currentPage: queryData.page, nextPage: mockApiResponse.total > (queryData.page + 1) * queryData.perPage ? queryData.page + 1 : undefined }; } }); // ===== Section 4: Display Customization - Background Types ===== // Configure how thumbnails scale in the asset library cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ['custom-images', 'custom-api-source'], gridBackgroundType: 'cover', // Crop to fill card previewBackgroundType: 'contain' // Fit entire image in preview }); // Audio thumbnails with contain to show full waveform // Note: Audio assets automatically show a play button overlay for previewing cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { sourceIds: ['custom-audio'], gridBackgroundType: 'contain', // Show full waveform previewBackgroundType: 'contain', cardBorder: true // Add border to make cards more visible }); // ===== Section 5: Display Customization - Grid Layout ===== // Configure grid columns and item height cesdk.ui.updateAssetLibraryEntry('ly.img.image', { gridColumns: 3, // 3 columns in grid view gridItemHeight: 'square' // Square aspect ratio for all cards }); cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { gridColumns: 2, // 2 columns for audio gridItemHeight: 'auto' // Auto height based on content }); // ===== Section 6: Display Customization - Card Background Preferences ===== // Configure fallback order for card backgrounds // Try vector path first, then thumbnail image cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { cardBackgroundPreferences: [ { path: 'meta.vectorPath', type: 'svgVectorPath' }, // Try SVG first { path: 'meta.thumbUri', type: 'image' } // Fallback to thumbnail ] }); // For images, prioritize thumbnail cesdk.ui.updateAssetLibraryEntry('ly.img.image', { cardBackgroundPreferences: [ { path: 'meta.thumbUri', type: 'image' } // Use thumbnail as primary background ] }); // Open the asset library to the audio and image panels to demonstrate thumbnails // Audio assets are previewable - hover over them to see a play button // Click the play button to hear the previewUri audio clip cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['ly.img.audio', 'ly.img.image'] } }); } } export default Example; ``` This guide covers configuring basic thumbnails, preview URIs for audio playback, custom thumbnail mapping for external APIs, and UI customization options. ## Understanding thumbUri vs previewUri Three URI properties control how assets are displayed and used in CE.SDK: | Property | Purpose | Used For | Media Type | Set On Block | |----------|---------|----------|------------|--------------| | `thumbUri` | Visual thumbnail (UI-only) | Asset library grid display | **Image only** | No | | `previewUri` | Preview content | Audio playback in library & set as block property on canvas | **Any media type** | Yes | | `uri` | Full asset | Final content on canvas | Any | Yes | **Key distinctions**: - **`thumbUri`** is UI-only and must be an image. It displays in the asset library but is never set on the block itself. - **`previewUri`** is set as a property on the block when the asset is applied to the canvas. It can be any media type (audio, video, etc.) and serves both for library preview playback and as the block's preview content. **For images and video**: Only `thumbUri` and `uri` are needed. The `thumbUri` provides the visual preview in the asset library. **For audio**: Use all three properties. The `thumbUri` shows a waveform visualization in the asset library (image only, UI-only). The `previewUri` is **set as block property** when asset is applied; it plays a short preview clip when users click play in the asset library and serves as the block's preview content on canvas. The `uri` loads the full audio file for final export. The `previewUri` is a performance optimization for large audio files. Without it, the engine loads the full `uri` for preview playback, which can be slow for multi-minute files. ## Thumbnail Configuration ### Basic Thumbnails Add thumbnails using the `thumbUri` property in asset metadata. We can register assets using `engine.asset.addSource()` for custom sources or `engine.asset.addAssetToSource()` for local sources. ```typescript highlight-basic-thumbnails // Add a local asset source with basic thumbnails engine.asset.addLocalSource('custom-images'); // Add an image with 512px width thumbnail (recommended size) engine.asset.addAssetToSource('custom-images', { id: 'sample-1', label: { en: 'Landscape Photo' }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', // 512px recommended blockType: '//ly.img.ubq/graphic' } }); ``` The `thumbUri` should point to a 512px width image for optimal quality across platforms. The engine displays this thumbnail in the asset library grid and preview panels. ### Preview URIs for Audio For audio assets, use `previewUri` to provide lightweight preview clips. The engine uses `previewUri` in two scenarios: 1. **Asset library playback** — The play button in audio asset cards loads `previewUri` instead of the full file 2. **Canvas insertion** — When adding an audio asset to the canvas, `previewUri` is **set as a block property** and serves as the block's content for preview playback Without `previewUri`, the engine falls back to `uri`, which can cause slow loading for large audio files. Use shorter preview clips (30 seconds recommended) to improve performance. ```typescript highlight-audio-preview-uri // Add audio assets with preview URIs for playback in the asset library engine.asset.addLocalSource('custom-audio'); // Audio with full URIs and preview clips engine.asset.addAssetToSource('custom-audio', { id: 'dance-harder', label: { en: 'Dance Harder' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a', // Full audio file thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/thumbnails/dance_harder.jpg', // Waveform visualization (image, UI-only) previewUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/dance_harder.m4a', // Preview clip - set as block property on canvas mimeType: 'audio/x-m4a', // Required for audio preview to work blockType: '//ly.img.ubq/audio', duration: '212.531995' } }); engine.asset.addAssetToSource('custom-audio', { id: 'far-from-home', label: { en: 'Far From Home' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/thumbnails/audio-wave.png', previewUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/far_from_home.m4a', mimeType: 'audio/x-m4a', blockType: '//ly.img.ubq/audio', duration: '98.716009' } }); engine.asset.addAssetToSource('custom-audio', { id: 'elsewhere', label: { en: 'Elsewhere' }, meta: { uri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/elsewhere.m4a', thumbUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/thumbnails/elsewhere.jpg', previewUri: 'https://cdn.img.ly/assets/demo/v3/ly.img.audio/audios/elsewhere.m4a', mimeType: 'audio/x-m4a', blockType: '//ly.img.ubq/audio', duration: '121.2' } }); ``` The `thumbUri` displays a waveform visualization in the asset library, while `previewUri` provides the audio content for playback preview. ### Custom Asset Source Thumbnails We can map external API responses to CE.SDK format with thumbnails in the `findAssets` method. This example shows how to transform API responses (like Unsplash) that use different thumbnail field names into CE.SDK's `meta.thumbUri` format. ```typescript highlight-custom-source-thumbnails // Create a custom asset source that maps external API responses to CE.SDK format // This example mimics how Unsplash thumbnails would be mapped engine.asset.addSource({ id: 'custom-api-source', async findAssets(queryData) { // Simulate external API response (e.g., from Unsplash) const mockApiResponse = { results: [ { id: 'photo-1', urls: { full: 'https://img.ly/static/ubq_samples/sample_4.jpg', // High-res small: 'https://img.ly/static/ubq_samples/sample_4.jpg' // 512px thumbnail }, alt_description: 'Mountain landscape' }, { id: 'photo-2', urls: { full: 'https://img.ly/static/ubq_samples/sample_5.jpg', small: 'https://img.ly/static/ubq_samples/sample_5.jpg' }, alt_description: 'Ocean waves' }, { id: 'photo-3', urls: { full: 'https://img.ly/static/ubq_samples/sample_6.jpg', small: 'https://img.ly/static/ubq_samples/sample_6.jpg' }, alt_description: 'Forest path' } ], total: 3 }; // Map external API format to CE.SDK AssetResult format return { assets: mockApiResponse.results.map((photo) => ({ id: photo.id, label: photo.alt_description, meta: { uri: photo.urls.full, // High-res image for canvas thumbUri: photo.urls.small, // Thumbnail for asset library (512px recommended) blockType: '//ly.img.ubq/graphic' } })), total: mockApiResponse.total, currentPage: queryData.page, nextPage: mockApiResponse.total > (queryData.page + 1) * queryData.perPage ? queryData.page + 1 : undefined }; } }); ``` The custom source maps `photo.urls.full` to `meta.uri` for the high-resolution canvas image and `photo.urls.small` to `meta.thumbUri` for the 512px asset library thumbnail. ## Display Customization ### Background Types We can configure how thumbnails scale in the asset library using `gridBackgroundType` and `previewBackgroundType`: - **cover** — Crops the thumbnail to fill the entire card - **contain** — Fits the entire thumbnail within the card, may leave empty space ```typescript highlight-background-types // Configure how thumbnails scale in the asset library cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ['custom-images', 'custom-api-source'], gridBackgroundType: 'cover', // Crop to fill card previewBackgroundType: 'contain' // Fit entire image in preview }); // Audio thumbnails with contain to show full waveform // Note: Audio assets automatically show a play button overlay for previewing cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { sourceIds: ['custom-audio'], gridBackgroundType: 'contain', // Show full waveform previewBackgroundType: 'contain', cardBorder: true // Add border to make cards more visible }); ``` Use `cover` for thumbnails that should fill cards completely (cropping if needed). Use `contain` to show the complete thumbnail without cropping, which is useful for waveforms or icons. ### Grid Layout Configure the number of columns and item aspect ratio in the asset library grid: ```typescript highlight-grid-layout // Configure grid columns and item height cesdk.ui.updateAssetLibraryEntry('ly.img.image', { gridColumns: 3, // 3 columns in grid view gridItemHeight: 'square' // Square aspect ratio for all cards }); cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { gridColumns: 2, // 2 columns for audio gridItemHeight: 'auto' // Auto height based on content }); ``` The `gridColumns` property controls how many thumbnails appear per row. The `gridItemHeight` can be `square` for uniform sizing or `auto` for dynamic height based on content. ### Card Background Preferences Configure the fallback order for card backgrounds when thumbnails are missing. The engine checks preferences in array order and uses the first available value. ```typescript highlight-card-background-preferences // Configure fallback order for card backgrounds // Try vector path first, then thumbnail image cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { cardBackgroundPreferences: [ { path: 'meta.vectorPath', type: 'svgVectorPath' }, // Try SVG first { path: 'meta.thumbUri', type: 'image' } // Fallback to thumbnail ] }); // For images, prioritize thumbnail cesdk.ui.updateAssetLibraryEntry('ly.img.image', { cardBackgroundPreferences: [ { path: 'meta.thumbUri', type: 'image' } // Use thumbnail as primary background ] }); ``` The `path` property uses dot notation to access asset properties (e.g., `meta.thumbUri` accesses `asset.meta.thumbUri`). The `type` determines rendering: - **svgVectorPath** — Renders SVG vector paths with theme-adaptive colors - **image** — Displays images using CSS background We can also provide a function that returns custom backgrounds per asset, allowing dynamic behavior based on asset properties. ## Best Practices - **Size**: Use 512px width for `thumbUri` to ensure quality across platforms - **Format**: Use JPEG for photos and PNG for graphics with transparency - **When to use previewUri**: - Audio: Provide shorter preview clip (30 seconds instead of 3 minutes). **Important**: Audio assets require `mimeType` (e.g., `'audio/x-m4a'`) for preview buttons to appear in the asset library - Video: Not supported (use `thumbUri` + `uri`, no `previewUri`) - Images: Not needed (`thumbUri` is sufficient) - **Media type constraints**: `thumbUri` must be an image, while `previewUri` can be any media type (currently used for audio, future support for video previews planned) - **Block property**: Unlike `thumbUri` (UI-only), `previewUri` is set as a property on the block itself when the asset is applied to canvas - **Performance**: Optimize thumbnail file sizes and use CDN with proper cache headers - **Caching**: Implement long cache TTLs for static thumbnails - **Memory**: Enable the `clampThumbnailTextureSizes` setting for large asset libraries - **Fallbacks**: Configure `cardBackgroundPreferences` for missing thumbnail handling ## Troubleshooting **Thumbnails not displaying**: Check CORS configuration, URL validity, and browser network tab for 404 or CORS errors. Ensure thumbnail URLs are accessible from the browser. **Slow loading**: Optimize file sizes (512px max width recommended), use a CDN, and enable compression. For audio, ensure `previewUri` points to short preview clips instead of full files. **Distorted appearance**: Choose the correct `backgroundType` setting. Use `cover` when thumbnails should fill cards (allowing crop) or `contain` when the entire thumbnail must be visible. **Missing fallback**: Configure `cardBackgroundPreferences` to define fallback order. For example, try `meta.vectorPath` first, then fall back to `meta.thumbUri` for graceful degradation. **Audio preview not working**: Ensure audio assets include the `mimeType` property (e.g., `'audio/x-m4a'` or `'audio/mpeg'`). The asset library requires this property to display the play button overlay on audio thumbnails. Also verify `previewUri` or `uri` points to a valid audio file. ## API Reference | Method/Property | Category | Purpose | |-----------------|----------|---------| | `meta.thumbUri` | Asset Metadata | Thumbnail for grid display (512px recommended) | | `meta.previewUri` | Asset Metadata | Preview for audio playback and canvas insertion | | `meta.mimeType` | Asset Metadata | MIME type for audio assets (required for preview buttons) | | `engine.asset.addSource()` | Asset API | Register custom source with thumbnails | | `engine.asset.addAssetToSource()` | Asset API | Add asset with thumbnail to source | | `cesdk.ui.updateAssetLibraryEntry()` | UI API | Configure thumbnail display | | `gridBackgroundType` | Entry Config | Thumbnail scaling in grid (cover/contain) | | `previewBackgroundType` | Entry Config | Thumbnail scaling in preview | | `cardBackgroundPreferences` | Entry Config | Background rendering priority and fallbacks | | `gridColumns` | Entry Config | Grid columns affecting thumbnail size | | `gridItemHeight` | Entry Config | Grid item aspect ratio (auto/square) | ## Next Steps - [Customize Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/) — UI customization options - [Asset Concepts](https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/) — Asset sources and metadata - [Unsplash Integration](https://img.ly/docs/cesdk/angular/import-media/from-remote-source/unsplash-8f31f0/) — Thumbnail mapping example - [Source Sets](https://img.ly/docs/cesdk/angular/import-media/source-sets-5679c8/) — Responsive asset rendering (not thumbnails) --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Capture From Camera" description: "Capture photos or videos directly from a connected camera for immediate use in your design." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/capture-from-camera-92f388/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/angular/import-media/capture-from-camera-92f388/) --- --- ## Related Pages - [Record Video](https://img.ly/docs/cesdk/angular/import-media/capture-from-camera/record-video-47819b/) - Display live camera feeds inside CE.SDK using PixelStreamFill for real-time preview with effects. --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Record Video" description: "Display live camera feeds inside CE.SDK using PixelStreamFill for real-time preview with effects." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/capture-from-camera/record-video-47819b/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/angular/import-media/capture-from-camera-92f388/) > [Record Video](https://img.ly/docs/cesdk/angular/import-media/capture-from-camera/record-video-47819b/) --- Display live camera feeds in CE.SDK scenes using `PixelStreamFill` and apply real-time effects to the video. This guide covers creating a video scene with a pixel stream fill, controlling feed orientation, accessing the camera, and updating the fill with video frames. ## Create a Video Scene with Camera Fill To display a camera feed, create a video scene and set up a page with `PixelStreamFill`. The pixel stream fill accepts live pixel data that you provide frame by frame: ```js engine.scene.createVideo(); const stack = engine.block.findByType('stack')[0]; const page = engine.block.create('page'); engine.block.appendChild(stack, page); const pixelStreamFill = engine.block.createFill('pixelStream'); engine.block.setFill(page, pixelStreamFill); engine.block.appendEffect(page, engine.block.createEffect('half_tone')); ``` We create a video scene, add a page to the stack, and assign a `PixelStreamFill` to receive camera data. The `PixelStreamFill` acts as a container that displays whatever pixel data you send to it. You can apply any CE.SDK effect to process the camera feed in real-time. ## Control Feed Orientation The `fill/pixelStream/orientation` property controls how the camera feed is displayed. Use `UpMirrored` to horizontally flip the image, which is common for front-facing cameras to create a natural mirror-like selfie view: ```js // Horizontal mirroring engine.block.setEnum( pixelStreamFill, 'fill/pixelStream/orientation', 'UpMirrored' ); ``` Available orientation values: | Value | Effect | |-------|--------| | `Up` | No rotation (default) | | `Down` | 180° rotation | | `Left` | 90° counter-clockwise | | `Right` | 90° clockwise | | `UpMirrored` | Horizontal flip | | `DownMirrored` | 180° + horizontal flip | | `LeftMirrored` | 90° CCW + horizontal flip | | `RightMirrored` | 90° CW + horizontal flip | These orientations let you rotate or flip the feed without expensive CPU transformations. ## Access Camera with Browser APIs Request camera access using the browser's `navigator.mediaDevices.getUserMedia()` API. Create an HTML video element to receive the MediaStream, then update the page dimensions to match the video: ```js navigator.mediaDevices.getUserMedia({ video: true }).then( (stream) => { const video = document.createElement('video'); video.autoplay = true; video.srcObject = stream; video.addEventListener('loadedmetadata', () => { engine.block.setWidth(page, video.videoWidth); engine.block.setHeight(page, video.videoHeight); engine.scene.zoomToBlock(page, 40, 40, 40, 40); // Continue with frame updates... }); }, (err) => { console.error(err); } ); ``` The `facingMode` option in `getUserMedia()` lets you request the front-facing camera (`'user'`) or rear camera (`'environment'`) on mobile devices. ## Update Fill with Video Frames Use `requestVideoFrameCallback()` to efficiently sync frame updates with the video's frame rate. Call `engine.block.setNativePixelBuffer()` in each callback to send the current video frame to CE.SDK: ```js const onVideoFrame = () => { engine.block.setNativePixelBuffer(pixelStreamFill, video); video.requestVideoFrameCallback(onVideoFrame); }; video.requestVideoFrameCallback(onVideoFrame); ``` The `setNativePixelBuffer()` method accepts either an `HTMLVideoElement` or `HTMLCanvasElement`, providing flexibility for different video processing workflows. Using `requestVideoFrameCallback` instead of `requestAnimationFrame` ensures frame updates are synchronized with the video's actual frame rate. ## Troubleshooting ### Camera Not Appearing Verify camera permissions are granted in the browser. Check that the video element has `autoplay` set to `true`. Ensure the `loadedmetadata` event fires before accessing video dimensions. ### Mirrored or Rotated Incorrectly Check the `fill/pixelStream/orientation` enum value. Front-facing cameras typically need `UpMirrored`. Mobile devices may require rotation adjustments based on device orientation. ### Effects Not Rendering Confirm effects are appended using `engine.block.appendEffect()`. Verify the effect block was created successfully with `engine.block.createEffect()`. ### Performance Issues Ensure `requestVideoFrameCallback` is used instead of `requestAnimationFrame` for better frame synchronization. Check that only one callback loop is running. Consider reducing effect complexity for smoother preview. ## API Reference | Method | Description | | --- | --- | | `engine.scene.createVideo()` | Create a video scene for camera preview | | `engine.block.create('page')` | Create a page block to hold the camera fill | | `engine.block.createFill('pixelStream')` | Create a fill that accepts live pixel data | | `engine.block.setFill()` | Assign the pixel stream fill to a block | | `engine.block.setNativePixelBuffer()` | Send video frame data to the fill | | `engine.block.setEnum()` | Set the orientation property for mirroring or rotation | | `engine.block.createEffect()` | Create an effect for real-time processing | | `engine.block.appendEffect()` | Apply an effect to the page | | `engine.block.setWidth()` | Set block width to match video dimensions | | `engine.block.setHeight()` | Set block height to match video dimensions | | `engine.scene.zoomToBlock()` | Fit the camera view in the viewport | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Asset Concepts" description: "This guide explains the foundational architecture of the CE.SDK asset system, including what asset sources are, how they organize content, and how they connect to the user interface." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Concepts](https://img.ly/docs/cesdk/angular/import-media/concepts-5e6197/) --- Understand the foundational architecture of CE.SDK's asset system and how asset sources organize content across platforms. Asset sources are CE.SDK's content delivery architecture. Instead of hardcoding asset knowledge into the editor, CE.SDK uses a modular system where any content can be provided through a standardized interface. This decouples what assets are available from how they're discovered and applied. ``` PLATFORM-SPECIFIC UI (Web) ┌─────────────────────────────────────────────────────────────────────────┐ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Dock Button │───▶│ Asset Panel │───▶│ Assets Grid │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ Configured via: Asset Library Entries, Dock config, Panel navigation │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ CROSS-PLATFORM ENGINE (engine.asset API) ┌─────────────────────────────────────────────────────────────────────────┐ │ │ │ findAssets() addSource() addLocalSource() apply() │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Custom │ │ Local │ │ JSON-Based │ │ │ │ Sources │ │ Sources │ │ Sources │ │ │ ├──────────────┤ ├──────────────┤ ├──────────────┤ │ │ │ Your API │ │ User Uploads │ │ Built-in │ │ │ │ Database │ │ Collections │ │ Asset Packs │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ Identical across Web, iOS, Android │ └─────────────────────────────────────────────────────────────────────────┘ ``` This guide covers the foundational concepts of asset sources. For implementation details, see the linked guides at the end. ## Asset Source Fundamentals An asset source provides content to the editor through a common interface. Every source has a unique identifier (e.g., `ly.img.image`, `ly.img.sticker`) and implements methods for discovering and applying assets. Sources support: - **Query-based discovery** with pagination and filtering - **Optional grouping** (e.g., sticker groups: "emoji", "doodle", "hand") - **Metadata** including credits, licenses, and format information Sources are content-agnostic—images, fonts, templates, and custom content all use the same pattern. ## Content Organized as Asset Sources Asset sources handle virtually all reusable creative content: | Category | Examples | | ---------- | ---------------------------------------- | | Media | Images, videos, audio clips | | Graphics | Stickers, shapes, vectors, icons | | Typography | Fonts, typefaces, text presets | | Colors | Color palettes, spot colors | | Effects | Blur types, filters, LUT effects | | Templates | Design templates, page presets | | Custom | User uploads, remote APIs, your own data | Built-in sources include `ly.img.image`, `ly.img.sticker`, `ly.img.template`, `ly.img.typeface`, `ly.img.filter`, `ly.img.blur`, `ly.img.effect`, and more. ## Types of Asset Sources There are three ways to provide assets to CE.SDK: ### Custom Sources Implement the source interface to connect any backend—database, API, or custom system. Custom sources provide full control over discovery and application logic. Use custom sources when you need to: - Connect to your existing content management system - Implement custom search or filtering logic - Control how assets are applied to the scene ### Local Sources Managed by the engine with dynamic add/remove operations. Local sources are suitable for user uploads or custom collections that change during the editing session. The engine handles storage and retrieval. ### JSON-Based Sources Pre-defined asset collections loaded from JSON files. All built-in asset packs use this approach. JSON sources are ideal for static content that doesn't change frequently. ## Asset Sources and the User Interface Asset sources are backend providers—they don't know about UI. The connection between sources and what users see happens through platform-specific UI configuration. On Web, asset sources connect to the UI through: - **Asset Library Entries** — Group one or more sources for display, configure grid layout and preview - **Dock Buttons** — Sidebar icons that open asset panels - **Panel Navigation** — Entry → Source → Groups → Assets grid This separation means you can: - Show multiple sources in one UI panel - Show the same source in different UI locations - Change UI presentation without changing the source ## Cross-Platform Architecture Asset sources use the `engine.asset` API consistently across all platforms (Web, iOS, Android). All platforms support: - Custom source registration - JSON-based asset loading - Local asset management - Group-based organization - Event subscriptions (source added, removed, updated) Code patterns transfer directly between platforms with only syntax changes. ## Asset Structure Each asset contains: - **ID** — Unique identifier within the source - **Meta** — URI, thumbnail, MIME type, dimensions, block type hints - **Label** — Localized display name - **Tags** — Searchable keywords (localized) - **Groups** — Category membership - **Context** — Source reference for tracking origin The engine uses metadata hints (`blockType`, `fillType`, `shapeType`) to determine what block type to create when applying an asset. ## Discovery and Application Assets are discovered through queries supporting pagination, text search, tag/group filtering, and sorting. When applied, assets either create new blocks or modify existing ones. Sources can customize application behavior or use the engine's default implementation. ## Source Lifecycle Events The engine emits events when sources change: added, removed, or contents updated. Subscribe to these events to keep UI synchronized with available content. ## Troubleshooting Common conceptual misunderstandings: - **Confusing sources with UI** — Asset sources are backend providers; they don't render UI. The UI (dock buttons, panels, grids) is configured separately and connects to sources. - **Expecting sources to filter themselves** — Sources return all matching assets; UI configuration determines what's displayed to users. - **Mixing source types** — Custom sources (your code), local sources (engine-managed), and JSON sources (static files) serve different purposes. Choose based on whether you need dynamic backend connections, runtime asset management, or static asset packs. ## API Reference | Method | Category | Purpose | | ------------------------------------- | ----------------- | ----------------------------------------------------------------- | | `engine.asset.addSource()` | Source Management | Register a custom asset source with discovery and apply callbacks | | `engine.asset.addLocalSource()` | Source Management | Create an engine-managed source for dynamic asset add/remove | | `engine.asset.findAssets()` | Discovery | Query assets with pagination, search, filtering, and sorting | | `engine.asset.apply()` | Application | Apply an asset to the active scene, creating a configured block | | `engine.asset.onAssetSourceAdded()` | Events | Subscribe to source registration events | | `engine.asset.onAssetSourceRemoved()` | Events | Subscribe to source removal events | | `engine.asset.onAssetSourceUpdated()` | Events | Subscribe to source content change events | ## Next Steps - [Your Server](https://img.ly/docs/cesdk/angular/import-media/from-remote-source/your-server-b91910/) — Connect your own backend as an asset source - [Asset Library Basics](https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/) — Configure the asset library UI on web - [Customize Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/) — Customize asset library appearance --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Asset Content JSON Schema" description: "Understand the JSON schema structure for defining asset source content including version, metadata, and payload properties for images, videos, fonts, and templates." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/content-json-schema-a7b3d2/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Asset Content JSON Schema](https://img.ly/docs/cesdk/angular/import-media/content-json-schema-a7b3d2/) --- Reference documentation for the JSON schema structure used to define asset source content in CE.SDK. Asset content JSON files define the structure and metadata for assets that CE.SDK loads into asset sources. This schema supports images, videos, audio, fonts, templates, colors, shapes, and effects. ## Manifest Structure Every `content.json` file requires three top-level fields: ```json { "version": "2.0.0", "id": "my.custom.source", "assets": [] } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `version` | `string` | Yes | Schema version | | `id` | `string` | Yes | Unique identifier for the asset source | | `assets` | `AssetDefinition[]` | Yes | Array of asset definitions | ## Asset Definition Each asset in the `assets` array follows this structure: | Property | Type | Required | Description | |----------|------|----------|-------------| | `id` | `string` | Yes | Unique identifier within the source | | `label` | `Record` | No | Localized display names for UI and tooltips | | `tags` | `Record` | No | Localized keywords for search and filtering | | `groups` | `string[]` | No | Categories for grouping assets in the UI | | `meta` | `AssetMetaData` | No | Content-specific metadata | | `payload` | `AssetPayload` | No | Structured data for specialized assets | ### Localization Labels and tags use locale codes as keys (e.g., `"en"`, `"de"`, `"fr"`). CE.SDK selects the appropriate translation based on the user's locale. ```json { "id": "mountain-photo", "label": { "en": "Mountain Landscape", "de": "Berglandschaft" }, "tags": { "en": ["nature", "mountain"], "de": ["natur", "berg"] }, "groups": ["landscapes", "nature"] } ``` ## Asset Metadata The `meta` object contains content-specific information for loading and applying assets. ### Content Properties Define URIs and file information for loading the asset content. The `uri` property points to the main asset file, while `thumbUri` and `previewUri` provide optimized versions for UI display. ```json { "meta": { "uri": "{{base_url}}/images/photo.jpg", "thumbUri": "{{base_url}}/thumbnails/photo-thumb.jpg", "previewUri": "{{base_url}}/previews/photo-preview.jpg", "filename": "photo.jpg", "mimeType": "image/jpeg" } } ``` | Property | Type | Description | |----------|------|-------------| | `uri` | `string` | Primary content URI. Supports `{{base_url}}` placeholder | | `thumbUri` | `string` | Thumbnail image URI for previews | | `previewUri` | `string` | Higher-quality preview URI | | `filename` | `string` | Original filename | | `mimeType` | `string` | MIME type (e.g., `"image/jpeg"`, `"video/mp4"`) | ### Dimension Properties Specify the pixel dimensions of the asset. CE.SDK uses these values for layout calculations and aspect ratio preservation when inserting assets into a design. ```json { "meta": { "width": 1920, "height": 1280 } } ``` | Property | Type | Description | |----------|------|-------------| | `width` | `number` | Content width in pixels | | `height` | `number` | Content height in pixels | ### Block Creation Properties Control what design block CE.SDK creates when the asset is applied. These properties determine how the asset integrates into the design structure. ```json { "meta": { "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "shapeType": "//ly.img.ubq/shape/rect", "kind": "image" } } ``` | Property | Type | Description | |----------|------|-------------| | `blockType` | `string` | Design block type to create | | `fillType` | `string` | Fill type for the block | | `shapeType` | `string` | Shape type for stickers/shapes | | `kind` | `string` | Asset category hint (e.g., `"image"`, `"video"`, `"template"`) | **Block Type Values:** | Value | Use Case | |-------|----------| | `//ly.img.ubq/graphic` | Images, stickers, graphics | | `//ly.img.ubq/text` | Text blocks | | `//ly.img.ubq/audio` | Audio clips | | `//ly.img.ubq/page` | Templates, pages | | `//ly.img.ubq/group` | Grouped elements | | `//ly.img.ubq/cutout` | Cutout shapes | **Fill Type Values:** | Value | Use Case | |-------|----------| | `//ly.img.ubq/fill/image` | Image fills | | `//ly.img.ubq/fill/video` | Video fills | | `//ly.img.ubq/fill/color` | Solid color fills | | `//ly.img.ubq/fill/gradient/linear` | Linear gradients | | `//ly.img.ubq/fill/gradient/radial` | Radial gradients | | `//ly.img.ubq/fill/gradient/conical` | Conical gradients | **Shape Type Values:** | Value | Use Case | |-------|----------| | `//ly.img.ubq/shape/rect` | Rectangles | | `//ly.img.ubq/shape/ellipse` | Circles, ovals | | `//ly.img.ubq/shape/polygon` | Polygons | | `//ly.img.ubq/shape/star` | Star shapes | | `//ly.img.ubq/shape/line` | Lines | | `//ly.img.ubq/shape/vector_path` | Custom vector paths | ### Media Properties Configure playback behavior for time-based media like video and audio. Use `duration` to specify length and `looping` to enable repeat playback for background music or ambient video. ```json { "meta": { "duration": "30", "looping": true, "vectorPath": "M10 10 L90 90" } } ``` | Property | Type | Description | |----------|------|-------------| | `duration` | `string` | Duration in seconds as a string (e.g., `"30"`, `"120"`) | | `looping` | `boolean` | Whether media should loop continuously. Use for background music or ambient video | | `vectorPath` | `string` | SVG path data for vector shapes | ### Effect Properties Define visual effects that can be applied to design blocks. Effects include filters, blurs, and color adjustments. ```json { "meta": { "effectType": "//ly.img.ubq/effect/lut_filter", "blurType": "//ly.img.ubq/blur/uniform" } } ``` | Property | Type | Description | |----------|------|-------------| | `effectType` | `string` | Effect type (e.g., `"//ly.img.ubq/effect/lut_filter"`, `"//ly.img.ubq/effect/duotone_filter"`) | | `blurType` | `string` | Blur type: `"//ly.img.ubq/blur/uniform"`, `"//ly.img.ubq/blur/linear"`, `"//ly.img.ubq/blur/mirrored"`, `"//ly.img.ubq/blur/radial"` | ### Responsive Sources The `sourceSet` property defines multiple resolutions for responsive loading. This enables CE.SDK to load an appropriately sized image based on the display context, reducing bandwidth for thumbnails while providing full resolution when needed. ```json { "meta": { "sourceSet": [ { "uri": "{{base_url}}/small.jpg", "width": 640, "height": 480 }, { "uri": "{{base_url}}/medium.jpg", "width": 1280, "height": 960 }, { "uri": "{{base_url}}/large.jpg", "width": 1920, "height": 1440 } ] } } ``` When a user browses assets in the library panel, CE.SDK loads the smallest appropriate resolution. When the asset is added to the canvas and zoomed in, higher resolutions are loaded on demand. This pattern significantly improves initial load times for asset libraries with many items. | Property | Type | Required | Description | |----------|------|----------|-------------| | `uri` | `string` | Yes | Source URI | | `width` | `number` | Yes | Source width in pixels | | `height` | `number` | Yes | Source height in pixels | ## Asset Payload The `payload` object contains structured data for specialized asset types like colors, fonts, and presets. | Property | Type | Description | |----------|------|-------------| | `color` | `AssetColor` | Color definition | | `typeface` | `Typeface` | Font family definition | | `transformPreset` | `AssetTransformPreset` | Page size or aspect ratio preset | | `sourceSet` | `Source[]` | Responsive sources (same as meta.sourceSet) | ### Color Payload Colors support three color spaces: sRGB, CMYK, and Spot Color. Use sRGB for screen-based designs, CMYK for print workflows, and Spot Color for brand-specific colors that require exact color matching. **sRGB Color:** ```json { "payload": { "color": { "colorSpace": "sRGB", "r": 0.2, "g": 0.4, "b": 0.8 } } } ``` sRGB is the standard color space for web and digital displays. Component values range from 0 to 1, where `{ r: 1, g: 0, b: 0 }` represents pure red. | Property | Type | Range | Description | |----------|------|-------|-------------| | `colorSpace` | `"sRGB"` | — | Color space identifier | | `r` | `number` | 0–1 | Red component | | `g` | `number` | 0–1 | Green component | | `b` | `number` | 0–1 | Blue component | **CMYK Color:** ```json { "payload": { "color": { "colorSpace": "CMYK", "c": 0.75, "m": 0.25, "y": 0.0, "k": 0.1 } } } ``` CMYK is used for print production. Component values represent ink percentages from 0 to 1, where higher values mean more ink coverage. | Property | Type | Range | Description | |----------|------|-------|-------------| | `colorSpace` | `"CMYK"` | — | Color space identifier | | `c` | `number` | 0–1 | Cyan component | | `m` | `number` | 0–1 | Magenta component | | `y` | `number` | 0–1 | Yellow component | | `k` | `number` | 0–1 | Black (key) component | **Spot Color:** ```json { "payload": { "color": { "colorSpace": "SpotColor", "name": "Pantone 286 C", "externalReference": "pantone://286-c", "representation": { "colorSpace": "sRGB", "r": 0.0, "g": 0.22, "b": 0.62 } } } } ``` Spot colors reference named colors from systems like Pantone. The `representation` provides a screen preview while the actual color is defined by the external reference for accurate print reproduction. | Property | Type | Description | |----------|------|-------------| | `colorSpace` | `"SpotColor"` | Color space identifier | | `name` | `string` | Spot color name | | `externalReference` | `string` | External reference URI | | `representation` | `AssetRGBColor \| AssetCMYKColor` | Screen/print representation | ### Typeface Payload Defines a font family with multiple font files for different weights and styles. This enables CE.SDK to load the correct font file when text formatting changes. ```json { "payload": { "typeface": { "name": "Roboto", "fonts": [ { "uri": "{{base_url}}/Roboto-Regular.ttf", "subFamily": "Regular", "weight": "normal", "style": "normal" }, { "uri": "{{base_url}}/Roboto-Bold.ttf", "subFamily": "Bold", "weight": "bold", "style": "normal" }, { "uri": "{{base_url}}/Roboto-Italic.ttf", "subFamily": "Italic", "weight": "normal", "style": "italic" } ] } } } ``` Each font entry in the `fonts` array represents a single font file. When a user applies bold formatting, CE.SDK automatically selects the font entry with `weight: "bold"`. Include all weight and style combinations you want to support. **Typeface Properties:** | Property | Type | Required | Description | |----------|------|----------|-------------| | `name` | `string` | Yes | Typeface family name | | `fonts` | `Font[]` | Yes | Array of font definitions | **Font Properties:** | Property | Type | Required | Description | |----------|------|----------|-------------| | `uri` | `string` | Yes | Font file URI (.ttf, .otf, .woff, .woff2) | | `subFamily` | `string` | Yes | Font subfamily name (e.g., "Regular", "Bold Italic") | | `weight` | `FontWeight` | No | Font weight | | `style` | `FontStyle` | No | Font style | **Font Weight Values:** `"thin"`, `"extraLight"`, `"light"`, `"normal"`, `"medium"`, `"semiBold"`, `"bold"`, `"extraBold"`, `"heavy"` **Font Style Values:** `"normal"`, `"italic"` ### Transform Preset Payload Defines page size or aspect ratio presets for templates, canvases, and crop tools. Use these to provide users with common format options like social media dimensions or print sizes. **Fixed Size:** ```json { "payload": { "transformPreset": { "type": "FixedSize", "width": 1080, "height": 1920, "designUnit": "Pixel" } } } ``` Fixed size presets lock both width and height to specific values. Use `designUnit` to specify whether dimensions are in pixels (for digital), millimeters, or inches (for print). | Property | Type | Description | |----------|------|-------------| | `type` | `"FixedSize"` | Preset type | | `width` | `number` | Width value | | `height` | `number` | Height value | | `designUnit` | `string` | Unit: `"Pixel"`, `"Millimeter"`, or `"Inch"` | **Fixed Aspect Ratio:** ```json { "payload": { "transformPreset": { "type": "FixedAspectRatio", "width": 16, "height": 9 } } } ``` Fixed aspect ratio presets maintain proportions while allowing flexible sizing. The width and height values represent the ratio components, not pixel dimensions. | Property | Type | Description | |----------|------|-------------| | `type` | `"FixedAspectRatio"` | Preset type | | `width` | `number` | Aspect ratio width component | | `height` | `number` | Aspect ratio height component | **Free Aspect Ratio:** ```json { "payload": { "transformPreset": { "type": "FreeAspectRatio" } } } ``` Free aspect ratio presets allow unrestricted resizing without maintaining proportions. ## Base URL Placeholder The `{{base_url}}` placeholder enables portable asset definitions. CE.SDK replaces this placeholder with the actual base path when loading: - **From URL:** The parent directory of the JSON file becomes the base URL - **From string:** You provide the base URL explicitly when loading ```json { "meta": { "uri": "{{base_url}}/images/photo.jpg", "thumbUri": "{{base_url}}/thumbnails/photo.jpg" } } ``` ## Asset Type Examples ### Image Asset Standard image assets are the most common type, used for photos, illustrations, and background images. They require a `blockType` of graphic with an image fill. ```json { "id": "photo-001", "label": { "en": "Mountain Landscape" }, "tags": { "en": ["nature", "mountain"] }, "meta": { "uri": "{{base_url}}/mountain.jpg", "thumbUri": "{{base_url}}/mountain-thumb.jpg", "mimeType": "image/jpeg", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "width": 1920, "height": 1280 } } ``` ### Video Asset Video assets include duration information and use a video fill type. Set `looping` to `true` for videos that should repeat continuously. ```json { "id": "video-001", "label": { "en": "Intro Animation" }, "meta": { "uri": "{{base_url}}/intro.mp4", "thumbUri": "{{base_url}}/intro-thumb.jpg", "mimeType": "video/mp4", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/video", "width": 1920, "height": 1080, "duration": "5", "looping": false } } ``` ### Audio Asset Audio assets use the audio block type and don't require visual dimensions. Set `looping` to `true` for background music that should repeat continuously throughout the design. ```json { "id": "audio-001", "label": { "en": "Background Music" }, "meta": { "uri": "{{base_url}}/music.mp3", "mimeType": "audio/mpeg", "blockType": "//ly.img.ubq/audio", "duration": "120", "looping": true } } ``` ### Sticker Asset Stickers are vector graphics that maintain quality at any size. They use the `vector_path` shape type and typically reference SVG files. ```json { "id": "sticker-001", "label": { "en": "Star Badge" }, "meta": { "uri": "{{base_url}}/star.svg", "thumbUri": "{{base_url}}/star-thumb.png", "mimeType": "image/svg+xml", "blockType": "//ly.img.ubq/graphic", "shapeType": "//ly.img.ubq/shape/vector_path", "width": 200, "height": 200 } } ``` ### Template Asset Templates are complete design scenes that can be loaded as starting points. Use `kind: "template"` to identify them in the UI. ```json { "id": "template-001", "label": { "en": "Social Media Story" }, "meta": { "uri": "{{base_url}}/story-template.scene", "thumbUri": "{{base_url}}/story-thumb.jpg", "kind": "template", "width": 1080, "height": 1920 } } ``` ### Crop Preset Asset Crop presets define aspect ratios for the crop tool. Use `transformPreset` in the payload to specify the ratio without fixed pixel dimensions. ```json { "id": "crop-square", "label": { "en": "Square" }, "groups": ["social"], "payload": { "transformPreset": { "type": "FixedAspectRatio", "width": 1, "height": 1 } } } ``` ### Page Format Preset Asset Page format presets define canvas sizes for new designs. Use `FixedSize` to specify exact dimensions in pixels, millimeters, or inches. ```json { "id": "format-instagram-story", "label": { "en": "Instagram Story" }, "groups": ["social"], "meta": { "thumbUri": "{{base_url}}/instagram-story-thumb.jpg" }, "payload": { "transformPreset": { "type": "FixedSize", "width": 1080, "height": 1920, "designUnit": "Pixel" } } } ``` ## Troubleshooting | Issue | Solution | |-------|----------| | Assets not appearing | Verify `version`, `id`, and `assets` fields exist at the top level | | Invalid asset | Ensure each asset has a unique `id` | | Missing thumbnails | Check `thumbUri` points to accessible image URLs | | Base URL not resolving | Use exact `{{base_url}}` syntax (double curly braces) | | CORS errors | Configure server headers to allow cross-origin requests | | Wrong block created | Verify `meta.blockType` matches the intended design block | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Using Default Assets" description: "Load shapes, stickers, images, and other built-in assets from IMG.LY's CDN to populate your CE.SDK editor using the Asset API." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/default-assets-d2763d/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Using Default Assets](https://img.ly/docs/cesdk/angular/import-media/default-assets-d2763d/) --- ```typescript file=@cesdk_web_examples/guides-import-media-default-assets-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; /** * CE.SDK Plugin: Using Default Assets Guide * * Demonstrates loading all asset sources from IMG.LY's CDN using * addLocalAssetSourceFromJSONURI and creating a scene with * a star shape, sticker, and image. */ class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk, engine }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Versioned CDN URLs using the SDK package (recommended) // For production, self-host these assets - see the Serve Assets guide const PACKAGE_BASE = `https://cdn.img.ly/packages/imgly/cesdk-js/${cesdk.version}/assets`; const DEFAULT_ASSETS_URL = `${PACKAGE_BASE}/v4/`; const DEMO_ASSETS_URL = `${PACKAGE_BASE}/demo/v3/`; // Load default asset sources (core editor components) await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.sticker/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.vector.shape/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.color.palette/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.filter/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.effect/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.blur/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.typeface/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.crop.presets/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.page.presets/content.json` ); // Load demo asset sources (sample content for testing) await engine.asset.addLocalAssetSourceFromJSONURI( `${DEMO_ASSETS_URL}ly.img.image/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEMO_ASSETS_URL}ly.img.video/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEMO_ASSETS_URL}ly.img.audio/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEMO_ASSETS_URL}ly.img.templates/content.json` ); await engine.asset.addLocalAssetSourceFromJSONURI( `${DEMO_ASSETS_URL}ly.img.text.components/content.json` ); // Update asset library entries to show the loaded sources in the UI cesdk.ui.updateAssetLibraryEntry('ly.img.sticker', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.sticker']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.vector.shape', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.vector.shape']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.color.palette', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.color.palette']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.filter', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.filter']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.effect', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.effect']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.blur', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.blur']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.typeface', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.typeface']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.crop.presets', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.crop.presets']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.page.presets', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.page.presets']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.image']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.video', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.video']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.audio', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.audio']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.templates', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.templates']) ] }); cesdk.ui.updateAssetLibraryEntry('ly.img.text.components', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.text.components']) ] }); // Create the design scene await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); // Get the page to add content to const pages = engine.block.findByType('page'); const page = pages[0]; if (page == null) return; const pageWidth = engine.block.getWidth(page); const pageHeight = engine.block.getHeight(page); // Define the three assets to add: star shape, sticker, and image const assetsToAdd = [ { sourceId: 'ly.img.vector.shape', assetId: '//ly.img.ubq/shapes/star/filled' }, { sourceId: 'ly.img.sticker', assetId: '//ly.img.cesdk.stickers.emoticons/alien' }, { sourceId: 'ly.img.image', assetId: 'ly.img.cesdk.images.samples/sample.1' } ]; // Calculate layout for 3 centered blocks const blockSize = Math.min(pageWidth, pageHeight) * 0.2; const spacing = blockSize * 0.3; const totalWidth = assetsToAdd.length * blockSize + (assetsToAdd.length - 1) * spacing; const startX = (pageWidth - totalWidth) / 2; const centerY = (pageHeight - blockSize) / 2; // Create and position each block for (let i = 0; i < assetsToAdd.length; i++) { const { sourceId, assetId } = assetsToAdd[i]; const asset = await engine.asset.fetchAsset(sourceId, assetId); if (asset != null) { const block = await engine.asset.apply(sourceId, asset); if (block != null) { engine.block.setWidth(block, blockSize); engine.block.setHeight(block, blockSize); engine.block.setPositionX(block, startX + i * (blockSize + spacing)); engine.block.setPositionY(block, centerY); } } } // Clear selection for cleaner visual for (const block of engine.block.findAllSelected()) { engine.block.setSelected(block, false); } // Open the Elements panel to showcase all loaded asset sources cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['ly.img.image', 'ly.img.vector.shape', 'ly.img.sticker'] } }); } } export default Example; ``` Load all asset sources from IMG.LY's CDN to populate your CE.SDK editor with shapes, stickers, filters, effects, fonts, images, and other media using the Asset API. ![Using Default Assets example showing the CE.SDK editor with a star shape, sticker, and image centered on the canvas](./assets/browser.hero.webp) > **Reading time:** 5 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-default-assets-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-default-assets-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-import-media-default-assets-browser/) CE.SDK provides built-in asset sources for shapes, stickers, filters, effects, fonts, and sample media. This guide demonstrates loading all available asset sources from IMG.LY's CDN and applying them to create a scene with a star shape, a sticker, and an image. > **Production Deployment:** The IMG.LY CDN is for development and prototyping only. For production, download and self-host assets from your own server. See the [Serve Assets](https://img.ly/docs/cesdk/angular/serve-assets-b0827c/) guide for instructions. ## What Are Default and Demo Assets? IMG.LY provides two categories of asset sources hosted on the IMG.LY CDN for development and prototyping: **Default Assets** are core editor components: | Source ID | Description | |-----------|-------------| | `ly.img.sticker` | Emojis, emoticons, decorations | | `ly.img.vector.shape` | Shapes: stars, arrows, polygons | | `ly.img.color.palette` | Default color palette | | `ly.img.filter` | Color filters (LUT and duotone) | | `ly.img.effect` | Visual effects | | `ly.img.blur` | Blur effects | | `ly.img.typeface` | Font families | | `ly.img.crop.presets` | Crop presets | | `ly.img.page.presets` | Page size presets | **Demo Assets** are sample content for development: | Source ID | Description | |-----------|-------------| | `ly.img.image` | Sample images | | `ly.img.video` | Sample videos | | `ly.img.audio` | Sample audio tracks | | `ly.img.templates` | Design and video templates | | `ly.img.text.components` | Text component presets | ## Loading Assets from URL Use `addLocalAssetSourceFromJSONURI()` to load an asset source directly from a JSON URL: ```typescript const baseURL = `https://cdn.img.ly/packages/imgly/cesdk-js/${cesdk.version}/assets/v4/`; await engine.asset.addLocalAssetSourceFromJSONURI( `${baseURL}ly.img.vector.shape/content.json` ); ``` ## Versioned CDN URLs Use the SDK version to construct versioned CDN URLs. This ensures assets are compatible with your SDK version. For production deployments, see the [Serve Assets](https://img.ly/docs/cesdk/angular/serve-assets-b0827c/) guide to self-host assets. ```typescript highlight-cdn-urls // Versioned CDN URLs using the SDK package (recommended) // For production, self-host these assets - see the Serve Assets guide const PACKAGE_BASE = `https://cdn.img.ly/packages/imgly/cesdk-js/${cesdk.version}/assets`; const DEFAULT_ASSETS_URL = `${PACKAGE_BASE}/v4/`; const DEMO_ASSETS_URL = `${PACKAGE_BASE}/demo/v3/`; ``` ## Loading Default Asset Sources Load a default asset source from the CDN. Repeat this pattern for each source you need: ```typescript highlight-load-default-assets // Load default asset sources (core editor components) await engine.asset.addLocalAssetSourceFromJSONURI( `${DEFAULT_ASSETS_URL}ly.img.sticker/content.json` ); ``` ## Loading Demo Asset Sources Load a demo asset source from the CDN. Repeat this pattern for each source you need: ```typescript highlight-load-demo-assets // Load demo asset sources (sample content for testing) await engine.asset.addLocalAssetSourceFromJSONURI( `${DEMO_ASSETS_URL}ly.img.image/content.json` ); ``` ## Updating the Asset Library After loading asset sources, update the asset library entries to display them in the UI. Repeat this pattern for each source: ```typescript highlight-update-library // Update asset library entries to show the loaded sources in the UI cesdk.ui.updateAssetLibraryEntry('ly.img.sticker', { sourceIds: ({ currentIds }) => [ ...new Set([...currentIds, 'ly.img.sticker']) ] }); ``` ## Filtering Assets with Matcher Use the `matcher` option to load only specific assets from a source: ```typescript const baseURL = `https://cdn.img.ly/packages/imgly/cesdk-js/${cesdk.version}/assets/v4/`; // Load only star and arrow shapes await engine.asset.addLocalAssetSourceFromJSONURI( `${baseURL}ly.img.vector.shape/content.json`, { matcher: ['*star*', '*arrow*'] } ); // Load only emoji stickers await engine.asset.addLocalAssetSourceFromJSONURI( `${baseURL}ly.img.sticker/content.json`, { matcher: ['*emoji*'] } ); ``` An asset is included if it matches ANY pattern in the array. Patterns support `*` wildcards. ## API Reference | Method | Description | |--------|-------------| | `engine.asset.addLocalAssetSourceFromJSONURI(contentURI, options?)` | Load an asset source from a JSON URL | **Parameters for `addLocalAssetSourceFromJSONURI`:** | Parameter | Type | Description | |-----------|------|-------------| | `contentURI` | `string` | Full URL to the content.json file | | `options.matcher` | `string[]` | Optional wildcard patterns to filter assets | **Returns:** `Promise` — The asset source ID from the JSON ## Next Steps - [Serve Assets](https://img.ly/docs/cesdk/angular/serve-assets-b0827c/) — Self-host assets for production deployments - [Customize Asset Library](https://img.ly/docs/cesdk/angular/import-media/asset-panel/customize-c9a4de/) — Configure the asset library UI and entries - [Asset Library Basics](https://img.ly/docs/cesdk/angular/import-media/asset-panel/basics-f29078/) — Understand asset library structure and concepts - [Import From Remote Source](https://img.ly/docs/cesdk/angular/import-media/from-remote-source-b65faf/) — Load assets from external URLs --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit or Remove Assets" description: "Manage assets in local asset sources by updating metadata, removing individual assets, or deleting entire sources in CE.SDK." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/edit-or-remove-assets-ce072c/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [Edit or Remove Assets](https://img.ly/docs/cesdk/angular/import-media/edit-or-remove-assets-ce072c/) --- Manage assets in local asset sources by updating metadata, removing individual assets, or deleting entire sources. ![Edit or Remove Assets](./assets/browser.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [Download examples](https://github.com/imgly/cesdk-web-examples/archive/refs/heads/main.zip) > > - [View source on GitHub](https://github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-edit-or-remove-assets-browser) > > - [Open in StackBlitz](https://stackblitz.com/~/github.com/imgly/cesdk-web-examples/tree/main/guides-import-media-edit-or-remove-assets-browser) > > - [Live demo](https://img.ly/docs/cesdk/examples/guides-import-media-edit-or-remove-assets-browser/) Assets in local sources can be modified or removed after they have been added. CE.SDK provides two levels of removal: individual assets within a source and entire asset sources. This guide covers how to query, update, and remove assets programmatically, as well as how to notify the UI when changes occur. ```typescript file=@cesdk_web_examples/guides-import-media-edit-or-remove-assets-browser/browser.ts reference-only import type { EditorPlugin, EditorPluginContext } from '@cesdk/cesdk-js'; import { BlurAssetSource, ColorPaletteAssetSource, CropPresetsAssetSource, DemoAssetSources, EffectsAssetSource, FiltersAssetSource, PagePresetsAssetSource, StickerAssetSource, TextAssetSource, TextComponentAssetSource, TypefaceAssetSource, UploadAssetSources, VectorShapeAssetSource } from '@cesdk/cesdk-js/plugins'; import { DesignEditorConfig } from './design-editor/plugin'; import packageJson from './package.json'; class Example implements EditorPlugin { name = packageJson.name; version = packageJson.version; async initialize({ cesdk }: EditorPluginContext): Promise { if (!cesdk) { throw new Error('CE.SDK instance is required for this plugin'); } await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const engine = cesdk.engine; // ===== Section 1: Create a Local Asset Source ===== // Create a local asset source to manage images engine.asset.addLocalSource('my-uploads'); // Add some sample assets to the source engine.asset.addAssetToSource('my-uploads', { id: 'image-1', label: { en: 'Mountain Landscape' }, tags: { en: ['nature', 'mountain'] }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', blockType: '//ly.img.ubq/graphic' } }); engine.asset.addAssetToSource('my-uploads', { id: 'image-2', label: { en: 'Ocean Waves' }, tags: { en: ['nature', 'water'] }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_2.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_2.jpg', blockType: '//ly.img.ubq/graphic' } }); engine.asset.addAssetToSource('my-uploads', { id: 'image-3', label: { en: 'Forest Path' }, tags: { en: ['nature', 'forest'] }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_3.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_3.jpg', blockType: '//ly.img.ubq/graphic' } }); // ===== Section 2: Find Assets in a Source ===== // Query assets from the source to find specific assets const result = await engine.asset.findAssets('my-uploads', { query: 'nature', page: 0, perPage: 100 }); console.log('Found assets:', result.assets.length); const assetToModify = result.assets.find((a) => a.id === 'image-1'); console.log('Asset to modify:', assetToModify?.label); // ===== Section 3: Update Asset Metadata ===== // To update an asset's metadata, remove it and add an updated version // This pattern ensures the asset library reflects the latest data engine.asset.removeAssetFromSource('my-uploads', 'image-1'); // Add the updated version with new metadata engine.asset.addAssetToSource('my-uploads', { id: 'image-1', label: { en: 'Updated Mountain Photo' }, // New label tags: { en: ['nature', 'mountain', 'updated'] }, // New tags meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', blockType: '//ly.img.ubq/graphic' } }); // ===== Section 4: Remove an Asset from a Source ===== // Remove a single asset from the source // Blocks already using this asset on the canvas remain unaffected engine.asset.removeAssetFromSource('my-uploads', 'image-2'); console.log('Removed image-2 from my-uploads'); // ===== Section 5: Notify UI of Changes ===== // After modifying assets, notify the UI to refresh // This triggers update events for components like the asset library panel engine.asset.assetSourceContentsChanged('my-uploads'); // ===== Section 6: Create a Second Source for Removal Demo ===== // Create a temporary source to demonstrate source removal engine.asset.addLocalSource('temporary-uploads'); engine.asset.addAssetToSource('temporary-uploads', { id: 'temp-1', label: { en: 'Temporary Image' }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_4.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_4.jpg', blockType: '//ly.img.ubq/graphic' } }); // ===== Section 7: Remove an Entire Asset Source ===== // Remove a complete asset source and all its assets // Any UI panels displaying this source will stop showing content engine.asset.removeSource('temporary-uploads'); console.log('Removed temporary-uploads source'); // ===== Section 8: Listen to Asset Source Events ===== // Subscribe to lifecycle events for asset sources const unsubscribeAdded = engine.asset.onAssetSourceAdded((sourceId) => { console.log(`Source added: ${sourceId}`); }); const unsubscribeRemoved = engine.asset.onAssetSourceRemoved((sourceId) => { console.log(`Source removed: ${sourceId}`); }); const unsubscribeUpdated = engine.asset.onAssetSourceUpdated((sourceId) => { console.log(`Source updated: ${sourceId}`); }); // Demonstrate events by creating and removing a source engine.asset.addLocalSource('event-demo-source'); engine.asset.assetSourceContentsChanged('event-demo-source'); engine.asset.removeSource('event-demo-source'); // Clean up subscriptions when no longer needed unsubscribeAdded(); unsubscribeRemoved(); unsubscribeUpdated(); // ===== Configure Asset Library Display ===== // Configure the asset library to show our custom source cesdk.ui.updateAssetLibraryEntry('ly.img.image', { sourceIds: ['my-uploads'], gridBackgroundType: 'cover', previewBackgroundType: 'contain' }); // Open the asset library to show the custom uploads cesdk.ui.openPanel('//ly.img.panel/assetLibrary', { payload: { entries: ['ly.img.image'] } }); } } export default Example; ``` ## Setup Initialize CE.SDK and create a local asset source with sample assets. The example creates a design scene and populates a local source with images. ```typescript highlight=highlight-setup await cesdk.addPlugin(new DesignEditorConfig()); // Add asset source plugins await cesdk.addPlugin(new BlurAssetSource()); await cesdk.addPlugin(new ColorPaletteAssetSource()); await cesdk.addPlugin(new CropPresetsAssetSource()); await cesdk.addPlugin(new UploadAssetSources({ include: ['ly.img.image.upload'] })); await cesdk.addPlugin( new DemoAssetSources({ include: [ 'ly.img.templates.blank.*', 'ly.img.templates.presentation.*', 'ly.img.templates.print.*', 'ly.img.templates.social.*', 'ly.img.image.*' ] }) ); await cesdk.addPlugin(new EffectsAssetSource()); await cesdk.addPlugin(new FiltersAssetSource()); await cesdk.addPlugin(new PagePresetsAssetSource()); await cesdk.addPlugin(new StickerAssetSource()); await cesdk.addPlugin(new TextAssetSource()); await cesdk.addPlugin(new TextComponentAssetSource()); await cesdk.addPlugin(new TypefaceAssetSource()); await cesdk.addPlugin(new VectorShapeAssetSource()); await cesdk.actions.run('scene.create', { page: { sourceId: 'ly.img.page.presets', assetId: 'ly.img.page.presets.print.iso.a6.landscape' } }); const engine = cesdk.engine; ``` ## Creating a Local Asset Source Before editing or removing assets, you need a local source with assets. Use `engine.asset.addLocalSource()` to create a source, then `engine.asset.addAssetToSource()` to populate it. ```typescript highlight=highlight-create-source // Create a local asset source to manage images engine.asset.addLocalSource('my-uploads'); // Add some sample assets to the source engine.asset.addAssetToSource('my-uploads', { id: 'image-1', label: { en: 'Mountain Landscape' }, tags: { en: ['nature', 'mountain'] }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', blockType: '//ly.img.ubq/graphic' } }); engine.asset.addAssetToSource('my-uploads', { id: 'image-2', label: { en: 'Ocean Waves' }, tags: { en: ['nature', 'water'] }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_2.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_2.jpg', blockType: '//ly.img.ubq/graphic' } }); engine.asset.addAssetToSource('my-uploads', { id: 'image-3', label: { en: 'Forest Path' }, tags: { en: ['nature', 'forest'] }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_3.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_3.jpg', blockType: '//ly.img.ubq/graphic' } }); ``` Each asset has a unique `id`, descriptive `label` and `tags` for searchability, and `meta` properties including the `uri` for the asset file. ## Finding Assets in a Source Use `engine.asset.findAssets()` to query assets from a source. You can search by query string to match labels and tags, and paginate results for large sources. ```typescript highlight=highlight-find-assets // Query assets from the source to find specific assets const result = await engine.asset.findAssets('my-uploads', { query: 'nature', page: 0, perPage: 100 }); console.log('Found assets:', result.assets.length); const assetToModify = result.assets.find((a) => a.id === 'image-1'); console.log('Asset to modify:', assetToModify?.label); ``` The `findAssets()` method returns an object containing the `assets` array, `total` count, `currentPage`, and optionally `nextPage` for pagination. ## Updating Asset Metadata CE.SDK does not provide a direct update method for assets. To modify an asset's metadata (labels, tags, URIs), remove the existing asset and add an updated version with the same ID. ```typescript highlight=highlight-update-metadata // To update an asset's metadata, remove it and add an updated version // This pattern ensures the asset library reflects the latest data engine.asset.removeAssetFromSource('my-uploads', 'image-1'); // Add the updated version with new metadata engine.asset.addAssetToSource('my-uploads', { id: 'image-1', label: { en: 'Updated Mountain Photo' }, // New label tags: { en: ['nature', 'mountain', 'updated'] }, // New tags meta: { uri: 'https://img.ly/static/ubq_samples/sample_1.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_1.jpg', blockType: '//ly.img.ubq/graphic' } }); ``` This pattern ensures the asset library reflects the latest metadata. The asset ID remains the same, so any references to the asset continue to work. ## Removing an Asset from a Source Remove individual assets using `engine.asset.removeAssetFromSource()`. The asset is permanently deleted from the source, but blocks already using that asset on the canvas remain unaffected. ```typescript highlight=highlight-remove-asset // Remove a single asset from the source // Blocks already using this asset on the canvas remain unaffected engine.asset.removeAssetFromSource('my-uploads', 'image-2'); console.log('Removed image-2 from my-uploads'); ``` Removing an asset only affects the asset library—it does not modify or delete any blocks that were created using that asset. ## Notifying the UI of Changes After modifying assets in a source, call `engine.asset.assetSourceContentsChanged()` to notify UI components. This triggers update events for the asset library panel and any other components displaying the source. ```typescript highlight=highlight-notify-ui // After modifying assets, notify the UI to refresh // This triggers update events for components like the asset library panel engine.asset.assetSourceContentsChanged('my-uploads'); ``` Without this notification, UI components may show stale data until the user navigates away and returns. ## Creating a Temporary Source You can create additional sources for temporary or session-specific assets. These sources can be removed entirely when no longer needed. ```typescript highlight=highlight-create-temp-source // Create a temporary source to demonstrate source removal engine.asset.addLocalSource('temporary-uploads'); engine.asset.addAssetToSource('temporary-uploads', { id: 'temp-1', label: { en: 'Temporary Image' }, meta: { uri: 'https://img.ly/static/ubq_samples/sample_4.jpg', thumbUri: 'https://img.ly/static/ubq_samples/sample_4.jpg', blockType: '//ly.img.ubq/graphic' } }); ``` ## Removing an Entire Asset Source Remove a complete asset source and all its assets using `engine.asset.removeSource()`. Any UI panels displaying this source will stop showing content for that source. ```typescript highlight=highlight-remove-source // Remove a complete asset source and all its assets // Any UI panels displaying this source will stop showing content engine.asset.removeSource('temporary-uploads'); console.log('Removed temporary-uploads source'); ``` Use this when cleaning up temporary sources or when a user explicitly deletes an entire category of assets. ## Listening to Asset Source Events Subscribe to lifecycle events to react when sources are added, removed, or updated. These events are useful for analytics, cleanup tasks, or syncing with external systems. ```typescript highlight=highlight-events // Subscribe to lifecycle events for asset sources const unsubscribeAdded = engine.asset.onAssetSourceAdded((sourceId) => { console.log(`Source added: ${sourceId}`); }); const unsubscribeRemoved = engine.asset.onAssetSourceRemoved((sourceId) => { console.log(`Source removed: ${sourceId}`); }); const unsubscribeUpdated = engine.asset.onAssetSourceUpdated((sourceId) => { console.log(`Source updated: ${sourceId}`); }); // Demonstrate events by creating and removing a source engine.asset.addLocalSource('event-demo-source'); engine.asset.assetSourceContentsChanged('event-demo-source'); engine.asset.removeSource('event-demo-source'); // Clean up subscriptions when no longer needed unsubscribeAdded(); unsubscribeRemoved(); unsubscribeUpdated(); ``` Each subscription returns an unsubscribe function. Call it when you no longer need to receive events to prevent memory leaks. ## Best Practices - **Query before modifying**: Use `findAssets()` to verify the asset exists before attempting removal - **Notify UI after changes**: Always call `assetSourceContentsChanged()` after modifying a source - **Clean up subscriptions**: Store unsubscribe functions and call them when the component unmounts or the source is removed - **Handle missing assets gracefully**: Check if `findAssets()` returns the expected asset before operating on it - **Use descriptive IDs**: Choose asset IDs that are unique and meaningful for easier debugging ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Asset not found | ID mismatch or asset already removed | Use `findAssets()` to list available assets | | UI not updating | Missing notification | Call `assetSourceContentsChanged()` after modifications | | Cannot remove asset | Source is not a local source | Only local sources support `removeAssetFromSource()` | | Events not firing | Subscription not active | Verify the subscription was created before the operation | ## API Reference | Method | Category | Purpose | |--------|----------|---------| | `engine.asset.findAssets()` | Asset Discovery | Query assets from a source with filtering and pagination | | `engine.asset.addAssetToSource()` | Asset Lifecycle | Add or update an asset in a local source | | `engine.asset.removeAssetFromSource()` | Asset Lifecycle | Remove a single asset from a local source | | `engine.asset.removeSource()` | Source Management | Remove an entire asset source and all its assets | | `engine.asset.assetSourceContentsChanged()` | UI Notification | Notify UI that source contents changed | | `engine.asset.onAssetSourceAdded()` | Event Subscriptions | Subscribe to source addition events | | `engine.asset.onAssetSourceRemoved()` | Event Subscriptions | Subscribe to source removal events | | `engine.asset.onAssetSourceUpdated()` | Event Subscriptions | Subscribe to source content change events | --- ## More Resources - **[Angular Documentation Index](https://img.ly/docs/cesdk/angular.md)** - Browse all Angular documentation - **[Complete Documentation](https://img.ly/docs/cesdk/angular/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/angular/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Supported File Formats for Import" description: "Review the supported image, video, audio, and template formats for importing assets into CE.SDK on the web." platform: angular url: "https://img.ly/docs/cesdk/angular/import-media/file-format-support-8cdc84/" --- > This is one page of the CE.SDK Angular documentation. For a complete overview, see the [Angular Documentation Index](https://img.ly/docs/cesdk/angular.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/angular/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/angular/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/angular/import-media-4e3703/) > [File Format Support](https://img.ly/docs/cesdk/angular/import-media/file-format-support-8cdc84/) --- When building creative applications with CE.SDK, understanding which file formats your users can import is crucial for delivering a smooth editing experience. CE.SDK supports a comprehensive range of modern media formats. This guide provides a complete reference of supported file formats for importing media, templates, and fonts into CE.SDK on web platforms. ## Supported Import Formats CE.SDK supports importing the following media types directly in the browser: | Category | Supported Formats | | --------------- | --------------------------------------------------------------------------------------- | | **Images** | `.png`, `.jpeg`, `.jpg`, `.gif`, `.webp`, `.svg`, `.bmp` | | **Video** | `.mp4` (H.264/AVC, H.265/HEVC), `.mov` (H.264/AVC, H.265/HEVC), `.webm` (VP8, VP9, AV1) | | **Audio** | `.mp3`, `.m4a`, `.mp4` (AAC or MP3), `.mov` (AAC or MP3) | | **Animation** | `.json` (Lottie) | | **Templates** | `.idml` (InDesign), `.psd` (Photoshop), `.scene` (CE.SDK Native) | > **Note:** Need to import a format not listed here? CE.SDK allows you to create custom > importers for any file type by using our Scene and Block APIs > programmatically. Contact our support team to learn more about implementing > custom importers. ## Video and Audio Codecs While container formats (`.mp4`, `.mov`, `.webm`) define how media is packaged, codecs determine how the content is compressed. CE.SDK supports the following codecs for web playback and editing: ### Video Codecs - **H.264 / AVC** (in `.mp4` or `.mov`) – Universally supported across all modern browsers - **H.265 / HEVC** (in `.mp4` or `.mov`) – Requires platform-specific support; availability varies by browser and operating system - **VP8, VP9, AV1** (in `.webm`) – Modern web-native codecs with broad browser support ### Audio Codecs - **MP3** (in `.mp3` files or within `.mp4`/`.mov` containers) - **AAC** (in `.m4a`, `.mp4`, or `.mov` containers) > **Warning:** H.265/HEVC support varies by browser and requires hardware acceleration or > licensed software decoders. Always test video playback on your target browsers > before deploying H.265 content to end users. ## Size Limits and Constraints CE.SDK processes all media client-side in the browser, which means performance is bounded by the user's hardware capabilities. Here are the practical limits to keep in mind: ### Image Resolution Limits | Constraint | Recommendation / Limit | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Input Resolution** | Maximum input resolution is **4096×4096 pixels**. Images from external sources (e.g., Unsplash) are resized to this size before rendering on the canvas. You can modify this value using the `maxImageSize` setting. | | **Output Resolution** | There is no enforced output resolution limit. Theoretically, the editor supports output sizes up to **16,384×16,384 pixels**. However, practical limits depend on the device's GPU capabilities and available memory. | All image processing in CE.SDK is handled client-side, so these values depend on the **maximum texture size** supported by the user's hardware. The default limit of 4096×4096 is a safe baseline that works universally. Higher resolutions (e.g., 8192×8192) may work on certain devices but could fail on others during export if the GPU texture size is exceeded. > **Note:** To ensure consistent results across devices, test higher output sizes on your > target hardware and set conservative defaults in production. ### Video Resolution and Duration Limits | Constraint | Recommendation / Limit | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Resolution** | Up to **4K UHD** is supported for **playback** and **export**, depending on the user's hardware and available GPU resources. For **import**, CE.SDK does not impose artificial limits, but maximum video size is bounded by the **32-bit address space of WebAssembly (wasm32)** and the **browser tab's memory cap (~2 GB)**. | | **Frame Rate** | 30 FPS at 1080p is broadly supported; 60 FPS and high-res exports benefit from hardware acceleration | | **Duration** | Stories and reels of up to **2 minutes** are fully supported. Longer videos are also supported, but we generally found a maximum duration of **10 minutes** to be a good balance for a smooth editing experience and a pleasant export duration of around one minute on modern hardware. | > **Note:** Performance scales with client hardware. For best results with high-resolution > or high-frame-rate video, modern CPUs/GPUs with hardware acceleration are > recommended. ## Format-Specific Considerations ### SVG Limitations CE.SDK uses Skia for SVG parsing and rendering. While most SVG files render correctly, there are some important limitations to be aware of: #### Text Elements - SVG text elements are not supported – any text in SVG files will not be rendered. - Convert text to paths in your vector editor before exporting if text is needed. #### Styling Limitations - CSS styles included in SVGs are not supported – use presentation attributes instead. - RGBA color syntax is not supported – use `fill-opacity` and `stroke-opacity` attributes. - When exporting SVGs from design tools, choose the "presentation attributes" option. #### Unsupported SVG Elements The following SVG elements are not supported: - Animation elements (``) - Foreign object (``) - Text-related elements (``, ``, ``) - Script elements (`