Search
Loading...
Skip to content

Duotone

Apply duotone effects to images in server-side workflows for batch processing and automated image enhancement.

5 mins
estimated time
Download
StackBlitz
GitHub

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. CE.SDK’s duotone system works identically in server environments, making it ideal for batch processing, automated pipelines, and headless image generation.

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.

Check Effect Support#

Before applying effects programmatically, verify the block supports them. Only graphic blocks with image or video fills can have effects applied:

// 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:

// Query duotone presets from the asset library
const duotoneResults = hasAssetSources
? await engine.asset.findAssets('ly.img.filter.duotone', {
page: 0,
perPage: 10
})
: { assets: [] };
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:

// 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:

// Configure effect with preset colors (convert hex to RGBA)
if (duotonePresets.length > 0) {
const preset = duotonePresets[0];
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)
);
}
} else {
// Fallback: Desert preset colors (#5c3a15, #f0d8b8)
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/darkColor',
hexToRgba('#5c3a15')
);
engine.block.setColor(
duotoneEffect,
'effect/duotone_filter/lightColor',
hexToRgba('#f0d8b8')
);
}
engine.block.setFloat(
duotoneEffect,
'effect/duotone_filter/intensity',
0.9
);

Append Effect to Block#

Attach the fully configured effect to an image block:

// 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():

// 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 RelationshipVisual EffectExample Use Case
High contrastBold, graphic lookSocial media, posters
Low contrastSubtle, sophisticatedEditorial, luxury brands
Warm to coolDynamic temperature shiftLifestyle, fashion
MonochromaticTinted photography styleVintage, 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)

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:

// 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:

// 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:

// 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:

// 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:

// Destroy removed effect blocks to free memory
if (effectsOnCustom.length > 0) {
engine.block.destroy(effectsOnCustom[0]);
}

Duotone Properties#

PropertyTypeRangeDescription
effect/duotone_filter/darkColorColorRGBA (0-1)Color applied to shadows and dark tones
effect/duotone_filter/lightColorColorRGBA (0-1)Color applied to highlights and light tones
effect/duotone_filter/intensityFloat0.0 - 1.0Effect 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 memory
  • Dispose the engine: In server environments, always call engine.dispose() when done to release all resources
  • Process in batches: For large image sets, process images in batches rather than loading all at once to manage memory usage

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#

MethodDescription
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