editmamei 0.19.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/NOTICES.md +17 -1
  2. package/README.md +9 -8
  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 +4 -0
  9. package/dist/cli/install.js +2 -2
  10. package/dist/cli/report.js +11 -0
  11. package/dist/cli/router.js +20 -0
  12. package/dist/core/server.js +8 -8
  13. package/dist/core/settings.js +2 -2
  14. package/dist/core/tool-groups.js +92 -82
  15. package/dist/core/tool-tiers.js +91 -81
  16. package/dist/detection/landmark-detection-client.js +8 -0
  17. package/dist/detection/mesh-eye-correction.js +126 -0
  18. package/dist/detection/sam-segmenter.js +83 -0
  19. package/dist/diagnostics/collect.js +169 -0
  20. package/dist/index.js +2 -0
  21. package/dist/license/entitlement.js +52 -0
  22. package/dist/modules/ce/index.js +6 -4
  23. package/dist/perception/grounding-anchors.js +37 -0
  24. package/dist/perception/grounding-corners.js +93 -0
  25. package/dist/perception/grounding-edge-trace.js +90 -0
  26. package/dist/perception/grounding-extrema.js +41 -0
  27. package/dist/perception/grounding-gate.js +154 -0
  28. package/dist/perception/grounding-geometry.js +132 -0
  29. package/dist/perception/grounding-grid.js +34 -0
  30. package/dist/perception/grounding-landmarks.js +40 -0
  31. package/dist/perception/grounding-locate.js +341 -0
  32. package/dist/perception/grounding-resolver.js +202 -0
  33. package/dist/perception/grounding-review-crop.js +85 -0
  34. package/dist/perception/grounding-warp.js +182 -0
  35. package/dist/perception/sky-mask-transfer.js +50 -18
  36. package/dist/skills/editmamei-skill.zip +0 -0
  37. package/dist/tools/adjustment-tools.js +3 -3
  38. package/dist/tools/brush-tools.js +111 -23
  39. package/dist/tools/channel-compose-tools.js +241 -0
  40. package/dist/tools/detection-tools.js +1 -1
  41. package/dist/tools/diagnostics-tools.js +66 -0
  42. package/dist/tools/document-tools.js +6 -6
  43. package/dist/tools/filter-tools.js +476 -19
  44. package/dist/tools/group-tools.js +10 -10
  45. package/dist/tools/guide-tools.js +4 -4
  46. package/dist/tools/history-tools.js +4 -4
  47. package/dist/tools/image-placement-tools.js +1 -1
  48. package/dist/tools/image-tools.js +62 -19
  49. package/dist/tools/inspect-tools.js +2 -2
  50. package/dist/tools/layer-ordering-tools.js +1 -1
  51. package/dist/tools/layer-properties-tools.js +42 -23
  52. package/dist/tools/layer-tools.js +10 -10
  53. package/dist/tools/layer-transform-tools.js +80 -41
  54. package/dist/tools/overview-tools.js +42 -42
  55. package/dist/tools/path-tools.js +53 -10
  56. package/dist/tools/portrait-tools.js +3 -3
  57. package/dist/tools/preview-tools.js +7 -82
  58. package/dist/tools/retouch-tools.js +93 -25
  59. package/dist/tools/scene-tools.js +13 -13
  60. package/dist/tools/selection-tools.js +319 -40
  61. package/dist/tools/shape-tools.js +216 -0
  62. package/dist/tools/text-tools.js +1 -1
  63. package/dist/tools/transform-canvas-tools.js +2 -2
  64. package/dist/tools/vector-mask-tools.js +23 -8
  65. package/dist/utils/log-buffer.js +41 -0
  66. package/dist/utils/logger.js +12 -1
  67. package/dist/utils/session-log-reader.js +27 -1
  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,5 +1,7 @@
1
1
  import { runScript } from '../utils/run-script.js';
2
2
  import { validateArgs } from '../utils/validate.js';
3
+ import { OnnxLandmarkDetectionClient } from '../detection/landmark-detection-client.js';
4
+ import { resolveGatedPlacement, PLACEMENT_SCHEMA } from '../perception/grounding-locate.js';
3
5
  const APPLY_TO_ACTIVE_LAYER_PROP = {
4
6
  type: 'boolean',
5
7
  description: 'If false (default), the filter is applied to a duplicate of the active layer named "<FilterName> (<Original Name>)" — the original is preserved and the LLM can undo simply by deleting the copy. If true, the filter bakes directly into the active layer (the historical destructive behavior).',
@@ -388,6 +390,10 @@ const radialBlurSchema = {
388
390
  maximum: 1,
389
391
  default: 0.5,
390
392
  },
393
+ center_placement: {
394
+ ...PLACEMENT_SCHEMA,
395
+ description: 'Grounded alternative to center_x/center_y: NAME the blur center (a `placement` resolving to a POINT — an object centroid, an extremum, a grid intersection). The resolved document-pixel point is normalized to the 0-1 center for you and WINS over center_x/center_y. The blur runs ONLY if the objective gate PASSES.',
396
+ },
391
397
  apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
392
398
  },
393
399
  };
@@ -396,8 +402,8 @@ const pixelateSchema = {
396
402
  properties: {
397
403
  mode: {
398
404
  type: 'string',
399
- enum: ['color_halftone', 'mosaic'],
400
- description: 'color_halftone = simulated CMYK halftone dot screen (retro comic / print-dot look); mosaic = blocky square cells (pixelation / censor look).',
405
+ enum: ['color_halftone', 'mosaic', 'crystallize', 'pointillize', 'facet', 'fragment'],
406
+ description: 'color_halftone = simulated CMYK halftone dot screen (retro comic / print-dot look); mosaic = blocky square cells (pixelation / censor look); crystallize = irregular polygonal cells of solid color; pointillize = random dots over the background color (painterly stipple); facet = clumps similar-colored pixels into flat blocks (parameterless); fragment = four offset copies averaged for a shaken/blurry look (parameterless).',
401
407
  },
402
408
  max_radius: {
403
409
  type: 'integer',
@@ -436,7 +442,7 @@ const pixelateSchema = {
436
442
  },
437
443
  cell_size: {
438
444
  type: 'integer',
439
- description: 'mosaic only: cell size in pixels (2-300). PS default 10.',
445
+ description: 'Cell size in pixels — used by mosaic (2-300, PS default 10), crystallize (3-300), and pointillize (3-300, PS default 5). Ignored by color_halftone/facet/fragment.',
440
446
  minimum: 2,
441
447
  maximum: 300,
442
448
  default: 10,
@@ -450,8 +456,8 @@ const distortSchema = {
450
456
  properties: {
451
457
  mode: {
452
458
  type: 'string',
453
- enum: ['twirl', 'ripple', 'polar', 'wave'],
454
- description: 'twirl = rotational swirl; ripple = fixed-size wavy displacement; polar = rectangular<->polar coordinate warp (wrap a strip into a circle / "tiny planet", or unwrap a panorama); wave = multi-generator sine/triangle/square waves.',
459
+ enum: ['twirl', 'ripple', 'polar', 'wave', 'pinch', 'spherize', 'zigzag'],
460
+ description: 'twirl = rotational swirl; ripple = fixed-size wavy displacement; polar = rectangular<->polar coordinate warp (wrap a strip into a circle / "tiny planet", or unwrap a panorama); wave = multi-generator sine/triangle/square waves; pinch = squeeze/bulge toward or away from center; spherize = wrap onto a sphere (bulge); zigzag = concentric ripples (pond-ripple style).',
455
461
  },
456
462
  angle: {
457
463
  type: 'integer',
@@ -462,11 +468,18 @@ const distortSchema = {
462
468
  },
463
469
  amount: {
464
470
  type: 'integer',
465
- description: 'ripple only: ripple amount (-999 to 999).',
471
+ description: 'Displacement amount. ripple: -999 to 999 (default 100). pinch: -100 to 100 (positive pinches inward, negative bulges; default 50). spherize: -100 to 100 (positive bulges out; default 100). zigzag: -100 to 100 (default 10).',
466
472
  minimum: -999,
467
473
  maximum: 999,
468
474
  default: 100,
469
475
  },
476
+ ridges: {
477
+ type: 'integer',
478
+ description: 'zigzag only: number of concentric ridges (0-20). PS default 5.',
479
+ minimum: 0,
480
+ maximum: 20,
481
+ default: 5,
482
+ },
470
483
  size: {
471
484
  type: 'string',
472
485
  enum: ['small', 'medium', 'large'],
@@ -550,6 +563,197 @@ const distortSchema = {
550
563
  },
551
564
  required: ['mode'],
552
565
  };
566
+ const stylizeSchema = {
567
+ type: 'object',
568
+ properties: {
569
+ mode: {
570
+ type: 'string',
571
+ enum: ['emboss', 'find_edges', 'solarize', 'wind', 'trace_contour', 'tiles'],
572
+ description: 'emboss = grey relief with raised edges (angle/height/amount); find_edges = outline edges on a white field (parameterless); solarize = partial tonal inversion (parameterless); wind = fine directional streaks (wind_method + wind_direction); trace_contour = thin contour lines at a brightness level (level + edge); tiles = break the image into offset square tiles (number + offset).',
573
+ },
574
+ angle: {
575
+ type: 'integer',
576
+ description: 'emboss only: relief angle in degrees (-360 to 360). PS default 135.',
577
+ minimum: -360,
578
+ maximum: 360,
579
+ default: 135,
580
+ },
581
+ amount: {
582
+ type: 'integer',
583
+ description: 'emboss only: amount percent (1-500). PS default 100.',
584
+ minimum: 1,
585
+ maximum: 500,
586
+ default: 100,
587
+ },
588
+ height: {
589
+ type: 'integer',
590
+ description: 'emboss only: relief height in pixels (1-100). PS default 3.',
591
+ minimum: 1,
592
+ maximum: 100,
593
+ default: 3,
594
+ },
595
+ wind_method: {
596
+ type: 'string',
597
+ enum: ['wind', 'blast', 'stagger'],
598
+ description: 'wind only: streak intensity/style. PS default wind.',
599
+ default: 'wind',
600
+ },
601
+ wind_direction: {
602
+ type: 'string',
603
+ enum: ['left', 'right'],
604
+ description: 'wind only: streak direction. PS default left.',
605
+ default: 'left',
606
+ },
607
+ level: {
608
+ type: 'integer',
609
+ description: 'trace_contour only: brightness level the contour traces (0-255). PS default 128.',
610
+ minimum: 0,
611
+ maximum: 255,
612
+ default: 128,
613
+ },
614
+ edge: {
615
+ type: 'string',
616
+ enum: ['lower', 'upper'],
617
+ description: 'trace_contour only: trace the lower or upper edge of the level. PS default lower.',
618
+ default: 'lower',
619
+ },
620
+ number: {
621
+ type: 'integer',
622
+ description: 'tiles only: number of tiles across (1-99). PS default 10.',
623
+ minimum: 1,
624
+ maximum: 99,
625
+ default: 10,
626
+ },
627
+ offset: {
628
+ type: 'integer',
629
+ description: 'tiles only: maximum tile offset percent (1-99). PS default 10.',
630
+ minimum: 1,
631
+ maximum: 99,
632
+ default: 10,
633
+ },
634
+ apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
635
+ },
636
+ required: ['mode'],
637
+ };
638
+ const renderSchema = {
639
+ type: 'object',
640
+ properties: {
641
+ mode: {
642
+ type: 'string',
643
+ enum: ['clouds', 'difference_clouds', 'fibers'],
644
+ description: 'clouds = soft random cloud texture from the current foreground/background colors (replaces the layer content); difference_clouds = clouds blended via Difference with the existing pixels (run repeatedly for marble/veining); fibers = woven fiber texture from the FG/BG colors. All three read the FG/BG colors — set them first for a specific palette.',
645
+ },
646
+ variance: {
647
+ type: 'integer',
648
+ description: 'fibers only: color variance / streak length (1-64). PS default 16.',
649
+ minimum: 1,
650
+ maximum: 64,
651
+ default: 16,
652
+ },
653
+ fiber_strength: {
654
+ type: 'integer',
655
+ description: 'fibers only: fiber strength / definition (1-64). PS default 4.',
656
+ minimum: 1,
657
+ maximum: 64,
658
+ default: 4,
659
+ },
660
+ seed: {
661
+ type: 'integer',
662
+ description: 'fibers only: randomize seed — change for a different fiber pattern. PS default 12345.',
663
+ minimum: 0,
664
+ default: 12345,
665
+ },
666
+ apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
667
+ },
668
+ required: ['mode'],
669
+ };
670
+ const otherSchema = {
671
+ type: 'object',
672
+ properties: {
673
+ mode: {
674
+ type: 'string',
675
+ enum: ['maximum', 'minimum', 'offset'],
676
+ description: 'maximum = spread/grow bright areas (choke masks, radius + preserve); minimum = spread dark areas (radius + preserve); offset = shift the layer by horizontal/vertical pixels and wrap the edges (seamless-tile workflow).',
677
+ },
678
+ radius: {
679
+ type: 'number',
680
+ description: 'maximum/minimum only: radius in pixels (1-500). PS default 3.',
681
+ minimum: 1,
682
+ maximum: 500,
683
+ default: 3,
684
+ },
685
+ preserve: {
686
+ type: 'string',
687
+ enum: ['roundness', 'squareness'],
688
+ description: 'maximum/minimum only: edge-preservation shape. PS default roundness.',
689
+ default: 'roundness',
690
+ },
691
+ horizontal: {
692
+ type: 'integer',
693
+ description: 'offset only: horizontal shift in pixels (positive = right).',
694
+ default: 0,
695
+ },
696
+ vertical: {
697
+ type: 'integer',
698
+ description: 'offset only: vertical shift in pixels (positive = down).',
699
+ default: 0,
700
+ },
701
+ apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
702
+ },
703
+ required: ['mode'],
704
+ };
705
+ const denoiseSchema = {
706
+ type: 'object',
707
+ properties: {
708
+ mode: {
709
+ type: 'string',
710
+ enum: ['median', 'dust_and_scratches', 'despeckle'],
711
+ description: 'median = replace each pixel with the median of its neighborhood (removes speckle, radius); dust_and_scratches = remove small defects above a threshold (radius + threshold); despeckle = light edge-preserving blur (parameterless).',
712
+ },
713
+ radius: {
714
+ type: 'number',
715
+ description: 'median/dust_and_scratches only: radius in pixels (1-100). PS default 4 (median) / 3 (dust).',
716
+ minimum: 1,
717
+ maximum: 100,
718
+ default: 4,
719
+ },
720
+ threshold: {
721
+ type: 'integer',
722
+ description: 'dust_and_scratches only: tonal threshold (0-255) below which differences are smoothed. PS default 10.',
723
+ minimum: 0,
724
+ maximum: 255,
725
+ default: 10,
726
+ },
727
+ apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
728
+ },
729
+ required: ['mode'],
730
+ };
731
+ const blurAdvSchema = {
732
+ type: 'object',
733
+ properties: {
734
+ mode: {
735
+ type: 'string',
736
+ enum: ['surface_blur', 'box_blur', 'average'],
737
+ description: 'surface_blur = blur while preserving edges above a threshold (skin-smoothing; radius + threshold); box_blur = fast square-kernel blur (radius); average = fill the layer with its single average color (parameterless).',
738
+ },
739
+ radius: {
740
+ type: 'number',
741
+ description: 'surface_blur/box_blur only: radius in pixels (1-500). PS default 15 (surface) / 12 (box).',
742
+ minimum: 1,
743
+ maximum: 500,
744
+ default: 15,
745
+ },
746
+ threshold: {
747
+ type: 'integer',
748
+ description: 'surface_blur only: tonal threshold (0-255) — edges differing by more than this are preserved. PS default 20.',
749
+ minimum: 0,
750
+ maximum: 255,
751
+ default: 20,
752
+ },
753
+ apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
754
+ },
755
+ required: ['mode'],
756
+ };
553
757
  const oilPaintSchema = {
554
758
  type: 'object',
555
759
  properties: {
@@ -663,6 +867,11 @@ const FILTER_TYPES = [
663
867
  'high_pass',
664
868
  'pixelate',
665
869
  'distort',
870
+ 'stylize',
871
+ 'render',
872
+ 'other',
873
+ 'denoise',
874
+ 'blur',
666
875
  'displace',
667
876
  'oil_paint',
668
877
  ];
@@ -678,7 +887,10 @@ const FILTER_INPUT_SCHEMA = {
678
887
  'sharpen=Unsharp Mask(amount, radius, threshold); smart_sharpen(amount, radius, remove_mode, …); ' +
679
888
  'noise=Add Noise(amount, distribution, monochromatic); ' +
680
889
  'reduce_noise(strength, preserve_details, color_noise, …); high_pass(radius); ' +
681
- 'pixelate(mode color_halftone|mosaic, …); distort(mode twirl|ripple|polar|wave, …); ' +
890
+ 'pixelate(mode color_halftone|mosaic|crystallize|pointillize|facet|fragment, …); distort(mode twirl|ripple|polar|wave|pinch|spherize|zigzag, …); ' +
891
+ 'stylize(mode emboss|find_edges|solarize|wind|trace_contour|tiles, …); ' +
892
+ 'render(mode clouds|difference_clouds|fibers, …); ' +
893
+ 'other(mode maximum|minimum|offset, …); denoise(mode median|dust_and_scratches|despeckle, …); blur(mode surface_blur|box_blur|average, …); ' +
682
894
  'displace(map_path, horizontal_scale, vertical_scale, …); oil_paint(stylization, cleanliness, …).',
683
895
  },
684
896
  ...gaussianBlurSchema.properties,
@@ -692,6 +904,11 @@ const FILTER_INPUT_SCHEMA = {
692
904
  ...highPassSchema.properties,
693
905
  ...pixelateSchema.properties,
694
906
  ...distortSchema.properties,
907
+ ...stylizeSchema.properties,
908
+ ...renderSchema.properties,
909
+ ...otherSchema.properties,
910
+ ...denoiseSchema.properties,
911
+ ...blurAdvSchema.properties,
695
912
  ...displaceSchema.properties,
696
913
  ...oilPaintSchema.properties,
697
914
  radius: {
@@ -710,11 +927,11 @@ const FILTER_INPUT_SCHEMA = {
710
927
  },
711
928
  required: ['type'],
712
929
  };
713
- export function createFilterTools(connection, snippetClient) {
930
+ export function createFilterTools(connection, snippetClient, client = new OnnxLandmarkDetectionClient()) {
714
931
  return [
715
932
  {
716
933
  tool: {
717
- name: 'photoshop_apply_filter',
934
+ name: 'ps_apply_filter',
718
935
  description: 'Apply a Photoshop filter to a DUPLICATE of the active layer by default (auto-duplicate-first per Bundle O — the original is preserved, undo by deleting the copy). Pass `apply_to_active_layer: true` to bake into the original. Auto-rasterizes text/smart-object layers. Choose the filter with `type`; each type takes its own parameters (see the `type` field). Covers blur (gaussian_blur/motion_blur/lens_blur/radial_blur), sharpen (`sharpen`=Unsharp Mask, `smart_sharpen`), noise (`noise`=Add Noise, `reduce_noise`), high_pass, pixelate, distort, displace, and oil_paint.',
719
936
  inputSchema: FILTER_INPUT_SCHEMA,
720
937
  outputSchema: FILTER_OUTPUT_SCHEMA,
@@ -724,11 +941,11 @@ export function createFilterTools(connection, snippetClient) {
724
941
  idempotentHint: false,
725
942
  },
726
943
  },
727
- handler: async (args) => applyFilter(connection, snippetClient, args),
944
+ handler: async (args) => applyFilter(connection, snippetClient, client, args),
728
945
  },
729
946
  ];
730
947
  }
731
- async function applyFilter(connection, snippetClient, rawArgs) {
948
+ async function applyFilter(connection, snippetClient, detClient, rawArgs) {
732
949
  const type = rawArgs.type;
733
950
  const { type: _omit, ...rest } = rawArgs;
734
951
  switch (type) {
@@ -739,7 +956,7 @@ async function applyFilter(connection, snippetClient, rawArgs) {
739
956
  case 'lens_blur':
740
957
  return applyLensBlur(connection, snippetClient, rest);
741
958
  case 'radial_blur':
742
- return applyRadialBlur(connection, snippetClient, rest);
959
+ return applyRadialBlur(connection, snippetClient, detClient, rest);
743
960
  case 'sharpen':
744
961
  return applySharpen(connection, snippetClient, rest);
745
962
  case 'smart_sharpen':
@@ -754,6 +971,16 @@ async function applyFilter(connection, snippetClient, rawArgs) {
754
971
  return applyPixelate(connection, snippetClient, rest);
755
972
  case 'distort':
756
973
  return applyDistort(connection, snippetClient, rest);
974
+ case 'stylize':
975
+ return applyStylize(connection, snippetClient, rest);
976
+ case 'render':
977
+ return applyRender(connection, snippetClient, rest);
978
+ case 'other':
979
+ return applyOther(connection, snippetClient, rest);
980
+ case 'denoise':
981
+ return applyDenoise(connection, snippetClient, rest);
982
+ case 'blur':
983
+ return applyBlurAdv(connection, snippetClient, rest);
757
984
  case 'displace':
758
985
  return applyDisplace(connection, snippetClient, rest);
759
986
  case 'oil_paint':
@@ -1126,14 +1353,24 @@ async function applyHighPass(connection, snippetClient, rawArgs) {
1126
1353
  };
1127
1354
  }
1128
1355
  }
1129
- async function applyRadialBlur(connection, snippetClient, rawArgs) {
1356
+ async function applyRadialBlur(connection, snippetClient, detClient, rawArgs) {
1130
1357
  try {
1131
1358
  const args = validateArgs(radialBlurSchema, rawArgs);
1132
1359
  const amount = args.amount ?? 10;
1133
1360
  const method = args.method ?? 'spin';
1134
1361
  const quality = args.quality ?? 'good';
1135
- const centerX = args.center_x ?? 0.5;
1136
- const centerY = args.center_y ?? 0.5;
1362
+ let centerX = args.center_x ?? 0.5;
1363
+ let centerY = args.center_y ?? 0.5;
1364
+ let grounded;
1365
+ if (args.center_placement) {
1366
+ const loc = await resolveGatedPlacement(connection, detClient, args.center_placement, {
1367
+ expect: 'point',
1368
+ label: 'radial-blur center',
1369
+ });
1370
+ centerX = loc.docW > 0 ? Math.min(1, Math.max(0, loc.geom.point.x / loc.docW)) : 0.5;
1371
+ centerY = loc.docH > 0 ? Math.min(1, Math.max(0, loc.geom.point.y / loc.docH)) : 0.5;
1372
+ grounded = { x: Math.round(loc.geom.point.x), y: Math.round(loc.geom.point.y) };
1373
+ }
1137
1374
  const applyToActiveLayer = args.apply_to_active_layer ?? false;
1138
1375
  const script = await snippetClient.build('applyRadialBlur', {
1139
1376
  amount,
@@ -1148,14 +1385,25 @@ async function applyRadialBlur(connection, snippetClient, rawArgs) {
1148
1385
  const target = r.target_was_copy
1149
1386
  ? `new copy "${r.target_layer_name ?? '?'}"`
1150
1387
  : 'active layer (in place)';
1388
+ const groundedNote = grounded
1389
+ ? ` centered on (${grounded.x},${grounded.y}) via placement (gate PASS)`
1390
+ : '';
1391
+ const structured = result;
1392
+ if (grounded) {
1393
+ structured.center_placement = {
1394
+ gate: { pass: true },
1395
+ point: grounded,
1396
+ center: { x: Number(centerX.toFixed(4)), y: Number(centerY.toFixed(4)) },
1397
+ };
1398
+ }
1151
1399
  return {
1152
1400
  content: [
1153
1401
  {
1154
1402
  type: 'text',
1155
- text: `Radial Blur (${method}, amount ${amount}) applied to ${target}.`,
1403
+ text: `Radial Blur (${method}, amount ${amount}) applied to ${target}${groundedNote}.`,
1156
1404
  },
1157
1405
  ],
1158
- structuredContent: result,
1406
+ structuredContent: structured,
1159
1407
  };
1160
1408
  }
1161
1409
  catch (error) {
@@ -1176,10 +1424,10 @@ async function applyPixelate(connection, snippetClient, rawArgs) {
1176
1424
  const mode = args.mode;
1177
1425
  const applyToActiveLayer = args.apply_to_active_layer ?? false;
1178
1426
  const params = { mode, applyToActiveLayer };
1179
- if (mode === 'mosaic') {
1427
+ if (mode === 'mosaic' || mode === 'crystallize' || mode === 'pointillize') {
1180
1428
  params.cellSize = args.cell_size ?? 10;
1181
1429
  }
1182
- else {
1430
+ else if (mode === 'color_halftone') {
1183
1431
  params.maxRadius = args.max_radius ?? 8;
1184
1432
  params.angle1 = args.angle_1 ?? 108;
1185
1433
  params.angle2 = args.angle_2 ?? 162;
@@ -1223,6 +1471,16 @@ async function applyDistort(connection, snippetClient, rawArgs) {
1223
1471
  if (mode === 'twirl') {
1224
1472
  params.angle = args.angle ?? 90;
1225
1473
  }
1474
+ else if (mode === 'pinch') {
1475
+ params.amount = args.amount ?? 50;
1476
+ }
1477
+ else if (mode === 'spherize') {
1478
+ params.amount = args.amount ?? 100;
1479
+ }
1480
+ else if (mode === 'zigzag') {
1481
+ params.amount = args.amount ?? 10;
1482
+ params.ridges = args.ridges ?? 5;
1483
+ }
1226
1484
  else if (mode === 'ripple') {
1227
1485
  params.amount = args.amount ?? 100;
1228
1486
  params.size = args.size ?? 'medium';
@@ -1270,6 +1528,205 @@ async function applyDistort(connection, snippetClient, rawArgs) {
1270
1528
  };
1271
1529
  }
1272
1530
  }
1531
+ async function applyStylize(connection, snippetClient, rawArgs) {
1532
+ try {
1533
+ const args = validateArgs(stylizeSchema, rawArgs);
1534
+ const mode = args.mode;
1535
+ const applyToActiveLayer = args.apply_to_active_layer ?? false;
1536
+ const params = { mode, applyToActiveLayer };
1537
+ if (mode === 'emboss') {
1538
+ params.angle = args.angle ?? 135;
1539
+ params.height = args.height ?? 3;
1540
+ params.amount = args.amount ?? 100;
1541
+ }
1542
+ else if (mode === 'wind') {
1543
+ params.method = args.wind_method ?? 'wind';
1544
+ params.direction = args.wind_direction ?? 'left';
1545
+ }
1546
+ else if (mode === 'trace_contour') {
1547
+ params.level = args.level ?? 128;
1548
+ params.edge = args.edge ?? 'lower';
1549
+ }
1550
+ else if (mode === 'tiles') {
1551
+ params.number = args.number ?? 10;
1552
+ params.offset = args.offset ?? 10;
1553
+ }
1554
+ const script = await snippetClient.build('applyStylize', params);
1555
+ const result = await runScript(connection, script);
1556
+ const r = result;
1557
+ const target = r.target_was_copy
1558
+ ? `new copy "${r.target_layer_name ?? '?'}"`
1559
+ : 'active layer (in place)';
1560
+ return {
1561
+ content: [
1562
+ {
1563
+ type: 'text',
1564
+ text: `Stylize (${mode}) applied to ${target}.`,
1565
+ },
1566
+ ],
1567
+ structuredContent: result,
1568
+ };
1569
+ }
1570
+ catch (error) {
1571
+ return {
1572
+ content: [
1573
+ {
1574
+ type: 'text',
1575
+ text: `Error applying Stylize: ${error instanceof Error ? error.message : String(error)}`,
1576
+ },
1577
+ ],
1578
+ isError: true,
1579
+ };
1580
+ }
1581
+ }
1582
+ async function applyRender(connection, snippetClient, rawArgs) {
1583
+ try {
1584
+ const args = validateArgs(renderSchema, rawArgs);
1585
+ const mode = args.mode;
1586
+ const applyToActiveLayer = args.apply_to_active_layer ?? false;
1587
+ const params = { mode, applyToActiveLayer };
1588
+ if (mode === 'fibers') {
1589
+ params.variance = args.variance ?? 16;
1590
+ params.strength = args.fiber_strength ?? 4;
1591
+ params.seed = args.seed ?? 12345;
1592
+ }
1593
+ const script = await snippetClient.build('applyRender', params);
1594
+ const result = await runScript(connection, script);
1595
+ const r = result;
1596
+ const target = r.target_was_copy
1597
+ ? `new copy "${r.target_layer_name ?? '?'}"`
1598
+ : 'active layer (in place)';
1599
+ return {
1600
+ content: [
1601
+ {
1602
+ type: 'text',
1603
+ text: `Render (${mode}) applied to ${target}.`,
1604
+ },
1605
+ ],
1606
+ structuredContent: result,
1607
+ };
1608
+ }
1609
+ catch (error) {
1610
+ return {
1611
+ content: [
1612
+ {
1613
+ type: 'text',
1614
+ text: `Error applying Render: ${error instanceof Error ? error.message : String(error)}`,
1615
+ },
1616
+ ],
1617
+ isError: true,
1618
+ };
1619
+ }
1620
+ }
1621
+ async function applyOther(connection, snippetClient, rawArgs) {
1622
+ try {
1623
+ const args = validateArgs(otherSchema, rawArgs);
1624
+ const mode = args.mode;
1625
+ const applyToActiveLayer = args.apply_to_active_layer ?? false;
1626
+ const params = { mode, applyToActiveLayer };
1627
+ if (mode === 'maximum' || mode === 'minimum') {
1628
+ params.radius = args.radius ?? 3;
1629
+ params.preserve = args.preserve ?? 'roundness';
1630
+ }
1631
+ else if (mode === 'offset') {
1632
+ params.horizontal = args.horizontal ?? 0;
1633
+ params.vertical = args.vertical ?? 0;
1634
+ }
1635
+ const script = await snippetClient.build('applyOther', params);
1636
+ const result = await runScript(connection, script);
1637
+ const r = result;
1638
+ const target = r.target_was_copy
1639
+ ? `new copy "${r.target_layer_name ?? '?'}"`
1640
+ : 'active layer (in place)';
1641
+ return {
1642
+ content: [{ type: 'text', text: `Other filter (${mode}) applied to ${target}.` }],
1643
+ structuredContent: result,
1644
+ };
1645
+ }
1646
+ catch (error) {
1647
+ return {
1648
+ content: [
1649
+ {
1650
+ type: 'text',
1651
+ text: `Error applying filter: ${error instanceof Error ? error.message : String(error)}`,
1652
+ },
1653
+ ],
1654
+ isError: true,
1655
+ };
1656
+ }
1657
+ }
1658
+ async function applyDenoise(connection, snippetClient, rawArgs) {
1659
+ try {
1660
+ const args = validateArgs(denoiseSchema, rawArgs);
1661
+ const mode = args.mode;
1662
+ const applyToActiveLayer = args.apply_to_active_layer ?? false;
1663
+ const params = { mode, applyToActiveLayer };
1664
+ if (mode === 'median') {
1665
+ params.radius = args.radius ?? 4;
1666
+ }
1667
+ else if (mode === 'dust_and_scratches') {
1668
+ params.radius = args.radius ?? 3;
1669
+ params.threshold = args.threshold ?? 10;
1670
+ }
1671
+ const script = await snippetClient.build('applyDenoise', params);
1672
+ const result = await runScript(connection, script);
1673
+ const r = result;
1674
+ const target = r.target_was_copy
1675
+ ? `new copy "${r.target_layer_name ?? '?'}"`
1676
+ : 'active layer (in place)';
1677
+ return {
1678
+ content: [{ type: 'text', text: `Noise reduction (${mode}) applied to ${target}.` }],
1679
+ structuredContent: result,
1680
+ };
1681
+ }
1682
+ catch (error) {
1683
+ return {
1684
+ content: [
1685
+ {
1686
+ type: 'text',
1687
+ text: `Error applying noise reduction: ${error instanceof Error ? error.message : String(error)}`,
1688
+ },
1689
+ ],
1690
+ isError: true,
1691
+ };
1692
+ }
1693
+ }
1694
+ async function applyBlurAdv(connection, snippetClient, rawArgs) {
1695
+ try {
1696
+ const args = validateArgs(blurAdvSchema, rawArgs);
1697
+ const mode = args.mode;
1698
+ const applyToActiveLayer = args.apply_to_active_layer ?? false;
1699
+ const params = { mode, applyToActiveLayer };
1700
+ if (mode === 'surface_blur') {
1701
+ params.radius = args.radius ?? 15;
1702
+ params.threshold = args.threshold ?? 20;
1703
+ }
1704
+ else if (mode === 'box_blur') {
1705
+ params.radius = args.radius ?? 12;
1706
+ }
1707
+ const script = await snippetClient.build('applyBlurAdv', params);
1708
+ const result = await runScript(connection, script);
1709
+ const r = result;
1710
+ const target = r.target_was_copy
1711
+ ? `new copy "${r.target_layer_name ?? '?'}"`
1712
+ : 'active layer (in place)';
1713
+ return {
1714
+ content: [{ type: 'text', text: `Blur (${mode}) applied to ${target}.` }],
1715
+ structuredContent: result,
1716
+ };
1717
+ }
1718
+ catch (error) {
1719
+ return {
1720
+ content: [
1721
+ {
1722
+ type: 'text',
1723
+ text: `Error applying blur: ${error instanceof Error ? error.message : String(error)}`,
1724
+ },
1725
+ ],
1726
+ isError: true,
1727
+ };
1728
+ }
1729
+ }
1273
1730
  async function applyOilPaint(connection, snippetClient, rawArgs) {
1274
1731
  try {
1275
1732
  const args = validateArgs(oilPaintSchema, rawArgs);