Skip to main content

Image Filters

Image filters let users apply visual effects (grayscale, sepia, vintage, etc.) to image elements on the canvas. Filters are processed server-side — the original image is sent to your server, processed with the selected filter, and the result replaces the element's source.

How It Works

  1. The user selects a raster image element (JPEG/PNG).
  2. The element toolbar shows the Filters tool (sliders icon).
  3. The user picks a filter from the thumbnail grid.
  4. A non-blocking notification toast appears while the server processes the image.
  5. The filtered image replaces the element source. The original is preserved for undo/revert.

Quick Start

1. Configure Filters

Add filter entries to toolbar.filters in your options. Each entry needs an id, name, and thumbnail:

const customizer = createCustomizer('#container', {
toolbar: {
filters: [
{ id: 'grayscale', name: 'Grayscale', thumbnail: '/images/filters/grayscale.jpg' },
{ id: 'sepia', name: 'Sepia', thumbnail: '/images/filters/sepia.jpg' },
{ id: 'black_white', name: 'Black & White', thumbnail: '/images/filters/black_white.jpg' },
{ id: 'vintage', name: 'Vintage', thumbnail: '/images/filters/vintage.jpg' },
{ id: 'warm', name: 'Warm', thumbnail: '/images/filters/warm.jpg' },
{ id: 'cool', name: 'Cool', thumbnail: '/images/filters/cool.jpg' },
{ id: 'vivid', name: 'Vivid', thumbnail: '/images/filters/vivid.jpg' },
{ id: 'fade', name: 'Fade', thumbnail: '/images/filters/fade.jpg' },
{ id: 'dramatic', name: 'Dramatic', thumbnail: '/images/filters/dramatic.jpg' },
{ id: 'sharpen', name: 'Sharpen', thumbnail: '/images/filters/sharpen.jpg' },
],
},
modulesConfig: {
uploads: {
fileServerURL: '/php/file-upload/index.php',
},
},
});

2. Select a Raster Image

No per-element flag is required. Once toolbar.filters is configured, the Filters tool appears automatically for any raster image element (JPEG/PNG). It is hidden for vector (SVG) elements.

3. Implement the Server Endpoint

The filter request is sent to the same fileServerURL used for uploads. See Server-Side Implementation below.

Available Filters

ChamevoJS ships with 10 recommended filter presets. You can use any subset or add your own custom filters.

IDNameEffect
grayscaleGrayscaleRemoves all color, converting to shades of gray.
sepiaSepiaWarm brownish tone reminiscent of old photographs.
black_whiteBlack & WhiteHigh-contrast grayscale for a bold monochrome look.
vintageVintageWarm-toned retro feel with boosted contrast and subtle color shift.
warmWarmAdds warm orange/red tones for a cozy, sunlit feel.
coolCoolAdds blue tones for a calm, modern look.
vividVividBoosted contrast and saturation for punchy, vibrant colors.
fadeFadeLifted shadows and muted tones for a dreamy, washed-out look.
dramaticDramaticDeep contrast with darkened shadows and subtle blue-shift.
sharpenSharpenEdge enhancement for crisper details.

Filter Thumbnails

Each filter needs a small preview thumbnail showing the effect applied to a sample image. Thumbnails should be:

  • Square — consistent aspect ratio in the grid
  • Small — 60–100px display size, 200×200px source recommended
  • JPEG — for small file sizes

You can generate thumbnails from a base image using ImageMagick or any image processing tool. A sample script is included in the repository at tools/scripts/generate-filter-thumbnails.sh.

Server-Side Implementation

When the user selects a filter, ChamevoJS sends a GET request to fileServerURL:

GET /php/file-upload/index.php?filter=sepia&url=https%3A%2F%2Fexample.com%2Fphoto.jpg
ParameterTypeDescription
filterstringThe filter id from the config.
urlstringURL-encoded source image URL. This is always the original image, even when switching between filters.

Expected Response

Return a JSON object with the processed image URL:

{
"image_src": "/uploads/2026/03/filtered_abc123.png"
}
FieldTypeDescription
image_srcstringPublic URL path to the filtered image on your server. Must be accessible by the browser.

Error Response

Return a JSON object with an error field:

{
"error": "Invalid or missing filter parameter."
}

PHP Example

Here's a complete PHP implementation using the GD library:

<?php
$filter_id = isset($_GET['filter']) ? trim($_GET['filter']) : '';
$source_url = isset($_GET['url']) ? trim($_GET['url']) : '';

$allowed_filters = [
'grayscale', 'sepia', 'black_white', 'vintage',
'warm', 'cool', 'vivid', 'fade', 'dramatic', 'sharpen'
];

if (empty($filter_id) || !in_array($filter_id, $allowed_filters)) {
die(json_encode(['error' => 'Invalid or missing filter parameter.']));
}

if (empty($source_url) || !preg_match('#^https?://#i', $source_url)) {
die(json_encode(['error' => 'Invalid source URL.']));
}

// Download and create image resource
$raw = @file_get_contents($source_url);
$src_image = @imagecreatefromstring($raw);
if ($src_image === false) {
die(json_encode(['error' => 'Not a supported image format.']));
}

$width = imagesx($src_image);
$height = imagesy($src_image);

// Create output canvas
$out = imagecreatetruecolor($width, $height);
imagealphablending($out, false);
imagesavealpha($out, true);
imagecopy($out, $src_image, 0, 0, 0, 0, $width, $height);
imagedestroy($src_image);

// Apply filter
switch ($filter_id) {
case 'grayscale':
imagefilter($out, IMG_FILTER_GRAYSCALE);
break;
case 'sepia':
imagefilter($out, IMG_FILTER_GRAYSCALE);
imagefilter($out, IMG_FILTER_COLORIZE, 90, 60, 30);
break;
case 'black_white':
imagefilter($out, IMG_FILTER_GRAYSCALE);
imagefilter($out, IMG_FILTER_CONTRAST, -30);
break;
case 'vintage':
imagefilter($out, IMG_FILTER_CONTRAST, -15);
imagefilter($out, IMG_FILTER_COLORIZE, 30, 15, -10);
imagefilter($out, IMG_FILTER_BRIGHTNESS, 5);
break;
case 'warm':
imagefilter($out, IMG_FILTER_COLORIZE, 15, 7, -10);
imagefilter($out, IMG_FILTER_BRIGHTNESS, 5);
break;
case 'cool':
imagefilter($out, IMG_FILTER_COLORIZE, -10, 0, 15);
imagefilter($out, IMG_FILTER_BRIGHTNESS, 5);
break;
case 'vivid':
imagefilter($out, IMG_FILTER_CONTRAST, -20);
imagefilter($out, IMG_FILTER_COLORIZE, 10, 5, 0);
imagefilter($out, IMG_FILTER_BRIGHTNESS, 10);
break;
case 'fade':
imagefilter($out, IMG_FILTER_CONTRAST, 15);
imagefilter($out, IMG_FILTER_BRIGHTNESS, 20);
imagefilter($out, IMG_FILTER_COLORIZE, 5, 5, 10);
break;
case 'dramatic':
imagefilter($out, IMG_FILTER_CONTRAST, -30);
imagefilter($out, IMG_FILTER_BRIGHTNESS, -10);
imagefilter($out, IMG_FILTER_COLORIZE, -5, -5, 5);
break;
case 'sharpen':
$matrix = [[0, -1, 0], [-1, 5, -1], [0, -1, 0]];
imageconvolution($out, $matrix, 1, 0);
break;
}

// Save and return URL
$filename = md5(uniqid(rand(), true)) . '.png';
$save_path = __DIR__ . '/uploads/' . $filename;
imagepng($out, $save_path, 9);
imagedestroy($out);

echo json_encode(['image_src' => '/php/file-upload/uploads/' . $filename]);
Other Languages

The server endpoint can be implemented in any language (Node.js, Python, Go, etc.) as long as it accepts the filter and url GET parameters and returns { "image_src": "..." }.

Custom Filters

You can add your own custom filters beyond the 10 built-in presets. Just add entries to the toolbar.filters array with your own IDs and implement the corresponding server-side logic:

toolbar: {
filters: [
// Built-in presets
{ id: 'grayscale', name: 'Grayscale', thumbnail: '/images/filters/grayscale.jpg' },
// Your custom filter
{ id: 'duotone_blue', name: 'Blue Duotone', thumbnail: '/images/filters/duotone_blue.jpg' },
],
},

Then handle duotone_blue in your server-side filter endpoint alongside the built-in IDs.

API Reference

chamevo.applyImageFilter(element, filterId)

Apply a filter programmatically:

const customizer = document.querySelector('cv-customizer');
customizer.addEventListener('cvReady', (e) => {
const chamevo = e.detail.chamevo;

// Apply a filter
await chamevo.applyImageFilter(selectedElement, 'sepia');

// Remove the filter (revert to original)
await chamevo.applyImageFilter(selectedElement, false);
});
ParameterTypeDescription
elementICVElementThe image element to filter. Must have getType() === 'image'.
filterIdstring | falseFilter ID to apply, or false to revert to the original image.

Returns a Promise<void> that resolves when the filtered image has been loaded.

How Original Images Are Preserved

  • On the first filter application, the element's current source is saved as _originalSource.
  • Switching filters always re-processes from _originalSource (not the previously filtered image).
  • Passing filterId: false restores _originalSource as the element's source and clears the filter state.
  • The active filter ID is stored on the element as filter (string or false).

UX Details

Toolbar Layout

The filter grid adapts to the toolbar display mode:

ModeLayoutLabels
Floating (smart mode)Horizontal scroll rowTooltip on hover
MobileHorizontal scroll rowTooltip on hover
Sidebar2-column gridText label below thumbnail

Loading State

While the server processes the filter:

  1. A non-blocking cv-notification toast appears with a spinner and "Applying filter..." message.
  2. The filter grid is dimmed and non-interactive (prevents concurrent filter requests).
  3. The user can still interact with the canvas and other UI elements.

Undo/Redo

Filter changes are recorded in the undo/redo history. Undoing a filter application restores the previous image source.

i18n

The loading notification label can be translated:

customizer.options = {
labels: {
'toolbar.applying_filter': 'Applying filter...',
},
};

Next Steps