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
- The user selects a raster image element (JPEG/PNG).
- The element toolbar shows the Filters tool (sliders icon).
- The user picks a filter from the thumbnail grid.
- A non-blocking notification toast appears while the server processes the image.
- 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.
| ID | Name | Effect |
|---|---|---|
grayscale | Grayscale | Removes all color, converting to shades of gray. |
sepia | Sepia | Warm brownish tone reminiscent of old photographs. |
black_white | Black & White | High-contrast grayscale for a bold monochrome look. |
vintage | Vintage | Warm-toned retro feel with boosted contrast and subtle color shift. |
warm | Warm | Adds warm orange/red tones for a cozy, sunlit feel. |
cool | Cool | Adds blue tones for a calm, modern look. |
vivid | Vivid | Boosted contrast and saturation for punchy, vibrant colors. |
fade | Fade | Lifted shadows and muted tones for a dreamy, washed-out look. |
dramatic | Dramatic | Deep contrast with darkened shadows and subtle blue-shift. |
sharpen | Sharpen | Edge 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
| Parameter | Type | Description |
|---|---|---|
filter | string | The filter id from the config. |
url | string | URL-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"
}
| Field | Type | Description |
|---|---|---|
image_src | string | Public 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]);
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);
});
| Parameter | Type | Description |
|---|---|---|
element | ICVElement | The image element to filter. Must have getType() === 'image'. |
filterId | string | false | Filter 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
sourceis saved as_originalSource. - Switching filters always re-processes from
_originalSource(not the previously filtered image). - Passing
filterId: falserestores_originalSourceas the element's source and clears the filter state. - The active filter ID is stored on the element as
filter(string orfalse).
UX Details
Toolbar Layout
The filter grid adapts to the toolbar display mode:
| Mode | Layout | Labels |
|---|---|---|
| Floating (smart mode) | Horizontal scroll row | Tooltip on hover |
| Mobile | Horizontal scroll row | Tooltip on hover |
| Sidebar | 2-column grid | Text label below thumbnail |
Loading State
While the server processes the filter:
- A non-blocking
cv-notificationtoast appears with a spinner and "Applying filter..." message. - The filter grid is dimmed and non-interactive (prevents concurrent filter requests).
- 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
- Options Reference — Full
toolbarconfig reference - Actions — Configure the element toolbar and action bars
- AI Try-On — Another image processing feature