xpict-web
v0.1.0
Published
Xpict Web is the browser-based version of Xpict: the generation of standardized images from declarative models using Canvas.
Maintainers
Readme
Xpict Web
Xpict Web is the browser version of Xpict, built on top of the native Canvas APIs (OffscreenCanvas / HTMLCanvasElement, createImageBitmap and FontFace) designed to create templates for generating standardized images with data—which can be static or dynamic. It features support for conditional rendering, runtime-generated position axes, and dynamic text.
📦 Installation
Installation is straightforward; just use your preferred package manager. Here is an example using NPM:
npm i xpict-web
🚀 Usage
Template
The foundation for creating images in Xpict is the Template. This is where you define your image layers and encapsulate all assembly logic. There are two ways to define an initial Template.
Using the config property (width, height, and fill):
import xpict from "xpict-web"
type RenderData = {} // Your data structure used in layer logic
const template = xpict.template<RenderData>({
config: {
width: 1280,
height: 720,
fill: "#020817" // optional
},
layers: [...]
})
Using an image as a background:
import xpict from "xpict-web"
type RenderData = {} // Your data structure used in layer logic
const template = xpict.template<RenderData>({
image: "/assets/background.png" // URL, Blob, ArrayBuffer, ImageBitmap, HTMLImageElement or canvas,
layers: [...]
})
Layers
Layers are another fundamental component of Xpict. They act as building blocks—sometimes functional, sometimes just wrappers—that organize code declaratively and provide instructions to the Canvas API on how to perform operations within our canvas context.
All axes (x, y) across all layer types support dynamic values:
x: ({ data, index /* index is useful if you are inside a `RepeatLayer` loop */ }) => index * 110,
y: ({ data, index /* index is useful if you are inside a `RepeatLayer` loop */ }) => index * 50,
Every layer also supports conditional rendering via the when property. If not defined, the layer renders by default.
when: ({ data, index }) => false // must return a boolean
TextLayer
To insert text into the canvas, use TextLayer:
import xpict from "xpict-web"
const myFont = xpict.utils.font({
name: "curse-casual",
source: "/fonts/font.ttf" // URL or binary data, loaded via FontFace,
color: "#ffffff",
})
type RenderData = {}
const template = xpict.template<RenderData>({
config: {
width: 1280,
height: 720,
fill: "#020817"
},
layers: [
xpict.text({
x: 640,
y: 360,
font: myFont({
size: 60,
color: "#ffffff" // optional, as it was previously defined in font config
}),
text: "Hello World",
align: "center", // optional
baseline: "middle", // optional
stroke: {
width: 4,
fill: "#000000"
} // optional
})
]
})
Using dynamic text:
text: ({ data, index }) => data.myText
ImageLayer
To insert images into the canvas, use ImageLayer:
import xpict from "xpict-web"
const template = xpict.template<RenderData>({
config: {
width: 1280,
height: 720,
fill: "#020817"
},
layers: [
xpict.image({
src: "path/to/image.png",
x: 0,
y: 0,
}),
]
})
Using a dynamic image:
src: ({ data, index }) => data.myImagePath
RectangleLayer
To insert rectangles into the canvas, use RectangleLayer:
xpict.rectangle({
x: 0,
y: 0,
fill: "#ffffff",
width: 150,
height: 50,
borderRadius: 5 // optional
})
CircleLayer
To insert circles into the canvas, use CircleLayer:
xpict.circle({
x: 0,
y: 0,
fill: "#ffffff",
radius: 50,
})
LineLayer
To insert lines into the canvas, use LineLayer:
xpict.line({
from: { x: 0, y: 0 },
to: { x: 0, y: 50 },
color: "#ffffff",
width: 1
})
GroupLayer
If you want to group layers that share a common context, you can use GroupLayer to keep your code organized and apply an offset if desired:
xpict.group({
x: 50,
y: 50,
layers: [...]
})
RepeatLayer
If you need to handle arrays to generate layers, use RepeatLayer to organize your code and loop logic:
xpict.repeat({
each: ({ data }) => data.images,
x: ({ index }) => index * 50,
y: ({ index }) => index * 50,
layer: (image, { index, length }) => xpict.image({
x: 0,
y: 0,
src: image
})
})
Image Transformations and Effects
If you want to manipulate an image or apply effects, you can do so using the transform property of ImageLayer, or via flipX, flipY, and rotate.
Using transform:
xpict.image({
x: 0,
y: 0,
src: "...",
transform: [
xpict.effects.blur(2.5),
({ canvas, ctx, data, index }) => {
// manipulate the `ctx` directly here to apply custom logic
}
]
})
🧪 Tests
Tests are written with Vitest and live in __tests__. The library maintains 100% coverage (statements, branches, functions and lines), enforced by a threshold in vitest.config.mts.
npm run test # run the test suite once
npm run test:watch # watch mode
npm run test:coverage # run with coverage report (also written to ./coverage)
🤝 Contributing
Want to contribute? Follow these steps:
- Fork the repository.
- Create a new branch (
git checkout -b feature-new). - Commit your changes (
git commit -m 'Add new feature'). - Push to the branch (
git push origin feature-new). - Open a Pull Request.
📝 License
This project is licensed under the MIT License.
🌐 Differences from the Node version
- No
@napi-rs/canvas: everything uses browser APIs (also works inside Web Workers viaOffscreenCanvas). Template.render()returns anHTMLCanvasElement(orOffscreenCanvasin workers). Usetemplate.renderToBlob(data, "image/png")to get aBlob.imagePathbecameimageandfilePath(fonts) becamesource; image sources can be URLs,Blobs, binary data,ImageBitmaps, images or canvases. URLs are fetched, so cross-origin images need CORS headers.- Fonts are loaded with
FontFace;render()automatically waits for them (xpict.utils.fontsReady()is also exposed). - The
blurandgrayscaleeffects rely onctx.filter, which Safari does not support; useboxBlurthere.
