Filters
Full-page experience · Open playground
Compare every filter, an ordered filter array, and nested filter boundaries in one static gallery.
Try it
Section titled “Try it”Scan all thirteen samples; this lesson needs no interaction.
What to learn
Section titled “What to learn”- Compare all ten filters against one shared multicolor gradient.
- Contrast an ordered filter array with nested boundaries that execute inside out.
Integration boundaries
Section titled “Integration boundaries”- The Group makes one combined silhouette, so its rectangle, circle, and text cast one shared drop shadow rather than separate shadow stacks.
- A filter list runs in declaration order: each function receives the pixels produced by the preceding function.
This is a Pibbl Canvas example. Its mount owns its runtime resources; call its controller’s dispose method when removing it. The preview starts automatically unless reduced motion is enabled; Stop disposes the mount. Preserve the source’s logical canvas size and aspect ratio. Native HTML is not captured by Canvas filters.
Related APIs
Section titled “Related APIs”Complete source
Section titled “Complete source”Host setup for this source: use a canvas with width 960 and height 660, compile with jsx: "react-jsx" and jsxImportSource: "@pibbl/core", import its default mount, and call const controller = mount(canvas) after attaching the canvas. Call controller.dispose() before removing it.
filters.example.tsx
Section titled “filters.example.tsx”import { blur, brightness, contrast, dropShadow, grayscale, hueRotate, invert, opacity, saturate, sepia } from '@pibbl/core/filters';import { Circle, pibbl, Group, Rectangle, SvgPath, Text, type PibblController, type PibblFilter,} from "@pibbl/core";
const SAMPLE_WIDTH = 160;
const FILTER_SAMPLES = [ { label: "BLUR", detail: "4px", filter: blur({ radius: 4 }) }, { label: "BRIGHTNESS", detail: "135%", filter: brightness({ amount: 1.35 }), }, { label: "CONTRAST", detail: "150%", filter: contrast({ amount: 1.5 }), }, { label: "DROP SHADOW", detail: "8px 7px 4px", filter: dropShadow({
offsetX: 8, offsetY: 7, blurRadius: 4, color: "rgb(15 23 42)", }), }, { label: "GRAYSCALE", detail: "100%", filter: grayscale({ amount: 1 }), }, { label: "HUE ROTATE", detail: "120deg", filter: hueRotate({ degrees: 120 }), }, { label: "INVERT", detail: "100%", filter: invert({ amount: 1 }), }, { label: "OPACITY", detail: "55%", filter: opacity({ amount: 0.55 }), }, { label: "SATURATE", detail: "200%", filter: saturate({ amount: 2 }), }, { label: "SEPIA", detail: "100%", filter: sepia({ amount: 1 }), },] satisfies readonly { label: string; detail: string; filter: PibblFilter;}[];
const ORDERED_FILTERS = [ grayscale({ amount: 0.35 }), contrast({ amount: 1.4 }), dropShadow({
offsetX: 7, offsetY: 6, blurRadius: 3, color: "rgb(15 23 42)", }),] satisfies readonly PibblFilter[];
const NESTED_SHADOW = dropShadow({
offsetX: 7, offsetY: 6, blurRadius: 3, color: "rgb(15 23 42)",}) satisfies PibblFilter;
interface GradientArtworkProps { gradient: CanvasGradient; composite?: boolean;}
function GradientArtwork({ gradient, composite = false,}: GradientArtworkProps) { if (!composite) { return ( <Rectangle style={{ width: SAMPLE_WIDTH, height: 78, fill: gradient, stroke: "#17211d", strokeWidth: 3, }} /> ); }
return ( <Group> <SvgPath style={{ d: `M 12 0 H ${SAMPLE_WIDTH} V 78 H 12 Z`, fill: gradient, stroke: "#17211d", strokeWidth: 3, }} /> <Circle style={{ cx: 24, cy: 39, radius: 28, fill: "rgb(255 255 255 / 72%)", stroke: "#17211d", strokeWidth: 3, }} /> <Text style={{ left: 108, top: 48, fill: "#ffffff", font: "900 20px sans-serif", textAlign: "center", }} > ONE GROUP </Text> </Group> );}
interface TileHeadingProps { label: string; detail: string;}
function TileHeading({ label, detail }: TileHeadingProps) { return ( <Group> <Text style={{ left: 90, top: 18, fill: "#17211d", font: "800 13px monospace", textAlign: "center", }} > {label} </Text> <Text style={{ left: 90, top: 37, fill: "#64748b", font: "600 10px monospace", textAlign: "center", }} > {detail} </Text> </Group> );}
interface FilterTileProps extends TileHeadingProps { filter?: PibblFilter; gradient: CanvasGradient; x: number; y: number;}
function FilterTile({ filter, gradient, label, detail, x, y,}: FilterTileProps) { return ( <Group style={{ translateX: x, translateY: y }}> <TileHeading label={label} detail={detail} /> <Group style={{ filter, translateX: 10, translateY: 52 }}> <GradientArtwork gradient={gradient} /> </Group> </Group> );}
interface CompositeTileProps extends TileHeadingProps { gradient: CanvasGradient; mode: "array" | "nested"; x: number; y: number;}
function CompositeTile({ gradient, mode, label, detail, x, y,}: CompositeTileProps) { const artwork = <GradientArtwork gradient={gradient} composite />; const filteredArtwork = mode === "array" ? ( <Group style={{ filter: ORDERED_FILTERS }}>{artwork}</Group> ) : ( <Group style={{ filter: blur({ radius: 1.5 }) }}> <Group style={{ filter: NESTED_SHADOW }}> <Group style={{ filter: hueRotate({ degrees: 155 }) }}> {artwork} </Group> </Group> </Group> );
return ( <Group style={{ translateX: x, translateY: y }}> <TileHeading label={label} detail={detail} /> <Group style={{ translateX: 10, translateY: 52 }}> {filteredArtwork} </Group> </Group> );}
function tilePosition(index: number): { x: number; y: number } { const column = index < 10 ? index % 5 : index - 9; return { x: 5 + column * 190, y: 92 + Math.floor(index / 5) * 178, };}
function FilterGallery({ gradient }: { gradient: CanvasGradient }) { const normalPosition = tilePosition(0); const orderedPosition = tilePosition(11); const nestedPosition = tilePosition(12);
return [ <Rectangle style={{ left: 0, top: 0, width: 960, height: 660, fill: "#f7f0df" }} />, <Text style={{ left: 480, top: 42, fill: "#17211d", font: "900 28px sans-serif", textAlign: "center", }} > FILTER GALLERY </Text>, <Text style={{ left: 480, top: 68, fill: "#64748b", font: "600 12px monospace", textAlign: "center", }} > SAME GRADIENT · ONE FRAME · EVERY FILTER </Text>, <FilterTile gradient={gradient} label="NORMAL" detail="unfiltered" x={normalPosition.x} y={normalPosition.y} />, ...FILTER_SAMPLES.map((sample, index) => { const position = tilePosition(index + 1); return ( <FilterTile key={sample.label} filter={sample.filter} gradient={gradient} label={sample.label} detail={sample.detail} x={position.x} y={position.y} /> ); }), <CompositeTile gradient={gradient} mode="array" label="ORDERED ARRAY" detail="gray → contrast → shadow" x={orderedPosition.x} y={orderedPosition.y} />, <CompositeTile gradient={gradient} mode="nested" label="NESTED" detail="hue → shadow → blur" x={nestedPosition.x} y={nestedPosition.y} />, <Text style={{ left: 480, top: 642, fill: "#475569", font: "700 11px monospace", textAlign: "center", }} > LISTS RUN LEFT TO RIGHT · NESTED BOUNDARIES RUN INSIDE OUT </Text>, ];}
function createSharedGradient(canvas: HTMLCanvasElement): CanvasGradient { const context = canvas.getContext("2d"); if (!context) throw new Error("The Filters example requires a 2D context.");
const gradient = context.createLinearGradient(0, 0, SAMPLE_WIDTH, 0); gradient.addColorStop(0, "#0f172a"); gradient.addColorStop(0.14, "#ef4444"); gradient.addColorStop(0.3, "#facc15"); gradient.addColorStop(0.48, "#f8fafc"); gradient.addColorStop(0.64, "#10b981"); gradient.addColorStop(0.82, "#2563eb"); gradient.addColorStop(1, "#7c3aed"); return gradient;}
export default function mount(canvas: HTMLCanvasElement): PibblController { const gradient = createSharedGradient(canvas); return pibbl(canvas, <FilterGallery gradient={gradient} />);}Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision 380919b. ALPHA — NOT FOR PRODUCTION USE.