An Image block's filters property (see {overlay}.json's image block for the full property reference) is a CSS-flavored chain of filter calls, applied to the resolved image - whatever source it came from: a static asset, a photo field, or a signature field (these all resolve to the same kind of file, so filters work identically regardless of which one a block is bound to).

{ "id": "logo", "type": "image", "source": "assets:dealer_logo.png",
  "fit": "contain", "filters": "grayscale() contrast(120%) sepia()" }

Calls are space-separated; a filter with no arguments can drop the parentheses (grayscale and grayscale() are the same thing). Leave filters unset for no effect - the default.

The Overlay Editor's canvas shows the filter chain live as you type, not just after generating a PDF.

Catalog

Deliberately a simple, reliable subset, not the full CSS filter vocabulary - every entry below is a plain per-pixel color operation (no blur, no drop shadow, no other spatial/kernel filter):

Filter Arguments
invert() none
grayscale() none
sepia() none
colorize(hex) one hex color, or an inputs:field_id reference (see {overlay}.json's "Color values") - tone-preserving recolor, see the callout below
rgbreplace(hex) one hex color, or an inputs:field_id reference (see {overlay}.json's "Color values") - flat recolor, see the callout below
brightness(pct) one percentage - 100% is unchanged
contrast(pct) one percentage - 100% is unchanged
saturate(pct) one percentage - 100% is unchanged
hue-rotate(deg) one angle

Resolution

Before any filter runs, a source image larger than its block's own print size is downsized to that size (200 DPI reference) - never upscaled. A photo straight off a phone camera can be many megapixels; the block it's printed into is usually a few square inches, so there's nothing to gain from filtering pixels that never make it into the final PDF. This has no visible effect on quality - nothing renders above print resolution anyway - it just keeps a large source photo fast to filter.

A second, absolute ceiling sits on top of that, independent of print size: neither axis is ever filtered above the app's Max Filter Resolution setting (default 1240px), even for a poster-sized block whose own print-size target would otherwise still be large. Since every filter here is a plain per-pixel color operation with no detail-dependent effect, a filtered image capped below its actual print resolution looks the same, just cheaper to produce - a deliberate quality/ speed tradeoff bounding worst-case filter time for very large physical documents, not a bug.

colorize() vs. rgbreplace()

rgbreplace() replaces every non-transparent pixel with one flat, uniform color, preserving only alpha - it never looks at the pixel's own brightness. That's exactly right for alpha-masked content: a captured signature (solid ink on a transparent background) or a logo SVG, where every "inked" pixel really is meant to become the same target color. It's the wrong tool for a fully-opaque source (any JPEG, most PNGs) - with no transparency to shape the effect, every pixel gets the same flat color and the whole image turns into a solid rectangle, all photo detail gone.

colorize() is the tool for that case instead: it keeps each pixel's own lightness and only shifts its hue/saturation to match the target color, so shadows stay dark and highlights stay light - a genuine duotone tint rather than a flat block:

{ "filters": "colorize(#1c4e80)" }

For finer manual control than a single target color gives you, the same effect can still be built by hand from the more basic filters:

{ "filters": "grayscale() sepia() hue-rotate(200deg) saturate(220%)" }

SVG assets

An SVG-sourced Image block never rasterizes - it stays crisp vector output at any size, no matter which filters are applied. rgbreplace() applies natively, the same way a fill/stroke recolor already works on an SVG. Every other filter, including colorize(), applies by reading the SVG's own literal fill/stroke colors and replacing each one with its own filtered value - correct for flat/icon SVGs (a small number of solid colors), which is the kind of SVG this app supports best anyway. A color the SVG expresses some other way (a gradient, currentColor, a named CSS color) is left as-is.

Failure handling

A syntax error, an unrecognized filter name, or the wrong argument count/type for a known filter doesn't fail the whole chain, or the block, or the PDF - that one call is simply skipped and every other valid call in the chain still applies. The Overlay Editor's Filters field flags this with a red outline as you type, but never blocks Save.