@12ui/design 0.2.70 → 0.2.72

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 (127) hide show
  1. package/README.md +5 -0
  2. package/SKILL.md +1 -1
  3. package/dist/cli-capabilities.d.ts +9 -0
  4. package/dist/cli-capabilities.d.ts.map +1 -1
  5. package/dist/cli-capabilities.js +10 -0
  6. package/dist/cli-capabilities.js.map +1 -1
  7. package/dist/cli-conversion-recovery-advice.d.ts +35 -0
  8. package/dist/cli-conversion-recovery-advice.d.ts.map +1 -0
  9. package/dist/cli-conversion-recovery-advice.js +72 -0
  10. package/dist/cli-conversion-recovery-advice.js.map +1 -0
  11. package/dist/cli-convert-command.d.ts.map +1 -1
  12. package/dist/cli-convert-command.js +153 -35
  13. package/dist/cli-convert-command.js.map +1 -1
  14. package/dist/cli-help/index.d.ts +93 -0
  15. package/dist/cli-help/index.d.ts.map +1 -1
  16. package/dist/cli-help/index.js +2 -0
  17. package/dist/cli-help/index.js.map +1 -1
  18. package/dist/cli-help/scene.d.ts +96 -0
  19. package/dist/cli-help/scene.d.ts.map +1 -0
  20. package/dist/cli-help/scene.js +162 -0
  21. package/dist/cli-help/scene.js.map +1 -0
  22. package/dist/cli-run.d.ts.map +1 -1
  23. package/dist/cli-run.js +6 -0
  24. package/dist/cli-run.js.map +1 -1
  25. package/dist/cli-scene-command.d.ts +3 -0
  26. package/dist/cli-scene-command.d.ts.map +1 -0
  27. package/dist/cli-scene-command.js +292 -0
  28. package/dist/cli-scene-command.js.map +1 -0
  29. package/dist/cli-usage.d.ts +8 -0
  30. package/dist/cli-usage.d.ts.map +1 -1
  31. package/dist/cli-usage.js +26 -1
  32. package/dist/cli-usage.js.map +1 -1
  33. package/dist/conversion-client.d.ts +11 -0
  34. package/dist/conversion-client.d.ts.map +1 -1
  35. package/dist/conversion-client.js +11 -1
  36. package/dist/conversion-client.js.map +1 -1
  37. package/dist/legacy-skill-catalog.d.ts.map +1 -1
  38. package/dist/legacy-skill-catalog.js +11 -0
  39. package/dist/legacy-skill-catalog.js.map +1 -1
  40. package/dist/prototype-browsers.d.ts +8 -2
  41. package/dist/prototype-browsers.d.ts.map +1 -1
  42. package/dist/prototype-browsers.js +8 -3
  43. package/dist/prototype-browsers.js.map +1 -1
  44. package/dist/prototype-process.js +1 -1
  45. package/dist/prototype-process.js.map +1 -1
  46. package/dist/run-journal-commands.d.ts.map +1 -1
  47. package/dist/run-journal-commands.js +6 -0
  48. package/dist/run-journal-commands.js.map +1 -1
  49. package/dist/scene/camera-model.d.ts +48 -0
  50. package/dist/scene/camera-model.d.ts.map +1 -0
  51. package/dist/scene/camera-model.js +89 -0
  52. package/dist/scene/camera-model.js.map +1 -0
  53. package/dist/scene/camera-solve.d.ts +58 -0
  54. package/dist/scene/camera-solve.d.ts.map +1 -0
  55. package/dist/scene/camera-solve.js +288 -0
  56. package/dist/scene/camera-solve.js.map +1 -0
  57. package/dist/scene/capture-plan.d.ts +28 -0
  58. package/dist/scene/capture-plan.d.ts.map +1 -0
  59. package/dist/scene/capture-plan.js +79 -0
  60. package/dist/scene/capture-plan.js.map +1 -0
  61. package/dist/scene/capture.d.ts +21 -0
  62. package/dist/scene/capture.d.ts.map +1 -0
  63. package/dist/scene/capture.js +118 -0
  64. package/dist/scene/capture.js.map +1 -0
  65. package/dist/scene/image-colors.d.ts +25 -0
  66. package/dist/scene/image-colors.d.ts.map +1 -0
  67. package/dist/scene/image-colors.js +110 -0
  68. package/dist/scene/image-colors.js.map +1 -0
  69. package/dist/scene/image-rectify.d.ts +25 -0
  70. package/dist/scene/image-rectify.d.ts.map +1 -0
  71. package/dist/scene/image-rectify.js +82 -0
  72. package/dist/scene/image-rectify.js.map +1 -0
  73. package/dist/scene/image-sheet.d.ts +19 -0
  74. package/dist/scene/image-sheet.d.ts.map +1 -0
  75. package/dist/scene/image-sheet.js +118 -0
  76. package/dist/scene/image-sheet.js.map +1 -0
  77. package/dist/scene/least-squares.d.ts +31 -0
  78. package/dist/scene/least-squares.d.ts.map +1 -0
  79. package/dist/scene/least-squares.js +173 -0
  80. package/dist/scene/least-squares.js.map +1 -0
  81. package/dist/scene/numbers.d.ts +17 -0
  82. package/dist/scene/numbers.d.ts.map +1 -0
  83. package/dist/scene/numbers.js +39 -0
  84. package/dist/scene/numbers.js.map +1 -0
  85. package/dist/scene/placement.d.ts +43 -0
  86. package/dist/scene/placement.d.ts.map +1 -0
  87. package/dist/scene/placement.js +107 -0
  88. package/dist/scene/placement.js.map +1 -0
  89. package/dist/scene/raster.d.ts +18 -0
  90. package/dist/scene/raster.d.ts.map +1 -0
  91. package/dist/scene/raster.js +31 -0
  92. package/dist/scene/raster.js.map +1 -0
  93. package/dist/scene/scene-kit.d.ts +20 -0
  94. package/dist/scene/scene-kit.d.ts.map +1 -0
  95. package/dist/scene/scene-kit.js +74 -0
  96. package/dist/scene/scene-kit.js.map +1 -0
  97. package/dist/scene/scene-verbs.d.ts +4 -0
  98. package/dist/scene/scene-verbs.d.ts.map +1 -0
  99. package/dist/scene/scene-verbs.js +5 -0
  100. package/dist/scene/scene-verbs.js.map +1 -0
  101. package/dist/scene/seeded-random.d.ts +20 -0
  102. package/dist/scene/seeded-random.d.ts.map +1 -0
  103. package/dist/scene/seeded-random.js +42 -0
  104. package/dist/scene/seeded-random.js.map +1 -0
  105. package/dist/scene/spec-expression.d.ts +25 -0
  106. package/dist/scene/spec-expression.d.ts.map +1 -0
  107. package/dist/scene/spec-expression.js +234 -0
  108. package/dist/scene/spec-expression.js.map +1 -0
  109. package/dist/scene/static-server.d.ts +13 -0
  110. package/dist/scene/static-server.d.ts.map +1 -0
  111. package/dist/scene/static-server.js +99 -0
  112. package/dist/scene/static-server.js.map +1 -0
  113. package/open-design.json +2 -2
  114. package/package.json +3 -1
  115. package/scene-kit/starter/camera.js +10 -0
  116. package/scene-kit/starter/gobo.js +60 -0
  117. package/scene-kit/starter/index.html +23 -0
  118. package/scene-kit/starter/lib.js +84 -0
  119. package/scene-kit/starter/main.js +78 -0
  120. package/scene-kit/starter/scene.js +33 -0
  121. package/scene-kit/vendor/LICENSE +21 -0
  122. package/scene-kit/vendor/addons/geometries/RoundedBoxGeometry.js +216 -0
  123. package/scene-kit/vendor/addons/renderers/CSS3DRenderer.js +454 -0
  124. package/scene-kit/vendor/three.core.js +60586 -0
  125. package/scene-kit/vendor/three.module.js +19719 -0
  126. package/skills/12ui-design/SKILL.md +7 -1
  127. package/skills/12ui-design/scene.md +172 -0
@@ -0,0 +1,118 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { isMissingPrototypeBrowserOutput, missingPrototypeBrowserMessage, resolvePrototypeBrowserProvisioning, } from '../prototype-browsers.js';
4
+ import { defaultCaptureViews, isCaptureNoise, isLocalRequestUrl, } from './capture-plan.js';
5
+ /**
6
+ * Headless captures of a served scene with the CLI's managed Chromium.
7
+ *
8
+ * This is where the local-first rule is enforced: every request that would
9
+ * leave the machine is aborted and counted, and the report says how many and
10
+ * which fonts loaded, so a remote font or texture is a visible defect rather
11
+ * than a silent dependency. Nothing is ever downloaded: a missing browser stops
12
+ * with the managed install command.
13
+ */
14
+ const SOFTWARE_GL_ARGS = [
15
+ '--use-angle=swiftshader',
16
+ '--use-gl=angle',
17
+ '--enable-unsafe-swiftshader',
18
+ '--enable-webgl',
19
+ '--ignore-gpu-blocklist',
20
+ '--force-color-profile=srgb',
21
+ '--hide-scrollbars',
22
+ ];
23
+ export const captureScene = async (options, log) => {
24
+ const provisioning = await resolvePrototypeBrowserProvisioning();
25
+ process.env.PLAYWRIGHT_BROWSERS_PATH = provisioning.browsersPath;
26
+ const { chromium } = await import('@playwright/test');
27
+ let browser;
28
+ try {
29
+ browser = await chromium.launch({ args: SOFTWARE_GL_ARGS });
30
+ }
31
+ catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ if (isMissingPrototypeBrowserOutput(message)) {
34
+ throw new Error(missingPrototypeBrowserMessage({ subject: 'Scene capture', provisioning }));
35
+ }
36
+ throw error;
37
+ }
38
+ const report = { written: [], blocked: [], fonts: { loaded: 0, failed: [] }, console: [] };
39
+ try {
40
+ const page = await browser.newPage({
41
+ viewport: { width: options.size[0], height: options.size[1] },
42
+ deviceScaleFactor: 1,
43
+ });
44
+ await page.route('**/*', async (route) => {
45
+ const url = route.request().url();
46
+ if (isLocalRequestUrl(url)) {
47
+ await route.continue();
48
+ return;
49
+ }
50
+ report.blocked.push(url);
51
+ await route.abort('blockedbyclient');
52
+ });
53
+ page.on('console', (message) => {
54
+ if (message.type() === 'error' || message.type() === 'warning') {
55
+ report.console.push(`[${message.type()}] ${message.text()}`);
56
+ }
57
+ });
58
+ page.on('pageerror', (error) => { report.console.push(`[pageerror] ${error.message}`); });
59
+ await mkdir(options.outDir, { recursive: true });
60
+ await page.goto(options.url, { waitUntil: 'load' });
61
+ await page.waitForFunction(() => window.__ready === true, null, { timeout: 120_000 });
62
+ await page.evaluate(() => document.fonts.ready.then(() => undefined));
63
+ await page.waitForTimeout(500);
64
+ const fonts = await page.evaluate(() => {
65
+ const faces = [];
66
+ document.fonts.forEach((face) => { faces.push({ family: face.family, status: face.status }); });
67
+ return faces;
68
+ });
69
+ report.fonts.loaded = fonts.filter((face) => face.status === 'loaded').length;
70
+ report.fonts.failed = fonts.filter((face) => face.status === 'error').map((face) => face.family);
71
+ if (options.evalScript)
72
+ await page.evaluate(options.evalScript);
73
+ const actions = await page.evaluate(() => window.__scene?.actions ?? []);
74
+ const views = options.views.length ? options.views : defaultCaptureViews(options.orbits, actions);
75
+ for (const view of views) {
76
+ await page.evaluate((v) => {
77
+ const scene = window.__scene;
78
+ scene.reset();
79
+ for (const action of v.actions)
80
+ scene.setAction(action.name, action.t);
81
+ scene.setOrbit(v.azimuth, v.elevation);
82
+ scene.render();
83
+ }, view);
84
+ if (options.hide.length) {
85
+ await page.evaluate((patterns) => {
86
+ const scene = window.__scene;
87
+ for (const pattern of patterns)
88
+ scene.setVisible?.(pattern, false);
89
+ scene.render();
90
+ }, [...options.hide]);
91
+ }
92
+ await page.waitForTimeout(250);
93
+ const file = path.join(options.outDir, `${view.name}.png`);
94
+ await page.screenshot({ path: file });
95
+ report.written.push(file);
96
+ log(`wrote ${file}`);
97
+ if (options.hide.length) {
98
+ await page.evaluate((patterns) => {
99
+ const scene = window.__scene;
100
+ for (const pattern of patterns)
101
+ scene.setVisible?.(pattern, true);
102
+ }, [...options.hide]);
103
+ }
104
+ }
105
+ }
106
+ finally {
107
+ await browser.close();
108
+ }
109
+ log(`remote requests blocked: ${report.blocked.length}`);
110
+ for (const url of report.blocked.slice(0, 10))
111
+ log(` blocked ${url}`);
112
+ log(`fonts: ${report.fonts.loaded} loaded, ${report.fonts.failed.length} failed${report.fonts.failed.length ? `: ${[...new Set(report.fonts.failed)].join(', ')}` : ''}`);
113
+ const consoleLines = report.console.filter((line) => !isCaptureNoise(line)).slice(0, 30);
114
+ for (const line of consoleLines)
115
+ log(line);
116
+ return report;
117
+ };
118
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/scene/capture.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Where does the render differ most from the source, and what colour should a
3
+ * region be? A port of `sample_colors.py`. Boxes are x0,y0,x1,y1 in SOURCE
4
+ * pixels, clamped to the image; the render is resized to the source size first.
5
+ */
6
+ export type Box = [number, number, number, number];
7
+ export type NamedRegion = {
8
+ name: string;
9
+ box: Box;
10
+ };
11
+ /** The source colour of one box: the hex to put in a material. */
12
+ export declare const sampleSourceBox: (sourceFile: string, box: Box) => Promise<string>;
13
+ /**
14
+ * Compares a render with its source. With regions: source hex, render hex and
15
+ * brightness ratio per region (ratio < 1: the render is darker). Without: the
16
+ * eight worst cells of a 12x8 grid by mean colour difference, the next thing to
17
+ * fix. Excluded boxes (DOM text, labels) are masked out of every comparison.
18
+ */
19
+ export declare const compareWithSource: (args: {
20
+ sourceFile: string;
21
+ renderFile: string;
22
+ exclude: readonly Box[];
23
+ regions: readonly NamedRegion[];
24
+ }) => Promise<string[]>;
25
+ //# sourceMappingURL=image-colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-colors.d.ts","sourceRoot":"","sources":["../../src/scene/image-colors.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACnD,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,GAAG,CAAA;CAAE,CAAC;AA8BrD,kEAAkE;AAClE,eAAO,MAAM,eAAe,GAAU,YAAY,MAAM,EAAE,KAAK,GAAG,KAAG,OAAO,CAAC,MAAM,CAKlF,CAAC;AAWF;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAU,MAAM;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,SAAS,GAAG,EAAE,CAAC;IACxB,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;CACjC,KAAG,OAAO,CAAC,MAAM,EAAE,CAyDnB,CAAC"}
@@ -0,0 +1,110 @@
1
+ import { SceneArgumentError } from './numbers.js';
2
+ import { readRgb, readRgbAt } from './raster.js';
3
+ const hex = (rgb) => `#${rgb.map((value) => (Math.trunc(value).toString(16).padStart(2, '0'))).join('')}`;
4
+ const clampBox = (box, width, height) => [
5
+ Math.min(Math.max(box[0], 0), width),
6
+ Math.min(Math.max(box[1], 0), height),
7
+ Math.min(Math.max(box[2], 0), width),
8
+ Math.min(Math.max(box[3], 0), height),
9
+ ];
10
+ /** Mean colour of the kept pixels in a box, or null when none are kept. */
11
+ const meanColour = (image, keep, box) => {
12
+ const [x0, y0, x1, y1] = clampBox(box, image.width, image.height);
13
+ let r = 0, g = 0, b = 0, n = 0;
14
+ for (let y = y0; y < y1; y += 1) {
15
+ for (let x = x0; x < x1; x += 1) {
16
+ const index = y * image.width + x;
17
+ if (keep && !keep[index])
18
+ continue;
19
+ r += image.data[index * 3];
20
+ g += image.data[index * 3 + 1];
21
+ b += image.data[index * 3 + 2];
22
+ n += 1;
23
+ }
24
+ }
25
+ return n ? [r / n, g / n, b / n] : null;
26
+ };
27
+ /** The source colour of one box: the hex to put in a material. */
28
+ export const sampleSourceBox = async (sourceFile, box) => {
29
+ const source = await readRgb(sourceFile);
30
+ const mean = meanColour(source, null, box);
31
+ if (!mean)
32
+ throw new SceneArgumentError(`box ${box.join(',')} holds no pixels of the ${source.width}x${source.height} source`);
33
+ return hex(mean);
34
+ };
35
+ const keepMask = (width, height, excluded) => {
36
+ const keep = new Uint8Array(width * height).fill(1);
37
+ for (const box of excluded) {
38
+ const [x0, y0, x1, y1] = clampBox(box, width, height);
39
+ for (let y = y0; y < y1; y += 1)
40
+ keep.fill(0, y * width + x0, y * width + x1);
41
+ }
42
+ return keep;
43
+ };
44
+ /**
45
+ * Compares a render with its source. With regions: source hex, render hex and
46
+ * brightness ratio per region (ratio < 1: the render is darker). Without: the
47
+ * eight worst cells of a 12x8 grid by mean colour difference, the next thing to
48
+ * fix. Excluded boxes (DOM text, labels) are masked out of every comparison.
49
+ */
50
+ export const compareWithSource = async (args) => {
51
+ const source = await readRgb(args.sourceFile);
52
+ const render = await readRgbAt(args.renderFile, source.width, source.height);
53
+ const { width, height } = source;
54
+ const keep = keepMask(width, height, args.exclude);
55
+ if (args.regions.length > 0) {
56
+ return args.regions.map(({ name, box }) => {
57
+ const a = meanColour(source, keep, box);
58
+ const c = meanColour(render, keep, box);
59
+ if (!a || !c)
60
+ return `${name.padEnd(16)} fully excluded`;
61
+ const mean = (rgb) => (rgb[0] + rgb[1] + rgb[2]) / 3;
62
+ return `${name.padEnd(16)} src ${hex(a)} render ${hex(c)} ratio ${(mean(c) / Math.max(mean(a), 1)).toFixed(2)}`;
63
+ });
64
+ }
65
+ const columns = 12, rows = 8;
66
+ const cells = [];
67
+ for (let row = 0; row < rows; row += 1) {
68
+ for (let column = 0; column < columns; column += 1) {
69
+ const box = [
70
+ Math.floor((column * width) / columns),
71
+ Math.floor((row * height) / rows),
72
+ Math.floor(((column + 1) * width) / columns),
73
+ Math.floor(((row + 1) * height) / rows),
74
+ ];
75
+ const a = meanColour(source, keep, box);
76
+ const c = meanColour(render, keep, box);
77
+ if (!a || !c)
78
+ continue;
79
+ let kept = 0;
80
+ for (let y = box[1]; y < box[3]; y += 1)
81
+ for (let x = box[0]; x < box[2]; x += 1)
82
+ kept += keep[y * width + x];
83
+ // Mostly masked: not comparable.
84
+ if (kept / ((box[2] - box[0]) * (box[3] - box[1])) < 0.4)
85
+ continue;
86
+ const difference = (Math.abs(a[0] - c[0]) + Math.abs(a[1] - c[1]) + Math.abs(a[2] - c[2])) / 3;
87
+ cells.push({ difference, box, source: hex(a), render: hex(c) });
88
+ }
89
+ }
90
+ cells.sort((left, right) => (right.difference - left.difference
91
+ || right.box[0] - left.box[0]
92
+ || right.box[1] - left.box[1]));
93
+ let total = 0, keptPixels = 0;
94
+ for (let index = 0; index < width * height; index += 1) {
95
+ if (!keep[index])
96
+ continue;
97
+ const o = index * 3;
98
+ total += (Math.abs(source.data[o] - render.data[o])
99
+ + Math.abs(source.data[o + 1] - render.data[o + 1])
100
+ + Math.abs(source.data[o + 2] - render.data[o + 2])) / 3;
101
+ keptPixels += 1;
102
+ }
103
+ const meanDifference = keptPixels ? total / keptPixels : Number.NaN;
104
+ return [
105
+ `mean abs diff ${meanDifference.toFixed(1)} / 255 over ${((keptPixels / (width * height)) * 100).toFixed(0)}%`
106
+ + ' of the frame. Worst cells (source px box, src colour, render colour):',
107
+ ...cells.slice(0, 8).map((cell) => (` ${cell.difference.toFixed(1).padStart(5)} (${cell.box.join(', ')}) src ${cell.source} render ${cell.render}`)),
108
+ ];
109
+ };
110
+ //# sourceMappingURL=image-colors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-colors.js","sourceRoot":"","sources":["../../src/scene/image-colors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type RgbImage } from './raster.js';
2
+ /**
3
+ * Flatten a perspective quad of the source (a screen, poster, label, sleeve
4
+ * print) into a flat WIDTHxHEIGHT texture.
5
+ *
6
+ * This is a true projective inverse: the homography that sends the output
7
+ * rectangle onto the quad, sampled bicubically. The Python helper used PIL's
8
+ * QUAD transform, a bilinear corner mapping that is not a perspective inverse,
9
+ * and returned foreshortened content visibly sheared.
10
+ */
11
+ export type Point = [number, number];
12
+ /** Row-major 3x3 with h[8] = 1. */
13
+ export type Homography = Float64Array;
14
+ /** The homography sending each `from` point to the matching `to` point (four pairs). */
15
+ export declare const homographyFromPoints: (from: readonly Point[], to: readonly Point[]) => Homography;
16
+ export declare const applyHomography: (h: Homography, x: number, y: number) => Point;
17
+ export declare const rectifyImage: (source: RgbImage, corners: readonly Point[], width: number, height: number) => RgbImage;
18
+ export declare const rectifyFile: (args: {
19
+ sourceFile: string;
20
+ corners: readonly Point[];
21
+ width: number;
22
+ height: number;
23
+ outFile: string;
24
+ }) => Promise<void>;
25
+ //# sourceMappingURL=image-rectify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-rectify.d.ts","sourceRoot":"","sources":["../../src/scene/image-rectify.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;;;GAQG;AAEH,MAAM,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACrC,mCAAmC;AACnC,MAAM,MAAM,UAAU,GAAG,YAAY,CAAC;AAEtC,wFAAwF;AACxF,eAAO,MAAM,oBAAoB,GAAI,MAAM,SAAS,KAAK,EAAE,EAAE,IAAI,SAAS,KAAK,EAAE,KAAG,UAcnF,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,GAAG,UAAU,EAAE,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,KAGrE,CAAC;AAiCF,eAAO,MAAM,YAAY,GAAI,QAAQ,QAAQ,EAAE,SAAS,SAAS,KAAK,EAAE,EAAE,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAG,QAgBzG,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,MAAM;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,KAAG,OAAO,CAAC,IAAI,CAGf,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { solveLinearSystem } from './least-squares.js';
2
+ import { SceneArgumentError } from './numbers.js';
3
+ import { readRgb, writePng } from './raster.js';
4
+ /** The homography sending each `from` point to the matching `to` point (four pairs). */
5
+ export const homographyFromPoints = (from, to) => {
6
+ if (from.length !== 4 || to.length !== 4)
7
+ throw new Error('a homography needs four point pairs');
8
+ const rows = [];
9
+ const rhs = new Float64Array(8);
10
+ for (let index = 0; index < 4; index += 1) {
11
+ const [x, y] = from[index];
12
+ const [u, v] = to[index];
13
+ rows.push(Float64Array.from([x, y, 1, 0, 0, 0, -u * x, -u * y]));
14
+ rhs[2 * index] = u;
15
+ rows.push(Float64Array.from([0, 0, 0, x, y, 1, -v * x, -v * y]));
16
+ rhs[2 * index + 1] = v;
17
+ }
18
+ const h = solveLinearSystem(rows, rhs);
19
+ return Float64Array.from([...h, 1]);
20
+ };
21
+ export const applyHomography = (h, x, y) => {
22
+ const w = h[6] * x + h[7] * y + h[8];
23
+ return [(h[0] * x + h[1] * y + h[2]) / w, (h[3] * x + h[4] * y + h[5]) / w];
24
+ };
25
+ /** Catmull-Rom cubic weight (a = -0.5), the kernel PIL's BICUBIC uses. */
26
+ const cubic = (t) => {
27
+ const a = -0.5;
28
+ const x = Math.abs(t);
29
+ if (x <= 1)
30
+ return (a + 2) * x ** 3 - (a + 3) * x ** 2 + 1;
31
+ if (x < 2)
32
+ return a * x ** 3 - 5 * a * x ** 2 + 8 * a * x - 4 * a;
33
+ return 0;
34
+ };
35
+ /** Bicubic sample at continuous pixel coordinates (pixel centres at +0.5), edges clamped. */
36
+ const sample = (image, u, v, out) => {
37
+ const fx = u - 0.5, fy = v - 0.5;
38
+ const x0 = Math.floor(fx), y0 = Math.floor(fy);
39
+ const dx = fx - x0, dy = fy - y0;
40
+ out[0] = 0;
41
+ out[1] = 0;
42
+ out[2] = 0;
43
+ for (let j = -1; j <= 2; j += 1) {
44
+ const wy = cubic(j - dy);
45
+ if (wy === 0)
46
+ continue;
47
+ const y = Math.min(Math.max(y0 + j, 0), image.height - 1);
48
+ for (let i = -1; i <= 2; i += 1) {
49
+ const w = wy * cubic(i - dx);
50
+ if (w === 0)
51
+ continue;
52
+ const x = Math.min(Math.max(x0 + i, 0), image.width - 1);
53
+ const o = (y * image.width + x) * 3;
54
+ out[0] += w * image.data[o];
55
+ out[1] += w * image.data[o + 1];
56
+ out[2] += w * image.data[o + 2];
57
+ }
58
+ }
59
+ };
60
+ export const rectifyImage = (source, corners, width, height) => {
61
+ if (corners.length !== 4)
62
+ throw new SceneArgumentError('--corners needs four points: TL;TR;BR;BL');
63
+ const h = homographyFromPoints([[0, 0], [width, 0], [width, height], [0, height]], corners);
64
+ const data = Buffer.alloc(width * height * 3);
65
+ const pixel = [0, 0, 0];
66
+ for (let y = 0; y < height; y += 1) {
67
+ for (let x = 0; x < width; x += 1) {
68
+ const [u, v] = applyHomography(h, x + 0.5, y + 0.5);
69
+ sample(source, u, v, pixel);
70
+ const o = (y * width + x) * 3;
71
+ for (let channel = 0; channel < 3; channel += 1) {
72
+ data[o + channel] = Math.min(255, Math.max(0, Math.round(pixel[channel])));
73
+ }
74
+ }
75
+ }
76
+ return { data, width, height };
77
+ };
78
+ export const rectifyFile = async (args) => {
79
+ const source = await readRgb(args.sourceFile);
80
+ await writePng(rectifyImage(source, args.corners, args.width, args.height), args.outFile);
81
+ };
82
+ //# sourceMappingURL=image-rectify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-rectify.js","sourceRoot":"","sources":["../../src/scene/image-rectify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export declare const writeContactSheet: (args: {
2
+ sourceFile: string;
3
+ captureFiles: readonly string[];
4
+ outFile: string;
5
+ columns?: number;
6
+ note?: string;
7
+ }) => Promise<{
8
+ width: number;
9
+ height: number;
10
+ }>;
11
+ export declare const writeBlend: (args: {
12
+ sourceFile: string;
13
+ renderFile: string;
14
+ outFile: string;
15
+ }) => Promise<{
16
+ width: number;
17
+ height: number;
18
+ }>;
19
+ //# sourceMappingURL=image-sheet.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-sheet.d.ts","sourceRoot":"","sources":["../../src/scene/image-sheet.ts"],"names":[],"mappings":"AAqFA,eAAO,MAAM,iBAAiB,GAAU,MAAM;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,KAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAoB5C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,MAAM;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,KAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA4B5C,CAAC"}
@@ -0,0 +1,118 @@
1
+ import path from 'node:path';
2
+ import sharp from 'sharp';
3
+ import { SceneArgumentError } from './numbers.js';
4
+ import { encodePng, readRgb, resizeRgb } from './raster.js';
5
+ /**
6
+ * The two pictures the self-review loop looks at. A port of `sheet.py`.
7
+ *
8
+ * Sheet: SOURCE first, then each capture, in 640 px wide tiles with a large
9
+ * label on each (the file name; --note is added to the SOURCE tile).
10
+ * Blend (2x2 of 900 px tiles): SOURCE | RENDER above, 50% BLEND | DIFFERENCE x3
11
+ * below. Labels are Pango text rendered by sharp's bundled libvips: no font file
12
+ * and no network.
13
+ */
14
+ const escapeMarkup = (text) => text
15
+ .replaceAll('&', '&amp;')
16
+ .replaceAll('<', '&lt;')
17
+ .replaceAll('>', '&gt;');
18
+ /** A white label on a black box, as a PNG to composite at (x - 6, y - 3) of the text origin. */
19
+ const label = async (text, fontPx) => {
20
+ const { data, info } = await sharp({
21
+ text: {
22
+ text: `<span foreground="white">${escapeMarkup(text)}</span>`,
23
+ font: `sans ${fontPx}`,
24
+ dpi: 72,
25
+ rgba: true,
26
+ },
27
+ }).png().toBuffer({ resolveWithObject: true });
28
+ const width = info.width + 12;
29
+ const height = info.height + 6;
30
+ const input = await sharp({
31
+ create: { width, height, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 1 } },
32
+ }).composite([{ input: data, left: 6, top: 3 }]).png().toBuffer();
33
+ return { input, width, height };
34
+ };
35
+ const composeGrid = async (args) => {
36
+ const rows = Math.ceil(args.tiles.length / args.columns);
37
+ const width = args.columns * args.tileWidth;
38
+ const height = rows * args.tileHeight;
39
+ const layers = [];
40
+ for (const [index, tile] of args.tiles.entries()) {
41
+ const left = (index % args.columns) * args.tileWidth;
42
+ const top = Math.floor(index / args.columns) * args.tileHeight;
43
+ const resized = await resizeRgb(tile.image, args.tileWidth, args.tileHeight);
44
+ layers.push({ input: await encodePng(resized), left, top });
45
+ const tag = await label(tile.name, args.fontPx);
46
+ layers.push({
47
+ input: tag.input,
48
+ left: Math.min(left + args.labelOffset - 6, Math.max(width - tag.width, 0)),
49
+ top: top + args.labelOffset - 3,
50
+ });
51
+ }
52
+ await sharp({ create: { width, height, channels: 3, background: args.background } })
53
+ .composite(layers)
54
+ .png()
55
+ .toFile(args.outFile);
56
+ return { width, height };
57
+ };
58
+ /** Python's round() for the tile height: ties go to the even neighbour. */
59
+ const roundHalfEven = (value) => {
60
+ const floor = Math.floor(value);
61
+ const fraction = value - floor;
62
+ if (Math.abs(fraction - 0.5) > 1e-9)
63
+ return Math.round(value);
64
+ return floor % 2 === 0 ? floor : floor + 1;
65
+ };
66
+ const captureName = (file) => path.basename(file).replace(/\.[^.]*$/u, '');
67
+ export const writeContactSheet = async (args) => {
68
+ const columns = args.columns ?? 3;
69
+ if (!Number.isInteger(columns) || columns < 1)
70
+ throw new SceneArgumentError('--cols must be a positive integer');
71
+ const source = await readRgb(args.sourceFile);
72
+ const tileWidth = 640;
73
+ const tileHeight = roundHalfEven((source.height * tileWidth) / source.width);
74
+ const tiles = [
75
+ { name: `SOURCE${args.note ? ` ${args.note}` : ''}`, image: source },
76
+ ...await Promise.all(args.captureFiles.map(async (file) => ({ name: captureName(file), image: await readRgb(file) }))),
77
+ ];
78
+ return composeGrid({
79
+ tiles,
80
+ columns,
81
+ tileWidth,
82
+ tileHeight,
83
+ background: { r: 24, g: 24, b: 24 },
84
+ labelOffset: 10,
85
+ fontPx: 22,
86
+ outFile: args.outFile,
87
+ });
88
+ };
89
+ export const writeBlend = async (args) => {
90
+ const source = await readRgb(args.sourceFile);
91
+ const render = await resizeRgb(await readRgb(args.renderFile), source.width, source.height);
92
+ const blend = Buffer.alloc(source.data.length);
93
+ const difference = Buffer.alloc(source.data.length);
94
+ for (let index = 0; index < source.data.length; index += 1) {
95
+ const a = source.data[index], b = render.data[index];
96
+ blend[index] = Math.round((a + b) / 2);
97
+ difference[index] = Math.min(255, Math.abs(a - b) * 3);
98
+ }
99
+ const tileWidth = 900;
100
+ const tileHeight = roundHalfEven((source.height * tileWidth) / source.width);
101
+ const same = { width: source.width, height: source.height };
102
+ return composeGrid({
103
+ tiles: [
104
+ { name: 'SOURCE', image: source },
105
+ { name: 'RENDER', image: render },
106
+ { name: '50% BLEND', image: { ...same, data: blend } },
107
+ { name: 'DIFFERENCE x3', image: { ...same, data: difference } },
108
+ ],
109
+ columns: 2,
110
+ tileWidth,
111
+ tileHeight,
112
+ background: { r: 0, g: 0, b: 0 },
113
+ labelOffset: 12,
114
+ fontPx: 26,
115
+ outFile: args.outFile,
116
+ });
117
+ };
118
+ //# sourceMappingURL=image-sheet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-sheet.js","sourceRoot":"","sources":["../../src/scene/image-sheet.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Box-constrained Levenberg-Marquardt with a numeric Jacobian, and a multistart
3
+ * around it. A line-for-line port of the proven Python solver
4
+ * (`solve_camera.py` lm/multistart): the same damping schedule, the same
5
+ * forward-difference step, the same clamping, so a spec solves to the same
6
+ * minimum in both.
7
+ */
8
+ export type Residuals = (parameters: Float64Array) => Float64Array;
9
+ export declare class SingularMatrixError extends Error {
10
+ constructor();
11
+ }
12
+ /**
13
+ * Solves `matrix * x = rhs` by Gaussian elimination with partial pivoting.
14
+ * An exactly zero pivot is a singular system, as LAPACK reports it.
15
+ */
16
+ export declare const solveLinearSystem: (matrix: readonly Float64Array[], rhs: Float64Array) => Float64Array;
17
+ export type LeastSquaresResult = {
18
+ parameters: Float64Array;
19
+ cost: number;
20
+ };
21
+ export declare const levenbergMarquardt: (residuals: Residuals, start: ArrayLike<number>, low: ArrayLike<number>, high: ArrayLike<number>, iterations?: number) => LeastSquaresResult;
22
+ /**
23
+ * Every start gets a short coarse run; the best `keep` are refined fully and
24
+ * the lowest cost wins. Ties keep the earlier start, as Python's stable sort did.
25
+ */
26
+ export declare const multistart: (residuals: Residuals, starts: readonly ArrayLike<number>[], low: ArrayLike<number>, high: ArrayLike<number>, { keep, coarse, refine }?: {
27
+ keep?: number;
28
+ coarse?: number;
29
+ refine?: number;
30
+ }) => LeastSquaresResult;
31
+ //# sourceMappingURL=least-squares.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"least-squares.d.ts","sourceRoot":"","sources":["../../src/scene/least-squares.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,SAAS,GAAG,CAAC,UAAU,EAAE,YAAY,KAAK,YAAY,CAAC;AAEnE,qBAAa,mBAAoB,SAAQ,KAAK;;CAK7C;AAgBD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,SAAS,YAAY,EAAE,EAAE,KAAK,YAAY,KAAG,YAoCtF,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAAE,UAAU,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,eAAO,MAAM,kBAAkB,GAC7B,WAAW,SAAS,EACpB,OAAO,SAAS,CAAC,MAAM,CAAC,EACxB,KAAK,SAAS,CAAC,MAAM,CAAC,EACtB,MAAM,SAAS,CAAC,MAAM,CAAC,EACvB,mBAAe,KACd,kBAmEF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,WAAW,SAAS,EACpB,QAAQ,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,EACpC,KAAK,SAAS,CAAC,MAAM,CAAC,EACtB,MAAM,SAAS,CAAC,MAAM,CAAC,EACvB,2BAAyC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,KAChG,kBAeF,CAAC"}