Your first composition
This guide walks you through creating a simple image composition with layers, transforms, and export. The API is identical in Node.js and the browser.
Import and create a composition
Start by importing the Composition class and creating a new document: import { Composition } from '@iamkaf/kimg' ;
const doc = await Composition . create ({ width: 128 , height: 128 });
The create method is async because it initializes the WASM module on first use.
Add an image layer
Add your first layer with RGBA pixel data: // Assume rgbaPixels is a Uint8Array with 128*128*4 bytes
const layerId = doc . addImageLayer ({
name: 'sprite' ,
rgba: rgbaPixels ,
width: 128 ,
height: 128 ,
x: 0 ,
y: 0 ,
});
The method returns a numeric layer ID you can use to reference this layer.
Apply transforms
Apply non-destructive transforms to the layer: doc . updateLayer ( layerId , {
opacity: 0.8 ,
anchor: 'center' ,
rotation: 22.5 ,
scaleX: 1.25 ,
scaleY: 0.75 ,
});
Transforms are non-destructive and cached for performance. You can change them at any time without re-rendering the original pixels.
Export to PNG
Render the composition and export as PNG: const png = doc . exportPng ();
The exportPng method returns a Uint8Array containing the PNG file data.
Complete example
Here’s a complete working example that creates a composition, adds layers, and exports:
import { Composition } from '@iamkaf/kimg' ;
// Create a 128×128 composition
const doc = await Composition . create ({ width: 128 , height: 128 });
// Add an image layer
const layerId = doc . addImageLayer ({
name: 'sprite' ,
rgba: rgbaPixels , // Your RGBA pixel data
width: 128 ,
height: 128 ,
x: 0 ,
y: 0 ,
});
// Apply transforms
doc . updateLayer ( layerId , {
opacity: 0.8 ,
anchor: 'center' ,
rotation: 22.5 ,
scaleX: 1.25 ,
scaleY: 0.75 ,
});
// Export to PNG
const png = doc . exportPng ();
// Create a download link
const blob = new Blob ([ png ], { type: 'image/png' });
const url = URL . createObjectURL ( blob );
const a = document . createElement ( 'a' );
a . href = url ;
a . download = 'composition.png' ;
a . click ();
import { Composition } from '@iamkaf/kimg' ;
import { writeFileSync } from 'node:fs' ;
// Create a 64×64 composition
const doc = await Composition . create ({ width: 64 , height: 64 });
// Add an image layer
const layerId = doc . addImageLayer ({
name: 'sprite' ,
rgba: rgbaPixels , // Your RGBA pixel data
width: 64 ,
height: 64 ,
x: 0 ,
y: 0 ,
});
// Apply transforms
doc . updateLayer ( layerId , {
opacity: 0.8 ,
anchor: 'center' ,
rotation: 22.5 ,
scaleX: 1.25 ,
scaleY: 0.75 ,
});
// Export to PNG and save to disk
const png = doc . exportPng ();
writeFileSync ( 'composition.png' , png );
Working with shape layers
Shape layers are vector-style primitives that render to pixels:
const doc = await Composition . create ({ width: 200 , height: 200 });
// Add a rounded rectangle badge
const badgeId = doc . addShapeLayer ({
name: "Badge" ,
type: "roundedRect" ,
x: 24 ,
y: 24 ,
width: 96 ,
height: 40 ,
radius: 12 ,
fill: [ 255 , 0 , 0 , 255 ], // Red
stroke: { color: [ 255 , 255 , 255 , 255 ], width: 2 }, // White border
});
const png = doc . exportPng ();
Shape types include: rectangle, roundedRect, ellipse, line, and polygon.
You can apply transforms to any layer type:
doc . updateLayer ( layerId , {
x: 10 ,
y: - 4 ,
anchor: "center" ,
flipX: false ,
flipY: true ,
rotation: 30 ,
scaleX: 1.25 ,
scaleY: 0.75 ,
});
Anchor points
topLeft or top_left — Transform around top-left corner (default)
center — Transform around center point
Bucket fill
Fill regions of a layer with a color:
// Bucket fill at layer-local coordinates (12, 18)
doc . bucketFillLayer ( layerId , {
x: 12 ,
y: 18 ,
color: [ 0 , 255 , 0 , 255 ], // Green
contiguous: true , // Only fill connected pixels
tolerance: 0 , // Exact color match
});
Tolerance is alpha-aware and checked per channel across RGBA. Higher values allow filling similar colors.
Importing images
kimg can import PNG, JPEG, WebP, and GIF files:
// Auto-detect format and import
const layerId = doc . importImage ({
name: 'photo' ,
bytes: imageBytes , // Uint8Array of file contents
x: 0 ,
y: 0 ,
});
// Import GIF frames as separate layers
const frameIds = doc . importGifFrames ({ bytes: gifBytes });
console . log ( `Imported ${ frameIds . length } frames` );
Export your composition to PNG, JPEG, or WebP:
const png = doc . exportPng ();
Layer management
Manage your layer stack programmatically:
// List all layers
const layers = doc . listLayers ();
layers . forEach ( layer => {
console . log ( ` ${ layer . name } : ${ layer . kind } at ( ${ layer . x } , ${ layer . y } )` );
});
// Get specific layer info
const info = doc . getLayer ( layerId );
console . log ( info . name , info . visible , info . opacity );
// Remove a layer
doc . removeLayer ( layerId );
// Count layers
const count = doc . layerCount ();
Working with groups
Organize layers into groups:
// Create a group
const groupId = doc . addGroupLayer ({ name: 'Effects' });
// Add layers to the group
const imageId = doc . addImageLayer ({
name: 'photo' ,
rgba: pixels ,
width: 128 ,
height: 128 ,
parentId: groupId , // Add to group
});
// Add a filter layer to the group (affects all children)
const filterId = doc . addFilterLayer ({
name: 'color adjust' ,
parentId: groupId ,
});
doc . setFilterLayerConfig ( filterId , {
saturation: 0.5 ,
brightness: 0.2 ,
});
Filter layers apply their effects to all layers below them in the same group, enabling scoped filter application.
Color utilities
kimg provides standalone color utilities:
import { hexToRgb , rgbToHex , relativeLuminance , contrastRatio } from '@iamkaf/kimg' ;
const rgb = await hexToRgb ( '#ff8000' ); // Uint8Array [255, 128, 0]
const hex = await rgbToHex ( 255 , 128 , 0 ); // '#ff8000'
const luma = await relativeLuminance ( '#3b82f6' ); // 0.2355 (WCAG 2.x)
const contrast = await contrastRatio ( '#ffffff' , '#000000' ); // 21.0
Memory management
kimg compositions are backed by WASM memory. Free them when done:
const doc = await Composition . create ({ width: 128 , height: 128 });
// ... use the composition
doc . free (); // Release WASM memory
If you’re using TypeScript with explicit resource management, compositions support Symbol.dispose: using doc = await Composition . create ({ width: 128 , height: 128 });
// Automatically freed when out of scope
Next steps
Now that you’ve created your first composition, explore more features:
Blend modes Learn about the 16 blend modes for layer compositing
Filters Apply HSL adjustments, blur, sharpen, and more
Masks Use layer masks and clipping masks for precise control
Sprite tools Pack sprite sheets and create contact sheets