editmamei 0.20.0 → 0.22.1

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 (72) hide show
  1. package/NOTICES.md +17 -1
  2. package/README.md +8 -7
  3. package/dist/api/extendscript/_helpers.js +1 -1
  4. package/dist/bin/editmamei-core-darwin-arm64 +0 -0
  5. package/dist/bin/editmamei-core-darwin-x64 +0 -0
  6. package/dist/bin/editmamei-core-win-x64.exe +0 -0
  7. package/dist/cli/config.js +3 -3
  8. package/dist/cli/help.js +2 -0
  9. package/dist/cli/install.js +2 -2
  10. package/dist/cli/repair.js +36 -0
  11. package/dist/cli/router.js +4 -0
  12. package/dist/core/server.js +81 -10
  13. package/dist/core/settings.js +2 -2
  14. package/dist/core/tool-groups.js +91 -82
  15. package/dist/core/tool-registry.js +6 -0
  16. package/dist/core/tool-tiers.js +91 -82
  17. package/dist/delivery/provision.js +2 -2
  18. package/dist/delivery/store.js +63 -4
  19. package/dist/detection/landmark-detection-client.js +8 -0
  20. package/dist/detection/mesh-eye-correction.js +126 -0
  21. package/dist/detection/sam-segmenter.js +83 -0
  22. package/dist/index.js +2 -0
  23. package/dist/kernel/host-api.js +1 -0
  24. package/dist/license/entitlement.js +52 -0
  25. package/dist/modules/ce/index.js +4 -4
  26. package/dist/perception/grounding-anchors.js +37 -0
  27. package/dist/perception/grounding-corners.js +93 -0
  28. package/dist/perception/grounding-edge-trace.js +90 -0
  29. package/dist/perception/grounding-extrema.js +41 -0
  30. package/dist/perception/grounding-gate.js +154 -0
  31. package/dist/perception/grounding-geometry.js +132 -0
  32. package/dist/perception/grounding-grid.js +34 -0
  33. package/dist/perception/grounding-landmarks.js +40 -0
  34. package/dist/perception/grounding-locate.js +341 -0
  35. package/dist/perception/grounding-resolver.js +202 -0
  36. package/dist/perception/grounding-review-crop.js +85 -0
  37. package/dist/perception/grounding-warp.js +182 -0
  38. package/dist/perception/sky-mask-transfer.js +50 -18
  39. package/dist/skills/editmamei-skill.zip +0 -0
  40. package/dist/tools/adjustment-tools.js +3 -3
  41. package/dist/tools/brush-tools.js +111 -23
  42. package/dist/tools/channel-compose-tools.js +241 -0
  43. package/dist/tools/detection-tools.js +1 -1
  44. package/dist/tools/diagnostics-tools.js +1 -1
  45. package/dist/tools/document-tools.js +6 -6
  46. package/dist/tools/filter-tools.js +476 -19
  47. package/dist/tools/group-tools.js +10 -10
  48. package/dist/tools/guide-tools.js +4 -4
  49. package/dist/tools/history-tools.js +4 -4
  50. package/dist/tools/image-placement-tools.js +1 -1
  51. package/dist/tools/image-tools.js +62 -19
  52. package/dist/tools/inspect-tools.js +2 -2
  53. package/dist/tools/layer-ordering-tools.js +1 -1
  54. package/dist/tools/layer-properties-tools.js +42 -23
  55. package/dist/tools/layer-tools.js +10 -10
  56. package/dist/tools/layer-transform-tools.js +80 -41
  57. package/dist/tools/overview-tools.js +42 -42
  58. package/dist/tools/path-tools.js +53 -10
  59. package/dist/tools/portrait-tools.js +3 -3
  60. package/dist/tools/preview-tools.js +7 -82
  61. package/dist/tools/retouch-tools.js +93 -25
  62. package/dist/tools/scene-tools.js +13 -13
  63. package/dist/tools/selection-tools.js +319 -40
  64. package/dist/tools/shape-tools.js +216 -0
  65. package/dist/tools/text-tools.js +1 -1
  66. package/dist/tools/transform-canvas-tools.js +2 -2
  67. package/dist/tools/vector-mask-tools.js +23 -8
  68. package/dist/version.js +1 -1
  69. package/package.json +1 -1
  70. package/dist/preview/coordinate-grid.js +0 -250
  71. package/dist/tools/object-tools.js +0 -193
  72. package/dist/tools/text-on-object-tools.js +0 -225
@@ -1,86 +1,95 @@
1
1
  export const TOOL_TIERS = {
2
- photoshop_ping: 'community',
3
- photoshop_list_capabilities: 'community',
4
- photoshop_report_problem: 'community',
5
- photoshop_list_actions: 'pro',
6
- photoshop_play_action: 'pro',
7
- photoshop_execute_script: 'pro',
8
- photoshop_add_adjustment_layer: 'community',
9
- photoshop_apply_adjustment: 'community',
10
- photoshop_create_document: 'community',
11
- photoshop_close_document: 'community',
12
- photoshop_open_document: 'community',
13
- photoshop_save_psd: 'community',
14
- photoshop_export: 'community',
15
- photoshop_apply_filter: 'community',
16
- photoshop_create_group: 'community',
17
- photoshop_move_layer_to_group: 'community',
18
- photoshop_set_group_blend_mode: 'community',
19
- photoshop_ungroup: 'community',
20
- photoshop_delete_group: 'community',
21
- photoshop_create_clipping_mask: 'community',
22
- photoshop_release_clipping_mask: 'dev',
23
- photoshop_undo: 'community',
24
- photoshop_redo: 'community',
25
- photoshop_place_image: 'community',
26
- photoshop_resize_image: 'community',
27
- photoshop_crop_document: 'community',
28
- photoshop_convert_image_mode: 'community',
29
- photoshop_move_layer_to_position: 'community',
30
- photoshop_convert_to_smart_object: 'community',
31
- photoshop_rasterize_layer: 'community',
32
- photoshop_set_layer: 'community',
33
- photoshop_duplicate_layer: 'community',
34
- photoshop_layer_via_copy: 'community',
35
- photoshop_merge: 'community',
36
- photoshop_bake_layer: 'community',
37
- photoshop_add_layer_style: 'community',
38
- photoshop_create_layer: 'community',
39
- photoshop_delete_layer: 'community',
40
- photoshop_create_text_layer: 'community',
41
- photoshop_fill_layer: 'community',
42
- photoshop_add_fill_layer: 'community',
43
- photoshop_select_layer: 'community',
44
- photoshop_transform_layer: 'community',
45
- photoshop_warp_layer: 'pro',
46
- photoshop_warp_layer_mesh: 'pro',
47
- photoshop_transform_canvas: 'community',
48
- photoshop_guides: 'community',
49
- photoshop_inspect: 'community',
50
- photoshop_overview: 'community',
51
- photoshop_get_preview: 'community',
52
- photoshop_get_layer_bounds_diff: 'community',
53
- photoshop_compare_regions: 'community',
54
- photoshop_get_histogram: 'community',
55
- photoshop_retouch: 'community',
56
- photoshop_apply_brush_stroke: 'dev',
57
- photoshop_detect: 'community',
58
- photoshop_object: 'dev',
59
- photoshop_portrait_touchup: 'dev',
60
- photoshop_text_on_object: 'dev',
61
- photoshop_detect_landmarks: 'pro',
62
- photoshop_select_face_feature: 'pro',
63
- photoshop_stroke_face_contour: 'dev',
64
- photoshop_scene: 'community',
65
- photoshop_select_by_reference: 'community',
66
- photoshop_select: 'community',
67
- photoshop_select_subject: 'pro',
68
- photoshop_select_sky: 'pro',
69
- photoshop_select_subject_instance: 'pro',
70
- photoshop_modify_selection: 'community',
71
- photoshop_get_selection_preview: 'community',
72
- photoshop_selection_channel: 'community',
73
- photoshop_layer_mask: 'community',
74
- photoshop_path: 'community',
75
- photoshop_vector_mask: 'community',
76
- photoshop_template_create_evidence: 'pro',
77
- photoshop_template_save: 'pro',
78
- photoshop_template_list: 'pro',
79
- photoshop_template_apply: 'pro',
80
- photoshop_template_verify: 'pro',
81
- photoshop_template_recall: 'pro',
82
- photoshop_template_delete: 'pro',
83
- photoshop_set_text: 'community',
2
+ ps_ping: 'community',
3
+ ps_list_capabilities: 'community',
4
+ ps_report_problem: 'community',
5
+ ps_list_actions: 'pro',
6
+ ps_play_action: 'pro',
7
+ ps_execute_script: 'pro',
8
+ ps_add_adjustment_layer: 'community',
9
+ ps_apply_adjustment: 'community',
10
+ ps_create_document: 'community',
11
+ ps_close_document: 'community',
12
+ ps_open_document: 'community',
13
+ ps_save_psd: 'community',
14
+ ps_export: 'community',
15
+ ps_apply_filter: 'community',
16
+ ps_create_group: 'community',
17
+ ps_move_layer_to_group: 'community',
18
+ ps_set_group_blend_mode: 'community',
19
+ ps_ungroup: 'community',
20
+ ps_delete_group: 'community',
21
+ ps_create_clipping_mask: 'community',
22
+ ps_release_clipping_mask: 'dev',
23
+ ps_undo: 'community',
24
+ ps_redo: 'community',
25
+ ps_place_image: 'community',
26
+ ps_resize_image: 'community',
27
+ ps_crop_document: 'community',
28
+ ps_convert_image_mode: 'community',
29
+ ps_move_layer_to_position: 'community',
30
+ ps_convert_to_smart_object: 'community',
31
+ ps_rasterize_layer: 'community',
32
+ ps_set_layer: 'community',
33
+ ps_duplicate_layer: 'community',
34
+ ps_copy_to_new_layer: 'community',
35
+ ps_merge: 'community',
36
+ ps_bake_layer: 'community',
37
+ ps_add_layer_style: 'community',
38
+ ps_create_layer: 'community',
39
+ ps_delete_layer: 'community',
40
+ ps_create_text_layer: 'community',
41
+ ps_fill_layer: 'community',
42
+ ps_add_fill_layer: 'community',
43
+ ps_select_layer: 'community',
44
+ ps_transform_layer: 'community',
45
+ ps_warp_layer: 'pro',
46
+ ps_warp_layer_mesh: 'pro',
47
+ ps_warp_layer_along: 'pro',
48
+ ps_warp_layer_region: 'pro',
49
+ ps_warp_layer_to: 'pro',
50
+ ps_apply_camera_raw: 'pro',
51
+ ps_transform_canvas: 'community',
52
+ ps_guides: 'community',
53
+ ps_inspect: 'community',
54
+ ps_overview: 'community',
55
+ ps_get_preview: 'community',
56
+ ps_get_layer_bounds_diff: 'community',
57
+ ps_compare_regions: 'community',
58
+ ps_get_histogram: 'community',
59
+ ps_retouch: 'community',
60
+ ps_apply_brush_stroke: 'dev',
61
+ ps_detect: 'community',
62
+ ps_edit_object: 'pro',
63
+ ps_portrait_touchup: 'dev',
64
+ ps_add_text_to_object: 'pro',
65
+ ps_detect_landmarks: 'pro',
66
+ ps_select_face_feature: 'pro',
67
+ ps_stroke_face_contour: 'dev',
68
+ ps_read_scene: 'community',
69
+ ps_select_by_reference: 'community',
70
+ ps_select: 'community',
71
+ ps_select_subject: 'community',
72
+ ps_select_sky: 'community',
73
+ ps_select_subject_instance: 'pro',
74
+ ps_select_object: 'pro',
75
+ ps_modify_selection: 'community',
76
+ ps_get_selection_preview: 'community',
77
+ ps_selection_channel: 'community',
78
+ ps_layer_mask: 'community',
79
+ ps_path: 'community',
80
+ ps_vector_mask: 'community',
81
+ ps_apply_image: 'community',
82
+ ps_calculations: 'community',
83
+ ps_shape: 'community',
84
+ ps_resolve_placement: 'pro',
85
+ ps_template_create_evidence: 'pro',
86
+ ps_template_save: 'pro',
87
+ ps_template_list: 'pro',
88
+ ps_template_apply: 'pro',
89
+ ps_template_verify: 'pro',
90
+ ps_template_recall: 'pro',
91
+ ps_template_delete: 'pro',
92
+ ps_set_text: 'community',
84
93
  };
85
94
  export function tierOf(toolName) {
86
95
  const tier = TOOL_TIERS[toolName];
@@ -51,7 +51,7 @@ export async function provisionModules(key, opts = {}) {
51
51
  continue;
52
52
  }
53
53
  const installed = readInstalledModule(sku, opts);
54
- if (installed && installed.version === latest) {
54
+ if (installed && installed.version === latest && !opts.force) {
55
55
  result.skipped.push({ sku, version: latest, reason: 'up-to-date' });
56
56
  continue;
57
57
  }
@@ -59,7 +59,7 @@ export async function provisionModules(key, opts = {}) {
59
59
  result.skipped.push({
60
60
  sku,
61
61
  version: latest,
62
- reason: `rollback-blocked (installed v${installed.version} is newer than manifest latest v${latest})`,
62
+ reason: `downgrade-blocked (installed v${installed.version} is newer than manifest latest v${latest})`,
63
63
  });
64
64
  continue;
65
65
  }
@@ -1,5 +1,5 @@
1
1
  import { join, dirname } from 'node:path';
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, readdirSync, rmSync, lstatSync, } from 'node:fs';
3
3
  import { settingsDir } from '../core/settings.js';
4
4
  import { installBundle } from './bundle.js';
5
5
  import { sha256Hex } from './crypto.js';
@@ -7,6 +7,8 @@ import { verifyModuleSignature, MODULE_SIGNING_PUBLIC_KEYS } from './signing.js'
7
7
  import { Logger } from '../utils/logger.js';
8
8
  const logger = new Logger('Modules');
9
9
  const INSTALLED_FILENAME = 'installed.json';
10
+ const TMP_PREFIX = '.tmp-';
11
+ const TMP_STALE_MS = 5 * 60 * 1000;
10
12
  export const PRO_SKU = 'pro';
11
13
  export const PRO_HANDLERS_ENTRY = 'pro-handlers.mjs';
12
14
  const MODULE_MANIFEST = 'manifest.json';
@@ -72,14 +74,71 @@ function writeFileAtomic(path, data) {
72
74
  renameSync(tmp, path);
73
75
  }
74
76
  export function installModule(rec, blob, opts = {}) {
75
- const dir = installedModuleDir(rec.sku, rec.version, opts);
76
- installBundle(blob, rec.content_key, dir);
77
- writeFileSync(moduleArtifactPath(rec.sku, rec.version, opts), Buffer.from(blob), { mode: 0o600 });
77
+ const finalDir = installedModuleDir(rec.sku, rec.version, opts);
78
+ const skuDir = dirname(finalDir);
79
+ const tmpDir = join(skuDir, `${TMP_PREFIX}${rec.version}-${process.pid}`);
80
+ rmSync(tmpDir, { recursive: true, force: true });
81
+ try {
82
+ installBundle(blob, rec.content_key, tmpDir);
83
+ writeFileSync(join(tmpDir, MODULE_ARTIFACT), Buffer.from(blob), { mode: 0o600 });
84
+ }
85
+ catch (err) {
86
+ rmSync(tmpDir, { recursive: true, force: true });
87
+ throw err;
88
+ }
89
+ if (opts.force && existsSync(finalDir)) {
90
+ rmSync(finalDir, { recursive: true, force: true });
91
+ }
92
+ try {
93
+ renameSync(tmpDir, finalDir);
94
+ }
95
+ catch (err) {
96
+ rmSync(tmpDir, { recursive: true, force: true });
97
+ if (!existsSync(finalDir))
98
+ throw err;
99
+ }
78
100
  const now = opts.now ?? Date.now;
79
101
  const installed = { ...rec, installed_at: new Date(now()).toISOString() };
80
102
  writeFileAtomic(installedPath(rec.sku, opts), JSON.stringify(installed, null, 2) + '\n');
103
+ pruneOldModuleVersions(rec.sku, rec.version, opts);
81
104
  return installed;
82
105
  }
106
+ export function pruneOldModuleVersions(sku, keepVersion, opts = {}) {
107
+ const skuDir = join(modulesRoot(opts), sku);
108
+ if (!existsSync(skuDir))
109
+ return;
110
+ let entries;
111
+ try {
112
+ entries = readdirSync(skuDir);
113
+ }
114
+ catch (err) {
115
+ logger.warn(`could not list module dir for '${sku}' to prune: ${err instanceof Error ? err.message : String(err)}`);
116
+ return;
117
+ }
118
+ const now = (opts.now ?? Date.now)();
119
+ for (const entry of entries) {
120
+ if (entry === keepVersion || entry === INSTALLED_FILENAME)
121
+ continue;
122
+ const full = join(skuDir, entry);
123
+ try {
124
+ const st = lstatSync(full);
125
+ if (entry.startsWith(TMP_PREFIX)) {
126
+ if (now - st.mtimeMs > TMP_STALE_MS) {
127
+ rmSync(full, { recursive: true, force: true });
128
+ logger.info(`Pruned stale staging dir '${sku}/${entry}'.`);
129
+ }
130
+ continue;
131
+ }
132
+ if (!st.isDirectory())
133
+ continue;
134
+ rmSync(full, { recursive: true, force: true });
135
+ logger.info(`Pruned stale module version '${sku}' v${entry}.`);
136
+ }
137
+ catch (err) {
138
+ logger.warn(`could not prune stale module entry '${sku}/${entry}': ${err instanceof Error ? err.message : String(err)}`);
139
+ }
140
+ }
141
+ }
83
142
  export function loadVerifiedModule(sku, opts = {}, pubKeys = MODULE_SIGNING_PUBLIC_KEYS) {
84
143
  const rec = readInstalledModule(sku, opts);
85
144
  if (!rec)
@@ -1,6 +1,7 @@
1
1
  import { decodeJpeg as realDecodeJpeg } from './runtime.js';
2
2
  import { detectLandmarks as realDetectLandmarks, } from './landmark-detector.js';
3
3
  import { OnnxDetectionClient, } from './detection-client.js';
4
+ import { correctMeshEyes } from './mesh-eye-correction.js';
4
5
  const LANDMARK_BACKEND = 'facemesh-468';
5
6
  export class OnnxLandmarkDetectionClient {
6
7
  base;
@@ -20,11 +21,18 @@ export class OnnxLandmarkDetectionClient {
20
21
  const img = this.decodeJpeg(imagePath);
21
22
  const minScore = this.opts.minScore ?? 0.5;
22
23
  const lmOpts = { margin: this.opts.margin };
24
+ const correctEyes = this.opts.correctEyes ?? true;
23
25
  for (const face of result.faces) {
24
26
  try {
25
27
  const mesh = await this.detectLandmarks(img, face.bbox, lmOpts);
26
28
  if (mesh.score >= minScore) {
27
29
  face.features = { points: mesh.points, score: mesh.score, backend: LANDMARK_BACKEND };
30
+ if (correctEyes) {
31
+ const c = correctMeshEyes(img, face.features.points);
32
+ face.features.points = c.points;
33
+ if (c.correction)
34
+ face.features.eyeCorrection = c.correction;
35
+ }
28
36
  }
29
37
  }
30
38
  catch {
@@ -0,0 +1,126 @@
1
+ import { LANDMARK_GROUPS, LANDMARK_COUNT } from './landmark-spec.js';
2
+ const EYE_RIGID_GROUPS = ['leftEye', 'rightEye', 'leftEyebrow', 'rightEyebrow', 'nose'];
3
+ const EYE_RIGID_INDICES = new Set();
4
+ for (const g of EYE_RIGID_GROUPS)
5
+ for (const i of LANDMARK_GROUPS[g])
6
+ EYE_RIGID_INDICES.add(i);
7
+ const centroid = (pts) => ({
8
+ x: pts.reduce((s, p) => s + p.x, 0) / pts.length,
9
+ y: pts.reduce((s, p) => s + p.y, 0) / pts.length,
10
+ });
11
+ const bbox = (pts) => ({
12
+ x0: Math.min(...pts.map((p) => p.x)),
13
+ y0: Math.min(...pts.map((p) => p.y)),
14
+ x1: Math.max(...pts.map((p) => p.x)),
15
+ y1: Math.max(...pts.map((p) => p.y)),
16
+ });
17
+ const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
18
+ function makeCtx(img) {
19
+ const { width: W, height: H, data } = img;
20
+ const luma = (x, y) => {
21
+ const i = (y * W + x) * 4;
22
+ return 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
23
+ };
24
+ const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
25
+ const blur = (x, y, r) => {
26
+ let s = 0, n = 0;
27
+ for (let dy = -r; dy <= r; dy++)
28
+ for (let dx = -r; dx <= r; dx++) {
29
+ s += luma(clamp(x + dx, 0, W - 1), clamp(y + dy, 0, H - 1));
30
+ n++;
31
+ }
32
+ return s / n;
33
+ };
34
+ return { W, H, luma, clamp, blur };
35
+ }
36
+ function findEyeCenter(ctx, center, ring) {
37
+ const { W, H, luma, clamp, blur } = ctx;
38
+ const rw = ring.x1 - ring.x0, rh = ring.y1 - ring.y0;
39
+ const x0 = clamp(Math.round(center.x - 0.55 * rw), 0, W - 1);
40
+ const x1 = clamp(Math.round(center.x + 0.55 * rw), 0, W - 1);
41
+ const y0 = clamp(Math.round(center.y - 0.5 * rh), 0, H - 1);
42
+ const y1 = clamp(Math.round(center.y + 2.0 * rh), 0, H - 1);
43
+ const ww = x1 - x0 + 1, wh = y1 - y0 + 1;
44
+ if (ww < 5 || wh < 5)
45
+ return { x: center.x, y: center.y, atEdge: false };
46
+ const L = new Float32Array(ww * wh);
47
+ for (let y = 0; y < wh; y++)
48
+ for (let x = 0; x < ww; x++)
49
+ L[y * ww + x] = luma(x0 + x, y0 + y);
50
+ const edges = [];
51
+ const gg = [];
52
+ let magSum = 0;
53
+ for (let y = 1; y < wh - 1; y++)
54
+ for (let x = 1; x < ww - 1; x++) {
55
+ const gx = (L[y * ww + x + 1] - L[y * ww + x - 1]) / 2;
56
+ const gy = (L[(y + 1) * ww + x] - L[(y - 1) * ww + x]) / 2;
57
+ const m = Math.hypot(gx, gy);
58
+ gg.push({ m, x, y, gx, gy });
59
+ magSum += m;
60
+ }
61
+ const mean = magSum / (gg.length || 1);
62
+ let v = 0;
63
+ for (const g of gg)
64
+ v += (g.m - mean) ** 2;
65
+ const std = Math.sqrt(v / (gg.length || 1));
66
+ const thr = mean + 0.3 * std;
67
+ for (const g of gg)
68
+ if (g.m > thr)
69
+ edges.push({ x: g.x, y: g.y, gx: g.gx / g.m, gy: g.gy / g.m });
70
+ let best = -Infinity, bx = Math.round(center.x - x0), by = Math.round(center.y - y0);
71
+ for (let cy = 2; cy < wh - 2; cy += 2)
72
+ for (let cx = 2; cx < ww - 2; cx += 2) {
73
+ let s = 0;
74
+ for (const e of edges) {
75
+ const dx = e.x - cx, dy = e.y - cy, l = Math.hypot(dx, dy);
76
+ if (l < 3)
77
+ continue;
78
+ const dot = (dx / l) * e.gx + (dy / l) * e.gy;
79
+ if (dot > 0)
80
+ s += dot * dot;
81
+ }
82
+ s *= 255 - blur(x0 + cx, y0 + cy, 3);
83
+ if (s > best) {
84
+ best = s;
85
+ bx = cx;
86
+ by = cy;
87
+ }
88
+ }
89
+ const atEdge = bx <= 3 || bx >= ww - 4 || by >= wh - 4;
90
+ return { x: x0 + bx, y: y0 + by, atEdge };
91
+ }
92
+ function similarity(m1, m2, t1, t2) {
93
+ const dmx = m2.x - m1.x, dmy = m2.y - m1.y, dtx = t2.x - t1.x, dty = t2.y - t1.y;
94
+ const s = Math.hypot(dtx, dty) / (Math.hypot(dmx, dmy) || 1);
95
+ const th = Math.atan2(dty, dtx) - Math.atan2(dmy, dmx);
96
+ const c = Math.cos(th) * s, sn = Math.sin(th) * s;
97
+ return (p) => ({
98
+ x: t1.x + c * (p.x - m1.x) - sn * (p.y - m1.y),
99
+ y: t1.y + sn * (p.x - m1.x) + c * (p.y - m1.y),
100
+ });
101
+ }
102
+ export function correctMeshEyes(img, points) {
103
+ if (!points || points.length < LANDMARK_COUNT)
104
+ return { points, correction: null };
105
+ if (!img || !img.data || img.data.length < img.width * img.height * 4) {
106
+ return { points, correction: null };
107
+ }
108
+ const ctx = makeCtx(img);
109
+ const leftEye = LANDMARK_GROUPS.leftEye.map((i) => points[i]);
110
+ const rightEye = LANDMARK_GROUPS.rightEye.map((i) => points[i]);
111
+ const eyeLm = centroid(leftEye), eyeRm = centroid(rightEye);
112
+ if (dist(eyeLm, eyeRm) < 4)
113
+ return { points, correction: null };
114
+ const eyeLc = findEyeCenter(ctx, eyeLm, bbox(leftEye));
115
+ const eyeRc = findEyeCenter(ctx, eyeRm, bbox(rightEye));
116
+ const T = similarity(eyeLm, eyeRm, eyeLc, eyeRc);
117
+ const corrected = points.map((p, i) => (EYE_RIGID_INDICES.has(i) ? { ...T(p), z: p.z } : p));
118
+ return {
119
+ points: corrected,
120
+ correction: {
121
+ drift_left: Math.round(dist(eyeLc, eyeLm)),
122
+ drift_right: Math.round(dist(eyeRc, eyeRm)),
123
+ low_confidence: eyeLc.atEdge || eyeRc.atEdge,
124
+ },
125
+ };
126
+ }
@@ -0,0 +1,83 @@
1
+ import { loadModel, resolveModelPath, ort } from './runtime.js';
2
+ const MODEL_ENCODER = 'pro/mobile_sam_encoder.onnx';
3
+ const MODEL_DECODER = 'pro/mobile_sam_decoder.onnx';
4
+ const SAM_INPUT = 1024;
5
+ export function boxToPromptCoords(box, imgW, imgH) {
6
+ const s = SAM_INPUT / Math.max(imgW, imgH);
7
+ const [x1, y1, x2, y2] = box;
8
+ return {
9
+ coords: Float32Array.from([x1 * s, y1 * s, x2 * s, y2 * s]),
10
+ labels: Float32Array.from([2, 3]),
11
+ };
12
+ }
13
+ export function pointsToPromptCoords(fg, bg, imgW, imgH) {
14
+ const s = SAM_INPUT / Math.max(imgW, imgH);
15
+ const pts = [...fg, ...bg, { x: 0, y: 0 }];
16
+ const labels = [...fg.map(() => 1), ...bg.map(() => 0), -1];
17
+ const coords = new Float32Array(pts.length * 2);
18
+ pts.forEach((p, i) => {
19
+ coords[i * 2] = p.x * s;
20
+ coords[i * 2 + 1] = p.y * s;
21
+ });
22
+ return { coords, labels: Float32Array.from(labels) };
23
+ }
24
+ export function toEncoderInput(img) {
25
+ const { width: w, height: h, data } = img;
26
+ const out = new Float32Array(h * w * 3);
27
+ for (let p = 0; p < h * w; p++) {
28
+ out[p * 3] = data[p * 4];
29
+ out[p * 3 + 1] = data[p * 4 + 1];
30
+ out[p * 3 + 2] = data[p * 4 + 2];
31
+ }
32
+ return out;
33
+ }
34
+ export function thresholdMask(maskData, n) {
35
+ const mask = new Uint8Array(n);
36
+ for (let i = 0; i < n; i++)
37
+ mask[i] = maskData[i] > 0 ? 1 : 0;
38
+ return mask;
39
+ }
40
+ export class OnnxSamSegmenter {
41
+ async encodeImage(img) {
42
+ const session = await loadModel(resolveModelPath(MODEL_ENCODER));
43
+ const input = new ort.Tensor('float32', toEncoderInput(img), [img.height, img.width, 3]);
44
+ const out = await session.run({ input_image: input });
45
+ return out.image_embeddings;
46
+ }
47
+ async runDecoder(embedding, coords, labels, imgW, imgH) {
48
+ const session = await loadModel(resolveModelPath(MODEL_DECODER));
49
+ const nPts = labels.length;
50
+ const out = await session.run({
51
+ image_embeddings: embedding,
52
+ point_coords: new ort.Tensor('float32', coords, [1, nPts, 2]),
53
+ point_labels: new ort.Tensor('float32', labels, [1, nPts]),
54
+ mask_input: new ort.Tensor('float32', new Float32Array(256 * 256), [1, 1, 256, 256]),
55
+ has_mask_input: new ort.Tensor('float32', Float32Array.from([0]), [1]),
56
+ orig_im_size: new ort.Tensor('float32', Float32Array.from([imgH, imgW]), [2]),
57
+ });
58
+ const masks = out.masks;
59
+ const [, , mh, mw] = masks.dims;
60
+ return {
61
+ mask: thresholdMask(masks.data, mh * mw),
62
+ width: mw,
63
+ height: mh,
64
+ iou: out.iou_predictions.data[0] ?? 0,
65
+ };
66
+ }
67
+ async segmentBox(embedding, box, imgW, imgH) {
68
+ const { coords, labels } = boxToPromptCoords(box, imgW, imgH);
69
+ return this.runDecoder(embedding, coords, labels, imgW, imgH);
70
+ }
71
+ async segmentPointsAt(embedding, fg, bg, imgW, imgH) {
72
+ const { coords, labels } = pointsToPromptCoords(fg, bg, imgW, imgH);
73
+ return this.runDecoder(embedding, coords, labels, imgW, imgH);
74
+ }
75
+ async segment(img, box) {
76
+ const embedding = await this.encodeImage(img);
77
+ return this.segmentBox(embedding, box, img.width, img.height);
78
+ }
79
+ async segmentPoints(img, fg, bg = []) {
80
+ const embedding = await this.encodeImage(img);
81
+ return this.segmentPointsAt(embedding, fg, bg, img.width, img.height);
82
+ }
83
+ }
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { pathToFileURL } from 'node:url';
4
4
  import { EditmameiServer } from './core/server.js';
5
5
  import { routeCli } from './cli/router.js';
6
6
  import { maybeActivateFromEnv } from './license/env-activation.js';
7
+ import { refreshIfStale } from './license/entitlement.js';
7
8
  import { Logger } from './utils/logger.js';
8
9
  const logger = new Logger('Main');
9
10
  function installProcessHandlers() {
@@ -37,6 +38,7 @@ async function main() {
37
38
  try {
38
39
  logger.info('Starting Editmamei...');
39
40
  await maybeActivateFromEnv();
41
+ await refreshIfStale();
40
42
  const server = new EditmameiServer();
41
43
  await server.start();
42
44
  let shuttingDown = false;
@@ -1 +1,2 @@
1
1
  export const KERNEL_ABI = 1;
2
+ export const HOST_MIN_ABI = 1;
@@ -2,7 +2,11 @@ import { resolvePolarConfig } from './config.js';
2
2
  import { computeDeviceHash } from './device-hash.js';
3
3
  import { readLicense, writeLicense, clearLicense, } from './store.js';
4
4
  import { PolarLicenseClient, PolarLicenseError } from './polar-client.js';
5
+ import { Logger } from '../utils/logger.js';
6
+ const logger = new Logger('License');
5
7
  export const GRACE_MS = 30 * 24 * 60 * 60 * 1000;
8
+ export const REFRESH_AFTER_MS = 7 * 24 * 60 * 60 * 1000;
9
+ export const EXPIRED_REFRESH_TIMEOUT_MS = 5_000;
6
10
  export function evaluateEntitlement(rec, now) {
7
11
  if (!rec)
8
12
  return { entitled: false, reason: 'no-license' };
@@ -59,6 +63,54 @@ export async function activate(key, ops = {}) {
59
63
  writeLicense(rec, ops);
60
64
  return rec;
61
65
  }
66
+ export async function refreshIfStale(ops = {}) {
67
+ const env = ops.env ?? process.env;
68
+ if (env.VITEST !== undefined || env.NODE_ENV === 'test')
69
+ return;
70
+ const rec = readLicense(ops);
71
+ if (!rec)
72
+ return;
73
+ if (rec.status === 'revoked' || rec.status === 'disabled')
74
+ return;
75
+ const now = (ops.now ?? Date.now)();
76
+ if (rec.expires_at !== null && Date.parse(rec.expires_at) <= now)
77
+ return;
78
+ const last = Date.parse(rec.last_validated_at);
79
+ const age = Number.isFinite(last) ? now - last : Infinity;
80
+ if (age <= REFRESH_AFTER_MS)
81
+ return;
82
+ if (age <= GRACE_MS) {
83
+ refresh(ops).catch((err) => {
84
+ logger.warn(`Background license refresh failed (grace covers offline use): ` +
85
+ `${err instanceof Error ? err.message : String(err)}`);
86
+ });
87
+ return;
88
+ }
89
+ const timeoutMs = ops.expiredRefreshTimeoutMs ?? EXPIRED_REFRESH_TIMEOUT_MS;
90
+ const attempt = refresh(ops);
91
+ attempt.catch(() => { });
92
+ let timer;
93
+ try {
94
+ const winner = await Promise.race([
95
+ attempt,
96
+ new Promise((resolve) => {
97
+ timer = setTimeout(() => resolve('timeout'), timeoutMs);
98
+ timer.unref?.();
99
+ }),
100
+ ]);
101
+ if (winner === 'timeout') {
102
+ logger.warn('License re-validation timed out past the grace window — continuing as Community; the next boot retries.');
103
+ }
104
+ }
105
+ catch (err) {
106
+ logger.warn(`License re-validation failed past the grace window (continuing as Community): ` +
107
+ `${err instanceof Error ? err.message : String(err)}`);
108
+ }
109
+ finally {
110
+ if (timer)
111
+ clearTimeout(timer);
112
+ }
113
+ }
62
114
  export async function refresh(ops = {}) {
63
115
  const rec = readLicense(ops);
64
116
  if (!rec)
@@ -23,12 +23,12 @@ import { createBrushTools } from '../../tools/brush-tools.js';
23
23
  import { createTransformCanvasTools } from '../../tools/transform-canvas-tools.js';
24
24
  import { createGuideTools } from '../../tools/guide-tools.js';
25
25
  import { createDetectionTools } from '../../tools/detection-tools.js';
26
- import { createObjectTools } from '../../tools/object-tools.js';
27
26
  import { createPortraitTools } from '../../tools/portrait-tools.js';
28
- import { createTextOnObjectTools } from '../../tools/text-on-object-tools.js';
29
27
  import { createSceneTools } from '../../tools/scene-tools.js';
30
28
  import { createPathTools } from '../../tools/path-tools.js';
31
29
  import { createVectorMaskTools } from '../../tools/vector-mask-tools.js';
30
+ import { createChannelComposeTools } from '../../tools/channel-compose-tools.js';
31
+ import { createShapeTools } from '../../tools/shape-tools.js';
32
32
  const ceFactories = [
33
33
  createDocumentTools,
34
34
  createLayerTools,
@@ -52,11 +52,11 @@ const ceFactories = [
52
52
  createTransformCanvasTools,
53
53
  createGuideTools,
54
54
  createDetectionTools,
55
- createObjectTools,
56
55
  createPortraitTools,
57
- createTextOnObjectTools,
58
56
  createPathTools,
59
57
  createVectorMaskTools,
58
+ createChannelComposeTools,
59
+ createShapeTools,
60
60
  ];
61
61
  export const ceModule = {
62
62
  manifest: { id: 'ce', name: 'Editmamei Community Tools', abi: KERNEL_ABI },