Skip to content

Filters

Stopped
The complete example source and integration notes are available below.

Read as Markdown

Full-page experience · Open playground

Compare every filter, an ordered filter array, and nested filter boundaries in one static gallery.

Scan all thirteen samples; this lesson needs no interaction.

  • Compare all ten filters against one shared multicolor gradient.
  • Contrast an ordered filter array with nested boundaries that execute inside out.
  • 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.

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.

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 built with @pibbl/core 0.0.2, revision 2dccb19. ALPHA — NOT FOR PRODUCTION USE.