@palmerama/hd-canvas 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.
- package/LICENSE +21 -0
- package/README.md +412 -0
- package/dist/bridge/Canvas2DBridge.d.ts +37 -0
- package/dist/bridge/Canvas2DBridge.d.ts.map +1 -0
- package/dist/bridge/Canvas2DBridge.js +116 -0
- package/dist/bridge/Canvas2DBridge.js.map +1 -0
- package/dist/core/ColorBuffer.d.ts +41 -0
- package/dist/core/ColorBuffer.d.ts.map +1 -0
- package/dist/core/ColorBuffer.js +138 -0
- package/dist/core/ColorBuffer.js.map +1 -0
- package/dist/core/HDCanvas.d.ts +80 -0
- package/dist/core/HDCanvas.d.ts.map +1 -0
- package/dist/core/HDCanvas.js +104 -0
- package/dist/core/HDCanvas.js.map +1 -0
- package/dist/core/PaperSize.d.ts +40 -0
- package/dist/core/PaperSize.d.ts.map +1 -0
- package/dist/core/PaperSize.js +63 -0
- package/dist/core/PaperSize.js.map +1 -0
- package/dist/export/ExportPipeline.d.ts +94 -0
- package/dist/export/ExportPipeline.d.ts.map +1 -0
- package/dist/export/ExportPipeline.js +121 -0
- package/dist/export/ExportPipeline.js.map +1 -0
- package/dist/export/PNGExporter.d.ts +62 -0
- package/dist/export/PNGExporter.d.ts.map +1 -0
- package/dist/export/PNGExporter.js +146 -0
- package/dist/export/PNGExporter.js.map +1 -0
- package/dist/export/ToneMapper.d.ts +88 -0
- package/dist/export/ToneMapper.d.ts.map +1 -0
- package/dist/export/ToneMapper.js +175 -0
- package/dist/export/ToneMapper.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/preview/FitStrategy.d.ts +27 -0
- package/dist/preview/FitStrategy.d.ts.map +1 -0
- package/dist/preview/FitStrategy.js +31 -0
- package/dist/preview/FitStrategy.js.map +1 -0
- package/dist/preview/PreviewRenderer.d.ts +71 -0
- package/dist/preview/PreviewRenderer.d.ts.map +1 -0
- package/dist/preview/PreviewRenderer.js +304 -0
- package/dist/preview/PreviewRenderer.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 palmerama
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# hd-canvas
|
|
2
|
+
|
|
3
|
+
A TypeScript library for generative art with **HDR color**, configurable **paper sizes**, and **print-quality export**.
|
|
4
|
+
|
|
5
|
+
Build artwork with unbounded float colors (values > 1.0 = super-bright HDR), preview it on screen with zoom/pan, and export print-ready PNGs at 300+ DPI with embedded resolution metadata.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Float32/Float64 HDR color buffers** — unbounded RGBA values, no 8-bit clamping during creation
|
|
10
|
+
- **Paper size presets** — A0–A6, US Letter/Legal/Tabloid with DPI-aware pixel calculations
|
|
11
|
+
- **Tone mapping** — Reinhard, ACES filmic, clamp, or custom algorithms to compress HDR → LDR on export
|
|
12
|
+
- **Print-ready PNG export** — pHYs chunk injection for correct DPI metadata, 8-bit and 16-bit output
|
|
13
|
+
- **Canvas 2D bridge** — draw with the familiar `fillRect`, `arc`, `fillText` API, auto-tiled for large formats
|
|
14
|
+
- **Zoom/pan preview** — scroll-wheel zoom centered on cursor, click-drag pan, keyboard shortcuts, visible-region-only rendering
|
|
15
|
+
- **Blend modes** — normal (alpha composite), additive, multiply — layer effects in HDR space
|
|
16
|
+
- **Zero native dependencies** — pure JS PNG encoding via `fast-png`, runs in browser and Node.js
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install hd-canvas
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import {
|
|
28
|
+
HDCanvas,
|
|
29
|
+
attachExportPipeline,
|
|
30
|
+
PreviewRenderer,
|
|
31
|
+
} from 'hd-canvas';
|
|
32
|
+
|
|
33
|
+
// 1. Create a canvas — A3 paper at 300 DPI
|
|
34
|
+
const canvas = new HDCanvas({ paperSize: 'A3', dpi: 300 });
|
|
35
|
+
|
|
36
|
+
// 2. Draw with HDR float colors
|
|
37
|
+
for (let y = 0; y < canvas.heightPx; y++) {
|
|
38
|
+
for (let x = 0; x < canvas.widthPx; x++) {
|
|
39
|
+
const intensity = Math.random() * 3.0; // HDR: values > 1.0
|
|
40
|
+
canvas.setPixel(x, y, intensity, intensity * 0.6, 0.1, 1.0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 3. Preview on screen (browser only)
|
|
45
|
+
const preview = new PreviewRenderer(canvas, {
|
|
46
|
+
container: document.getElementById('preview')!,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// 4. Export print-ready PNG
|
|
50
|
+
attachExportPipeline(canvas);
|
|
51
|
+
const blob = await canvas.export({ toneMap: 'aces', exposure: 0.5 });
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### Core
|
|
57
|
+
|
|
58
|
+
#### `HDCanvas`
|
|
59
|
+
|
|
60
|
+
The main class. Wraps a `ColorBuffer` with paper size and DPI configuration.
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const canvas = new HDCanvas({
|
|
64
|
+
paperSize: 'A4', // or 'A3', 'letter', 'tabloid', etc.
|
|
65
|
+
dpi: 300, // default: 300
|
|
66
|
+
colorDepth: 32, // 32 (Float32) or 64 (Float64), default: 32
|
|
67
|
+
orientation: 'portrait', // 'portrait' or 'landscape', default: 'portrait'
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
canvas.widthPx; // pixel width (e.g., 2480 for A4 @ 300 DPI)
|
|
71
|
+
canvas.heightPx; // pixel height (e.g., 3508 for A4 @ 300 DPI)
|
|
72
|
+
canvas.dpi; // configured DPI
|
|
73
|
+
canvas.memoryBytes; // buffer memory usage in bytes
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Pixel Drawing
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Set a pixel (RGBA, unbounded floats)
|
|
80
|
+
canvas.setPixel(x, y, r, g, b, a);
|
|
81
|
+
|
|
82
|
+
// Read a pixel back
|
|
83
|
+
const [r, g, b, a] = canvas.getPixel(x, y);
|
|
84
|
+
|
|
85
|
+
// Blend onto existing content
|
|
86
|
+
canvas.blendPixel(x, y, r, g, b, a, 'normal'); // alpha composite
|
|
87
|
+
canvas.blendPixel(x, y, r, g, b, a, 'add'); // additive (glow effects)
|
|
88
|
+
canvas.blendPixel(x, y, r, g, b, a, 'multiply'); // multiply (shadows)
|
|
89
|
+
|
|
90
|
+
// Fill entire buffer
|
|
91
|
+
canvas.clear(0, 0, 0, 1); // solid black
|
|
92
|
+
|
|
93
|
+
// Region operations
|
|
94
|
+
const region = canvas.getRegion(x, y, width, height); // extract sub-buffer
|
|
95
|
+
canvas.putRegion(x, y, region); // paste sub-buffer
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `ColorBuffer`
|
|
99
|
+
|
|
100
|
+
The raw float pixel buffer. Used directly for advanced operations.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { ColorBuffer } from 'hd-canvas';
|
|
104
|
+
|
|
105
|
+
const buf = new ColorBuffer(1920, 1080, 32); // width, height, depth
|
|
106
|
+
buf.setPixel(0, 0, 1.5, 0.3, 0.0, 1.0); // HDR orange
|
|
107
|
+
buf.data; // Float32Array — direct access for bulk operations
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Paper Sizes
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { PAPER_SIZES, sizeToPx, resolvePaperSize, estimateBufferBytes } from 'hd-canvas';
|
|
114
|
+
|
|
115
|
+
// All presets: A0–A6, letter, legal, tabloid
|
|
116
|
+
PAPER_SIZES.A4; // { widthMM: 210, heightMM: 297 }
|
|
117
|
+
PAPER_SIZES.letter; // { widthMM: 215.9, heightMM: 279.4 }
|
|
118
|
+
|
|
119
|
+
// Calculate pixel dimensions
|
|
120
|
+
sizeToPx({ widthMM: 210, heightMM: 297 }, 300);
|
|
121
|
+
// → { width: 2480, height: 3508 }
|
|
122
|
+
|
|
123
|
+
// Resolve with orientation
|
|
124
|
+
resolvePaperSize('A3', 'landscape');
|
|
125
|
+
// → { widthMM: 420, heightMM: 297 }
|
|
126
|
+
|
|
127
|
+
// Estimate memory before allocating
|
|
128
|
+
estimateBufferBytes('A0', 300, 32); // ~2.07 GB for Float32
|
|
129
|
+
estimateBufferBytes('A0', 300, 64); // ~4.14 GB for Float64
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Canvas 2D Bridge
|
|
133
|
+
|
|
134
|
+
Draw with the familiar Canvas 2D API — shapes, text, paths, gradients — then continue with HDR pixel operations on top.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Draw convenience shapes with Canvas 2D
|
|
138
|
+
canvas.drawWith2D((ctx) => {
|
|
139
|
+
ctx.fillStyle = '#1a1a2e';
|
|
140
|
+
ctx.fillRect(0, 0, canvas.widthPx, canvas.heightPx);
|
|
141
|
+
|
|
142
|
+
ctx.strokeStyle = 'white';
|
|
143
|
+
ctx.lineWidth = 3;
|
|
144
|
+
ctx.beginPath();
|
|
145
|
+
ctx.arc(canvas.widthPx / 2, canvas.heightPx / 2, 200, 0, Math.PI * 2);
|
|
146
|
+
ctx.stroke();
|
|
147
|
+
|
|
148
|
+
ctx.font = '72px serif';
|
|
149
|
+
ctx.fillStyle = 'white';
|
|
150
|
+
ctx.fillText('Hello HD', 100, 400);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Then layer HDR effects on top
|
|
154
|
+
canvas.blendPixel(x, y, 2.0, 0.5, 0.0, 0.8, 'add'); // HDR glow
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Options:**
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
canvas.drawWith2D(callback, {
|
|
161
|
+
mode: 'blend', // 'overwrite' (default) or 'blend'
|
|
162
|
+
blendMode: 'normal', // 'normal', 'add', or 'multiply' (when mode is 'blend')
|
|
163
|
+
region: { x, y, width, height }, // draw into a sub-region only
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> **Note:** Canvas 2D is 8-bit, so this is for convenience shapes/text. For HDR drawing, use the pixel API directly. Large canvases (A0+) are automatically tiled at 4096px for browser compatibility.
|
|
168
|
+
|
|
169
|
+
### Preview
|
|
170
|
+
|
|
171
|
+
Interactive zoom/pan preview for browser environments.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { PreviewRenderer } from 'hd-canvas';
|
|
175
|
+
|
|
176
|
+
const preview = new PreviewRenderer(canvas, {
|
|
177
|
+
container: document.getElementById('preview')!,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
preview.refresh(); // re-render after drawing changes
|
|
181
|
+
preview.destroy(); // clean up event listeners
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Controls:**
|
|
185
|
+
- Scroll wheel: zoom (centered on cursor)
|
|
186
|
+
- Click + drag: pan
|
|
187
|
+
- `+` / `-` keys: zoom in/out
|
|
188
|
+
- `0` key: reset zoom
|
|
189
|
+
- Double-click: fit to view
|
|
190
|
+
|
|
191
|
+
### Tone Mapping
|
|
192
|
+
|
|
193
|
+
Compress HDR float values to displayable/exportable range.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { ToneMapper, reinhard, aces, clamp } from 'hd-canvas';
|
|
197
|
+
|
|
198
|
+
// Use standalone functions
|
|
199
|
+
reinhard(2.0); // → 0.667 (smooth compression)
|
|
200
|
+
aces(2.0); // → 0.928 (filmic look)
|
|
201
|
+
clamp(2.0); // → 1.0 (hard clip)
|
|
202
|
+
|
|
203
|
+
// Or the full pipeline
|
|
204
|
+
const mapper = new ToneMapper({
|
|
205
|
+
algorithm: 'aces', // 'reinhard', 'aces', 'clamp', or custom function
|
|
206
|
+
exposure: 1.0, // stops: multiply by 2^exposure before mapping
|
|
207
|
+
gamma: 2.2, // sRGB gamma correction (default: 2.2)
|
|
208
|
+
outputDepth: 8, // 8 → Uint8Array, 16 → Uint16Array
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const ldrPixels = mapper.map(canvas.buffer); // Uint8Array RGBA
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Custom tone mapping:**
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const mapper = new ToneMapper({
|
|
218
|
+
algorithm: (v: number) => Math.sqrt(Math.min(1, v)), // square root compression
|
|
219
|
+
gamma: 1.0,
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Export
|
|
224
|
+
|
|
225
|
+
Print-ready PNG export with DPI metadata.
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { attachExportPipeline, exportBuffer, exportAndDownload } from 'hd-canvas';
|
|
229
|
+
|
|
230
|
+
// Option 1: Attach to HDCanvas (recommended)
|
|
231
|
+
attachExportPipeline(canvas);
|
|
232
|
+
const blob = await canvas.export({
|
|
233
|
+
toneMap: 'aces', // tone mapping algorithm
|
|
234
|
+
exposure: 0.5, // exposure adjustment (stops)
|
|
235
|
+
gamma: 2.2, // gamma correction
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Option 2: Export with progress tracking
|
|
239
|
+
attachExportPipeline(canvas, (percent) => {
|
|
240
|
+
console.log(`Export: ${percent}%`);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Option 3: Standalone function (no HDCanvas needed)
|
|
244
|
+
const blob2 = exportBuffer(colorBuffer, {
|
|
245
|
+
dpi: 300,
|
|
246
|
+
toneMap: 'reinhard',
|
|
247
|
+
exposure: 0,
|
|
248
|
+
gamma: 2.2,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Option 4: Export + browser download in one call
|
|
252
|
+
await exportAndDownload(canvas, { toneMap: 'aces' }, 'my-artwork.png');
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### PNG Exporter (low-level)
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { PNGExporter, dpiToPixelsPerMeter } from 'hd-canvas';
|
|
259
|
+
|
|
260
|
+
const exporter = new PNGExporter();
|
|
261
|
+
|
|
262
|
+
// 8-bit export
|
|
263
|
+
const result = exporter.export(uint8Data, {
|
|
264
|
+
width: 2480,
|
|
265
|
+
height: 3508,
|
|
266
|
+
dpi: 300,
|
|
267
|
+
depth: 8,
|
|
268
|
+
});
|
|
269
|
+
// result.data: Uint8Array (raw PNG bytes)
|
|
270
|
+
// result.mimeType: 'image/png'
|
|
271
|
+
// result.filename: 'artwork-2480x3508-300dpi.png'
|
|
272
|
+
|
|
273
|
+
// 16-bit export for maximum quality
|
|
274
|
+
const result16 = exporter.export(uint16Data, {
|
|
275
|
+
width: 2480,
|
|
276
|
+
height: 3508,
|
|
277
|
+
dpi: 300,
|
|
278
|
+
depth: 16,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// DPI conversion utility
|
|
282
|
+
dpiToPixelsPerMeter(300); // → 11811
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Examples
|
|
286
|
+
|
|
287
|
+
### Generative Flow Field
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { HDCanvas, attachExportPipeline } from 'hd-canvas';
|
|
291
|
+
|
|
292
|
+
const canvas = new HDCanvas({ paperSize: 'A3', dpi: 300 });
|
|
293
|
+
canvas.clear(0.02, 0.02, 0.05, 1.0); // near-black background
|
|
294
|
+
|
|
295
|
+
// Generate flow field with HDR highlights
|
|
296
|
+
for (let i = 0; i < 50000; i++) {
|
|
297
|
+
let x = Math.random() * canvas.widthPx;
|
|
298
|
+
let y = Math.random() * canvas.heightPx;
|
|
299
|
+
|
|
300
|
+
for (let step = 0; step < 100; step++) {
|
|
301
|
+
const angle = noise2D(x * 0.001, y * 0.001) * Math.PI * 4;
|
|
302
|
+
x += Math.cos(angle) * 2;
|
|
303
|
+
y += Math.sin(angle) * 2;
|
|
304
|
+
|
|
305
|
+
if (x < 0 || x >= canvas.widthPx || y < 0 || y >= canvas.heightPx) break;
|
|
306
|
+
|
|
307
|
+
// Additive blending creates natural HDR glow at intersections
|
|
308
|
+
canvas.blendPixel(
|
|
309
|
+
Math.floor(x), Math.floor(y),
|
|
310
|
+
0.02, 0.015, 0.03, // subtle per-step contribution
|
|
311
|
+
0.5, // semi-transparent
|
|
312
|
+
'add' // accumulates beyond 1.0 = HDR
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ACES tone mapping compresses the HDR glow beautifully
|
|
318
|
+
attachExportPipeline(canvas);
|
|
319
|
+
const blob = await canvas.export({ toneMap: 'aces', exposure: 1.5 });
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Layered Composition
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const canvas = new HDCanvas({ paperSize: 'letter', dpi: 300 });
|
|
326
|
+
|
|
327
|
+
// Layer 1: Canvas 2D background
|
|
328
|
+
canvas.drawWith2D((ctx) => {
|
|
329
|
+
const grad = ctx.createLinearGradient(0, 0, 0, canvas.heightPx);
|
|
330
|
+
grad.addColorStop(0, '#0a0a2e');
|
|
331
|
+
grad.addColorStop(1, '#1a0a3e');
|
|
332
|
+
ctx.fillStyle = grad;
|
|
333
|
+
ctx.fillRect(0, 0, canvas.widthPx, canvas.heightPx);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Layer 2: HDR particle system (blend on top)
|
|
337
|
+
for (const particle of particles) {
|
|
338
|
+
canvas.blendPixel(
|
|
339
|
+
particle.x, particle.y,
|
|
340
|
+
particle.energy * 2.0, // HDR intensity
|
|
341
|
+
particle.energy * 0.8,
|
|
342
|
+
particle.energy * 0.3,
|
|
343
|
+
0.6,
|
|
344
|
+
'add'
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Layer 3: Canvas 2D text overlay (blend mode preserves HDR underneath)
|
|
349
|
+
canvas.drawWith2D((ctx) => {
|
|
350
|
+
ctx.font = 'bold 120px sans-serif';
|
|
351
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
|
|
352
|
+
ctx.fillText('ENERGY', 100, canvas.heightPx / 2);
|
|
353
|
+
}, { mode: 'blend' });
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Custom Paper Size
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
const canvas = new HDCanvas({
|
|
360
|
+
paperSize: { widthMM: 300, heightMM: 300 }, // 30cm square
|
|
361
|
+
dpi: 600, // high quality
|
|
362
|
+
colorDepth: 64, // Float64 for maximum precision
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Paper Size Reference
|
|
367
|
+
|
|
368
|
+
| Size | Dimensions (mm) | Pixels @ 300 DPI | Memory (Float32) |
|
|
369
|
+
|------|-----------------|-------------------|-------------------|
|
|
370
|
+
| A6 | 105 × 148 | 1240 × 1748 | 8.3 MB |
|
|
371
|
+
| A5 | 148 × 210 | 1748 × 2480 | 16.6 MB |
|
|
372
|
+
| A4 | 210 × 297 | 2480 × 3508 | 33.3 MB |
|
|
373
|
+
| A3 | 297 × 420 | 3508 × 4961 | 66.6 MB |
|
|
374
|
+
| A2 | 420 × 594 | 4961 × 7016 | 133 MB |
|
|
375
|
+
| A1 | 594 × 841 | 7016 × 9933 | 267 MB |
|
|
376
|
+
| A0 | 841 × 1189 | 9933 × 14043 | 534 MB |
|
|
377
|
+
| Letter | 215.9 × 279.4 | 2550 × 3300 | 32.2 MB |
|
|
378
|
+
| Legal | 215.9 × 355.6 | 2550 × 4200 | 41.0 MB |
|
|
379
|
+
| Tabloid | 279.4 × 431.8 | 3300 × 5100 | 64.5 MB |
|
|
380
|
+
|
|
381
|
+
> Memory shown is for the pixel buffer only (4 × Float32 per pixel). Float64 doubles these values.
|
|
382
|
+
|
|
383
|
+
## Architecture
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
hd-canvas/
|
|
387
|
+
src/
|
|
388
|
+
core/
|
|
389
|
+
ColorBuffer.ts — Float32/Float64 RGBA pixel buffer
|
|
390
|
+
PaperSize.ts — Paper size registry + DPI calculations
|
|
391
|
+
HDCanvas.ts — Main class, wires everything together
|
|
392
|
+
preview/
|
|
393
|
+
PreviewRenderer.ts — Zoom/pan interactive preview
|
|
394
|
+
FitStrategy.ts — Contain/cover fitting math
|
|
395
|
+
bridge/
|
|
396
|
+
Canvas2DBridge.ts — Canvas 2D API → float buffer bridge
|
|
397
|
+
export/
|
|
398
|
+
ToneMapper.ts — HDR → LDR tone mapping algorithms
|
|
399
|
+
PNGExporter.ts — PNG encoding with DPI metadata
|
|
400
|
+
ExportPipeline.ts — Glue: tone map → encode → Blob
|
|
401
|
+
index.ts — Unified public API
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**Design principles:**
|
|
405
|
+
- **Dependency injection** — preview and export are pluggable, core has no DOM dependency
|
|
406
|
+
- **Interface segregation** — export pipeline codes against `IColorBuffer`, not the full `ColorBuffer`
|
|
407
|
+
- **Fail hard** — out-of-bounds pixels, invalid dimensions, and bad options throw immediately
|
|
408
|
+
- **One code path** — no duplicated logic, no silent fallbacks
|
|
409
|
+
|
|
410
|
+
## License
|
|
411
|
+
|
|
412
|
+
MIT
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas2DBridge — Draw with the familiar Canvas 2D API into an HDR float buffer
|
|
3
|
+
*
|
|
4
|
+
* Creates a temporary offscreen canvas at full resolution (or tiled for large
|
|
5
|
+
* formats), passes the 2D context to a user callback, reads back ImageData,
|
|
6
|
+
* and writes into the ColorBuffer as float values (0–255 → 0.0–1.0).
|
|
7
|
+
*
|
|
8
|
+
* This is a one-way bridge: Canvas 2D → float buffer. The 2D context is 8-bit,
|
|
9
|
+
* so this is for convenience shapes/text, not HDR input.
|
|
10
|
+
*/
|
|
11
|
+
import { ColorBuffer, type BlendMode } from '../core/ColorBuffer.js';
|
|
12
|
+
export interface DrawWith2DOptions {
|
|
13
|
+
/** How to combine with existing buffer content. Default: 'overwrite' */
|
|
14
|
+
mode?: 'overwrite' | 'blend';
|
|
15
|
+
/** Blend mode when mode is 'blend'. Default: 'normal' */
|
|
16
|
+
blendMode?: BlendMode;
|
|
17
|
+
/** Region to draw into (default: full canvas). */
|
|
18
|
+
region?: {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Execute a Canvas 2D drawing callback and write the result into a ColorBuffer.
|
|
27
|
+
*
|
|
28
|
+
* For buffers larger than MAX_TILE_SIZE in either dimension, the drawing is
|
|
29
|
+
* automatically tiled: the callback is invoked once per tile with the context
|
|
30
|
+
* translated so the user draws in full-canvas coordinates.
|
|
31
|
+
*/
|
|
32
|
+
export declare function drawWith2D(buffer: ColorBuffer, callback: (ctx: CanvasRenderingContext2D) => void, options?: DrawWith2DOptions): void;
|
|
33
|
+
/** Exported for testing */
|
|
34
|
+
export declare const _internals: {
|
|
35
|
+
MAX_TILE_SIZE: number;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=Canvas2DBridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Canvas2DBridge.d.ts","sourceRoot":"","sources":["../../src/bridge/Canvas2DBridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAC7B,yDAAyD;IACzD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,kDAAkD;IAClD,MAAM,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAClE;AASD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,CAAC,GAAG,EAAE,wBAAwB,KAAK,IAAI,EACjD,OAAO,GAAE,iBAAsB,GAC9B,IAAI,CA8BN;AAgGD,2BAA2B;AAC3B,eAAO,MAAM,UAAU;;CAAoB,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas2DBridge — Draw with the familiar Canvas 2D API into an HDR float buffer
|
|
3
|
+
*
|
|
4
|
+
* Creates a temporary offscreen canvas at full resolution (or tiled for large
|
|
5
|
+
* formats), passes the 2D context to a user callback, reads back ImageData,
|
|
6
|
+
* and writes into the ColorBuffer as float values (0–255 → 0.0–1.0).
|
|
7
|
+
*
|
|
8
|
+
* This is a one-way bridge: Canvas 2D → float buffer. The 2D context is 8-bit,
|
|
9
|
+
* so this is for convenience shapes/text, not HDR input.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Maximum tile dimension in pixels. Conservative to support all major browsers.
|
|
13
|
+
* Chrome: ~16384, Firefox: ~11180, Safari: ~4096.
|
|
14
|
+
* We use 4096 for maximum compatibility.
|
|
15
|
+
*/
|
|
16
|
+
const MAX_TILE_SIZE = 4096;
|
|
17
|
+
/**
|
|
18
|
+
* Execute a Canvas 2D drawing callback and write the result into a ColorBuffer.
|
|
19
|
+
*
|
|
20
|
+
* For buffers larger than MAX_TILE_SIZE in either dimension, the drawing is
|
|
21
|
+
* automatically tiled: the callback is invoked once per tile with the context
|
|
22
|
+
* translated so the user draws in full-canvas coordinates.
|
|
23
|
+
*/
|
|
24
|
+
export function drawWith2D(buffer, callback, options = {}) {
|
|
25
|
+
const mode = options.mode ?? 'overwrite';
|
|
26
|
+
const blendMode = options.blendMode ?? 'normal';
|
|
27
|
+
// Determine the target region
|
|
28
|
+
const region = options.region ?? { x: 0, y: 0, width: buffer.width, height: buffer.height };
|
|
29
|
+
validateRegion(region, buffer.width, buffer.height);
|
|
30
|
+
const { x: rx, y: ry, width: rw, height: rh } = region;
|
|
31
|
+
// Determine if we need tiling
|
|
32
|
+
if (rw <= MAX_TILE_SIZE && rh <= MAX_TILE_SIZE) {
|
|
33
|
+
// Single pass — no tiling needed
|
|
34
|
+
drawTile(buffer, callback, rx, ry, rw, rh, mode, blendMode);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// Tiled rendering
|
|
38
|
+
for (let ty = 0; ty < rh; ty += MAX_TILE_SIZE) {
|
|
39
|
+
const tileH = Math.min(MAX_TILE_SIZE, rh - ty);
|
|
40
|
+
for (let tx = 0; tx < rw; tx += MAX_TILE_SIZE) {
|
|
41
|
+
const tileW = Math.min(MAX_TILE_SIZE, rw - tx);
|
|
42
|
+
drawTile(buffer, callback, rx + tx, ry + ty, tileW, tileH, mode, blendMode);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Draw a single tile: create an offscreen canvas, invoke the callback with
|
|
49
|
+
* a translated context, read back pixels, write into the buffer.
|
|
50
|
+
*/
|
|
51
|
+
function drawTile(buffer, callback, tileX, tileY, tileW, tileH, mode, blendMode) {
|
|
52
|
+
// Create offscreen canvas for this tile
|
|
53
|
+
const offscreen = document.createElement('canvas');
|
|
54
|
+
offscreen.width = tileW;
|
|
55
|
+
offscreen.height = tileH;
|
|
56
|
+
const ctx = offscreen.getContext('2d');
|
|
57
|
+
if (!ctx) {
|
|
58
|
+
throw new Error('Failed to get 2D rendering context for offscreen canvas');
|
|
59
|
+
}
|
|
60
|
+
// Translate so the user draws in full-canvas coordinates
|
|
61
|
+
ctx.translate(-tileX, -tileY);
|
|
62
|
+
// Clip to the tile region (prevents drawing outside tile bounds)
|
|
63
|
+
ctx.beginPath();
|
|
64
|
+
ctx.rect(tileX, tileY, tileW, tileH);
|
|
65
|
+
ctx.clip();
|
|
66
|
+
// Let the user draw
|
|
67
|
+
callback(ctx);
|
|
68
|
+
// Read back pixels
|
|
69
|
+
const imageData = ctx.getImageData(0, 0, tileW, tileH);
|
|
70
|
+
const pixels = imageData.data; // Uint8ClampedArray, RGBA
|
|
71
|
+
// Write into the float buffer
|
|
72
|
+
const inv255 = 1 / 255;
|
|
73
|
+
if (mode === 'overwrite') {
|
|
74
|
+
for (let row = 0; row < tileH; row++) {
|
|
75
|
+
for (let col = 0; col < tileW; col++) {
|
|
76
|
+
const srcIdx = (row * tileW + col) * 4;
|
|
77
|
+
const r = pixels[srcIdx] * inv255;
|
|
78
|
+
const g = pixels[srcIdx + 1] * inv255;
|
|
79
|
+
const b = pixels[srcIdx + 2] * inv255;
|
|
80
|
+
const a = pixels[srcIdx + 3] * inv255;
|
|
81
|
+
buffer.setPixel(tileX + col, tileY + row, r, g, b, a);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Blend mode — alpha composite onto existing content
|
|
87
|
+
for (let row = 0; row < tileH; row++) {
|
|
88
|
+
for (let col = 0; col < tileW; col++) {
|
|
89
|
+
const srcIdx = (row * tileW + col) * 4;
|
|
90
|
+
const r = pixels[srcIdx] * inv255;
|
|
91
|
+
const g = pixels[srcIdx + 1] * inv255;
|
|
92
|
+
const b = pixels[srcIdx + 2] * inv255;
|
|
93
|
+
const a = pixels[srcIdx + 3] * inv255;
|
|
94
|
+
// Skip fully transparent pixels (common optimization)
|
|
95
|
+
if (a === 0)
|
|
96
|
+
continue;
|
|
97
|
+
buffer.blendPixel(tileX + col, tileY + row, r, g, b, a, blendMode);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Clean up — remove references to allow GC
|
|
102
|
+
offscreen.width = 0;
|
|
103
|
+
offscreen.height = 0;
|
|
104
|
+
}
|
|
105
|
+
function validateRegion(region, bufferWidth, bufferHeight) {
|
|
106
|
+
const { x, y, width, height } = region;
|
|
107
|
+
if (x < 0 || y < 0 || width <= 0 || height <= 0) {
|
|
108
|
+
throw new RangeError(`Region must have non-negative origin and positive dimensions, got (${x},${y} ${width}×${height})`);
|
|
109
|
+
}
|
|
110
|
+
if (x + width > bufferWidth || y + height > bufferHeight) {
|
|
111
|
+
throw new RangeError(`Region (${x},${y} ${width}×${height}) exceeds buffer bounds ${bufferWidth}×${bufferHeight}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Exported for testing */
|
|
115
|
+
export const _internals = { MAX_TILE_SIZE };
|
|
116
|
+
//# sourceMappingURL=Canvas2DBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Canvas2DBridge.js","sourceRoot":"","sources":["../../src/bridge/Canvas2DBridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ColorBuffer — Float32/Float64 RGBA pixel buffer
|
|
3
|
+
*
|
|
4
|
+
* The foundation of the HD Canvas framework. Stores pixel data as
|
|
5
|
+
* unbounded floats (0.0–1.0 is "standard" range, >1.0 is HDR).
|
|
6
|
+
* Row-major layout, 4 floats per pixel (R, G, B, A).
|
|
7
|
+
*/
|
|
8
|
+
export type ColorDepth = 32 | 64;
|
|
9
|
+
export type BlendMode = 'normal' | 'add' | 'multiply';
|
|
10
|
+
export type RGBA = [r: number, g: number, b: number, a: number];
|
|
11
|
+
/** Minimal interface for reading pixel data — used by the export pipeline. */
|
|
12
|
+
export interface IColorBuffer {
|
|
13
|
+
readonly width: number;
|
|
14
|
+
readonly height: number;
|
|
15
|
+
readonly depth: ColorDepth;
|
|
16
|
+
readonly data: Float32Array | Float64Array;
|
|
17
|
+
}
|
|
18
|
+
export declare class ColorBuffer implements IColorBuffer {
|
|
19
|
+
readonly width: number;
|
|
20
|
+
readonly height: number;
|
|
21
|
+
readonly depth: ColorDepth;
|
|
22
|
+
readonly data: Float32Array | Float64Array;
|
|
23
|
+
constructor(width: number, height: number, depth?: ColorDepth);
|
|
24
|
+
/** Byte size of the underlying typed array */
|
|
25
|
+
get byteLength(): number;
|
|
26
|
+
private offset;
|
|
27
|
+
setPixel(x: number, y: number, r: number, g: number, b: number, a?: number): void;
|
|
28
|
+
getPixel(x: number, y: number): RGBA;
|
|
29
|
+
/**
|
|
30
|
+
* Blend a color onto the existing pixel using the specified blend mode.
|
|
31
|
+
* All modes use standard alpha compositing for the alpha channel.
|
|
32
|
+
*/
|
|
33
|
+
blendPixel(x: number, y: number, r: number, g: number, b: number, a: number, mode?: BlendMode): void;
|
|
34
|
+
/** Fill the entire buffer with a single color (default: transparent black) */
|
|
35
|
+
clear(r?: number, g?: number, b?: number, a?: number): void;
|
|
36
|
+
/** Extract a rectangular region as a new ColorBuffer */
|
|
37
|
+
getRegion(x: number, y: number, w: number, h: number): ColorBuffer;
|
|
38
|
+
/** Write a ColorBuffer region into this buffer at the given position */
|
|
39
|
+
putRegion(x: number, y: number, region: ColorBuffer): void;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ColorBuffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ColorBuffer.d.ts","sourceRoot":"","sources":["../../src/core/ColorBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,CAAC;AACjC,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,KAAK,GAAG,UAAU,CAAC;AACtD,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;AAEhE,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;CAC5C;AAED,qBAAa,WAAY,YAAW,YAAY;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,YAAY,CAAC;gBAE/B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,UAAe;IAgBjE,8CAA8C;IAC9C,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,OAAO,CAAC,MAAM;IASd,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,GAAE,MAAY,GAAG,IAAI;IAQtF,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAUpC;;;OAGG;IACH,UAAU,CACR,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,IAAI,GAAE,SAAoB,GACzB,IAAI;IAgDP,8EAA8E;IAC9E,KAAK,CAAC,CAAC,GAAE,MAAU,EAAE,CAAC,GAAE,MAAU,EAAE,CAAC,GAAE,MAAU,EAAE,CAAC,GAAE,MAAU,GAAG,IAAI;IASvE,wDAAwD;IACxD,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,WAAW;IAmBlE,wEAAwE;IACxE,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;CAa3D"}
|