Hypercomp
An easy to use, powerful and expressive image processing library for TypeScript that compiles to SVG filters, letting you apply complex visual effects to your HTML and SVG elements.
Motivation
SVG filters provide an extremely powerful node-based effect system that can be used across HTML and SVG, but they are held back by the fact that it is impossible to express function composition in XML in a natural way, making them very hard to write, compose and reuse. Hypercomp fixes this by providing a functional API for composing effects in TypeScript, and compiling them down to an equivalent SVG filter.
Features
- Composable filters: Define complex, composable and reusable filter effects using JavaScript functions.
- Full SVG filter support: Anything you can do with SVG filters, you can do with Hypercomp.
- A fluid, functional API: Write expressive and readable code that is easy to understand and maintain.
Installation
npm install hypercomp
Examples
The following example creates a squiggly line around the source image by extracting the source image from a slightly dilated version of itself, and then displacing it using fractal noise.
import { Effect, css } from "hypercomp";
const radius = 2;
const width = 2;
const freq = 0.01;
const octaves = 1;
const scale = 15;
const effects =
Effect.flood("#30597E")
.in(Effect.source.dilate(radius + width))
.out(Effect.source.dilate(radius))
.displace(fractalNoise(freq, { octaves }), scale)
.over(Effect.source);
const style = {
filter: css(effects),
};
return <div style={style}>hello world</div>;
The following example takes the source, extracts sharp edges, applies a dilation effect, and adds a blue shadow.
import { Effect, css } from "hypercomp";
const withEdges = Effect.merge([
Effect.source,
Effect.source.convolve([1, 0, -1, 2, 0, -2, 1, 0, -1]),
Effect.source.convolve([1, 2, 1, 0, 0, 0, -1, -2, -1]),
])
.dilate(2)
.shadow(1, { color: "blue" });
const style = {
filter: css(withEdges),
};
return <div style={style}>hello world</div>;
The following example blurs the source image, uses it as the bump map for lighting, and then composites the lighting onto a black background using screen blending.
import { Effect, Light, css } from "hypercomp";
const withLight = Effect.flood("black").screen(
Effect.sourceAlpha
.blur(1)
.specular(Light.pointLight({ x: 460, y: 110, z: 680 }), {
strength: 4,
shininess: 20,
})
);
const style = {
filter: css(withLight),
};
return <div style={style}>hello world</div>;
More examples
Prerequisites
Knowledge of image manipulation functions or SVG filter effects is helpful. Study the examples to get an intuition for how certain effects can be achieved.
API
- Render functions
- Creating filters
- Constants
- Basis functions
- Compositing functions
- Common functions
- Blending functions
.normal(input, config?)
.screen(input, config?)
.multiply(input, config?)
.overlay(input, config?)
.darken(input, config?)
.lighten(input, config?)
.colorDodge(input, config?)
.colorBurn(input, config?)
.hardLight(input, config?)
.softLight(input, config?)
.difference(input, config?)
.exclusion(input, config?)
.hue(input, config?)
.saturation(input, config?)
.color(input, config?)
.luminosity(input, config?)
- Morphology functions
- Color manipulation functions
- Lighting functions
- Light sources
- Component transfer functions
Render functions
css(filter, key?)
Compiles the filter and returns a CSS filter
property value.
When key is present, the filter is rendered to the dom. Subsequent calls with the same key will return the same data URL, and update the rendered filter. When key is omitted, the effect is rendered to a data URL.
IMPORTANT:
Currently, using the keyless version and rendering to a data url is not recommended.
- Safari does not support data URLs for CSS filters.
- The
.image()
function won't work in Chrome.
It is thus recommended to always use the key
parameter, or to use the useFilter
hook from hypercomp/react
which will create and unmount filters automatically.
- filter: The filter to render.
- key: (optional) Key to use for rendering to the DOM. The same key can be passed to
unmount()
to unmount the filter from the dom. - Returns: A
string
suitable for use in CSS filter
property.
unmount(key)
Unmounts the filter with the given key from the DOM.
- key: Key of the filter to unmount.
compile(filter)
Returns the compiled filter as a a string. If you pass the result of an effect function directly, it will only return the equivalent effects without the outer <filter>
tag. If you wrap it in a filter()
call, it will return the full SVG filter including the <filter>
tag.
For example, compile(Effect.source.blur(2))
will return <feGaussianBlur stdDeviation="2"/>
, while compile(filter(Effect.source.blur(2)))
will return <filter><feGaussianBlur stdDeviation="2"/></filter>
.
Usage with React
The hypercomp/react
module provides the useFilter
hook, which you can use to tie the lifetime of a dom-rendered filter to the lifetime of a component. Recommended for React projects.
import { Effect, useFilter } from "hypercomp/react";
const MyComponent = () => {
return (
<div style={{ filter: useFilter(Effect.source.dilate(2)) }}>
hello world
</div>
);
};
Creating filters
You create filters by composing effects. They can be arbitrarily chained, provided as inputs to other effects, saved, reused, or compiled to an output using the css
or compile
functions.
Effect functions usually take 0-2 effect inputs, 0-1 effect arguments and an optional configuration object. Each configuration object takes at the minimum the x
, y
, width
, and height
properties to define the region of the filter effect, which is not documented individually here.
When you are done composing your filter, you can compile it or render it by passing it to the compile
or css
functions.
Optionally, you can call .filter()
on your effect, which lets you specify the attributes of the rendered filter tag.
.filter(attributes?)
Lets you specify attributes of the rendered filter tag. This is optional, if you don't call .filter()
, the behavior will depend on the render function you use.
- attributes: (optional) SVG filter attributes.
- id:
string
- Filter ID. - x:
number
- X coordinate. - y:
number
- Y coordinate. - width:
number
- Width. - height:
number
- Height. - filterUnits:
"userSpaceOnUse" | "objectBoundingBox"
- Filter units. - primitiveUnits:
"userSpaceOnUse" | "objectBoundingBox"
- Primitive units. - colorInterpolationFilters:
"auto" | "sRGB" | "linearRGB" | "inherit"
- Color interpolation filters.
Constants
Constants are available as static properties on the Effect
class and can be used as inputs or starting points to effect chains.
Example:
Effect.flood("red").in(Effect.source.blur(2));
Effect.source
: Represents SourceGraphic
.Effect.sourceAlpha
: Represents SourceAlpha
.Effect.background
: Represents BackgroundImage
.Effect.backgroundAlpha
: Represents BackgroundAlpha
.Effect.fill
: Represents FillPaint
.Effect.stroke
: Represents StrokePaint
.
Basis functions
In addition to constants, the following functions are available as static methods on the Effect
class and can be used as inputs or starting points to effects.
Effect.flood(color, config?)
Applies a flood effect with the given color and opacity.
- color:
string
(optional) - The flood color. - config: (optional) Configuration options.
- opacity:
number
- Opacity of the flood effect.
Compiles to: <feFlood>
Effect.image(href, config?)
Creates an image.
- href:
string
- URL of the image. - config: (optional) Configuration options.
- preserveAspectRatio:
string
- Aspect ratio handling. - crossorigin:
"anonymous" | "use-credentials"
- Cross-origin settings.
Compiles to: <feImage>
Effect.merge(inputs, config?)
Merges an array of inputs with the last element as the topmost layer.
- inputs:
Effect[]
- Array of inputs to merge. - config: (optional) Configuration options.
Compiles to: <feMerge>
Effect.turbulence(frequency, config?)
Creates turbulence noise.
- frequency:
number
- Base frequency. - config: (optional) Configuration options.
- octaves:
number
- Number of octaves. - seed:
number
- Random seed value. - stitch:
"noStitch" | "stitch"
- Tile stitching mode.
Compiles to: <feTurbulence>
Effect.fractalNoise(frequency, config?)
Creates fractal noise.
- frequency:
number
- Base frequency. - config: (optional) Configuration options.
- octaves:
number
- Number of octaves. - seed:
number
- Random seed value. - stitch:
"noStitch" | "stitch"
- Tile stitching mode.
Compositing functions
.over(input, config?)
Composites the input using the "over" operator.
- input: The bottom layer.
- config: (optional) Configuration options.
Compiles to: <feComposite>
.inside(input, config?)
Composites the input using the "in" operator.
- input: The bottom layer.
- config: (optional) Configuration options.
Compiles to: <feComposite>
.out(input, config?)
Composites the input using the "out" operator.
- input: The bottom layer.
- config: (optional) Configuration options.
Compiles to: <feComposite>
.xor(input, config?)
Composites the input using the "xor" operator.
- input: The bottom layer.
- config: (optional) Configuration options.
Compiles to: <feComposite>
.atop(input, config?)
Composites the input using the "atop" operator.
- input: The bottom layer.
- config: (optional) Configuration options.
Compiles to: <feComposite>
.arithmetic(input, config?)
Composites the input using the "arithmetic" operator.
- input: The bottom layer.
- config: (optional) Configuration options.
- k1, k2, k3, k4:
number
- Coefficients for the arithmetic operation.
Compiles to: <feComposite>
Common functions
.blur(stdDeviation, config?)
Applies a Gaussian blur.
- stdDeviation:
number
- Standard deviation of the blur. - config: (optional) Configuration options.
- edgeMode:
"duplicate" | "wrap" | "none"
- Edge handling mode.
Compiles to: <feGaussianBlur>
.convolve(kernel, config?)
Applies a convolution matrix.
- kernel:
number[]
- Kernel matrix values. - config: (optional) Configuration options.
- edgeMode:
"duplicate" | "wrap" | "none"
- Edge handling mode. - order:
number | number[]
- Kernel size. If a single number is provided, it is assumed to be a square kernel. When an array is provided, the first element is the width and the second element is the height. - bias:
number
- Bias value. - divisor:
number
- Divisor value. - targetX:
number
- Target X coordinate. - targetY:
number
- Target Y coordinate. - preserveAlpha:
boolean
- Preserve alpha channel.
Compiles to: <feConvolveMatrix>
.displace(scale, config?)
Applies a displacement map.
- input1: The input to displace.
- input2: The displacement map.
- scale:
number
- Scale factor for displacement. - config: (optional) Configuration options.
- xChannel:
"R" | "G" | "B" | "A"
- X channel. - yChannel:
"R" | "G" | "B" | "A"
- Y channel.
Compiles to: <feDisplacementMap>
.offset(config?)
Applies an offset.
- config: (optional) Configuration options.
- dx:
number
- Horizontal offset. - dy:
number
- Vertical offset.
Compiles to: <feOffset>
.shadow(stdDeviation?, config?)
Adds a drop shadow.
- stdDeviation:
number
(optional) - Standard deviation for the blur. Defaults to 2. - config: (optional) Configuration options.
- dx:
number
- Horizontal offset. - dy:
number
- Vertical offset. - color:
string
- Shadow color. - opacity:
number
- Shadow opacity.
Compiles to: <feDropShadow>
.tile(config?)
Applies a tile effect.
- config: (optional) Configuration options.
Compiles to: <feTile>
Blending functions
.normal(input, config?)
Blends the input using the "normal" mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.screen(input, config?)
Blends the input using the screen mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.multiply(input, config?)
Blends the input using the multiply mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.overlay(input, config?)
Blends the input using the overlay mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.darken(input, config?)
Blends the input using the darken mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.lighten(input, config?)
Blends the input using the lighten mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.colorDodge(input, config?)
Blends the input using the color dodge mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.colorBurn(input, config?)
Blends the input using the color burn mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.hardLight(input, config?)
Blends
the input using the hard light mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.softLight(input, config?)
Blends the input using the soft light mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.difference(input, config?)
Blends the input using the difference mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.exclusion(input, config?)
Blends the input using the exclusion mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.hue(input, config?)
Blends the input using the hue mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.saturation(input, config?)
Blends the input using the saturation mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.color(input, config?)
Blends the input using the color mode.
- input: The bottom layer.
- config: (optional) Configuration options.
.luminosity(input, config?)
Blends the input using the luminosity mode.
- input: The bottom layer.
- config: (optional) Configuration options.
Morphology functions
.dilate(radius, config?)
Applies a dilation effect.
- radius:
number
- Radius for the effect. - config: (optional) Configuration options.
Compiles to: <feMorphology>
.erode(radius, config?)
Applies an erosion effect.
- radius:
number
- Radius for the effect. - config: (optional) Configuration options.
Compiles to: <feMorphology>
Color manipulation functions
.colorMatrix(matrix, config?)
Applies a color matrix. The matrix is a 4x5 matrix, where the last column is the offset. The matrix can be provided either as an array of 20 numbers, or as a ColorMatrix
object where the default values are 1 for the diagonal and 0 for the rest.
type ColorMatrixRow = {
r?: number;
g?: number;
b?: number;
a?: number;
o?: number;
};
type ColorMatrix = {
r?: ColorMatrixRow;
g?: ColorMatrixRow;
b?: ColorMatrixRow;
a?: ColorMatrixRow;
};
- matrix:
number[] | ColorMatrix
- Matrix values. - config: (optional) Configuration options.
Compiles to: <feColorMatrix>
.componentTransfer(config?)
Applies component transfer.
- config: (optional) Configuration options.
Compiles to: <feComponentTransfer>
.hueRotate(angle, config?)
Rotates the hue.
- angle:
number
- Angle of rotation. - config: (optional) Configuration options.
Compiles to: <feColorMatrix>
.luminanceToAlpha(config?)
Converts luminance to alpha.
- config: (optional) Configuration options.
Compiles to: <feColorMatrix>
.saturate(amount, config?)
Changes the saturation.
- amount:
number
- Saturation amount. - config: (optional) Configuration options.
Compiles to: <feColorMatrix>
Lighting functions
Lighting functions take a light source, and create a lighting effect using their input as height map.
Example:
Effect.sourceAlpha.blur(1).specular(pointLight({ x: 460, y: 110, z: 680 }), {
strength: 4,
shininess: 20,
});
.specular(light, config?)
Creates a specular lighting effect. You should composite it using additive blending.
- light: The light source to use.
- config: (optional) Configuration options.
- strength:
number
- Specular strength. - shininess:
number
- Specular exponent. - scale:
number
- Surface scale. - color:
string
- Lighting color.
Compiles to: <feSpecularLighting>
.diffuse(light, config?)
Applies a diffuse lighting effect to the input. You should composite it using the multiply
blending mode.
- light: The light source to use.
- config: (optional) Configuration options.
- strength:
number
- Diffuse strength. - scale:
number
- Surface scale. - color:
string
- Lighting color.
Compiles to: <feDiffuseLighting>
Light sources
Light sources are available on the Light
export and can be used as inputs to lighting functions.
Light.pointLight(config?)
Creates a point light source.
- x:
number
- X coordinate. - y:
number
- Y coordinate. - z:
number
- Z coordinate.
Light.distantLight(config?)
Creates a distant light source.
- azimuth:
number
- Azimuth angle. - elevation:
number
- Elevation angle.
Light.spotlight(config?)
Creates a spot light source.
- x:
number
- X coordinate. - y:
number
- Y coordinate. - z:
number
- Z coordinate. - pointsAtX:
number
- X coordinate to point at. - pointsAtY:
number
- Y coordinate to point at. - pointsAtZ:
number
- Z coordinate to point at. - falloff:
number
- The falloff of the spotlight.
Component transfer functions
Component transfer functions are available on the Transfer
export and are used in combination with the componentTransfer
effect.
Example:
Effect.source.componentTransfer({
a: Transfer.linear(18, -9),
});
Transfer.gamma(amplitude, exponent, offset)
Creates a gamma transfer function.
- amplitude:
number
- Amplitude. - exponent:
number
- Exponent. - offset:
number
- Offset.
Transfer.linear(slope, intercept)
Creates a linear transfer function.
- slope:
number
- Slope. - intercept:
number
- Intercept.
Transfer.table(values)
Creates a table transfer function.
- values:
number[]
- Table values.
Transfer.discrete(values)
Creates a discrete transfer function.
- values:
number[]
- Table values.
Contributing
Feel free to contribute by submitting issues or pull requests. For major changes, please open an issue first to discuss what you would like to change.
License
This project is licensed under the MIT License - see the LICENSE file for details.