@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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +412 -0
  3. package/dist/bridge/Canvas2DBridge.d.ts +37 -0
  4. package/dist/bridge/Canvas2DBridge.d.ts.map +1 -0
  5. package/dist/bridge/Canvas2DBridge.js +116 -0
  6. package/dist/bridge/Canvas2DBridge.js.map +1 -0
  7. package/dist/core/ColorBuffer.d.ts +41 -0
  8. package/dist/core/ColorBuffer.d.ts.map +1 -0
  9. package/dist/core/ColorBuffer.js +138 -0
  10. package/dist/core/ColorBuffer.js.map +1 -0
  11. package/dist/core/HDCanvas.d.ts +80 -0
  12. package/dist/core/HDCanvas.d.ts.map +1 -0
  13. package/dist/core/HDCanvas.js +104 -0
  14. package/dist/core/HDCanvas.js.map +1 -0
  15. package/dist/core/PaperSize.d.ts +40 -0
  16. package/dist/core/PaperSize.d.ts.map +1 -0
  17. package/dist/core/PaperSize.js +63 -0
  18. package/dist/core/PaperSize.js.map +1 -0
  19. package/dist/export/ExportPipeline.d.ts +94 -0
  20. package/dist/export/ExportPipeline.d.ts.map +1 -0
  21. package/dist/export/ExportPipeline.js +121 -0
  22. package/dist/export/ExportPipeline.js.map +1 -0
  23. package/dist/export/PNGExporter.d.ts +62 -0
  24. package/dist/export/PNGExporter.d.ts.map +1 -0
  25. package/dist/export/PNGExporter.js +146 -0
  26. package/dist/export/PNGExporter.js.map +1 -0
  27. package/dist/export/ToneMapper.d.ts +88 -0
  28. package/dist/export/ToneMapper.d.ts.map +1 -0
  29. package/dist/export/ToneMapper.js +175 -0
  30. package/dist/export/ToneMapper.js.map +1 -0
  31. package/dist/index.d.ts +16 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +22 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/preview/FitStrategy.d.ts +27 -0
  36. package/dist/preview/FitStrategy.d.ts.map +1 -0
  37. package/dist/preview/FitStrategy.js +31 -0
  38. package/dist/preview/FitStrategy.js.map +1 -0
  39. package/dist/preview/PreviewRenderer.d.ts +71 -0
  40. package/dist/preview/PreviewRenderer.d.ts.map +1 -0
  41. package/dist/preview/PreviewRenderer.js +304 -0
  42. package/dist/preview/PreviewRenderer.js.map +1 -0
  43. package/package.json +47 -0
@@ -0,0 +1,121 @@
1
+ /**
2
+ * ExportPipeline — Wires ToneMapper + PNGExporter into HDCanvas.export()
3
+ *
4
+ * This is the integration layer. It creates the export function that
5
+ * HDCanvas.setExportFn() expects, connecting the float buffer to the
6
+ * tone mapping → PNG encoding → Blob pipeline.
7
+ *
8
+ * Usage:
9
+ * import { HDCanvas } from '../core/HDCanvas.js';
10
+ * import { attachExportPipeline } from './ExportPipeline.js';
11
+ *
12
+ * const canvas = new HDCanvas({ paperSize: 'A3', dpi: 300 });
13
+ * attachExportPipeline(canvas);
14
+ *
15
+ * // Now canvas.export() works:
16
+ * const blob = await canvas.export({ toneMap: 'aces', exposure: 1.2 });
17
+ */
18
+ import { ToneMapper } from './ToneMapper.js';
19
+ import { PNGExporter } from './PNGExporter.js';
20
+ // ─── Core export function ────────────────────────────────────────────
21
+ /**
22
+ * Execute the full export pipeline: tone map → encode PNG → return Blob.
23
+ *
24
+ * This is the pure function that does the work. It takes a ColorBuffer
25
+ * and options, returns a Blob. No side effects, no DOM dependency.
26
+ */
27
+ export function exportBuffer(buffer, options) {
28
+ const { toneMap = 'reinhard', exposure = 0, gamma = 2.2, format = 'png', dpi, onProgress, } = options;
29
+ if (format !== 'png') {
30
+ throw new Error(`Unsupported export format: "${format}". Currently supported: png`);
31
+ }
32
+ // Step 1: Tone map (HDR float → 8-bit LDR)
33
+ onProgress?.(0);
34
+ const toneMapper = new ToneMapper({
35
+ algorithm: toneMap,
36
+ exposure,
37
+ gamma,
38
+ outputDepth: 8,
39
+ });
40
+ const ldrData = toneMapper.map(buffer);
41
+ onProgress?.(50);
42
+ // Step 2: Encode PNG with DPI metadata
43
+ const pngExporter = new PNGExporter();
44
+ const result = pngExporter.export(ldrData, {
45
+ width: buffer.width,
46
+ height: buffer.height,
47
+ dpi,
48
+ depth: 8,
49
+ });
50
+ onProgress?.(90);
51
+ // Step 3: Create Blob
52
+ const blob = new Blob([result.data.buffer], { type: 'image/png' });
53
+ onProgress?.(100);
54
+ return blob;
55
+ }
56
+ /**
57
+ * Attach the export pipeline to an HDCanvas instance.
58
+ *
59
+ * After calling this, `canvas.export()` will work:
60
+ * const blob = await canvas.export({ toneMap: 'aces', exposure: 1.2 });
61
+ *
62
+ * @param canvas - An HDCanvas instance (or anything matching ExportableCanvas)
63
+ * @param onProgress - Optional progress callback for all exports
64
+ */
65
+ export function attachExportPipeline(canvas, onProgress) {
66
+ canvas.setExportFn(async (buffer, options) => {
67
+ return exportBuffer(buffer, {
68
+ ...options,
69
+ dpi: canvas.dpi,
70
+ onProgress,
71
+ });
72
+ });
73
+ }
74
+ // ─── Download helper ─────────────────────────────────────────────────
75
+ /**
76
+ * Trigger a browser download of a Blob.
77
+ *
78
+ * Creates a temporary <a> element, clicks it, and cleans up.
79
+ * Only works in browser environments with DOM access.
80
+ */
81
+ export function downloadBlob(blob, filename) {
82
+ if (typeof document === 'undefined') {
83
+ throw new Error('downloadBlob() requires a browser environment with DOM access');
84
+ }
85
+ const url = URL.createObjectURL(blob);
86
+ const a = document.createElement('a');
87
+ a.href = url;
88
+ a.download = filename;
89
+ a.style.display = 'none';
90
+ document.body.appendChild(a);
91
+ a.click();
92
+ // Clean up after a tick to ensure download starts
93
+ setTimeout(() => {
94
+ URL.revokeObjectURL(url);
95
+ document.body.removeChild(a);
96
+ }, 100);
97
+ }
98
+ /**
99
+ * Generate a descriptive filename for an export.
100
+ *
101
+ * @example generateFilename(3508, 4961, 300) → "artwork-3508x4961-300dpi.png"
102
+ */
103
+ export function generateFilename(width, height, dpi, prefix = 'artwork') {
104
+ return `${prefix}-${width}x${height}-${dpi}dpi.png`;
105
+ }
106
+ /**
107
+ * Convenience: export an HDCanvas and trigger a browser download.
108
+ *
109
+ * @param canvas - HDCanvas with export pipeline attached
110
+ * @param options - Export options (toneMap, exposure, gamma)
111
+ * @param filename - Custom filename (auto-generated if omitted)
112
+ */
113
+ export async function exportAndDownload(canvas, options = {}, filename) {
114
+ const blob = await exportBuffer(canvas.buffer, {
115
+ ...options,
116
+ dpi: canvas.dpi,
117
+ });
118
+ const name = filename ?? generateFilename(canvas.buffer.width, canvas.buffer.height, canvas.dpi);
119
+ downloadBlob(blob, name);
120
+ }
121
+ //# sourceMappingURL=ExportPipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ExportPipeline.js","sourceRoot":"","sources":["../../src/export/ExportPipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * PNGExporter — Full-resolution PNG export with DPI metadata.
3
+ *
4
+ * Takes tone-mapped pixel data (Uint8Array or Uint16Array RGBA) and
5
+ * produces a PNG file with the pHYs chunk set for correct print DPI.
6
+ *
7
+ * Uses fast-png for encoding — pure JS, works in both Node.js and browser.
8
+ * No native dependencies.
9
+ *
10
+ * The pHYs chunk tells image viewers/print software the physical pixel
11
+ * density, so an A3@300DPI image opens at the correct physical size.
12
+ */
13
+ export interface PNGExportOptions {
14
+ /** Pixel width of the image. */
15
+ width: number;
16
+ /** Pixel height of the image. */
17
+ height: number;
18
+ /** DPI for the pHYs metadata chunk. */
19
+ dpi: number;
20
+ /**
21
+ * Bit depth of the input data.
22
+ * 8 → expects Uint8Array, 16 → expects Uint16Array.
23
+ * Default: 8.
24
+ */
25
+ depth?: 8 | 16;
26
+ }
27
+ export interface PNGExportResult {
28
+ /** Raw PNG file bytes. */
29
+ data: Uint8Array;
30
+ /** MIME type. */
31
+ mimeType: 'image/png';
32
+ /** Suggested filename. */
33
+ filename: string;
34
+ }
35
+ /**
36
+ * Convert DPI to pixels-per-meter for the PNG pHYs chunk.
37
+ * 1 inch = 0.0254 meters, so ppm = dpi / 0.0254.
38
+ */
39
+ export declare function dpiToPixelsPerMeter(dpi: number): number;
40
+ /**
41
+ * Inject a pHYs chunk into an existing PNG buffer.
42
+ *
43
+ * PNG structure: signature (8 bytes) → IHDR chunk → ... → IDAT → IEND
44
+ * pHYs must appear before the first IDAT chunk.
45
+ * We insert it right after IHDR (which is always the first chunk).
46
+ */
47
+ export declare function injectPHYs(png: Uint8Array, dpi: number): Uint8Array;
48
+ export declare class PNGExporter {
49
+ /**
50
+ * Export tone-mapped pixel data to PNG with DPI metadata.
51
+ *
52
+ * @param data - Tone-mapped RGBA pixel data (Uint8Array for 8-bit, Uint16Array for 16-bit)
53
+ * @param options - Export options (width, height, dpi, depth)
54
+ * @returns PNG file data with pHYs chunk embedded
55
+ */
56
+ export(data: Uint8Array | Uint16Array, options: PNGExportOptions): PNGExportResult;
57
+ /**
58
+ * Export and return as a Blob (browser convenience).
59
+ */
60
+ exportBlob(data: Uint8Array | Uint16Array, options: PNGExportOptions): Blob;
61
+ }
62
+ //# sourceMappingURL=PNGExporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PNGExporter.d.ts","sourceRoot":"","sources":["../../src/export/PNGExporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,0BAA0B;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,iBAAiB;IACjB,QAAQ,EAAE,WAAW,CAAC;IACtB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AA2DD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAoBnE;AAID,qBAAa,WAAW;IACtB;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,EAAE,OAAO,EAAE,gBAAgB,GAAG,eAAe;IA2ClF;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,EAAE,OAAO,EAAE,gBAAgB,GAAG,IAAI;CAK5E"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * PNGExporter — Full-resolution PNG export with DPI metadata.
3
+ *
4
+ * Takes tone-mapped pixel data (Uint8Array or Uint16Array RGBA) and
5
+ * produces a PNG file with the pHYs chunk set for correct print DPI.
6
+ *
7
+ * Uses fast-png for encoding — pure JS, works in both Node.js and browser.
8
+ * No native dependencies.
9
+ *
10
+ * The pHYs chunk tells image viewers/print software the physical pixel
11
+ * density, so an A3@300DPI image opens at the correct physical size.
12
+ */
13
+ import { encode } from 'fast-png';
14
+ // ─── DPI conversion ──────────────────────────────────────────────────
15
+ /**
16
+ * Convert DPI to pixels-per-meter for the PNG pHYs chunk.
17
+ * 1 inch = 0.0254 meters, so ppm = dpi / 0.0254.
18
+ */
19
+ export function dpiToPixelsPerMeter(dpi) {
20
+ return Math.round(dpi / 0.0254);
21
+ }
22
+ // ─── PNG pHYs chunk injection ────────────────────────────────────────
23
+ /**
24
+ * CRC32 lookup table for PNG chunk checksums.
25
+ */
26
+ const CRC_TABLE = new Uint32Array(256);
27
+ for (let n = 0; n < 256; n++) {
28
+ let c = n;
29
+ for (let k = 0; k < 8; k++) {
30
+ c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
31
+ }
32
+ CRC_TABLE[n] = c;
33
+ }
34
+ function crc32(data) {
35
+ let crc = 0xffffffff;
36
+ for (let i = 0; i < data.length; i++) {
37
+ crc = CRC_TABLE[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
38
+ }
39
+ return (crc ^ 0xffffffff) >>> 0;
40
+ }
41
+ /**
42
+ * Build a PNG pHYs chunk.
43
+ *
44
+ * Format: 4 bytes length + 4 bytes "pHYs" + 9 bytes data + 4 bytes CRC
45
+ * Data: 4 bytes X ppm (big-endian) + 4 bytes Y ppm + 1 byte unit (1=meter)
46
+ */
47
+ function buildPHYsChunk(dpi) {
48
+ const ppm = dpiToPixelsPerMeter(dpi);
49
+ const chunk = new Uint8Array(21); // 4 + 4 + 9 + 4
50
+ const view = new DataView(chunk.buffer);
51
+ // Length of data section (9 bytes)
52
+ view.setUint32(0, 9, false);
53
+ // Chunk type: "pHYs"
54
+ chunk[4] = 0x70; // p
55
+ chunk[5] = 0x48; // H
56
+ chunk[6] = 0x59; // Y
57
+ chunk[7] = 0x73; // s
58
+ // X pixels per unit (big-endian)
59
+ view.setUint32(8, ppm, false);
60
+ // Y pixels per unit (big-endian)
61
+ view.setUint32(12, ppm, false);
62
+ // Unit: 1 = meter
63
+ chunk[16] = 1;
64
+ // CRC over type + data (bytes 4..16 inclusive)
65
+ const crcData = chunk.slice(4, 17);
66
+ const crcVal = crc32(crcData);
67
+ view.setUint32(17, crcVal, false);
68
+ return chunk;
69
+ }
70
+ /**
71
+ * Inject a pHYs chunk into an existing PNG buffer.
72
+ *
73
+ * PNG structure: signature (8 bytes) → IHDR chunk → ... → IDAT → IEND
74
+ * pHYs must appear before the first IDAT chunk.
75
+ * We insert it right after IHDR (which is always the first chunk).
76
+ */
77
+ export function injectPHYs(png, dpi) {
78
+ // PNG signature is 8 bytes
79
+ // IHDR chunk: 4 (length) + 4 (type) + 13 (data) + 4 (crc) = 25 bytes
80
+ // So IHDR ends at byte 33
81
+ const IHDR_END = 8 + 25;
82
+ // Verify this is a PNG
83
+ if (png[0] !== 0x89 || png[1] !== 0x50 || png[2] !== 0x4e || png[3] !== 0x47) {
84
+ throw new Error('Not a valid PNG file');
85
+ }
86
+ const phys = buildPHYsChunk(dpi);
87
+ // Concatenate: [signature + IHDR] + [pHYs] + [rest of PNG]
88
+ const result = new Uint8Array(png.length + phys.length);
89
+ result.set(png.subarray(0, IHDR_END), 0);
90
+ result.set(phys, IHDR_END);
91
+ result.set(png.subarray(IHDR_END), IHDR_END + phys.length);
92
+ return result;
93
+ }
94
+ // ─── PNGExporter class ───────────────────────────────────────────────
95
+ export class PNGExporter {
96
+ /**
97
+ * Export tone-mapped pixel data to PNG with DPI metadata.
98
+ *
99
+ * @param data - Tone-mapped RGBA pixel data (Uint8Array for 8-bit, Uint16Array for 16-bit)
100
+ * @param options - Export options (width, height, dpi, depth)
101
+ * @returns PNG file data with pHYs chunk embedded
102
+ */
103
+ export(data, options) {
104
+ const { width, height, dpi, depth = 8 } = options;
105
+ if (width <= 0 || height <= 0) {
106
+ throw new Error(`Invalid dimensions: ${width}x${height}`);
107
+ }
108
+ if (dpi <= 0) {
109
+ throw new Error(`DPI must be positive, got: ${dpi}`);
110
+ }
111
+ const expectedLength = width * height * 4;
112
+ if (data.length !== expectedLength) {
113
+ throw new Error(`Data length mismatch: expected ${expectedLength} (${width}x${height}x4), got ${data.length}`);
114
+ }
115
+ if (depth === 16 && !(data instanceof Uint16Array)) {
116
+ throw new Error('16-bit depth requires Uint16Array input');
117
+ }
118
+ if (depth === 8 && !(data instanceof Uint8Array)) {
119
+ throw new Error('8-bit depth requires Uint8Array input');
120
+ }
121
+ // Encode PNG using fast-png
122
+ const pngBytes = encode({
123
+ width,
124
+ height,
125
+ data,
126
+ depth,
127
+ channels: 4,
128
+ });
129
+ // Inject pHYs chunk for DPI metadata
130
+ const pngWithDpi = injectPHYs(pngBytes, dpi);
131
+ return {
132
+ data: pngWithDpi,
133
+ mimeType: 'image/png',
134
+ filename: `artwork-${width}x${height}-${dpi}dpi.png`,
135
+ };
136
+ }
137
+ /**
138
+ * Export and return as a Blob (browser convenience).
139
+ */
140
+ exportBlob(data, options) {
141
+ const result = this.export(data, options);
142
+ // Cast needed: Node.js types have SharedArrayBuffer in ArrayBufferLike
143
+ return new Blob([result.data.buffer], { type: result.mimeType });
144
+ }
145
+ }
146
+ //# sourceMappingURL=PNGExporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PNGExporter.js","sourceRoot":"","sources":["../../src/export/PNGExporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ToneMapper — HDR → LDR tone mapping with pluggable algorithms.
3
+ *
4
+ * Takes an HDR ColorBuffer (unbounded float RGBA) and produces a
5
+ * quantized Uint8Array or Uint16Array suitable for image encoding.
6
+ *
7
+ * Pipeline per-pixel:
8
+ * 1. Exposure: multiply RGB by 2^exposure
9
+ * 2. Tone map: compress HDR range → [0, 1] using selected algorithm
10
+ * 3. Gamma: apply gamma correction (output = value^(1/gamma))
11
+ * 4. Quantize: float [0,1] → uint8 [0,255] or uint16 [0,65535]
12
+ *
13
+ * Alpha is passed through without tone mapping (clamped to [0,1]).
14
+ */
15
+ import type { IColorBuffer } from '../core/ColorBuffer.js';
16
+ /**
17
+ * A tone mapping function. Takes an unbounded HDR luminance value (≥0)
18
+ * and returns a value in [0, 1].
19
+ */
20
+ export type ToneMapFn = (v: number) => number;
21
+ /** Simple clamp to [0, 1]. No compression — just clips. */
22
+ export declare function clamp(v: number): number;
23
+ /**
24
+ * Reinhard tone mapping: v / (1 + v).
25
+ * Smoothly compresses the full HDR range into [0, 1).
26
+ * Never clips, but can look washed out without exposure adjustment.
27
+ */
28
+ export declare function reinhard(v: number): number;
29
+ /**
30
+ * ACES filmic tone mapping (simplified fit by Krzysztof Narkowicz).
31
+ * Attempt at a cinematic look with good contrast and saturation.
32
+ *
33
+ * f(x) = (x(ax + b)) / (x(cx + d) + e)
34
+ * where a=2.51, b=0.03, c=2.43, d=0.59, e=0.14
35
+ */
36
+ export declare function aces(v: number): number;
37
+ /** Built-in algorithm registry. */
38
+ export declare const TONE_MAP_ALGORITHMS: {
39
+ readonly clamp: typeof clamp;
40
+ readonly reinhard: typeof reinhard;
41
+ readonly aces: typeof aces;
42
+ };
43
+ export type ToneMapAlgorithm = keyof typeof TONE_MAP_ALGORITHMS;
44
+ export type OutputDepth = 8 | 16;
45
+ export interface ToneMapOptions {
46
+ /** Tone mapping algorithm name or custom function. */
47
+ algorithm: ToneMapAlgorithm | ToneMapFn;
48
+ /**
49
+ * Exposure adjustment in stops. RGB values are multiplied by 2^exposure
50
+ * before tone mapping. Default: 0 (no adjustment).
51
+ */
52
+ exposure?: number;
53
+ /**
54
+ * Gamma correction value. Applied after tone mapping as v^(1/gamma).
55
+ * Default: 2.2 (standard sRGB).
56
+ */
57
+ gamma?: number;
58
+ /**
59
+ * Output bit depth. 8 → Uint8Array [0,255], 16 → Uint16Array [0,65535].
60
+ * Default: 8.
61
+ */
62
+ outputDepth?: OutputDepth;
63
+ }
64
+ export declare class ToneMapper {
65
+ private readonly mapFn;
66
+ private readonly exposureMultiplier;
67
+ private readonly invGamma;
68
+ private readonly outputDepth;
69
+ private readonly maxVal;
70
+ constructor(options: ToneMapOptions);
71
+ /**
72
+ * Map a single HDR channel value through the full pipeline.
73
+ * Useful for testing. Does NOT apply to alpha.
74
+ */
75
+ mapValue(v: number): number;
76
+ /**
77
+ * Quantize a [0,1] float to the output integer range.
78
+ */
79
+ quantize(v: number): number;
80
+ /**
81
+ * Process an entire ColorBuffer → quantized RGBA output.
82
+ *
83
+ * Returns Uint8Array (outputDepth=8) or Uint16Array (outputDepth=16).
84
+ * Output has the same pixel layout: 4 values per pixel [R, G, B, A].
85
+ */
86
+ map(input: IColorBuffer): Uint8Array | Uint16Array;
87
+ }
88
+ //# sourceMappingURL=ToneMapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneMapper.d.ts","sourceRoot":"","sources":["../../src/export/ToneMapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAI3D;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAE9C,2DAA2D;AAC3D,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG1C;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAStC;AAED,mCAAmC;AACnC,eAAO,MAAM,mBAAmB;;;;CAItB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAIhE,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAID,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAY;IAClC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,EAAE,cAAc;IA+BnC;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAS3B;;OAEG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM;IAK3B;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,GAAG,WAAW;CAyEnD"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * ToneMapper — HDR → LDR tone mapping with pluggable algorithms.
3
+ *
4
+ * Takes an HDR ColorBuffer (unbounded float RGBA) and produces a
5
+ * quantized Uint8Array or Uint16Array suitable for image encoding.
6
+ *
7
+ * Pipeline per-pixel:
8
+ * 1. Exposure: multiply RGB by 2^exposure
9
+ * 2. Tone map: compress HDR range → [0, 1] using selected algorithm
10
+ * 3. Gamma: apply gamma correction (output = value^(1/gamma))
11
+ * 4. Quantize: float [0,1] → uint8 [0,255] or uint16 [0,65535]
12
+ *
13
+ * Alpha is passed through without tone mapping (clamped to [0,1]).
14
+ */
15
+ /** Simple clamp to [0, 1]. No compression — just clips. */
16
+ export function clamp(v) {
17
+ return v < 0 ? 0 : v > 1 ? 1 : v;
18
+ }
19
+ /**
20
+ * Reinhard tone mapping: v / (1 + v).
21
+ * Smoothly compresses the full HDR range into [0, 1).
22
+ * Never clips, but can look washed out without exposure adjustment.
23
+ */
24
+ export function reinhard(v) {
25
+ if (v < 0)
26
+ return 0;
27
+ return v / (1 + v);
28
+ }
29
+ /**
30
+ * ACES filmic tone mapping (simplified fit by Krzysztof Narkowicz).
31
+ * Attempt at a cinematic look with good contrast and saturation.
32
+ *
33
+ * f(x) = (x(ax + b)) / (x(cx + d) + e)
34
+ * where a=2.51, b=0.03, c=2.43, d=0.59, e=0.14
35
+ */
36
+ export function aces(v) {
37
+ if (v < 0)
38
+ return 0;
39
+ const a = 2.51;
40
+ const b = 0.03;
41
+ const c = 2.43;
42
+ const d = 0.59;
43
+ const e = 0.14;
44
+ const mapped = (v * (a * v + b)) / (v * (c * v + d) + e);
45
+ return mapped < 0 ? 0 : mapped > 1 ? 1 : mapped;
46
+ }
47
+ /** Built-in algorithm registry. */
48
+ export const TONE_MAP_ALGORITHMS = {
49
+ clamp,
50
+ reinhard,
51
+ aces,
52
+ };
53
+ // ─── ToneMapper class ────────────────────────────────────────────────
54
+ export class ToneMapper {
55
+ mapFn;
56
+ exposureMultiplier;
57
+ invGamma;
58
+ outputDepth;
59
+ maxVal;
60
+ constructor(options) {
61
+ // Resolve algorithm
62
+ if (typeof options.algorithm === 'function') {
63
+ this.mapFn = options.algorithm;
64
+ }
65
+ else {
66
+ const fn = TONE_MAP_ALGORITHMS[options.algorithm];
67
+ if (!fn) {
68
+ throw new Error(`Unknown tone map algorithm: "${options.algorithm}". ` +
69
+ `Available: ${Object.keys(TONE_MAP_ALGORITHMS).join(', ')}`);
70
+ }
71
+ this.mapFn = fn;
72
+ }
73
+ const exposure = options.exposure ?? 0;
74
+ this.exposureMultiplier = Math.pow(2, exposure);
75
+ const gamma = options.gamma ?? 2.2;
76
+ if (gamma <= 0) {
77
+ throw new Error(`Gamma must be positive, got: ${gamma}`);
78
+ }
79
+ this.invGamma = 1 / gamma;
80
+ this.outputDepth = options.outputDepth ?? 8;
81
+ if (this.outputDepth !== 8 && this.outputDepth !== 16) {
82
+ throw new Error(`Output depth must be 8 or 16, got: ${this.outputDepth}`);
83
+ }
84
+ this.maxVal = this.outputDepth === 16 ? 65535 : 255;
85
+ }
86
+ /**
87
+ * Map a single HDR channel value through the full pipeline.
88
+ * Useful for testing. Does NOT apply to alpha.
89
+ */
90
+ mapValue(v) {
91
+ // 1. Exposure
92
+ const exposed = v * this.exposureMultiplier;
93
+ // 2. Tone map
94
+ const mapped = this.mapFn(exposed);
95
+ // 3. Gamma
96
+ return Math.pow(mapped, this.invGamma);
97
+ }
98
+ /**
99
+ * Quantize a [0,1] float to the output integer range.
100
+ */
101
+ quantize(v) {
102
+ const clamped = v < 0 ? 0 : v > 1 ? 1 : v;
103
+ return Math.round(clamped * this.maxVal);
104
+ }
105
+ /**
106
+ * Process an entire ColorBuffer → quantized RGBA output.
107
+ *
108
+ * Returns Uint8Array (outputDepth=8) or Uint16Array (outputDepth=16).
109
+ * Output has the same pixel layout: 4 values per pixel [R, G, B, A].
110
+ */
111
+ map(input) {
112
+ const pixelCount = input.width * input.height;
113
+ const totalValues = pixelCount * 4;
114
+ const src = input.data;
115
+ const dst = this.outputDepth === 16
116
+ ? new Uint16Array(totalValues)
117
+ : new Uint8Array(totalValues);
118
+ const exposureMul = this.exposureMultiplier;
119
+ const mapFn = this.mapFn;
120
+ const invGamma = this.invGamma;
121
+ const maxVal = this.maxVal;
122
+ // Build a LUT for gamma correction when outputting 8-bit.
123
+ // Maps integer [0..LUT_SIZE] → gamma-corrected [0..maxVal].
124
+ // This replaces per-pixel Math.pow with a table lookup.
125
+ const useGammaLut = this.outputDepth === 8;
126
+ const LUT_SIZE = 4096;
127
+ let gammaLut = null;
128
+ if (useGammaLut) {
129
+ gammaLut = new Uint8Array(LUT_SIZE + 1);
130
+ for (let i = 0; i <= LUT_SIZE; i++) {
131
+ const v = i / LUT_SIZE;
132
+ gammaLut[i] = Math.round(Math.pow(v, invGamma) * maxVal);
133
+ }
134
+ }
135
+ // Hot loop — fully unrolled RGB channels, no inner loop.
136
+ // Non-null assertions are safe: loop bounds are derived from array length.
137
+ if (gammaLut) {
138
+ // Fast path: 8-bit output with LUT gamma
139
+ for (let i = 0; i < totalValues; i += 4) {
140
+ // R
141
+ let mapped = mapFn(src[i] * exposureMul);
142
+ mapped = mapped < 0 ? 0 : mapped > 1 ? 1 : mapped;
143
+ dst[i] = gammaLut[(mapped * LUT_SIZE + 0.5) | 0];
144
+ // G
145
+ mapped = mapFn(src[i + 1] * exposureMul);
146
+ mapped = mapped < 0 ? 0 : mapped > 1 ? 1 : mapped;
147
+ dst[i + 1] = gammaLut[(mapped * LUT_SIZE + 0.5) | 0];
148
+ // B
149
+ mapped = mapFn(src[i + 2] * exposureMul);
150
+ mapped = mapped < 0 ? 0 : mapped > 1 ? 1 : mapped;
151
+ dst[i + 2] = gammaLut[(mapped * LUT_SIZE + 0.5) | 0];
152
+ // Alpha: clamp and quantize directly
153
+ const a = src[i + 3];
154
+ dst[i + 3] = Math.round((a < 0 ? 0 : a > 1 ? 1 : a) * maxVal);
155
+ }
156
+ }
157
+ else {
158
+ // 16-bit path: use Math.pow (LUT would be too large)
159
+ for (let i = 0; i < totalValues; i += 4) {
160
+ for (let c = 0; c < 3; c++) {
161
+ const exposed = src[i + c] * exposureMul;
162
+ const mapped = mapFn(exposed);
163
+ const gammaed = Math.pow(mapped, invGamma);
164
+ const clamped = gammaed < 0 ? 0 : gammaed > 1 ? 1 : gammaed;
165
+ dst[i + c] = Math.round(clamped * maxVal);
166
+ }
167
+ const a = src[i + 3];
168
+ const aClamped = a < 0 ? 0 : a > 1 ? 1 : a;
169
+ dst[i + 3] = Math.round(aClamped * maxVal);
170
+ }
171
+ }
172
+ return dst;
173
+ }
174
+ }
175
+ //# sourceMappingURL=ToneMapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ToneMapper.js","sourceRoot":"","sources":["../../src/export/ToneMapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * HD Canvas Framework — Public API
3
+ *
4
+ * A reusable library for generative art with high dynamic range color,
5
+ * configurable paper sizes, and print-quality export.
6
+ */
7
+ export { ColorBuffer, type IColorBuffer, type ColorDepth, type BlendMode, type RGBA, } from './core/ColorBuffer.js';
8
+ export { PAPER_SIZES, sizeToPx, resolvePaperSize, estimateBufferBytes, type PaperSizeKey, type PaperDimensions, type Orientation, } from './core/PaperSize.js';
9
+ export { HDCanvas, type HDCanvasOptions, type ExportOptions, } from './core/HDCanvas.js';
10
+ export { PreviewRenderer, type PreviewRendererOptions } from './preview/PreviewRenderer.js';
11
+ export { calculateFit, type FitMode, type FitResult } from './preview/FitStrategy.js';
12
+ export { drawWith2D, type DrawWith2DOptions } from './bridge/Canvas2DBridge.js';
13
+ export { ToneMapper, clamp, reinhard, aces, TONE_MAP_ALGORITHMS, type ToneMapFn, type ToneMapAlgorithm, type ToneMapOptions, type OutputDepth, } from './export/ToneMapper.js';
14
+ export { PNGExporter, dpiToPixelsPerMeter, injectPHYs, type PNGExportOptions, type PNGExportResult, } from './export/PNGExporter.js';
15
+ export { exportBuffer, attachExportPipeline, downloadBlob, generateFilename, exportAndDownload, type ExportPipelineOptions, type ExportProgressFn, type ExportableCanvas, } from './export/ExportPipeline.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,IAAI,GACV,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,WAAW,EACX,QAAQ,EACR,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,WAAW,GACjB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC5F,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGtF,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGhF,OAAO,EACL,UAAU,EACV,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,WAAW,GACjB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,UAAU,EACV,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EACL,YAAY,EACZ,oBAAoB,EACpB,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC"}