Image Proxy Loading
High-resolution photos are the fastest way to crash a mobile browser. Canvas memory is driven by decoded pixels (width × height × 4 bytes), not file size — a single 6000×4000 photo costs ~96 MB of RAM decoded, regardless of how well the JPEG compresses on disk. A few of those on one canvas will exhaust iOS Safari's canvas budget and kill the tab.
Image proxy loading solves this by rendering a downscaled proxy on the canvas while keeping the full-resolution original for print export. Editing stays smooth and light; orders and exports are always full quality. source never changes — it always references the original.
There are two modes, and they compose:
| Mode | Where the proxy comes from | Saves |
|---|---|---|
| Client-side proxying (default) | Generated in the browser after the original downloads | Memory |
| Hybrid (server-side) proxying | A separate server-hosted variant URL (proxySource); the original is never fetched client-side | Memory and bandwidth |
Both leave source untouched, so exports and orders are identical either way.
Client-side proxying (automatic)
By default, any raster image whose natural width or height exceeds the imageProxyMaxDimension option (default 1600) is downscaled to an in-memory proxy for on-canvas editing. A 25% slack applies — images only slightly over the cap aren't worth downscaling — so the effective trigger is 1600 × 1.25 = 2000px.
The proxy is a <canvas> element generated from the loaded original. It is never re-encoded to JPEG/PNG and never serialized, so there is no format, transparency, or payload concern.
What is preserved:
source, logical dimensions, andscaleX/scaleYare unchanged — serialization and orders always reference the full-resolution original.- Print exports — both server-side and
exportPrintAreaToSVG()— render from the original, never the proxy. - DPI warnings and the size tooltip rate the original via
originalWidth/originalHeight, which are serialized in the element JSON for proxied images (alongsideoriginalFileSizewhen known). isProxyis exposed at runtime on the element so your UI can react to it. It is not serialized.
Notes:
- SVGs are never proxied — they're vector, so there's no decoded-pixel cost to avoid.
- Generation prefers
createImageBitmapwith resize options (downscales during decode, avoiding a full-resolution intermediate canvas) and falls back to stepped canvas halving where that isn't supported. - Proxies are cached in-memory (bounded LRU) keyed by
source+ max dimension, so the same image reused across multiple views generates one proxy. - If generation fails for any reason, the canvas renders the full-resolution original — proxying never blocks an image from appearing.
Configuration
imageProxyMaxDimension is a global option:
const chamevo = await createCustomizer('#el', {
imageProxyMaxDimension: 1600, // default
});
| Value | Effect |
|---|---|
1600 (default) | Downscale raster images larger than ~2000px (with slack). |
Higher (e.g. 2400) | Sharper on-screen zoom, more memory per image. |
0 | Disable proxying entirely — always render originals. |
See the options reference for the full option list.
Opting out per element or per group
Set imageProxy: false on an element to always render its original, regardless of imageProxyMaxDimension:
await chamevo.canvas.addElement('image', largePosterUrl, 'Poster', {
imageProxy: false, // always render full resolution
});
Because imageProxy flows through the parameter cascade, you can exempt whole groups of images:
const options = {
// Configured product images always render at full resolution…
imageParameters: { imageProxy: false },
// …while user uploads stay proxied (the default).
};
Use customImageParameters: { imageProxy: false } for the reverse — proxy configured images but never user uploads.
Hybrid (server-side) proxying
When an element carries a proxySource URL plus originalWidth/originalHeight, the canvas loads only that server-hosted variant. The full-resolution source is never downloaded client-side, so you save the download bandwidth on top of the decode memory — a significant win on mobile connections and for very large originals.
await chamevo.canvas.addElement('image', originalUrl, 'Photo', {
proxySource: 'https://cdn.example.com/uploads/photo-1600.jpg',
originalWidth: 5760,
originalHeight: 3840,
});
Behavior:
- The canvas renders the variant at
proxySource;sourcestays the full-resolution original for orders and export. originalWidth/originalHeightare required — DPI warnings and the size tooltip rate the original, and they must be known without downloading it.proxySourceis serialized. Restored designs keep skipping the original, so reopening a saved design is just as light as the first load.- Fallback: if the variant fails to load, the original loads with client-side proxying applied — the image always appears.
Automatic wiring
You rarely set proxySource by hand. The built-in modules wire it for you:
- Uploads module (
cv-image-source-upload) — when the upload server's JSON response includes animage_proxy_src, the module attaches it asproxySourcealong with the returnedwidth/heightasoriginalWidth/originalHeight. - Pixabay picks (
cv-image-source-pixabay) — high-resolution hits use Pixabay's ownwebformatURLas the variant, so the canvas loads the web-sized image whilesourcestays the fullimageURL.
Upload server contract
To enable hybrid proxying for uploads, your upload endpoint returns the variant URL alongside the original. The response fields the uploads module reads:
{
"source": "https://cdn.example.com/uploads/photo.jpg",
"image_proxy_src": "https://cdn.example.com/uploads/photo-1600.jpg",
"width": 5760,
"height": 3840
}
image_proxy_src→ elementproxySource(omit ornullto fall back to client-side proxying)width/height→originalWidth/originalHeight
The playground ships a reference implementation that generates the downscaled variant server-side and returns these fields — see apps/playground/php/file-upload/upload-image.php for the full flow.
Element fields reference
Set on ElementParameters (see the elements guide):
| Field | Type | Default | Description |
|---|---|---|---|
imageProxy | boolean | true | Allow proxy loading for this element. false = always render the full-resolution original. |
proxySource | string | — | Server-hosted downscaled variant URL (hybrid mode). Requires originalWidth/originalHeight. Serialized. |
originalWidth | number | — | Natural pixel width of the original. Serialized for proxied images; required for proxySource. |
originalHeight | number | — | Natural pixel height of the original. Serialized for proxied images; required for proxySource. |
originalFileSize | number | — | Original file size in bytes, when known. Serialized. |
isProxy | boolean | — | Runtime only (not serialized). true when the canvas is currently rendering a proxy. |
Global option (see the options reference):
| Option | Type | Default | Scope | Description |
|---|---|---|---|---|
imageProxyMaxDimension | number | 1600 | global | Max on-canvas pixel dimension for raster images. Larger images are proxied. 0 disables. |
Guarantees
Whichever mode is active, the contract is the same: source always stays the full-resolution original, and orders plus print exports are byte-for-byte identical to a non-proxied design. Proxying is a rendering optimization — it never changes what the customer buys or what ships to production.