editmamei 0.17.4 → 0.18.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.
- package/NOTICES.md +43 -0
- package/README.md +91 -8
- package/dist/api/extendscript/_helpers.js +8 -0
- package/dist/bin/editmamei-core-darwin-arm64 +0 -0
- package/dist/bin/editmamei-core-darwin-x64 +0 -0
- package/dist/bin/editmamei-core-win-x64.exe +0 -0
- package/dist/cli/clients/json-config.js +1 -1
- package/dist/core/server.js +92 -24
- package/dist/core/tool-groups.js +178 -0
- package/dist/core/tool-tiers.js +34 -48
- package/dist/delivery/bundle.js +8 -0
- package/dist/delivery/config.js +23 -0
- package/dist/delivery/provision.js +30 -2
- package/dist/delivery/store.js +40 -1
- package/dist/detection/detect-active-doc.js +84 -0
- package/dist/detection/detection-client.js +62 -0
- package/dist/detection/face-contour-geometry.js +137 -0
- package/dist/detection/face-detector.js +34 -0
- package/dist/detection/face-feature-geometry.js +180 -0
- package/dist/detection/geometry.js +35 -0
- package/dist/detection/landmark-detection-client.js +38 -0
- package/dist/detection/landmark-detector.js +63 -0
- package/dist/detection/landmark-spec.js +29 -0
- package/dist/detection/mesh-face.js +36 -0
- package/dist/detection/object-detector.js +135 -0
- package/dist/detection/runtime.js +46 -0
- package/dist/license/env-activation.js +46 -7
- package/dist/models/dfine-s-coco-quant.onnx +0 -0
- package/dist/models/ultraface-rfb-320.onnx +0 -0
- package/dist/modules/ce/index.js +23 -5
- package/dist/perception/facets.js +148 -0
- package/dist/perception/region-precompute.js +122 -0
- package/dist/perception/region-scorer.js +151 -0
- package/dist/perception/scene-model.js +156 -0
- package/dist/perception/select-recipes.js +493 -0
- package/dist/perception/sky-ground-flood.js +234 -0
- package/dist/perception/sky-mask-transfer.js +77 -0
- package/dist/platform/macos-executor.js +0 -1
- package/dist/platform/windows-executor.js +6 -1
- package/dist/preview/coordinate-grid.js +250 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/events.js +2 -1
- package/dist/telemetry/sanitize.js +15 -1
- package/dist/tools/adjustment-tools.js +46 -51
- package/dist/tools/detection-tools.js +209 -0
- package/dist/tools/document-tools.js +41 -30
- package/dist/tools/filter-tools.js +616 -101
- package/dist/tools/guide-tools.js +186 -0
- package/dist/tools/history-tools.js +1 -39
- package/dist/tools/image-tools.js +88 -0
- package/dist/tools/inspect-tools.js +86 -0
- package/dist/tools/layer-properties-tools.js +195 -106
- package/dist/tools/layer-tools.js +55 -14
- package/dist/tools/layer-transform-tools.js +236 -73
- package/dist/tools/metadata-tools.js +2 -32
- package/dist/tools/object-tools.js +193 -0
- package/dist/tools/overview-tools.js +69 -43
- package/dist/tools/path-tools.js +274 -0
- package/dist/tools/portrait-tools.js +146 -0
- package/dist/tools/preview-tools.js +78 -3
- package/dist/tools/retouch-tools.js +45 -33
- package/dist/tools/scene-tools.js +401 -0
- package/dist/tools/selection-tools.js +766 -228
- package/dist/tools/text-on-object-tools.js +225 -0
- package/dist/tools/text-tools.js +47 -56
- package/dist/tools/transform-canvas-tools.js +138 -0
- package/dist/tools/vector-mask-tools.js +116 -0
- package/dist/version.js +1 -1
- package/package.json +4 -2
|
@@ -352,6 +352,294 @@ const highPassSchema = {
|
|
|
352
352
|
},
|
|
353
353
|
required: ['radius'],
|
|
354
354
|
};
|
|
355
|
+
const radialBlurSchema = {
|
|
356
|
+
type: 'object',
|
|
357
|
+
properties: {
|
|
358
|
+
amount: {
|
|
359
|
+
type: 'integer',
|
|
360
|
+
description: 'Blur strength (1-100). PS default 10.',
|
|
361
|
+
minimum: 1,
|
|
362
|
+
maximum: 100,
|
|
363
|
+
default: 10,
|
|
364
|
+
},
|
|
365
|
+
method: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
description: 'spin = rotational blur around the center (turntable motion); zoom = radial streaks toward/from the center (the classic "god rays" / speed-zoom look).',
|
|
368
|
+
enum: ['spin', 'zoom'],
|
|
369
|
+
default: 'spin',
|
|
370
|
+
},
|
|
371
|
+
quality: {
|
|
372
|
+
type: 'string',
|
|
373
|
+
description: 'Render quality. best is smoothest but slowest.',
|
|
374
|
+
enum: ['draft', 'good', 'best'],
|
|
375
|
+
default: 'good',
|
|
376
|
+
},
|
|
377
|
+
center_x: {
|
|
378
|
+
type: 'number',
|
|
379
|
+
description: 'Horizontal blur center, normalized 0-1 (0.5 = middle). Resolution-independent.',
|
|
380
|
+
minimum: 0,
|
|
381
|
+
maximum: 1,
|
|
382
|
+
default: 0.5,
|
|
383
|
+
},
|
|
384
|
+
center_y: {
|
|
385
|
+
type: 'number',
|
|
386
|
+
description: 'Vertical blur center, normalized 0-1 (0.5 = middle). Resolution-independent.',
|
|
387
|
+
minimum: 0,
|
|
388
|
+
maximum: 1,
|
|
389
|
+
default: 0.5,
|
|
390
|
+
},
|
|
391
|
+
apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
const pixelateSchema = {
|
|
395
|
+
type: 'object',
|
|
396
|
+
properties: {
|
|
397
|
+
mode: {
|
|
398
|
+
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).',
|
|
401
|
+
},
|
|
402
|
+
max_radius: {
|
|
403
|
+
type: 'integer',
|
|
404
|
+
description: 'color_halftone only: maximum dot radius in pixels (4-127). PS default 8.',
|
|
405
|
+
minimum: 4,
|
|
406
|
+
maximum: 127,
|
|
407
|
+
default: 8,
|
|
408
|
+
},
|
|
409
|
+
angle_1: {
|
|
410
|
+
type: 'integer',
|
|
411
|
+
description: 'color_halftone only: screen angle for channel 1 in degrees. PS default 108.',
|
|
412
|
+
minimum: -360,
|
|
413
|
+
maximum: 360,
|
|
414
|
+
default: 108,
|
|
415
|
+
},
|
|
416
|
+
angle_2: {
|
|
417
|
+
type: 'integer',
|
|
418
|
+
description: 'color_halftone only: screen angle for channel 2. PS default 162.',
|
|
419
|
+
minimum: -360,
|
|
420
|
+
maximum: 360,
|
|
421
|
+
default: 162,
|
|
422
|
+
},
|
|
423
|
+
angle_3: {
|
|
424
|
+
type: 'integer',
|
|
425
|
+
description: 'color_halftone only: screen angle for channel 3. PS default 90.',
|
|
426
|
+
minimum: -360,
|
|
427
|
+
maximum: 360,
|
|
428
|
+
default: 90,
|
|
429
|
+
},
|
|
430
|
+
angle_4: {
|
|
431
|
+
type: 'integer',
|
|
432
|
+
description: 'color_halftone only: screen angle for channel 4. PS default 45.',
|
|
433
|
+
minimum: -360,
|
|
434
|
+
maximum: 360,
|
|
435
|
+
default: 45,
|
|
436
|
+
},
|
|
437
|
+
cell_size: {
|
|
438
|
+
type: 'integer',
|
|
439
|
+
description: 'mosaic only: cell size in pixels (2-300). PS default 10.',
|
|
440
|
+
minimum: 2,
|
|
441
|
+
maximum: 300,
|
|
442
|
+
default: 10,
|
|
443
|
+
},
|
|
444
|
+
apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
|
|
445
|
+
},
|
|
446
|
+
required: ['mode'],
|
|
447
|
+
};
|
|
448
|
+
const distortSchema = {
|
|
449
|
+
type: 'object',
|
|
450
|
+
properties: {
|
|
451
|
+
mode: {
|
|
452
|
+
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.',
|
|
455
|
+
},
|
|
456
|
+
angle: {
|
|
457
|
+
type: 'integer',
|
|
458
|
+
description: 'twirl only: swirl angle in degrees (-999 to 999; positive = clockwise).',
|
|
459
|
+
minimum: -999,
|
|
460
|
+
maximum: 999,
|
|
461
|
+
default: 90,
|
|
462
|
+
},
|
|
463
|
+
amount: {
|
|
464
|
+
type: 'integer',
|
|
465
|
+
description: 'ripple only: ripple amount (-999 to 999).',
|
|
466
|
+
minimum: -999,
|
|
467
|
+
maximum: 999,
|
|
468
|
+
default: 100,
|
|
469
|
+
},
|
|
470
|
+
size: {
|
|
471
|
+
type: 'string',
|
|
472
|
+
enum: ['small', 'medium', 'large'],
|
|
473
|
+
description: 'ripple only: ripple wavelength size.',
|
|
474
|
+
default: 'medium',
|
|
475
|
+
},
|
|
476
|
+
conversion: {
|
|
477
|
+
type: 'string',
|
|
478
|
+
enum: ['rect_to_polar', 'polar_to_rect'],
|
|
479
|
+
description: 'polar only: rect_to_polar wraps the image into a circle; polar_to_rect unwraps it.',
|
|
480
|
+
default: 'rect_to_polar',
|
|
481
|
+
},
|
|
482
|
+
wave_type: {
|
|
483
|
+
type: 'string',
|
|
484
|
+
enum: ['sine', 'triangle', 'square'],
|
|
485
|
+
description: 'wave only: waveform shape.',
|
|
486
|
+
default: 'sine',
|
|
487
|
+
},
|
|
488
|
+
generators: {
|
|
489
|
+
type: 'integer',
|
|
490
|
+
description: 'wave only: number of wave generators (1-999).',
|
|
491
|
+
minimum: 1,
|
|
492
|
+
maximum: 999,
|
|
493
|
+
default: 5,
|
|
494
|
+
},
|
|
495
|
+
wavelength_min: {
|
|
496
|
+
type: 'integer',
|
|
497
|
+
description: 'wave only: minimum wavelength (1-998; must be <= wavelength_max).',
|
|
498
|
+
minimum: 1,
|
|
499
|
+
maximum: 998,
|
|
500
|
+
default: 10,
|
|
501
|
+
},
|
|
502
|
+
wavelength_max: {
|
|
503
|
+
type: 'integer',
|
|
504
|
+
description: 'wave only: maximum wavelength (1-999).',
|
|
505
|
+
minimum: 1,
|
|
506
|
+
maximum: 999,
|
|
507
|
+
default: 120,
|
|
508
|
+
},
|
|
509
|
+
amplitude_min: {
|
|
510
|
+
type: 'integer',
|
|
511
|
+
description: 'wave only: minimum amplitude (1-998; must be <= amplitude_max).',
|
|
512
|
+
minimum: 1,
|
|
513
|
+
maximum: 998,
|
|
514
|
+
default: 5,
|
|
515
|
+
},
|
|
516
|
+
amplitude_max: {
|
|
517
|
+
type: 'integer',
|
|
518
|
+
description: 'wave only: maximum amplitude (1-999).',
|
|
519
|
+
minimum: 1,
|
|
520
|
+
maximum: 999,
|
|
521
|
+
default: 35,
|
|
522
|
+
},
|
|
523
|
+
scale_horizontal: {
|
|
524
|
+
type: 'integer',
|
|
525
|
+
description: 'wave only: horizontal scale percent (1-100).',
|
|
526
|
+
minimum: 1,
|
|
527
|
+
maximum: 100,
|
|
528
|
+
default: 100,
|
|
529
|
+
},
|
|
530
|
+
scale_vertical: {
|
|
531
|
+
type: 'integer',
|
|
532
|
+
description: 'wave only: vertical scale percent (1-100).',
|
|
533
|
+
minimum: 1,
|
|
534
|
+
maximum: 100,
|
|
535
|
+
default: 100,
|
|
536
|
+
},
|
|
537
|
+
undefined_areas: {
|
|
538
|
+
type: 'string',
|
|
539
|
+
enum: ['repeat_edge', 'wrap_around'],
|
|
540
|
+
description: 'wave only: how to fill areas pushed outside the layer.',
|
|
541
|
+
default: 'repeat_edge',
|
|
542
|
+
},
|
|
543
|
+
random_seed: {
|
|
544
|
+
type: 'integer',
|
|
545
|
+
description: 'wave only: random seed for the wave pattern (change for a different pattern).',
|
|
546
|
+
minimum: 0,
|
|
547
|
+
default: 12345,
|
|
548
|
+
},
|
|
549
|
+
apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
|
|
550
|
+
},
|
|
551
|
+
required: ['mode'],
|
|
552
|
+
};
|
|
553
|
+
const oilPaintSchema = {
|
|
554
|
+
type: 'object',
|
|
555
|
+
properties: {
|
|
556
|
+
stylization: {
|
|
557
|
+
type: 'number',
|
|
558
|
+
description: 'Brush-stroke stylization (0.1-10).',
|
|
559
|
+
minimum: 0.1,
|
|
560
|
+
maximum: 10,
|
|
561
|
+
default: 4,
|
|
562
|
+
},
|
|
563
|
+
cleanliness: {
|
|
564
|
+
type: 'number',
|
|
565
|
+
description: 'Stroke cleanliness / smoothness (0-10).',
|
|
566
|
+
minimum: 0,
|
|
567
|
+
maximum: 10,
|
|
568
|
+
default: 2.3,
|
|
569
|
+
},
|
|
570
|
+
brush_scale: {
|
|
571
|
+
type: 'number',
|
|
572
|
+
description: 'Brush scale (0.1-2).',
|
|
573
|
+
minimum: 0.1,
|
|
574
|
+
maximum: 2,
|
|
575
|
+
default: 0.8,
|
|
576
|
+
},
|
|
577
|
+
bristle_detail: {
|
|
578
|
+
type: 'number',
|
|
579
|
+
description: 'Bristle detail (0-10).',
|
|
580
|
+
minimum: 0,
|
|
581
|
+
maximum: 10,
|
|
582
|
+
default: 10,
|
|
583
|
+
},
|
|
584
|
+
light_direction: {
|
|
585
|
+
type: 'integer',
|
|
586
|
+
description: 'Lighting angle in degrees (-180 to 180).',
|
|
587
|
+
minimum: -180,
|
|
588
|
+
maximum: 180,
|
|
589
|
+
default: -60,
|
|
590
|
+
},
|
|
591
|
+
shine: {
|
|
592
|
+
type: 'number',
|
|
593
|
+
description: 'Specular shine (0-10).',
|
|
594
|
+
minimum: 0,
|
|
595
|
+
maximum: 10,
|
|
596
|
+
default: 1.3,
|
|
597
|
+
},
|
|
598
|
+
lighting_on: {
|
|
599
|
+
type: 'boolean',
|
|
600
|
+
description: 'Whether the lighting/shine relief is applied.',
|
|
601
|
+
default: true,
|
|
602
|
+
},
|
|
603
|
+
apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
|
|
604
|
+
},
|
|
605
|
+
};
|
|
606
|
+
const displaceSchema = {
|
|
607
|
+
type: 'object',
|
|
608
|
+
properties: {
|
|
609
|
+
map_path: {
|
|
610
|
+
type: 'string',
|
|
611
|
+
description: "Absolute path to the displacement-map .psd file (required). The map's brightness drives the per-pixel warp — mid-gray = no shift, lighter/darker push in opposite directions.",
|
|
612
|
+
},
|
|
613
|
+
horizontal_scale: {
|
|
614
|
+
type: 'integer',
|
|
615
|
+
description: 'Horizontal displacement scale (-999 to 999). PS default 10.',
|
|
616
|
+
minimum: -999,
|
|
617
|
+
maximum: 999,
|
|
618
|
+
default: 10,
|
|
619
|
+
},
|
|
620
|
+
vertical_scale: {
|
|
621
|
+
type: 'integer',
|
|
622
|
+
description: 'Vertical displacement scale (-999 to 999). PS default 10.',
|
|
623
|
+
minimum: -999,
|
|
624
|
+
maximum: 999,
|
|
625
|
+
default: 10,
|
|
626
|
+
},
|
|
627
|
+
displacement_map: {
|
|
628
|
+
type: 'string',
|
|
629
|
+
enum: ['stretch_to_fit', 'tile'],
|
|
630
|
+
description: 'How the map fits the layer: stretch_to_fit (resize the map) or tile (repeat it).',
|
|
631
|
+
default: 'stretch_to_fit',
|
|
632
|
+
},
|
|
633
|
+
undefined_areas: {
|
|
634
|
+
type: 'string',
|
|
635
|
+
enum: ['repeat_edge', 'wrap_around'],
|
|
636
|
+
description: 'How to fill areas pushed outside the layer.',
|
|
637
|
+
default: 'repeat_edge',
|
|
638
|
+
},
|
|
639
|
+
apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
|
|
640
|
+
},
|
|
641
|
+
required: ['map_path'],
|
|
642
|
+
};
|
|
355
643
|
const FILTER_OUTPUT_SCHEMA = {
|
|
356
644
|
type: 'object',
|
|
357
645
|
properties: {
|
|
@@ -363,122 +651,125 @@ const FILTER_OUTPUT_SCHEMA = {
|
|
|
363
651
|
context: { type: 'object' },
|
|
364
652
|
},
|
|
365
653
|
};
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
tool: {
|
|
398
|
-
name: 'photoshop_apply_noise',
|
|
399
|
-
description: 'Apply Add Noise filter to a DUPLICATE of the active layer by default (auto-duplicate-first per Bundle O 2026-06-01). The original is preserved. Pass `apply_to_active_layer: true` to bake into the original. Auto-rasterizes text/smart-object layers.',
|
|
400
|
-
inputSchema: noiseSchema,
|
|
401
|
-
outputSchema: FILTER_OUTPUT_SCHEMA,
|
|
402
|
-
annotations: {
|
|
403
|
-
title: 'Add Noise',
|
|
404
|
-
destructiveHint: true,
|
|
405
|
-
idempotentHint: false,
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
handler: async (args) => applyNoise(connection, snippetClient, args),
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
tool: {
|
|
412
|
-
name: 'photoshop_apply_motion_blur',
|
|
413
|
-
description: 'Apply Motion Blur to a DUPLICATE of the active layer by default (auto-duplicate-first per Bundle O 2026-06-01). The original is preserved. Pass `apply_to_active_layer: true` to bake into the original. Auto-rasterizes text/smart-object layers.',
|
|
414
|
-
inputSchema: motionBlurSchema,
|
|
415
|
-
outputSchema: FILTER_OUTPUT_SCHEMA,
|
|
416
|
-
annotations: {
|
|
417
|
-
title: 'Motion Blur',
|
|
418
|
-
destructiveHint: true,
|
|
419
|
-
idempotentHint: false,
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
handler: async (args) => applyMotionBlur(connection, snippetClient, args),
|
|
654
|
+
const FILTER_TYPES = [
|
|
655
|
+
'gaussian_blur',
|
|
656
|
+
'motion_blur',
|
|
657
|
+
'lens_blur',
|
|
658
|
+
'radial_blur',
|
|
659
|
+
'sharpen',
|
|
660
|
+
'smart_sharpen',
|
|
661
|
+
'noise',
|
|
662
|
+
'reduce_noise',
|
|
663
|
+
'high_pass',
|
|
664
|
+
'pixelate',
|
|
665
|
+
'distort',
|
|
666
|
+
'displace',
|
|
667
|
+
'oil_paint',
|
|
668
|
+
];
|
|
669
|
+
const FILTER_INPUT_SCHEMA = {
|
|
670
|
+
type: 'object',
|
|
671
|
+
properties: {
|
|
672
|
+
type: {
|
|
673
|
+
type: 'string',
|
|
674
|
+
enum: [...FILTER_TYPES],
|
|
675
|
+
description: 'Which filter to apply. Each type uses its own parameters: ' +
|
|
676
|
+
'gaussian_blur(radius); motion_blur(angle, radius); lens_blur(radius, iris_shape, …); ' +
|
|
677
|
+
'radial_blur(amount, method spin|zoom, quality, center_x, center_y); ' +
|
|
678
|
+
'sharpen=Unsharp Mask(amount, radius, threshold); smart_sharpen(amount, radius, remove_mode, …); ' +
|
|
679
|
+
'noise=Add Noise(amount, distribution, monochromatic); ' +
|
|
680
|
+
'reduce_noise(strength, preserve_details, color_noise, …); high_pass(radius); ' +
|
|
681
|
+
'pixelate(mode color_halftone|mosaic, …); distort(mode twirl|ripple|polar|wave, …); ' +
|
|
682
|
+
'displace(map_path, horizontal_scale, vertical_scale, …); oil_paint(stylization, cleanliness, …).',
|
|
423
683
|
},
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
684
|
+
...gaussianBlurSchema.properties,
|
|
685
|
+
...motionBlurSchema.properties,
|
|
686
|
+
...lensBlurSchema.properties,
|
|
687
|
+
...radialBlurSchema.properties,
|
|
688
|
+
...sharpenSchema.properties,
|
|
689
|
+
...smartSharpenSchema.properties,
|
|
690
|
+
...noiseSchema.properties,
|
|
691
|
+
...reduceNoiseSchema.properties,
|
|
692
|
+
...highPassSchema.properties,
|
|
693
|
+
...pixelateSchema.properties,
|
|
694
|
+
...distortSchema.properties,
|
|
695
|
+
...displaceSchema.properties,
|
|
696
|
+
...oilPaintSchema.properties,
|
|
697
|
+
radius: {
|
|
698
|
+
type: 'number',
|
|
699
|
+
description: 'Radius/distance in px. Range depends on type — gaussian_blur 0.1–250, motion_blur 1–999, lens_blur 0–100, smart_sharpen 0.1–64, high_pass 0.1–1000.',
|
|
437
700
|
},
|
|
438
|
-
{
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
description: 'Apply Smart Sharpen (modern detail-aware sharpening) to a DUPLICATE of the active layer by default. Prefer this over Unsharp Mask for photo workflows — it has noise-aware sharpening, three blur-removal models (Gaussian/Lens/Motion), and per-tonal-region fade (Shadows/Highlights tabs) to keep shadow noise from amplifying. Default remove_mode is lensBlur which gives the best results for general photo sharpening. Auto-rasterizes text/smart-object layers.',
|
|
442
|
-
inputSchema: smartSharpenSchema,
|
|
443
|
-
outputSchema: FILTER_OUTPUT_SCHEMA,
|
|
444
|
-
annotations: {
|
|
445
|
-
title: 'Smart Sharpen',
|
|
446
|
-
destructiveHint: true,
|
|
447
|
-
idempotentHint: false,
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
handler: async (args) => applySmartSharpen(connection, snippetClient, args),
|
|
701
|
+
amount: {
|
|
702
|
+
type: 'number',
|
|
703
|
+
description: 'Filter strength. Range depends on type — sharpen 1–500, noise 0.1–400, radial_blur 1–100, distort/ripple −999–999.',
|
|
451
704
|
},
|
|
452
|
-
{
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
description: "Apply Reduce Noise to a DUPLICATE of the active layer by default. Photoshop's general-purpose noise reduction for high-ISO / low-light photos. Handles luminance noise (strength) and color noise separately. Optional per-channel mode (per_channel=true) lets you target one RGB channel more aggressively — typically the blue channel in low-light shots. Optional JPEG-artifact removal cleans up 8x8 block boundaries from compressed sources. Auto-rasterizes text/smart-object layers.",
|
|
456
|
-
inputSchema: reduceNoiseSchema,
|
|
457
|
-
outputSchema: FILTER_OUTPUT_SCHEMA,
|
|
458
|
-
annotations: {
|
|
459
|
-
title: 'Reduce Noise',
|
|
460
|
-
destructiveHint: true,
|
|
461
|
-
idempotentHint: false,
|
|
462
|
-
},
|
|
463
|
-
},
|
|
464
|
-
handler: async (args) => applyReduceNoise(connection, snippetClient, args),
|
|
705
|
+
angle: {
|
|
706
|
+
type: 'number',
|
|
707
|
+
description: 'Angle in degrees. Used by motion_blur (−360–360) and distort/twirl (−999–999).',
|
|
465
708
|
},
|
|
709
|
+
apply_to_active_layer: APPLY_TO_ACTIVE_LAYER_PROP,
|
|
710
|
+
},
|
|
711
|
+
required: ['type'],
|
|
712
|
+
};
|
|
713
|
+
export function createFilterTools(connection, snippetClient) {
|
|
714
|
+
return [
|
|
466
715
|
{
|
|
467
716
|
tool: {
|
|
468
|
-
name: '
|
|
469
|
-
description:
|
|
470
|
-
inputSchema:
|
|
717
|
+
name: 'photoshop_apply_filter',
|
|
718
|
+
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
|
+
inputSchema: FILTER_INPUT_SCHEMA,
|
|
471
720
|
outputSchema: FILTER_OUTPUT_SCHEMA,
|
|
472
721
|
annotations: {
|
|
473
|
-
title: '
|
|
722
|
+
title: 'Apply Filter',
|
|
474
723
|
destructiveHint: true,
|
|
475
724
|
idempotentHint: false,
|
|
476
725
|
},
|
|
477
726
|
},
|
|
478
|
-
handler: async (args) =>
|
|
727
|
+
handler: async (args) => applyFilter(connection, snippetClient, args),
|
|
479
728
|
},
|
|
480
729
|
];
|
|
481
730
|
}
|
|
731
|
+
async function applyFilter(connection, snippetClient, rawArgs) {
|
|
732
|
+
const type = rawArgs.type;
|
|
733
|
+
const { type: _omit, ...rest } = rawArgs;
|
|
734
|
+
switch (type) {
|
|
735
|
+
case 'gaussian_blur':
|
|
736
|
+
return applyGaussianBlur(connection, snippetClient, rest);
|
|
737
|
+
case 'motion_blur':
|
|
738
|
+
return applyMotionBlur(connection, snippetClient, rest);
|
|
739
|
+
case 'lens_blur':
|
|
740
|
+
return applyLensBlur(connection, snippetClient, rest);
|
|
741
|
+
case 'radial_blur':
|
|
742
|
+
return applyRadialBlur(connection, snippetClient, rest);
|
|
743
|
+
case 'sharpen':
|
|
744
|
+
return applySharpen(connection, snippetClient, rest);
|
|
745
|
+
case 'smart_sharpen':
|
|
746
|
+
return applySmartSharpen(connection, snippetClient, rest);
|
|
747
|
+
case 'noise':
|
|
748
|
+
return applyNoise(connection, snippetClient, rest);
|
|
749
|
+
case 'reduce_noise':
|
|
750
|
+
return applyReduceNoise(connection, snippetClient, rest);
|
|
751
|
+
case 'high_pass':
|
|
752
|
+
return applyHighPass(connection, snippetClient, rest);
|
|
753
|
+
case 'pixelate':
|
|
754
|
+
return applyPixelate(connection, snippetClient, rest);
|
|
755
|
+
case 'distort':
|
|
756
|
+
return applyDistort(connection, snippetClient, rest);
|
|
757
|
+
case 'displace':
|
|
758
|
+
return applyDisplace(connection, snippetClient, rest);
|
|
759
|
+
case 'oil_paint':
|
|
760
|
+
return applyOilPaint(connection, snippetClient, rest);
|
|
761
|
+
default:
|
|
762
|
+
return {
|
|
763
|
+
content: [
|
|
764
|
+
{
|
|
765
|
+
type: 'text',
|
|
766
|
+
text: `Error: unknown filter type "${String(type)}". Allowed: ${FILTER_TYPES.join(', ')}.`,
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
isError: true,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
}
|
|
482
773
|
async function applyGaussianBlur(connection, snippetClient, rawArgs) {
|
|
483
774
|
try {
|
|
484
775
|
const args = validateArgs(gaussianBlurSchema, rawArgs);
|
|
@@ -835,3 +1126,227 @@ async function applyHighPass(connection, snippetClient, rawArgs) {
|
|
|
835
1126
|
};
|
|
836
1127
|
}
|
|
837
1128
|
}
|
|
1129
|
+
async function applyRadialBlur(connection, snippetClient, rawArgs) {
|
|
1130
|
+
try {
|
|
1131
|
+
const args = validateArgs(radialBlurSchema, rawArgs);
|
|
1132
|
+
const amount = args.amount ?? 10;
|
|
1133
|
+
const method = args.method ?? 'spin';
|
|
1134
|
+
const quality = args.quality ?? 'good';
|
|
1135
|
+
const centerX = args.center_x ?? 0.5;
|
|
1136
|
+
const centerY = args.center_y ?? 0.5;
|
|
1137
|
+
const applyToActiveLayer = args.apply_to_active_layer ?? false;
|
|
1138
|
+
const script = await snippetClient.build('applyRadialBlur', {
|
|
1139
|
+
amount,
|
|
1140
|
+
method,
|
|
1141
|
+
quality,
|
|
1142
|
+
centerX,
|
|
1143
|
+
centerY,
|
|
1144
|
+
applyToActiveLayer,
|
|
1145
|
+
});
|
|
1146
|
+
const result = await runScript(connection, script);
|
|
1147
|
+
const r = result;
|
|
1148
|
+
const target = r.target_was_copy
|
|
1149
|
+
? `new copy "${r.target_layer_name ?? '?'}"`
|
|
1150
|
+
: 'active layer (in place)';
|
|
1151
|
+
return {
|
|
1152
|
+
content: [
|
|
1153
|
+
{
|
|
1154
|
+
type: 'text',
|
|
1155
|
+
text: `Radial Blur (${method}, amount ${amount}) applied to ${target}.`,
|
|
1156
|
+
},
|
|
1157
|
+
],
|
|
1158
|
+
structuredContent: result,
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
catch (error) {
|
|
1162
|
+
return {
|
|
1163
|
+
content: [
|
|
1164
|
+
{
|
|
1165
|
+
type: 'text',
|
|
1166
|
+
text: `Error applying Radial Blur: ${error instanceof Error ? error.message : String(error)}`,
|
|
1167
|
+
},
|
|
1168
|
+
],
|
|
1169
|
+
isError: true,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
async function applyPixelate(connection, snippetClient, rawArgs) {
|
|
1174
|
+
try {
|
|
1175
|
+
const args = validateArgs(pixelateSchema, rawArgs);
|
|
1176
|
+
const mode = args.mode;
|
|
1177
|
+
const applyToActiveLayer = args.apply_to_active_layer ?? false;
|
|
1178
|
+
const params = { mode, applyToActiveLayer };
|
|
1179
|
+
if (mode === 'mosaic') {
|
|
1180
|
+
params.cellSize = args.cell_size ?? 10;
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
params.maxRadius = args.max_radius ?? 8;
|
|
1184
|
+
params.angle1 = args.angle_1 ?? 108;
|
|
1185
|
+
params.angle2 = args.angle_2 ?? 162;
|
|
1186
|
+
params.angle3 = args.angle_3 ?? 90;
|
|
1187
|
+
params.angle4 = args.angle_4 ?? 45;
|
|
1188
|
+
}
|
|
1189
|
+
const script = await snippetClient.build('applyPixelate', params);
|
|
1190
|
+
const result = await runScript(connection, script);
|
|
1191
|
+
const r = result;
|
|
1192
|
+
const target = r.target_was_copy
|
|
1193
|
+
? `new copy "${r.target_layer_name ?? '?'}"`
|
|
1194
|
+
: 'active layer (in place)';
|
|
1195
|
+
return {
|
|
1196
|
+
content: [
|
|
1197
|
+
{
|
|
1198
|
+
type: 'text',
|
|
1199
|
+
text: `Pixelate (${mode}) applied to ${target}.`,
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
structuredContent: result,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
catch (error) {
|
|
1206
|
+
return {
|
|
1207
|
+
content: [
|
|
1208
|
+
{
|
|
1209
|
+
type: 'text',
|
|
1210
|
+
text: `Error applying Pixelate: ${error instanceof Error ? error.message : String(error)}`,
|
|
1211
|
+
},
|
|
1212
|
+
],
|
|
1213
|
+
isError: true,
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
async function applyDistort(connection, snippetClient, rawArgs) {
|
|
1218
|
+
try {
|
|
1219
|
+
const args = validateArgs(distortSchema, rawArgs);
|
|
1220
|
+
const mode = args.mode;
|
|
1221
|
+
const applyToActiveLayer = args.apply_to_active_layer ?? false;
|
|
1222
|
+
const params = { mode, applyToActiveLayer };
|
|
1223
|
+
if (mode === 'twirl') {
|
|
1224
|
+
params.angle = args.angle ?? 90;
|
|
1225
|
+
}
|
|
1226
|
+
else if (mode === 'ripple') {
|
|
1227
|
+
params.amount = args.amount ?? 100;
|
|
1228
|
+
params.size = args.size ?? 'medium';
|
|
1229
|
+
}
|
|
1230
|
+
else if (mode === 'polar') {
|
|
1231
|
+
params.conversion = args.conversion ?? 'rect_to_polar';
|
|
1232
|
+
}
|
|
1233
|
+
else if (mode === 'wave') {
|
|
1234
|
+
params.waveType = args.wave_type ?? 'sine';
|
|
1235
|
+
params.generators = args.generators ?? 5;
|
|
1236
|
+
params.wavelengthMin = args.wavelength_min ?? 10;
|
|
1237
|
+
params.wavelengthMax = args.wavelength_max ?? 120;
|
|
1238
|
+
params.amplitudeMin = args.amplitude_min ?? 5;
|
|
1239
|
+
params.amplitudeMax = args.amplitude_max ?? 35;
|
|
1240
|
+
params.scaleHorizontal = args.scale_horizontal ?? 100;
|
|
1241
|
+
params.scaleVertical = args.scale_vertical ?? 100;
|
|
1242
|
+
params.undefinedAreas = args.undefined_areas ?? 'repeat_edge';
|
|
1243
|
+
params.randomSeed = args.random_seed ?? 12345;
|
|
1244
|
+
}
|
|
1245
|
+
const script = await snippetClient.build('applyDistort', params);
|
|
1246
|
+
const result = await runScript(connection, script);
|
|
1247
|
+
const r = result;
|
|
1248
|
+
const target = r.target_was_copy
|
|
1249
|
+
? `new copy "${r.target_layer_name ?? '?'}"`
|
|
1250
|
+
: 'active layer (in place)';
|
|
1251
|
+
return {
|
|
1252
|
+
content: [
|
|
1253
|
+
{
|
|
1254
|
+
type: 'text',
|
|
1255
|
+
text: `Distort (${mode}) applied to ${target}.`,
|
|
1256
|
+
},
|
|
1257
|
+
],
|
|
1258
|
+
structuredContent: result,
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
return {
|
|
1263
|
+
content: [
|
|
1264
|
+
{
|
|
1265
|
+
type: 'text',
|
|
1266
|
+
text: `Error applying Distort: ${error instanceof Error ? error.message : String(error)}`,
|
|
1267
|
+
},
|
|
1268
|
+
],
|
|
1269
|
+
isError: true,
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
async function applyOilPaint(connection, snippetClient, rawArgs) {
|
|
1274
|
+
try {
|
|
1275
|
+
const args = validateArgs(oilPaintSchema, rawArgs);
|
|
1276
|
+
const params = {
|
|
1277
|
+
stylization: args.stylization ?? 4,
|
|
1278
|
+
cleanliness: args.cleanliness ?? 2.3,
|
|
1279
|
+
brushScale: args.brush_scale ?? 0.8,
|
|
1280
|
+
bristleDetail: args.bristle_detail ?? 10,
|
|
1281
|
+
lightDirection: args.light_direction ?? -60,
|
|
1282
|
+
shine: args.shine ?? 1.3,
|
|
1283
|
+
lightingOn: args.lighting_on ?? true,
|
|
1284
|
+
applyToActiveLayer: args.apply_to_active_layer ?? false,
|
|
1285
|
+
};
|
|
1286
|
+
const script = await snippetClient.build('applyOilPaint', params);
|
|
1287
|
+
const result = await runScript(connection, script);
|
|
1288
|
+
const r = result;
|
|
1289
|
+
const target = r.target_was_copy
|
|
1290
|
+
? `new copy "${r.target_layer_name ?? '?'}"`
|
|
1291
|
+
: 'active layer (in place)';
|
|
1292
|
+
return {
|
|
1293
|
+
content: [
|
|
1294
|
+
{
|
|
1295
|
+
type: 'text',
|
|
1296
|
+
text: `Oil Paint applied to ${target}.`,
|
|
1297
|
+
},
|
|
1298
|
+
],
|
|
1299
|
+
structuredContent: result,
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
catch (error) {
|
|
1303
|
+
return {
|
|
1304
|
+
content: [
|
|
1305
|
+
{
|
|
1306
|
+
type: 'text',
|
|
1307
|
+
text: `Error applying Oil Paint: ${error instanceof Error ? error.message : String(error)}`,
|
|
1308
|
+
},
|
|
1309
|
+
],
|
|
1310
|
+
isError: true,
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
async function applyDisplace(connection, snippetClient, rawArgs) {
|
|
1315
|
+
try {
|
|
1316
|
+
const args = validateArgs(displaceSchema, rawArgs);
|
|
1317
|
+
const params = {
|
|
1318
|
+
mapPath: args.map_path,
|
|
1319
|
+
horizontalScale: args.horizontal_scale ?? 10,
|
|
1320
|
+
verticalScale: args.vertical_scale ?? 10,
|
|
1321
|
+
displacementMap: args.displacement_map ?? 'stretch_to_fit',
|
|
1322
|
+
undefinedAreas: args.undefined_areas ?? 'repeat_edge',
|
|
1323
|
+
applyToActiveLayer: args.apply_to_active_layer ?? false,
|
|
1324
|
+
};
|
|
1325
|
+
const script = await snippetClient.build('applyDisplace', params);
|
|
1326
|
+
const result = await runScript(connection, script);
|
|
1327
|
+
const r = result;
|
|
1328
|
+
const target = r.target_was_copy
|
|
1329
|
+
? `new copy "${r.target_layer_name ?? '?'}"`
|
|
1330
|
+
: 'active layer (in place)';
|
|
1331
|
+
return {
|
|
1332
|
+
content: [
|
|
1333
|
+
{
|
|
1334
|
+
type: 'text',
|
|
1335
|
+
text: `Displace applied to ${target} using map ${params.mapPath}.`,
|
|
1336
|
+
},
|
|
1337
|
+
],
|
|
1338
|
+
structuredContent: result,
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
catch (error) {
|
|
1342
|
+
return {
|
|
1343
|
+
content: [
|
|
1344
|
+
{
|
|
1345
|
+
type: 'text',
|
|
1346
|
+
text: `Error applying Displace: ${error instanceof Error ? error.message : String(error)}`,
|
|
1347
|
+
},
|
|
1348
|
+
],
|
|
1349
|
+
isError: true,
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
}
|