@12ui/design 0.2.71 → 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.
- package/README.md +5 -0
- package/SKILL.md +1 -1
- package/dist/cli-capabilities.d.ts +9 -0
- package/dist/cli-capabilities.d.ts.map +1 -1
- package/dist/cli-capabilities.js +10 -0
- package/dist/cli-capabilities.js.map +1 -1
- package/dist/cli-help/index.d.ts +93 -0
- package/dist/cli-help/index.d.ts.map +1 -1
- package/dist/cli-help/index.js +2 -0
- package/dist/cli-help/index.js.map +1 -1
- package/dist/cli-help/scene.d.ts +96 -0
- package/dist/cli-help/scene.d.ts.map +1 -0
- package/dist/cli-help/scene.js +162 -0
- package/dist/cli-help/scene.js.map +1 -0
- package/dist/cli-run.d.ts.map +1 -1
- package/dist/cli-run.js +6 -0
- package/dist/cli-run.js.map +1 -1
- package/dist/cli-scene-command.d.ts +3 -0
- package/dist/cli-scene-command.d.ts.map +1 -0
- package/dist/cli-scene-command.js +292 -0
- package/dist/cli-scene-command.js.map +1 -0
- package/dist/cli-usage.d.ts +8 -0
- package/dist/cli-usage.d.ts.map +1 -1
- package/dist/cli-usage.js +26 -1
- package/dist/cli-usage.js.map +1 -1
- package/dist/legacy-skill-catalog.d.ts.map +1 -1
- package/dist/legacy-skill-catalog.js +11 -0
- package/dist/legacy-skill-catalog.js.map +1 -1
- package/dist/prototype-browsers.d.ts +8 -2
- package/dist/prototype-browsers.d.ts.map +1 -1
- package/dist/prototype-browsers.js +8 -3
- package/dist/prototype-browsers.js.map +1 -1
- package/dist/prototype-process.js +1 -1
- package/dist/prototype-process.js.map +1 -1
- package/dist/run-journal-commands.d.ts.map +1 -1
- package/dist/run-journal-commands.js +6 -0
- package/dist/run-journal-commands.js.map +1 -1
- package/dist/scene/camera-model.d.ts +48 -0
- package/dist/scene/camera-model.d.ts.map +1 -0
- package/dist/scene/camera-model.js +89 -0
- package/dist/scene/camera-model.js.map +1 -0
- package/dist/scene/camera-solve.d.ts +58 -0
- package/dist/scene/camera-solve.d.ts.map +1 -0
- package/dist/scene/camera-solve.js +288 -0
- package/dist/scene/camera-solve.js.map +1 -0
- package/dist/scene/capture-plan.d.ts +28 -0
- package/dist/scene/capture-plan.d.ts.map +1 -0
- package/dist/scene/capture-plan.js +79 -0
- package/dist/scene/capture-plan.js.map +1 -0
- package/dist/scene/capture.d.ts +21 -0
- package/dist/scene/capture.d.ts.map +1 -0
- package/dist/scene/capture.js +118 -0
- package/dist/scene/capture.js.map +1 -0
- package/dist/scene/image-colors.d.ts +25 -0
- package/dist/scene/image-colors.d.ts.map +1 -0
- package/dist/scene/image-colors.js +110 -0
- package/dist/scene/image-colors.js.map +1 -0
- package/dist/scene/image-rectify.d.ts +25 -0
- package/dist/scene/image-rectify.d.ts.map +1 -0
- package/dist/scene/image-rectify.js +82 -0
- package/dist/scene/image-rectify.js.map +1 -0
- package/dist/scene/image-sheet.d.ts +19 -0
- package/dist/scene/image-sheet.d.ts.map +1 -0
- package/dist/scene/image-sheet.js +118 -0
- package/dist/scene/image-sheet.js.map +1 -0
- package/dist/scene/least-squares.d.ts +31 -0
- package/dist/scene/least-squares.d.ts.map +1 -0
- package/dist/scene/least-squares.js +173 -0
- package/dist/scene/least-squares.js.map +1 -0
- package/dist/scene/numbers.d.ts +17 -0
- package/dist/scene/numbers.d.ts.map +1 -0
- package/dist/scene/numbers.js +39 -0
- package/dist/scene/numbers.js.map +1 -0
- package/dist/scene/placement.d.ts +43 -0
- package/dist/scene/placement.d.ts.map +1 -0
- package/dist/scene/placement.js +107 -0
- package/dist/scene/placement.js.map +1 -0
- package/dist/scene/raster.d.ts +18 -0
- package/dist/scene/raster.d.ts.map +1 -0
- package/dist/scene/raster.js +31 -0
- package/dist/scene/raster.js.map +1 -0
- package/dist/scene/scene-kit.d.ts +20 -0
- package/dist/scene/scene-kit.d.ts.map +1 -0
- package/dist/scene/scene-kit.js +74 -0
- package/dist/scene/scene-kit.js.map +1 -0
- package/dist/scene/scene-verbs.d.ts +4 -0
- package/dist/scene/scene-verbs.d.ts.map +1 -0
- package/dist/scene/scene-verbs.js +5 -0
- package/dist/scene/scene-verbs.js.map +1 -0
- package/dist/scene/seeded-random.d.ts +20 -0
- package/dist/scene/seeded-random.d.ts.map +1 -0
- package/dist/scene/seeded-random.js +42 -0
- package/dist/scene/seeded-random.js.map +1 -0
- package/dist/scene/spec-expression.d.ts +25 -0
- package/dist/scene/spec-expression.d.ts.map +1 -0
- package/dist/scene/spec-expression.js +234 -0
- package/dist/scene/spec-expression.js.map +1 -0
- package/dist/scene/static-server.d.ts +13 -0
- package/dist/scene/static-server.d.ts.map +1 -0
- package/dist/scene/static-server.js +99 -0
- package/dist/scene/static-server.js.map +1 -0
- package/open-design.json +2 -2
- package/package.json +3 -1
- package/scene-kit/starter/camera.js +10 -0
- package/scene-kit/starter/gobo.js +60 -0
- package/scene-kit/starter/index.html +23 -0
- package/scene-kit/starter/lib.js +84 -0
- package/scene-kit/starter/main.js +78 -0
- package/scene-kit/starter/scene.js +33 -0
- package/scene-kit/vendor/LICENSE +21 -0
- package/scene-kit/vendor/addons/geometries/RoundedBoxGeometry.js +216 -0
- package/scene-kit/vendor/addons/renderers/CSS3DRenderer.js +454 -0
- package/scene-kit/vendor/three.core.js +60586 -0
- package/scene-kit/vendor/three.module.js +19719 -0
- package/skills/12ui-design/SKILL.md +7 -1
- package/skills/12ui-design/scene.md +172 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a request stays on this machine. Loopback http(s)/ws(s) and local
|
|
3
|
+
* schemes pass; every other host is a remote request, which capture blocks
|
|
4
|
+
* and counts.
|
|
5
|
+
*/
|
|
6
|
+
export declare const isLocalRequestUrl: (url: string) => boolean;
|
|
7
|
+
/** Only a page served on this machine can be captured (`12ui scene serve`). */
|
|
8
|
+
export declare const isLoopbackPageUrl: (url: string) => boolean;
|
|
9
|
+
export type CaptureView = {
|
|
10
|
+
name: string;
|
|
11
|
+
azimuth: number;
|
|
12
|
+
elevation: number;
|
|
13
|
+
actions: Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
t: number;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* One view: `name=azimuth:elevation[:action=t,action=t]`, degrees about the
|
|
20
|
+
* scene pivot and each action's t in 0..1, e.g. `half=0:0:lid=0.5`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const parseCaptureView: (text: string) => CaptureView;
|
|
23
|
+
/** The default set: landing, each orbit angle, and every action at t = 1. */
|
|
24
|
+
export declare const defaultCaptureViews: (orbits: readonly number[], actions: readonly string[]) => CaptureView[];
|
|
25
|
+
export declare const parseOrbits: (text: string) => number[];
|
|
26
|
+
/** Console noise from software GL that says nothing about the scene. */
|
|
27
|
+
export declare const isCaptureNoise: (line: string) => boolean;
|
|
28
|
+
//# sourceMappingURL=capture-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-plan.d.ts","sourceRoot":"","sources":["../../src/scene/capture-plan.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,OAW/C,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,GAAI,KAAK,MAAM,KAAG,OAO/C,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,WAqB/C,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,GAAI,QAAQ,SAAS,MAAM,EAAE,EAAE,SAAS,SAAS,MAAM,EAAE,KAAG,WAAW,EAItG,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAAM,EAI/C,CAAC;AAEH,wEAAwE;AACxE,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,KAAG,OAAmD,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SceneArgumentError } from './numbers.js';
|
|
2
|
+
/**
|
|
3
|
+
* The pure half of `12ui scene capture`: which URLs a scene may load, and which
|
|
4
|
+
* views to shoot. Kept apart from the browser so both are testable without one.
|
|
5
|
+
*/
|
|
6
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
|
|
7
|
+
/** Schemes that never leave the machine: the page's own bytes, not a request. */
|
|
8
|
+
const LOCAL_SCHEMES = new Set(['data:', 'blob:', 'about:', 'file:']);
|
|
9
|
+
/**
|
|
10
|
+
* Whether a request stays on this machine. Loopback http(s)/ws(s) and local
|
|
11
|
+
* schemes pass; every other host is a remote request, which capture blocks
|
|
12
|
+
* and counts.
|
|
13
|
+
*/
|
|
14
|
+
export const isLocalRequestUrl = (url) => {
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = new URL(url);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (LOCAL_SCHEMES.has(parsed.protocol))
|
|
23
|
+
return true;
|
|
24
|
+
if (!['http:', 'https:', 'ws:', 'wss:'].includes(parsed.protocol))
|
|
25
|
+
return false;
|
|
26
|
+
const host = parsed.hostname.toLowerCase();
|
|
27
|
+
return LOOPBACK_HOSTS.has(host) || /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/u.test(host) || host.endsWith('.localhost');
|
|
28
|
+
};
|
|
29
|
+
/** Only a page served on this machine can be captured (`12ui scene serve`). */
|
|
30
|
+
export const isLoopbackPageUrl = (url) => {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = new URL(url);
|
|
33
|
+
return ['http:', 'https:'].includes(parsed.protocol) && isLocalRequestUrl(url);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* One view: `name=azimuth:elevation[:action=t,action=t]`, degrees about the
|
|
41
|
+
* scene pivot and each action's t in 0..1, e.g. `half=0:0:lid=0.5`.
|
|
42
|
+
*/
|
|
43
|
+
export const parseCaptureView = (text) => {
|
|
44
|
+
const equals = text.indexOf('=');
|
|
45
|
+
const name = equals > 0 ? text.slice(0, equals) : '';
|
|
46
|
+
if (!/^[A-Za-z0-9._+-]+$/u.test(name)) {
|
|
47
|
+
throw new SceneArgumentError(`view "${text}" must be name=azimuth:elevation[:action=t,...] with a file-safe name`);
|
|
48
|
+
}
|
|
49
|
+
const [az = '0', el = '0', ...rest] = text.slice(equals + 1).split(':');
|
|
50
|
+
const azimuth = Number(az || '0');
|
|
51
|
+
const elevation = Number(el || '0');
|
|
52
|
+
if (!Number.isFinite(azimuth) || !Number.isFinite(elevation)) {
|
|
53
|
+
throw new SceneArgumentError(`view "${text}": azimuth and elevation must be numbers of degrees`);
|
|
54
|
+
}
|
|
55
|
+
const actions = rest.join(':').split(',').filter(Boolean).map((pair) => {
|
|
56
|
+
const [action, t] = pair.split('=');
|
|
57
|
+
const value = Number(t);
|
|
58
|
+
if (!action || t === undefined || !Number.isFinite(value)) {
|
|
59
|
+
throw new SceneArgumentError(`view "${text}": each action must be name=t with t in 0..1`);
|
|
60
|
+
}
|
|
61
|
+
return { name: action, t: value };
|
|
62
|
+
});
|
|
63
|
+
return { name, azimuth, elevation, actions };
|
|
64
|
+
};
|
|
65
|
+
/** The default set: landing, each orbit angle, and every action at t = 1. */
|
|
66
|
+
export const defaultCaptureViews = (orbits, actions) => [
|
|
67
|
+
{ name: 'landing', azimuth: 0, elevation: 0, actions: [] },
|
|
68
|
+
...orbits.map((azimuth) => ({ name: `az${azimuth > 0 ? '+' : ''}${azimuth}`, azimuth, elevation: 0, actions: [] })),
|
|
69
|
+
...actions.map((action) => ({ name: `act-${action}`, azimuth: 0, elevation: 0, actions: [{ name: action, t: 1 }] })),
|
|
70
|
+
];
|
|
71
|
+
export const parseOrbits = (text) => text.split(',').filter(Boolean).map((value) => {
|
|
72
|
+
const angle = Number(value);
|
|
73
|
+
if (!Number.isFinite(angle))
|
|
74
|
+
throw new SceneArgumentError(`--orbit must be comma-separated degrees, got "${text}"`);
|
|
75
|
+
return angle;
|
|
76
|
+
});
|
|
77
|
+
/** Console noise from software GL that says nothing about the scene. */
|
|
78
|
+
export const isCaptureNoise = (line) => /GPU stall|Clock|ReadPixels/u.test(line);
|
|
79
|
+
//# sourceMappingURL=capture-plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-plan.js","sourceRoot":"","sources":["../../src/scene/capture-plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type CaptureView } from './capture-plan.js';
|
|
2
|
+
export type CaptureOptions = {
|
|
3
|
+
url: string;
|
|
4
|
+
outDir: string;
|
|
5
|
+
size: [number, number];
|
|
6
|
+
orbits: readonly number[];
|
|
7
|
+
hide: readonly string[];
|
|
8
|
+
evalScript?: string;
|
|
9
|
+
views: readonly CaptureView[];
|
|
10
|
+
};
|
|
11
|
+
export type CaptureReport = {
|
|
12
|
+
written: string[];
|
|
13
|
+
blocked: string[];
|
|
14
|
+
fonts: {
|
|
15
|
+
loaded: number;
|
|
16
|
+
failed: string[];
|
|
17
|
+
};
|
|
18
|
+
console: string[];
|
|
19
|
+
};
|
|
20
|
+
export declare const captureScene: (options: CaptureOptions, log: (line: string) => void) => Promise<CaptureReport>;
|
|
21
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/scene/capture.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAsB3B,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAcF,eAAO,MAAM,YAAY,GACvB,SAAS,cAAc,EACvB,KAAK,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAC1B,OAAO,CAAC,aAAa,CAwFvB,CAAC"}
|
|
@@ -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":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAiB,MAAM,aAAa,CAAC;AAWhE,MAAM,GAAG,GAAG,CAAC,GAAsB,EAAU,EAAE,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CACrE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAChD,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAEd,MAAM,QAAQ,GAAG,CAAC,GAAQ,EAAE,KAAa,EAAE,MAAc,EAAO,EAAE,CAAC;IACjE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC;IACpC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IACrC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC;IACpC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,2EAA2E;AAC3E,MAAM,UAAU,GAAG,CAAC,KAAe,EAAE,IAAuB,EAAE,GAAQ,EAAmB,EAAE;IACzF,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,MAAM,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;YAClC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;gBAAE,SAAS;YACnC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAE,CAAC;YAC5B,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAE,CAAC;YAChC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAE,CAAC;YAChC,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1C,CAAC,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,UAAkB,EAAE,GAAQ,EAAmB,EAAE;IACrF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,kBAAkB,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,SAAS,CAAC,CAAC;IAC/H,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,MAAc,EAAE,QAAwB,EAAc,EAAE;IACvF,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACtD,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAAE,IAKvC,EAAqB,EAAE;IACtB,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IACjC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;YACxC,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACxC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;gBAAE,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC;YACzD,MAAM,IAAI,GAAG,CAAC,GAAa,EAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAE,GAAG,GAAG,CAAC,CAAC,CAAE,GAAG,GAAG,CAAC,CAAC,CAAE,CAAC,GAAG,CAAC,CAAC;YAC1E,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACpH,CAAC,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAG,EAAE,EAAE,IAAI,GAAG,CAAC,CAAC;IAC7B,MAAM,KAAK,GAA4E,EAAE,CAAC;IAC1F,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACvC,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,GAAG,GAAQ;gBACf,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC;gBACtC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;gBACjC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC;gBAC5C,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;aACxC,CAAC;YACF,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACxC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;gBAAE,SAAS;YACvB,IAAI,IAAI,GAAG,CAAC,CAAC;YACb,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC;gBAAE,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC;oBAAE,IAAI,IAAI,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAE,CAAC;YAC/G,iCAAiC;YACjC,IAAI,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG;gBAAE,SAAS;YACnE,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,GAAG,CAAC,CAAC;YACrG,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAC1B,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU;WAC/B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;WAC1B,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAC9B,CAAC,CAAC;IACH,IAAI,KAAK,GAAG,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC;IAC9B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,SAAS;QAC3B,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;QACpB,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;cACjD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;cACnD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAC7D,UAAU,IAAI,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,cAAc,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;IACpE,OAAO;QACL,iBAAiB,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,UAAU,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;cAC5G,wEAAwE;QAC1E,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACjC,KAAK,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,MAAM,YAAY,IAAI,CAAC,MAAM,EAAE,CACnH,CAAC;KACH,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -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"}
|