@lacspace/image 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Shared types for @lacspace/image.
3
+ */
4
+ /** A raster format this library can produce. `svg` passes through untouched. */
5
+ type ImageFormat = "png" | "jpeg" | "webp";
6
+ /** A CSS-ish colour: `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()/rgba()`, or `transparent`. */
7
+ type Color = string;
8
+ /** One stop in a gradient. `offset` is 0..1; `color` any supported Color. */
9
+ interface GradientStop {
10
+ offset: number;
11
+ color: Color;
12
+ }
13
+ interface LinearGradientOptions {
14
+ /** Angle in degrees, clockwise from the positive x-axis (0 = left→right, 90 = top→bottom). */
15
+ angle?: number;
16
+ stops: GradientStop[];
17
+ }
18
+ interface RadialGradientOptions {
19
+ /** Centre x in 0..1 of the surface width (default 0.5). */
20
+ cx?: number;
21
+ /** Centre y in 0..1 of the surface height (default 0.5). */
22
+ cy?: number;
23
+ /** Radius in 0..1 of the larger surface dimension (default 0.75). */
24
+ radius?: number;
25
+ stops: GradientStop[];
26
+ }
27
+ type PatternKind = "checker" | "grid" | "dots" | "stripes" | "noise";
28
+ interface PatternOptions {
29
+ /** Cell/line size in px (default 24). */
30
+ size?: number;
31
+ /** Foreground colour (default #ffffff22 over the current surface). */
32
+ color?: Color;
33
+ /** For `stripes`: angle 0 (vertical) | 90 (horizontal) | 45 (diagonal). Default 45. */
34
+ angle?: 0 | 45 | 90;
35
+ /** For `noise`: 0..1 strength (default 0.06). */
36
+ strength?: number;
37
+ /** Deterministic seed for `noise` (default 1). */
38
+ seed?: number;
39
+ }
40
+ type Fit = "cover" | "contain" | "fill" | "stretch";
41
+ interface DrawImageOptions {
42
+ x?: number;
43
+ y?: number;
44
+ /** Target width; defaults to source width (or fills the box when x/y/w/h given). */
45
+ width?: number;
46
+ height?: number;
47
+ /** How to place a source into the target box when both are sized. Default "cover". */
48
+ fit?: Fit;
49
+ /** Resampling quality when scaling (default "bilinear"). */
50
+ resample?: "nearest" | "bilinear";
51
+ }
52
+ /** Anything we can read pixels from. */
53
+ interface PixelSource {
54
+ width: number;
55
+ height: number;
56
+ /** RGBA, row-major, 4 bytes per pixel. */
57
+ data: Uint8ClampedArray;
58
+ }
59
+ interface EncodeOptions {
60
+ format: ImageFormat;
61
+ /** 1..100 for lossy formats (jpeg/webp). Ignored for png. Default 82. */
62
+ quality?: number;
63
+ /** Background painted under transparent pixels when flattening to jpeg. Default #ffffff. */
64
+ background?: Color;
65
+ }
66
+ interface EncodeResult {
67
+ bytes: Uint8Array;
68
+ format: ImageFormat;
69
+ width: number;
70
+ height: number;
71
+ /** Effective quality used (lossy formats). */
72
+ quality?: number;
73
+ /** Byte length — same as bytes.length, provided for convenience. */
74
+ size: number;
75
+ }
76
+ interface FitOptions {
77
+ format: ImageFormat;
78
+ /** Target ceiling. Accepts a number of bytes or a string like "200kb" / "1.5mb". */
79
+ maxBytes?: number;
80
+ maxSize?: string | number;
81
+ /** Lowest acceptable quality before we start downscaling (default 30). */
82
+ minQuality?: number;
83
+ /** Highest quality to try first (default 92). */
84
+ maxQuality?: number;
85
+ /** Allow shrinking dimensions if quality alone can't hit the budget (default true). */
86
+ allowResize?: boolean;
87
+ /** Smallest scale factor when resizing (default 0.35). */
88
+ minScale?: number;
89
+ background?: Color;
90
+ }
91
+
92
+ /**
93
+ * Surface — a zero-dependency RGBA pixel buffer with a small, deterministic
94
+ * drawing API (fills, gradients, patterns, image placement, resize, crop).
95
+ * Works identically in Node and the browser; nothing here touches the DOM.
96
+ */
97
+
98
+ declare class Surface implements PixelSource {
99
+ readonly width: number;
100
+ readonly height: number;
101
+ readonly data: Uint8ClampedArray;
102
+ constructor(width: number, height: number, data?: Uint8ClampedArray);
103
+ /** Wrap an existing RGBA source (e.g. a Canvas ImageData) without copying. */
104
+ static from(src: PixelSource): Surface;
105
+ private idx;
106
+ private set;
107
+ /** Paint the whole surface a solid colour (replaces, does not blend). */
108
+ fill(color: Color): this;
109
+ /** Blend a rectangle of colour over the surface. */
110
+ rect(x: number, y: number, w: number, h: number, color: Color): this;
111
+ linearGradient(opts: LinearGradientOptions): this;
112
+ radialGradient(opts: RadialGradientOptions): this;
113
+ pattern(kind: PatternKind, opts?: PatternOptions): this;
114
+ /** Bilinear sample of a source at fractional (u,v) in source pixel space. */
115
+ private static sampleBilinear;
116
+ /** Place another image/surface onto this one, with fit + resampling. */
117
+ drawImage(src: PixelSource, o?: DrawImageOptions): this;
118
+ /** Return a NEW surface resampled to the given dimensions (bilinear). */
119
+ resize(width: number, height: number): Surface;
120
+ /** Return a NEW surface cropped to the given rectangle. */
121
+ crop(x: number, y: number, w: number, h: number): Surface;
122
+ /** Flatten transparency onto a solid background, returning a NEW opaque surface. */
123
+ flatten(background?: Color): Surface;
124
+ /**
125
+ * Return a NEW surface with each channel reduced to `bits` bits (2..8).
126
+ * Fewer distinct values compress far better as lossless PNG — the main lever
127
+ * for hitting a PNG size budget on noisy/photographic content.
128
+ */
129
+ posterize(bits: number): Surface;
130
+ clone(): Surface;
131
+ }
132
+ /** Convenience: a new surface pre-filled with a linear gradient. */
133
+ declare function gradient(width: number, height: number, opts: LinearGradientOptions): Surface;
134
+ /** Convenience: a new surface pre-filled with a radial gradient. */
135
+ declare function radial(width: number, height: number, opts: RadialGradientOptions): Surface;
136
+ /** Convenience: a solid surface with a pattern painted over it. */
137
+ declare function pattern(width: number, height: number, base: Color, kind: PatternKind, opts?: PatternOptions): Surface;
138
+
139
+ /**
140
+ * Unified, isomorphic encode(): pick the best available encoder for the format
141
+ * and runtime. Browser/Worker → native Canvas (all formats). Node → pure-JS PNG
142
+ * (zlib) and JPEG; WebP needs a Canvas runtime.
143
+ */
144
+
145
+ /** Encode an RGBA source to the requested format. */
146
+ declare function encode(source: PixelSource, opts: EncodeOptions): Promise<EncodeResult>;
147
+
148
+ /**
149
+ * fit() — encode an image to land at or under a file-size budget.
150
+ *
151
+ * Lossy formats (jpeg/webp): binary-search the quality knob for the highest
152
+ * quality that fits; if even the floor is too big, progressively downscale.
153
+ * Lossless png: quality can't move bytes, so we downscale toward the budget.
154
+ *
155
+ * It is a best-effort ceiling: the result is the closest fit ≤ budget we can
156
+ * reach within the quality/scale bounds (never padded up to the budget).
157
+ */
158
+
159
+ declare function fit(source: PixelSource, opts: FitOptions): Promise<EncodeResult>;
160
+
161
+ /**
162
+ * Minimal, dependency-free PNG encoder (8-bit RGBA, non-interlaced).
163
+ * Compression: `node:zlib` when running in Node (kept external in browser
164
+ * bundles), otherwise a valid *stored* (uncompressed) DEFLATE stream — so the
165
+ * output is always a spec-correct PNG everywhere, just larger without zlib.
166
+ * In the browser the orchestrator prefers Canvas, which compresses properly.
167
+ */
168
+
169
+ /** Encode an RGBA source to PNG bytes. `level` 0..9 (zlib), default 9. */
170
+ declare function encodePng(source: PixelSource, level?: number): Promise<Uint8Array>;
171
+ /** Synchronous PNG using the pure stored-deflate path (valid, uncompressed). */
172
+ declare function encodePngSync(source: PixelSource): Uint8Array;
173
+
174
+ /**
175
+ * Pure-JS baseline JPEG encoder (4:4:4, no external deps).
176
+ * Adapted from the classic public-domain JPEG encoder (AAN forward DCT +
177
+ * standard Huffman tables). Runs identically in Node and the browser and is
178
+ * the engine the size-budget fitter tunes via the `quality` knob.
179
+ */
180
+
181
+ declare function encodeJpeg(source: PixelSource, quality?: number): Uint8Array;
182
+
183
+ /**
184
+ * SVG → raster. In the browser we draw the SVG onto a canvas (zero-dep). In
185
+ * Node, rich SVG text/paths need a real renderer, so we use an OPTIONAL peer
186
+ * (`sharp`) if it's installed — otherwise we throw a clear, actionable error.
187
+ * This keeps the package itself zero-dependency while still enabling server-side
188
+ * raster for teams that opt in.
189
+ */
190
+
191
+ interface RasterizeSvgOptions {
192
+ width?: number;
193
+ height?: number;
194
+ /** Painted under the SVG (SVGs are transparent by default). */
195
+ background?: Color;
196
+ }
197
+ /** Rasterize an SVG string to a Surface (browser: Canvas; Node: optional `sharp`). */
198
+ declare function rasterizeSvg(svg: string, opts?: RasterizeSvgOptions): Promise<Surface>;
199
+
200
+ /**
201
+ * Human file-size parsing and formatting. Binary units (1 KB = 1024 bytes) to
202
+ * match what operating systems and upload limits usually report.
203
+ */
204
+ /** Parse "200kb", "1.5 MB", "500", 4096 → bytes. Throws on nonsense. */
205
+ declare function parseSize(input: string | number): number;
206
+ /** Format a byte count as a short human string, e.g. 1536 → "1.5 KB". */
207
+ declare function formatBytes(bytes: number, decimals?: number): string;
208
+
209
+ /**
210
+ * Isomorphic canvas layer. When a real Canvas is available (browser main thread,
211
+ * a Worker with OffscreenCanvas), we use it to encode PNG/JPEG/WebP with the
212
+ * platform's own well-tuned encoders. In Node there is no canvas, so callers
213
+ * fall back to the pure-JS PNG/JPEG encoders in this package.
214
+ */
215
+
216
+ /** Does this runtime expose a usable 2D canvas we can encode from? */
217
+ declare function hasCanvas(): boolean;
218
+ /** True where the platform can actually emit this format via canvas. */
219
+ declare function canvasSupports(format: ImageFormat): Promise<boolean>;
220
+
221
+ /**
222
+ * Tiny, dependency-free colour parser → RGBA tuple (0..255 each).
223
+ * Supports: #rgb, #rgba, #rrggbb, #rrggbbaa, rgb()/rgba(), and "transparent".
224
+ */
225
+
226
+ type RGBA = [number, number, number, number];
227
+ declare function parseColor(input: Color): RGBA;
228
+
229
+ export { type Color, type DrawImageOptions, type EncodeOptions, type EncodeResult, type Fit, type FitOptions, type GradientStop, type ImageFormat, type LinearGradientOptions, type PatternKind, type PatternOptions, type PixelSource, type RGBA, type RadialGradientOptions, type RasterizeSvgOptions, Surface, canvasSupports, encode, encodeJpeg, encodePng, encodePngSync, fit, formatBytes, gradient, hasCanvas, parseColor, parseSize, pattern, radial, rasterizeSvg };