@pixerate/editor 0.259.0 → 0.260.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 (74) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/animation-editor/index.cjs +61 -59
  4. package/dist/animation-editor/index.js +5 -3
  5. package/dist/brushes/index.cjs +52 -52
  6. package/dist/brushes/index.js +6 -6
  7. package/dist/{brushstrokeRender-JI4W6F2G.js → brushstrokeRender-25Q5BEY6.js} +17 -7
  8. package/dist/{brushstrokeRender-MQ6PSDM5.cjs → brushstrokeRender-N6UPZSQS.cjs} +31 -21
  9. package/dist/canvas/brushTypes.d.cts +5 -0
  10. package/dist/canvas/brushTypes.d.ts +5 -0
  11. package/dist/canvas/drawController.d.cts +15 -1
  12. package/dist/canvas/drawController.d.ts +15 -1
  13. package/dist/canvas/index.cjs +136 -102
  14. package/dist/canvas/index.js +35 -1
  15. package/dist/{chunk-3PPJRMIG.js → chunk-3O47TPGX.js} +197 -18
  16. package/dist/{chunk-5Q5MLXAD.cjs → chunk-3TQW5MPW.cjs} +23 -23
  17. package/dist/{chunk-JBBJE76O.cjs → chunk-3ZRNK3RO.cjs} +1138 -85
  18. package/dist/{chunk-YSGW7VV4.cjs → chunk-43NT242D.cjs} +1401 -228
  19. package/dist/{chunk-SMTUQWGY.js → chunk-5IW65VQ6.js} +1 -1
  20. package/dist/{chunk-YQ4PQTBB.js → chunk-6NZL24W4.js} +9 -1
  21. package/dist/{chunk-O3ZVZSNJ.cjs → chunk-7AUQ2UJ3.cjs} +137 -129
  22. package/dist/{chunk-4M4E3N6X.js → chunk-CQVTGBQI.js} +1 -1
  23. package/dist/{chunk-SSFMWJDK.js → chunk-D4TE5JNW.js} +1192 -19
  24. package/dist/{chunk-AORZ5M5C.js → chunk-EHALWBBD.js} +1 -1
  25. package/dist/{chunk-6WCQ35GI.cjs → chunk-NHA26O5X.cjs} +2 -2
  26. package/dist/{chunk-DPYAO5U6.cjs → chunk-PVJMTLPJ.cjs} +197 -18
  27. package/dist/{chunk-MQGQTKVW.cjs → chunk-PYTJBVW5.cjs} +29 -29
  28. package/dist/{chunk-IXQZHCOF.js → chunk-QAR2N7HZ.js} +1 -1
  29. package/dist/{chunk-5P4JEOIV.js → chunk-TMDDMJCR.js} +2 -2
  30. package/dist/{chunk-RSIKDWOP.cjs → chunk-TZOX7KWV.cjs} +15 -15
  31. package/dist/{chunk-IPUTIUGQ.cjs → chunk-UVZ7RQOE.cjs} +1 -1
  32. package/dist/{chunk-A7GJ6BXI.js → chunk-WJ4WQJCC.js} +1062 -9
  33. package/dist/{chunk-ICTPLVSR.js → chunk-X2EXYY4J.js} +1 -1
  34. package/dist/{chunk-WQCNC4DK.js → chunk-XC6DPRYI.js} +695 -4
  35. package/dist/{chunk-KBYB7OWB.cjs → chunk-YHDHFJUC.cjs} +776 -85
  36. package/dist/{chunk-P3PZ63M3.cjs → chunk-YLUIFXSL.cjs} +35 -35
  37. package/dist/commands/index.cjs +2 -2
  38. package/dist/commands/index.js +1 -1
  39. package/dist/image-editor/index.cjs +1385 -2484
  40. package/dist/image-editor/index.js +139 -1238
  41. package/dist/image-editor/selection-worker.cjs +3 -4
  42. package/dist/image-editor/selection-worker.js +1 -2
  43. package/dist/paint/index.cjs +3 -5
  44. package/dist/paint/index.js +40 -42
  45. package/dist/paint-editor/contract.d.cts +29 -0
  46. package/dist/paint-editor/contract.d.ts +29 -0
  47. package/dist/paint-editor/dab-stroke.d.cts +5 -0
  48. package/dist/paint-editor/dab-stroke.d.ts +5 -0
  49. package/dist/paint-editor/index.cjs +402 -258
  50. package/dist/paint-editor/index.d.cts +1 -0
  51. package/dist/paint-editor/index.d.ts +1 -0
  52. package/dist/paint-editor/index.js +197 -53
  53. package/dist/paint-editor/stamp-tip.d.cts +17 -0
  54. package/dist/paint-editor/stamp-tip.d.ts +17 -0
  55. package/dist/paint-editor/ui.d.cts +21 -1
  56. package/dist/paint-editor/ui.d.ts +21 -1
  57. package/dist/raster/index.cjs +2 -4
  58. package/dist/raster/index.js +6 -8
  59. package/dist/vector/brushstroke.d.cts +6 -0
  60. package/dist/vector/brushstroke.d.ts +6 -0
  61. package/dist/vector/controller.d.cts +11 -2
  62. package/dist/vector/controller.d.ts +11 -2
  63. package/dist/vector/index.cjs +990 -973
  64. package/dist/vector/index.js +28 -11
  65. package/package.json +1 -1
  66. package/skills/pixerate-brushes/SKILL.md +1 -1
  67. package/skills/pixerate-canvas/SKILL.md +3 -1
  68. package/skills/pixerate-vector/SKILL.md +2 -2
  69. package/dist/chunk-2VJAVXLI.js +0 -192
  70. package/dist/chunk-62Z5WLRA.js +0 -700
  71. package/dist/chunk-BOHDJJ2O.cjs +0 -192
  72. package/dist/chunk-NKGB64CW.cjs +0 -700
  73. package/dist/chunk-SS53NNZO.cjs +0 -1066
  74. package/dist/chunk-ZNA3IQZF.js +0 -1066
@@ -8,46 +8,7 @@ import {
8
8
  parseOpenTypeFont,
9
9
  removeAnchorsKeepingShape,
10
10
  traceField
11
- } from "../chunk-4M4E3N6X.js";
12
- import {
13
- ColorGmm,
14
- GAMMA,
15
- GridGraph,
16
- REFINE_EDIT_KINDS,
17
- SelectionWorkerClient,
18
- UNITS,
19
- WORK_SIZE,
20
- applyRefineEdits,
21
- boxBlur,
22
- brushStrokeMask,
23
- deserializeMask,
24
- ellipsePoints,
25
- ellipseSelection,
26
- findSky,
27
- findSubject,
28
- focusArea,
29
- grabCutLabels,
30
- keepMainPieces,
31
- normalizeRect,
32
- otsuThreshold,
33
- polygonCoverage,
34
- polygonCoverageBox,
35
- polygonSelection,
36
- quickSelectStroke,
37
- rectSelection,
38
- runSelectionJob,
39
- selectObjectIn,
40
- serializeMask,
41
- slicSuperpixels,
42
- smoothnessWeights,
43
- softMaskToSelection,
44
- startSelectionWorker,
45
- superpixelSaliency,
46
- upscaleRegion,
47
- workImage,
48
- workLuma,
49
- workPlanes
50
- } from "../chunk-A7GJ6BXI.js";
11
+ } from "../chunk-CQVTGBQI.js";
51
12
  import {
52
13
  ADJUSTMENT_KEYS,
53
14
  ADJUSTMENT_KINDS,
@@ -58,14 +19,19 @@ import {
58
19
  BACKGROUND_LAYER_NAME,
59
20
  BLUR_FILTER_ID,
60
21
  BROWSER_IMAGE_TYPES,
22
+ BRUSH_OPTION_CONTROLS,
23
+ CLONE_TRANSFORM_CONTROLS,
24
+ COLOR_RANGE_PRESET_LABELS,
61
25
  D50,
62
26
  D65,
63
27
  DEFAULT_ADJUSTMENTS_VALUES,
64
28
  DEFAULT_DEPTH_MASK_VALUES,
65
29
  DEFAULT_MITER_LIMIT,
30
+ DEFAULT_SELECTION_TOOL_OPTIONS,
66
31
  DEFAULT_TRANSFORM_VALUES,
67
32
  DEVELOP_FILTER_ID,
68
33
  EDGE_COLOR_RADIUS,
34
+ EdgeMap,
69
35
  FREE_TRANSFORM_MODES,
70
36
  HUE_RANGE_LABELS,
71
37
  IDENTITY_MATRIX3,
@@ -81,14 +47,26 @@ import {
81
47
  MAX_KELVIN,
82
48
  MIN_KELVIN,
83
49
  MIRED_PER_TEMPERATURE_UNIT,
50
+ MIXER_MIXING_OPTIONS,
51
+ MIXER_OPTION_CONTROLS,
52
+ PAINT_TOOLS,
84
53
  PARAGRAPH_STYLE_FIELDS,
85
54
  PATTERN_BLOCK_KEYS,
86
55
  PSD_BLEND_KEYS,
56
+ PaintGesture,
87
57
  PsdError,
58
+ REFINE_EDGE_CONTROLS,
59
+ REFINE_OUTPUTS,
60
+ RETOUCH_CHOICES,
61
+ RETOUCH_OPTION_CONTROLS,
62
+ RETOUCH_TOGGLES,
63
+ SELECTION_MODE_LABELS,
64
+ SELECTION_TOOLS,
88
65
  SHAPE_OPERATIONS,
89
66
  SHAPE_RENDER_MARGIN,
90
67
  SMART_RENDER_MARGIN,
91
68
  SUPPORTED_ADJUSTMENT_KEYS,
69
+ SelectionGesture,
92
70
  TEXT_ALIGNS,
93
71
  TEXT_ORIENTATIONS,
94
72
  TEXT_RENDER_MARGIN,
@@ -142,6 +120,8 @@ import {
142
120
  calculateCropDrag,
143
121
  canvasToImagePoint,
144
122
  clampCropToImage,
123
+ cloneSourceLabel,
124
+ cloneTransformView,
145
125
  colorBalanceChannel,
146
126
  compileAdjustmentLayer,
147
127
  componentPath,
@@ -170,6 +150,7 @@ import {
170
150
  depthMaskAlpha,
171
151
  depthRasterMask,
172
152
  describeLayerEffects,
153
+ describeSelection,
173
154
  describeTransform,
174
155
  deserializeEffects,
175
156
  deserializeLayer,
@@ -198,6 +179,10 @@ import {
198
179
  getViewportMetrics,
199
180
  getViewportProjection,
200
181
  groupLayers,
182
+ handleMaskViewKey,
183
+ handlePaintShortcut,
184
+ handleQuickMaskKey,
185
+ handleSelectionShortcut,
201
186
  hasEdgeUnmix,
202
187
  hasResampledLegacyMask,
203
188
  hasSoftBand,
@@ -225,9 +210,11 @@ import {
225
210
  isDefaultDepthMask,
226
211
  isIdentityTransform,
227
212
  isLegacyShape,
213
+ isPaintTool,
228
214
  isPaintableLayer,
229
215
  isQuarterTurnSwapped,
230
216
  isRenderedLayer,
217
+ isRetouchPaintTool,
231
218
  isSmartSurfaceIncomplete,
232
219
  isSpatialFilter,
233
220
  isTransformAnchor,
@@ -251,6 +238,8 @@ import {
251
238
  linearToSrgb as linearToSrgb2,
252
239
  liveComponent,
253
240
  liveShapeSubpaths,
241
+ liveWire,
242
+ magneticAnchorSpacing,
254
243
  mapCropHandle,
255
244
  mapSubpaths,
256
245
  maskCoverageOverCanvas,
@@ -336,20 +325,24 @@ import {
336
325
  resolveVectorMasks,
337
326
  rgbToGray,
338
327
  rgbToHsl,
328
+ rgbUnitToHex,
339
329
  runLayerFilters,
340
330
  runLayerFiltersFloat,
341
331
  sampleLut,
342
332
  sampleToneLut,
333
+ selectionModeFromModifiers,
343
334
  serializeContents,
344
335
  serializeEffects,
345
336
  serializeLayer,
346
337
  serializeLayers,
347
338
  serializeLayersExternal,
348
339
  serializePatterns,
340
+ setCloneTransformValue,
349
341
  setLayerEffects,
350
342
  setLayerEffectsEnabled,
351
343
  setLayerMask,
352
344
  setLayerProperties,
345
+ setRetouchChoice,
353
346
  setSmartFilterMask,
354
347
  setSmartFiltersEnabled,
355
348
  setSmartPlacement,
@@ -359,8 +352,12 @@ import {
359
352
  shapeLayers,
360
353
  shapeOutline,
361
354
  shapeRenderClip,
355
+ simplifyPixelPath,
362
356
  smartContentsFile,
363
357
  smartObjectLayers,
358
+ snapPolygonToEdges,
359
+ snapTo45,
360
+ snapToEdge,
364
361
  srgbToLinear,
365
362
  tempTintToXy,
366
363
  temperatureToKelvin,
@@ -413,15 +410,25 @@ import {
413
410
  writePsd,
414
411
  xyToTempTint,
415
412
  xyToUv
416
- } from "../chunk-SSFMWJDK.js";
413
+ } from "../chunk-D4TE5JNW.js";
417
414
  import {
415
+ ColorGmm,
418
416
  DEFAULT_REFINE_EDGE_PARAMS,
419
417
  EMPTY_DISTANCE,
418
+ GAMMA,
420
419
  GUIDED_FILTER_EPS,
420
+ GridGraph,
421
+ REFINE_EDIT_KINDS,
421
422
  SELECTION_MODES,
422
423
  SELECTION_MODIFY_OPS,
424
+ SelectionWorkerClient,
425
+ UNITS,
426
+ WORK_SIZE,
427
+ applyRefineEdits,
423
428
  borderCoverage,
429
+ boxBlur,
424
430
  boxMean,
431
+ brushStrokeMask,
425
432
  combineSelections,
426
433
  computeSelectionBounds,
427
434
  contractCoverage,
@@ -429,13 +436,20 @@ import {
429
436
  createSelectionMask,
430
437
  decodeSelectionRle,
431
438
  decontaminateColors,
439
+ deserializeMask,
432
440
  deserializeSelection,
433
441
  distanceTransform,
434
442
  edgeWidth,
443
+ ellipsePoints,
444
+ ellipseSelection,
435
445
  encodeSelectionRle,
436
446
  expandCoverage,
437
447
  featherCoverage,
448
+ findSky,
449
+ findSubject,
450
+ focusArea,
438
451
  fullSelection,
452
+ grabCutLabels,
439
453
  greyMorphology,
440
454
  guidePlanes,
441
455
  guidedFilterColor,
@@ -443,13 +457,23 @@ import {
443
457
  invertSelection,
444
458
  isSelectionEmpty,
445
459
  isSelectionMode,
460
+ keepMainPieces,
446
461
  modifySelection,
462
+ normalizeRect,
447
463
  normalizeRefineEdgeParams,
464
+ otsuThreshold,
448
465
  parseSelectionMode,
466
+ polygonCoverage,
467
+ polygonCoverageBox,
468
+ polygonSelection,
469
+ quickSelectStroke,
470
+ rectSelection,
449
471
  refineCoverage,
450
472
  refineEdgeHalo,
451
473
  refineSelection,
452
474
  resizeSelection,
475
+ runSelectionJob,
476
+ selectObjectIn,
453
477
  selectionArea,
454
478
  selectionFromCoverage,
455
479
  selectionFromPixels,
@@ -458,30 +482,28 @@ import {
458
482
  selectionToCoverage,
459
483
  selectionToPixels,
460
484
  selectionValueAt,
485
+ serializeMask,
461
486
  serializeSelection,
462
487
  shiftCoverage,
463
- smoothCoverage
464
- } from "../chunk-ZNA3IQZF.js";
465
- import {
466
- brushColourHex
467
- } from "../chunk-FJBQTZS4.js";
468
- import {
469
- parseCssColor
470
- } from "../chunk-3CI7FRMK.js";
488
+ slicSuperpixels,
489
+ smoothCoverage,
490
+ smoothnessWeights,
491
+ softMaskToSelection,
492
+ startSelectionWorker,
493
+ superpixelSaliency,
494
+ upscaleRegion,
495
+ workImage,
496
+ workLuma,
497
+ workPlanes
498
+ } from "../chunk-WJ4WQJCC.js";
471
499
  import {
472
500
  createCaptureTool
473
501
  } from "../chunk-5ED73EC6.js";
474
- import {
475
- LAYER_EFFECT_MENU,
476
- effectControlChange,
477
- layerEffectControls,
478
- layerEffectsBadge,
479
- layerEffectsPanel
480
- } from "../chunk-2VJAVXLI.js";
481
502
  import {
482
503
  DEFAULT_GLOBAL_LIGHT,
483
504
  INTERPOLATIONS,
484
505
  LAYER_EFFECT_KINDS,
506
+ LAYER_EFFECT_MENU,
485
507
  LayerEffectError,
486
508
  MAX_EFFECT_REACH,
487
509
  MAX_TRANSFORM_PIXELS,
@@ -497,6 +519,7 @@ import {
497
519
  defaultEffectGradient,
498
520
  defaultLayerEffect,
499
521
  effectColorHex,
522
+ effectControlChange,
500
523
  effectReach,
501
524
  effectsFingerprint,
502
525
  effectsMargin,
@@ -510,8 +533,11 @@ import {
510
533
  isLayerEffectKind,
511
534
  isLayerLocked,
512
535
  keysWeight,
536
+ layerEffectControls,
513
537
  layerEffectFields,
514
538
  layerEffectLabel,
539
+ layerEffectsBadge,
540
+ layerEffectsPanel,
515
541
  mapLayer,
516
542
  mergeLayerEffect,
517
543
  newEffectId,
@@ -527,7 +553,7 @@ import {
527
553
  validateLayerEffects,
528
554
  warpSurface,
529
555
  workingToSamples
530
- } from "../chunk-3PPJRMIG.js";
556
+ } from "../chunk-3O47TPGX.js";
531
557
  import {
532
558
  applyHomography,
533
559
  boxQuad,
@@ -541,50 +567,10 @@ import {
541
567
  DIRECT_GAUSSIAN_MAX_SIGMA,
542
568
  gaussianBlur
543
569
  } from "../chunk-ZIF4UJJ6.js";
544
- import {
545
- DEFAULT_RETOUCH,
546
- IDENTITY_CLONE_TRANSFORM,
547
- RETOUCH_SAMPLES,
548
- RegionPixelSource,
549
- RetouchDabStroke,
550
- SPOT_HEAL_TYPES,
551
- TONE_RANGES,
552
- TilePixelSource,
553
- TransformedPixelSource,
554
- applyRetouchToTile,
555
- applyToneToDeepTile,
556
- boxBlurPixels,
557
- cloneSamplePoint,
558
- dodgeBurn,
559
- dodgeBurnCurve,
560
- healPixels,
561
- isIdentityCloneTransform,
562
- luma,
563
- normalizeRetouch,
564
- perDabStrength,
565
- readSourceRegion,
566
- retouchFilter,
567
- rgbaViewOfGrey,
568
- saturationOf,
569
- smudgeEffect,
570
- sponge,
571
- spongeEffect,
572
- spotHealPixels,
573
- spotHealWindow,
574
- toneOf
575
- } from "../chunk-62Z5WLRA.js";
576
- import {
577
- bestOffset,
578
- contentAwareFill,
579
- dilateMask,
580
- maskBounds,
581
- membraneFill,
582
- seamlessClone,
583
- solveMembrane
584
- } from "../chunk-VWEWY4FY.js";
585
570
  import {
586
571
  BODY_SCATTER,
587
572
  DEFAULT_FLOW,
573
+ DEFAULT_RETOUCH,
588
574
  DEFAULT_WET_PAPER,
589
575
  DEFAULT_WET_PARAMS,
590
576
  DRY_EPS,
@@ -599,6 +585,7 @@ import {
599
585
  FLAT,
600
586
  FREE_INTERVAL,
601
587
  HIDE,
588
+ IDENTITY_CLONE_TRANSFORM,
602
589
  MASS_CHANNELS,
603
590
  MAX_BATCH,
604
591
  MAX_TILT_ACCEL,
@@ -624,12 +611,19 @@ import {
624
611
  P_SW,
625
612
  P_UX,
626
613
  P_UY,
614
+ RETOUCH_SAMPLES,
615
+ RegionPixelSource,
616
+ RetouchDabStroke,
627
617
  SET_PLANES,
628
618
  SLOT_BYTES,
629
619
  SLOT_PLANES,
630
620
  SLOT_SIDE,
621
+ SPOT_HEAL_TYPES,
631
622
  SRGB_TO_LINEAR,
632
623
  STRIP,
624
+ TONE_RANGES,
625
+ TilePixelSource,
626
+ TransformedPixelSource,
633
627
  U_MAX,
634
628
  W9,
635
629
  WET_FLOW_KINDS,
@@ -643,11 +637,17 @@ import {
643
637
  WetWorkerPool,
644
638
  WetWorld,
645
639
  WetWorldError,
640
+ applyRetouchToTile,
641
+ applyToneToDeepTile,
642
+ boxBlurPixels,
646
643
  canShareMemory,
644
+ cloneSamplePoint,
647
645
  createPigmentTools,
648
646
  createWetGpuEngine,
649
647
  dexp,
650
648
  dlog,
649
+ dodgeBurn,
650
+ dodgeBurnCurve,
651
651
  dpow,
652
652
  dsincos,
653
653
  edgeBits,
@@ -656,28 +656,52 @@ import {
656
656
  filmReflectance,
657
657
  findWebGpu,
658
658
  flowField,
659
+ healPixels,
660
+ isIdentityCloneTransform,
659
661
  latticeViscosity,
660
662
  linearToSrgb8,
661
663
  loadWetKernel,
664
+ luma,
662
665
  moveWetSnapshot,
663
666
  normalizeFlow,
667
+ normalizeRetouch,
664
668
  normalizeTilt,
665
669
  normalizeWetParams,
666
670
  packWetParams,
671
+ perDabStrength,
667
672
  pigmentColor,
668
673
  pigmentOf,
669
674
  pixelPigment,
670
675
  pointerPressure,
671
676
  pointerStrokeSample,
672
677
  pointerStrokeSamples,
678
+ readSourceRegion,
679
+ retouchFilter,
680
+ rgbaViewOfGrey,
681
+ saturationOf,
673
682
  shadeLightTile,
674
683
  shadeTile,
684
+ smudgeEffect,
685
+ sponge,
686
+ spongeEffect,
687
+ spotHealPixels,
688
+ spotHealWindow,
675
689
  srgbByteToLinear,
676
690
  stepSlotTs,
691
+ toneOf,
677
692
  wetGpuSupport,
678
693
  wetSimdSupported,
679
694
  wetSupport
680
- } from "../chunk-WQCNC4DK.js";
695
+ } from "../chunk-XC6DPRYI.js";
696
+ import {
697
+ bestOffset,
698
+ contentAwareFill,
699
+ dilateMask,
700
+ maskBounds,
701
+ membraneFill,
702
+ seamlessClone,
703
+ solveMembrane
704
+ } from "../chunk-VWEWY4FY.js";
681
705
  import {
682
706
  BUILTIN_PROFILES,
683
707
  CmsError,
@@ -746,14 +770,14 @@ import {
746
770
  warpMeshParamAt,
747
771
  warpMeshRows,
748
772
  warpPresetMesh
749
- } from "../chunk-YQ4PQTBB.js";
773
+ } from "../chunk-6NZL24W4.js";
750
774
  import {
751
775
  IMAGE_PROFILE,
752
776
  checkBrush,
753
777
  closest,
754
778
  createBrushLibrary,
755
779
  resolveBrush
756
- } from "../chunk-SMTUQWGY.js";
780
+ } from "../chunk-5IW65VQ6.js";
757
781
  import "../chunk-APX63ILU.js";
758
782
  import "../chunk-M6DNZNVY.js";
759
783
  import {
@@ -762,6 +786,12 @@ import {
762
786
  segmentNearest
763
787
  } from "../chunk-WEIK3UYB.js";
764
788
  import "../chunk-PWBZH3FK.js";
789
+ import {
790
+ brushColourHex
791
+ } from "../chunk-FJBQTZS4.js";
792
+ import {
793
+ parseCssColor
794
+ } from "../chunk-3CI7FRMK.js";
765
795
  import "../chunk-SZTI6EER.js";
766
796
  import {
767
797
  EffectRendererPool
@@ -3935,312 +3965,6 @@ function pathToSelection(components, width, height, options = {}) {
3935
3965
  return feather > 0 && mask.bounds ? modifySelection(mask, "feather", feather) : mask;
3936
3966
  }
3937
3967
 
3938
- // src/image-editor/selection/magnetic.ts
3939
- var BLOCK = 64;
3940
- var W_ZERO = 0.3;
3941
- var W_GRADIENT = 0.4;
3942
- var W_DIRECTION = 0.3;
3943
- var EdgeMap = class {
3944
- width;
3945
- height;
3946
- contrast;
3947
- luma;
3948
- blocks = /* @__PURE__ */ new Map();
3949
- constructor(pixels, options = {}) {
3950
- const { width, height, data } = pixels;
3951
- if (!(width >= 1 && height >= 1) || data.length < width * height * 4) throw new Error("Invalid pixel buffer for edge detection.");
3952
- this.width = width;
3953
- this.height = height;
3954
- this.contrast = Math.min(1, Math.max(0, options.contrast ?? 0.1));
3955
- const luma2 = new Uint8Array(width * height);
3956
- for (let i = 0, p = 0; i < luma2.length; i++, p += 4) {
3957
- const a = data[p + 3] / 255;
3958
- const y = 0.299 * data[p] + 0.587 * data[p + 1] + 0.114 * data[p + 2];
3959
- luma2[i] = Math.round(y * a + 255 * (1 - a));
3960
- }
3961
- this.luma = luma2;
3962
- }
3963
- /** Edge strength at (x, y) in whole pixels, 0..1 (0 outside the image or below the contrast). */
3964
- strength(x, y) {
3965
- if (x < 0 || y < 0 || x >= this.width || y >= this.height) return 0;
3966
- const b = this.block(x, y);
3967
- return b.strength[y % BLOCK * BLOCK + x % BLOCK];
3968
- }
3969
- block(x, y) {
3970
- const bx = Math.floor(x / BLOCK);
3971
- const by = Math.floor(y / BLOCK);
3972
- const key2 = by * 65536 + bx;
3973
- let b = this.blocks.get(key2);
3974
- if (!b) {
3975
- b = this.computeBlock(bx * BLOCK, by * BLOCK);
3976
- this.blocks.set(key2, b);
3977
- }
3978
- return b;
3979
- }
3980
- computeBlock(x0, y0) {
3981
- const { width: W, height: H, luma: luma2 } = this;
3982
- const M = 2;
3983
- const S = BLOCK + 2 * M;
3984
- const smooth = new Float32Array(S * S);
3985
- const at = (x, y) => luma2[Math.min(H - 1, Math.max(0, y)) * W + Math.min(W - 1, Math.max(0, x))];
3986
- for (let j = 0; j < S; j++) {
3987
- const y = y0 - M + j;
3988
- for (let i = 0; i < S; i++) {
3989
- const x = x0 - M + i;
3990
- const v = at(x - 1, y - 1) + 2 * at(x, y - 1) + at(x + 1, y - 1) + 2 * at(x - 1, y) + 4 * at(x, y) + 2 * at(x + 1, y) + at(x - 1, y + 1) + 2 * at(x, y + 1) + at(x + 1, y + 1);
3991
- smooth[j * S + i] = v / (16 * 255);
3992
- }
3993
- }
3994
- const lap = new Float32Array(S * S);
3995
- for (let j = 1; j < S - 1; j++) {
3996
- for (let i = 1; i < S - 1; i++) {
3997
- const k = j * S + i;
3998
- lap[k] = smooth[k - 1] + smooth[k + 1] + smooth[k - S] + smooth[k + S] - 4 * smooth[k];
3999
- }
4000
- }
4001
- const n = BLOCK * BLOCK;
4002
- const strength = new Float32Array(n);
4003
- const dirX = new Float32Array(n);
4004
- const dirY = new Float32Array(n);
4005
- const zero = new Uint8Array(n);
4006
- const c = this.contrast;
4007
- for (let j = 0; j < BLOCK; j++) {
4008
- if (y0 + j >= H) break;
4009
- for (let i = 0; i < BLOCK; i++) {
4010
- if (x0 + i >= W) break;
4011
- const k = (j + M) * S + (i + M);
4012
- const gx = (smooth[k + 1] - smooth[k - 1]) / 2;
4013
- const gy = (smooth[k + S] - smooth[k - S]) / 2;
4014
- const g = Math.hypot(gx, gy);
4015
- const e = Math.min(1, 2 * g);
4016
- if (e < c || g === 0) continue;
4017
- const o = j * BLOCK + i;
4018
- strength[o] = e;
4019
- dirX[o] = gy / g;
4020
- dirY[o] = -gx / g;
4021
- const l = lap[k];
4022
- for (const d of [1, -1, S, -S]) {
4023
- const m = lap[k + d];
4024
- if (l > 0 && m < 0 || l < 0 && m > 0 || l === 0) {
4025
- if (Math.abs(l) <= Math.abs(m)) {
4026
- zero[o] = 1;
4027
- break;
4028
- }
4029
- }
4030
- }
4031
- }
4032
- }
4033
- return { strength, dirX, dirY, zero };
4034
- }
4035
- /** Features of the pixel at (x, y), which must be inside the image. */
4036
- features(x, y) {
4037
- const b = this.block(x, y);
4038
- const o = y % BLOCK * BLOCK + x % BLOCK;
4039
- return { strength: b.strength[o], dirX: b.dirX[o], dirY: b.dirY[o], zero: b.zero[o] };
4040
- }
4041
- };
4042
- var clampPixel = (v, max) => Math.min(max - 1, Math.max(0, Math.floor(v)));
4043
- function snapToEdge(map, point2, width = 10) {
4044
- const cx = clampPixel(point2.x, map.width);
4045
- const cy = clampPixel(point2.y, map.height);
4046
- const r = Math.max(0, Math.round(width));
4047
- let best = 0;
4048
- let bestD = Infinity;
4049
- let bx = cx;
4050
- let by = cy;
4051
- for (let y = Math.max(0, cy - r); y <= Math.min(map.height - 1, cy + r); y++) {
4052
- for (let x = Math.max(0, cx - r); x <= Math.min(map.width - 1, cx + r); x++) {
4053
- const d = (x - cx) ** 2 + (y - cy) ** 2;
4054
- if (d > r * r) continue;
4055
- const s = map.strength(x, y);
4056
- if (s > best + 0.02 || s > 0 && Math.abs(s - best) <= 0.02 && d < bestD) {
4057
- if (s > best) best = s;
4058
- bestD = d;
4059
- bx = x;
4060
- by = y;
4061
- }
4062
- }
4063
- }
4064
- return best > 0 ? { x: bx + 0.5, y: by + 0.5 } : point2;
4065
- }
4066
- var Heap = class {
4067
- ids = new Int32Array(256);
4068
- keys = new Float64Array(256);
4069
- size = 0;
4070
- push(id, key2) {
4071
- if (this.size === this.ids.length) {
4072
- const ids = new Int32Array(this.size * 2);
4073
- ids.set(this.ids);
4074
- const keys = new Float64Array(this.size * 2);
4075
- keys.set(this.keys);
4076
- this.ids = ids;
4077
- this.keys = keys;
4078
- }
4079
- let i = this.size++;
4080
- while (i > 0) {
4081
- const p = i - 1 >> 1;
4082
- if (this.keys[p] <= key2) break;
4083
- this.ids[i] = this.ids[p];
4084
- this.keys[i] = this.keys[p];
4085
- i = p;
4086
- }
4087
- this.ids[i] = id;
4088
- this.keys[i] = key2;
4089
- }
4090
- /** Removes the cheapest entry; read it from `topId` / `topKey` first. */
4091
- pop() {
4092
- const n = --this.size;
4093
- const id = this.ids[n];
4094
- const key2 = this.keys[n];
4095
- let i = 0;
4096
- for (; ; ) {
4097
- let c = 2 * i + 1;
4098
- if (c >= n) break;
4099
- if (c + 1 < n && this.keys[c + 1] < this.keys[c]) c++;
4100
- if (this.keys[c] >= key2) break;
4101
- this.ids[i] = this.ids[c];
4102
- this.keys[i] = this.keys[c];
4103
- i = c;
4104
- }
4105
- this.ids[i] = id;
4106
- this.keys[i] = key2;
4107
- }
4108
- get topId() {
4109
- return this.ids[0];
4110
- }
4111
- get topKey() {
4112
- return this.keys[0];
4113
- }
4114
- };
4115
- var NEIGHBOURS = [
4116
- [1, 0],
4117
- [-1, 0],
4118
- [0, 1],
4119
- [0, -1],
4120
- [1, 1],
4121
- [1, -1],
4122
- [-1, 1],
4123
- [-1, -1]
4124
- ];
4125
- function liveWire(map, from, to, band) {
4126
- const ax = clampPixel(from.x, map.width);
4127
- const ay = clampPixel(from.y, map.height);
4128
- const bx = clampPixel(to.x, map.width);
4129
- const by = clampPixel(to.y, map.height);
4130
- if (ax === bx && ay === by) return [{ x: ax + 0.5, y: ay + 0.5 }];
4131
- const len = Math.hypot(bx - ax, by - ay);
4132
- const half = Math.max(2, band ?? Math.max(16, len / 4));
4133
- const rx0 = Math.max(0, Math.floor(Math.min(ax, bx) - half));
4134
- const ry0 = Math.max(0, Math.floor(Math.min(ay, by) - half));
4135
- const rx1 = Math.min(map.width - 1, Math.ceil(Math.max(ax, bx) + half));
4136
- const ry1 = Math.min(map.height - 1, Math.ceil(Math.max(ay, by) + half));
4137
- const w = rx1 - rx0 + 1;
4138
- const h = ry1 - ry0 + 1;
4139
- const n = w * h;
4140
- const strength = new Float32Array(n);
4141
- const dirX = new Float32Array(n);
4142
- const dirY = new Float32Array(n);
4143
- const zero = new Uint8Array(n);
4144
- const inside = new Uint8Array(n);
4145
- let maxStrength = 0;
4146
- const ux = (bx - ax) / len;
4147
- const uy = (by - ay) / len;
4148
- for (let y = 0; y < h; y++) {
4149
- for (let x = 0; x < w; x++) {
4150
- const px = rx0 + x - ax;
4151
- const py = ry0 + y - ay;
4152
- const t = Math.min(len, Math.max(0, px * ux + py * uy));
4153
- if (Math.hypot(px - t * ux, py - t * uy) > half) continue;
4154
- const i = y * w + x;
4155
- inside[i] = 1;
4156
- const f = map.features(rx0 + x, ry0 + y);
4157
- strength[i] = f.strength;
4158
- dirX[i] = f.dirX;
4159
- dirY[i] = f.dirY;
4160
- zero[i] = f.zero;
4161
- if (f.strength > maxStrength) maxStrength = f.strength;
4162
- }
4163
- }
4164
- const scale = maxStrength > 0 ? 1 / maxStrength : 0;
4165
- const dist2 = new Float64Array(n).fill(Infinity);
4166
- const prev = new Int32Array(n).fill(-1);
4167
- const done = new Uint8Array(n);
4168
- const start = (ay - ry0) * w + (ax - rx0);
4169
- const goal = (by - ry0) * w + (bx - rx0);
4170
- dist2[start] = 0;
4171
- const heap = new Heap();
4172
- heap.push(start, 0);
4173
- while (heap.size) {
4174
- const p = heap.topId;
4175
- const d = heap.topKey;
4176
- heap.pop();
4177
- if (done[p] || d > dist2[p]) continue;
4178
- done[p] = 1;
4179
- if (p === goal) break;
4180
- const x = p % w;
4181
- const y = (p - x) / w;
4182
- for (const [dx, dy] of NEIGHBOURS) {
4183
- const qx = x + dx;
4184
- const qy = y + dy;
4185
- if (qx < 0 || qy < 0 || qx >= w || qy >= h) continue;
4186
- const q = qy * w + qx;
4187
- if (!inside[q] || done[q]) continue;
4188
- const step = dx && dy ? Math.SQRT2 : 1;
4189
- const lx = dx / step;
4190
- const ly = dy / step;
4191
- const dp = Math.min(1, Math.abs(dirX[p] * lx + dirY[p] * ly));
4192
- const dq = Math.min(1, Math.abs(dirX[q] * lx + dirY[q] * ly));
4193
- const fD = (Math.acos(dp) + Math.acos(dq)) / Math.PI;
4194
- const fG = 1 - strength[q] * scale;
4195
- const fZ = 1 - zero[q];
4196
- const cost = (W_ZERO * fZ + W_GRADIENT * fG + W_DIRECTION * fD + 0.01) * step;
4197
- const nd = d + cost;
4198
- if (nd < dist2[q]) {
4199
- dist2[q] = nd;
4200
- prev[q] = p;
4201
- heap.push(q, nd);
4202
- }
4203
- }
4204
- }
4205
- if (prev[goal] < 0) return [{ x: ax + 0.5, y: ay + 0.5 }, { x: bx + 0.5, y: by + 0.5 }];
4206
- const path = [];
4207
- for (let p = goal; p >= 0; p = prev[p]) {
4208
- const x = p % w;
4209
- path.push({ x: rx0 + x + 0.5, y: ry0 + (p - x) / w + 0.5 });
4210
- }
4211
- return path.reverse();
4212
- }
4213
- function simplifyPixelPath(points) {
4214
- if (points.length < 3) return points.slice();
4215
- const out = [points[0]];
4216
- for (let i = 1; i < points.length - 1; i++) {
4217
- const a = out[out.length - 1];
4218
- const b = points[i];
4219
- const c = points[i + 1];
4220
- const cross = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
4221
- if (Math.abs(cross) > 1e-9) out.push(b);
4222
- }
4223
- out.push(points[points.length - 1]);
4224
- return out;
4225
- }
4226
- function snapPolygonToEdges(pixels, points, options = {}) {
4227
- const map = pixels instanceof EdgeMap ? pixels : new EdgeMap(pixels, options);
4228
- const width = options.width ?? 10;
4229
- const snapped = points.map((p) => snapToEdge(map, p, width));
4230
- const out = [];
4231
- for (let i = 0; i < snapped.length; i++) {
4232
- const a = snapped[i];
4233
- const b = snapped[(i + 1) % snapped.length];
4234
- const seg = liveWire(map, a, b, Math.max(2 * width, 16, Math.hypot(b.x - a.x, b.y - a.y) / 4));
4235
- out.push(...seg.slice(0, -1));
4236
- }
4237
- return simplifyPixelPath(out);
4238
- }
4239
- function magneticAnchorSpacing(frequency = 57) {
4240
- const f = Math.min(100, Math.max(0, frequency));
4241
- return 8 + (100 - f);
4242
- }
4243
-
4244
3968
  // src/image-editor/paths.ts
4245
3969
  var WORK_PATH_NAME = "Work Path";
4246
3970
  var counter = 0;
@@ -8066,8 +7790,8 @@ var ImageEditorController = class {
8066
7790
  const { layer } = this.requireVectorMask(id);
8067
7791
  const { width, height } = this.canvasSize;
8068
7792
  const raster = layer.mask;
8069
- const plain3 = { ...layer, vectorMask: { ...layer.vectorMask, enabled: true }, mask: raster ? { surface: raster.surface, defaultValue: raster.defaultValue } : void 0 };
8070
- const baked = effectiveLayerMask(plain3, width, height, this.guideSurfaceOf);
7793
+ const plain2 = { ...layer, vectorMask: { ...layer.vectorMask, enabled: true }, mask: raster ? { surface: raster.surface, defaultValue: raster.defaultValue } : void 0 };
7794
+ const baked = effectiveLayerMask(plain2, width, height, this.guideSurfaceOf);
8071
7795
  const mask = raster ? { ...raster, surface: baked.surface, defaultValue: baked.defaultValue } : baked;
8072
7796
  this.commitLayers(
8073
7797
  mapLayer(this.state.layers, id, (l) => {
@@ -8762,8 +8486,8 @@ var ImageEditorController = class {
8762
8486
  const old = layer?.contents;
8763
8487
  const { width, height } = edited.imageDimensions;
8764
8488
  const only = edited.layers.length === 1 ? edited.layers[0] : null;
8765
- const plain3 = only?.type === "pixel" && only.visible && only.opacity === 1 && only.blend === "normal" && !only.mask && !only.effects && !only.filters?.length && !only.clipped && (only.fillOpacity ?? 1) === 1 && only.source !== "document";
8766
- if (old?.kind === "raster" && plain3) {
8489
+ const plain2 = only?.type === "pixel" && only.visible && only.opacity === 1 && only.blend === "normal" && !only.mask && !only.effects && !only.filters?.length && !only.clipped && (only.fillOpacity ?? 1) === 1 && only.source !== "document";
8490
+ if (old?.kind === "raster" && plain2) {
8767
8491
  const px = readRegion(only.surface, { x: -only.offset.x, y: -only.offset.y, width, height });
8768
8492
  next = rasterContents({ width, height, data: px }, old.name, old.id);
8769
8493
  } else {
@@ -10698,8 +10422,8 @@ var ImageEditorController = class {
10698
10422
  return { output, layerId, refineMode, ...documentId ? { documentId } : {} };
10699
10423
  }
10700
10424
  return this.asOneStep(() => {
10701
- const plain3 = output === "selection" && !edits.length && !regions2 && refineMode === "color";
10702
- if (plain3) this.withRecipeStep({ op: "refine", params: { ...params }, ...samplingFields(options) }, () => this.setSelection(refined));
10425
+ const plain2 = output === "selection" && !edits.length && !regions2 && refineMode === "color";
10426
+ if (plain2) this.withRecipeStep({ op: "refine", params: { ...params }, ...samplingFields(options) }, () => this.setSelection(refined));
10703
10427
  else this.setSelection(refined);
10704
10428
  if (output === "selection") return { output, refineMode };
10705
10429
  if (output === "layer-mask") {
@@ -12459,10 +12183,10 @@ var ImageEditorController = class {
12459
12183
  const mask = layer.mask;
12460
12184
  if (!mask?.source) return mask;
12461
12185
  add2("Parametric (depth) masks were baked into pixel masks", layer.name);
12462
- const plain3 = { ...layer, mask: { ...mask, density: 1, feather: 0, enabled: true } };
12186
+ const plain2 = { ...layer, mask: { ...mask, density: 1, feather: 0, enabled: true } };
12463
12187
  const ox = layer.type === "pixel" ? layer.offset.x : 0;
12464
12188
  const oy = layer.type === "pixel" ? layer.offset.y : 0;
12465
- const coverage = compositor.maskCoverage(plain3, canvas, sources);
12189
+ const coverage = compositor.maskCoverage(plain2, canvas, sources);
12466
12190
  const bytes = new Uint8ClampedArray(width * height);
12467
12191
  if (coverage) for (let i = 0; i < bytes.length; i++) bytes[i] = Math.round(coverage[i] * 255);
12468
12192
  else bytes.fill(255);
@@ -13809,95 +13533,6 @@ function agentDragDraft(mode, box, image) {
13809
13533
  };
13810
13534
  }
13811
13535
 
13812
- // src/image-editor/selection/tool-options.ts
13813
- var SELECTION_TOOLS = [
13814
- { id: "wand", label: "Magic Wand" },
13815
- { id: "rect", label: "Rectangle" },
13816
- { id: "ellipse", label: "Ellipse" },
13817
- { id: "row", label: "Single Row" },
13818
- { id: "column", label: "Single Column" },
13819
- { id: "lasso", label: "Lasso" },
13820
- { id: "polygon", label: "Polygonal Lasso" },
13821
- { id: "magnetic", label: "Magnetic Lasso" },
13822
- { id: "quick", label: "Quick Selection" },
13823
- { id: "object", label: "Object Selection" },
13824
- { id: "range", label: "Color Range" },
13825
- { id: "type", label: "Type Mask" },
13826
- { id: "type-vertical", label: "Vertical Type Mask" }
13827
- ];
13828
- var COLOR_RANGE_PRESET_LABELS = {
13829
- reds: "Reds",
13830
- yellows: "Yellows",
13831
- greens: "Greens",
13832
- cyans: "Cyans",
13833
- blues: "Blues",
13834
- magentas: "Magentas",
13835
- highlights: "Highlights",
13836
- midtones: "Midtones",
13837
- shadows: "Shadows",
13838
- skinTones: "Skin Tones"
13839
- };
13840
- var SELECTION_MODE_LABELS = {
13841
- replace: "New",
13842
- add: "Add",
13843
- subtract: "Subtract",
13844
- intersect: "Intersect"
13845
- };
13846
- var DEFAULT_SELECTION_TOOL_OPTIONS = {
13847
- tool: "wand",
13848
- mode: "replace",
13849
- tolerance: 32,
13850
- contiguous: true,
13851
- antiAlias: true,
13852
- sampleMerged: false,
13853
- distance: "rgb",
13854
- feather: 0,
13855
- fuzziness: 40,
13856
- localized: 0,
13857
- rangeInvert: false,
13858
- magneticWidth: 10,
13859
- magneticContrast: 10,
13860
- magneticFrequency: 57,
13861
- quickSize: 30,
13862
- quickSampleAll: true,
13863
- quickEnhance: false,
13864
- objectSampleAll: true,
13865
- objectEnhance: false,
13866
- focusRange: 50,
13867
- focusNoise: 0,
13868
- focusSoftness: 4,
13869
- typeText: "Type",
13870
- typeSize: 72,
13871
- typeFamily: "sans-serif"
13872
- };
13873
- function selectionModeFromModifiers(modifiers, fallback) {
13874
- if (modifiers.shiftKey && modifiers.altKey) return "intersect";
13875
- if (modifiers.shiftKey) return "add";
13876
- if (modifiers.altKey) return "subtract";
13877
- return fallback;
13878
- }
13879
- var REFINE_EDGE_CONTROLS = [
13880
- { id: "radius", label: "Radius", min: 0, max: 64, step: 1, unit: "px" },
13881
- { id: "smooth", label: "Smooth", min: 0, max: 100, step: 1, unit: "" },
13882
- { id: "feather", label: "Feather", min: 0, max: 50, step: 0.5, unit: "px" },
13883
- { id: "contrast", label: "Contrast", min: 0, max: 100, step: 1, unit: "%" },
13884
- { id: "shiftEdge", label: "Shift Edge", min: -100, max: 100, step: 1, unit: "%" }
13885
- ];
13886
- var REFINE_OUTPUTS = [
13887
- { id: "selection", label: "Selection" },
13888
- { id: "layer-mask", label: "Layer Mask" },
13889
- { id: "new-layer", label: "New Layer" },
13890
- { id: "new-layer-with-mask", label: "New Layer with Layer Mask" },
13891
- { id: "new-document", label: "New Document", needsDocuments: true },
13892
- { id: "new-document-with-mask", label: "New Document with Layer Mask", needsDocuments: true }
13893
- ];
13894
- function describeSelection(mask) {
13895
- if (!mask || !mask.bounds) return "No selection";
13896
- const pct = selectionArea(mask) / (mask.width * mask.height) * 100;
13897
- const shown = pct >= 10 ? Math.round(pct) : Math.round(pct * 10) / 10;
13898
- return `${mask.bounds.width} \xD7 ${mask.bounds.height} px selected (${shown}% of the image)`;
13899
- }
13900
-
13901
13536
  // src/image-editor/selection-tools.ts
13902
13537
  var modeSchema = {
13903
13538
  type: "string",
@@ -16339,740 +15974,6 @@ function createImageLayerTools(controller) {
16339
15974
  ];
16340
15975
  }
16341
15976
 
16342
- // src/image-editor/selection/gesture.ts
16343
- function snapTo45(from, point2) {
16344
- const dx = point2.x - from.x;
16345
- const dy = point2.y - from.y;
16346
- const len = Math.hypot(dx, dy);
16347
- if (len === 0) return point2;
16348
- const a = Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) * (Math.PI / 4);
16349
- return { x: from.x + Math.cos(a) * len, y: from.y + Math.sin(a) * len };
16350
- }
16351
- var SelectionGesture = class {
16352
- constructor(controller, getOptions) {
16353
- this.controller = controller;
16354
- this.getOptions = getOptions;
16355
- }
16356
- controller;
16357
- getOptions;
16358
- start = null;
16359
- points = [];
16360
- current = null;
16361
- modifiers = {};
16362
- /** Polygonal lasso corners while one is being drawn. */
16363
- polygon = null;
16364
- pressedAt = null;
16365
- freehand = false;
16366
- magnetic = null;
16367
- /** A click this close to the first corner (natural pixels) closes a polygonal lasso. */
16368
- closeRadius = 8;
16369
- /** The last error (e.g. pixels unavailable for the wand), for the UI to show. */
16370
- error = null;
16371
- pending = Promise.resolve();
16372
- /** Quick Selection while dragging: the selection before the stroke, how the stroke combines, the pixels. */
16373
- quick = null;
16374
- livePreviews = Promise.resolve();
16375
- get active() {
16376
- return this.start !== null || this.polygon !== null || this.magnetic !== null;
16377
- }
16378
- /** Resolves once a Quick Selection stroke or an Object Selection has been applied. */
16379
- get settled() {
16380
- return this.pending;
16381
- }
16382
- /** Resolves once no live Quick Selection preview is being computed. */
16383
- get previewed() {
16384
- return this.livePreviews;
16385
- }
16386
- /** Grows the Quick Selection preview to the stroke so far: one at a time, the newest points following. */
16387
- growPreview() {
16388
- const q = this.quick;
16389
- if (!q) return;
16390
- if (q.running) {
16391
- q.dirty = true;
16392
- return;
16393
- }
16394
- q.running = true;
16395
- this.livePreviews = (async () => {
16396
- do {
16397
- q.dirty = false;
16398
- const stroke = this.points.slice();
16399
- const options = this.getOptions();
16400
- try {
16401
- const pixels = await q.pixels;
16402
- if (!pixels || this.quick !== q) break;
16403
- const { mask } = await this.controller.runSelectionJob(
16404
- {
16405
- op: "quick",
16406
- stroke,
16407
- size: options.quickSize ?? 30,
16408
- grow: q.mode === "add",
16409
- enhanceEdge: options.quickEnhance ?? false,
16410
- selection: q.mode === "add" && q.base?.bounds ? serializeMask(q.base) : null
16411
- },
16412
- pixels
16413
- );
16414
- if (this.quick === q) this.controller.setSelectionPreview(combineSelections(q.base, mask, q.mode));
16415
- } catch {
16416
- break;
16417
- }
16418
- } while (q.dirty && this.quick === q);
16419
- q.running = false;
16420
- })();
16421
- }
16422
- /** The anchors of a Magnetic Lasso being drawn (empty otherwise). */
16423
- get magneticAnchors() {
16424
- const m = this.magnetic;
16425
- return m ? m.anchors.map((i) => m.fixed[i]) : [];
16426
- }
16427
- /** The outline of the shape being drawn, or `null`. */
16428
- get draftPath() {
16429
- if (this.magnetic) return [...this.magnetic.fixed, ...this.magnetic.live.slice(1)];
16430
- if (this.polygon) return this.current ? [...this.polygon, this.current] : this.polygon.slice();
16431
- if (!this.start || !this.current) return null;
16432
- const { tool } = this.getOptions();
16433
- if (tool === "quick") return null;
16434
- if (tool === "lasso") return this.points.length > 1 ? this.points.slice() : null;
16435
- const rect3 = normalizeRect({
16436
- x: this.start.x,
16437
- y: this.start.y,
16438
- width: this.current.x - this.start.x,
16439
- height: this.current.y - this.start.y
16440
- });
16441
- if (rect3.width < 1 && rect3.height < 1) return null;
16442
- if (tool === "ellipse") return ellipsePoints(rect3);
16443
- return [
16444
- { x: rect3.x, y: rect3.y },
16445
- { x: rect3.x + rect3.width, y: rect3.y },
16446
- { x: rect3.x + rect3.width, y: rect3.y + rect3.height },
16447
- { x: rect3.x, y: rect3.y + rect3.height }
16448
- ];
16449
- }
16450
- /** Pointer down. Resolves once a wand click has been applied. */
16451
- async begin(point2, modifiers = {}) {
16452
- const options = this.getOptions();
16453
- this.error = null;
16454
- if (this.polygon && options.tool !== "polygon" || this.magnetic && options.tool !== "magnetic") this.cancel();
16455
- if (options.tool === "magnetic") return this.magneticClick(point2, modifiers, options);
16456
- if (options.tool === "polygon") {
16457
- this.pressedAt = point2;
16458
- this.freehand = false;
16459
- if (!this.polygon) {
16460
- this.modifiers = modifiers;
16461
- this.polygon = [point2];
16462
- this.current = point2;
16463
- return;
16464
- }
16465
- const last = this.polygon[this.polygon.length - 1];
16466
- const p = modifiers.shiftKey ? snapTo45(last, point2) : point2;
16467
- const first = this.polygon[0];
16468
- if (this.polygon.length >= 3 && Math.hypot(p.x - first.x, p.y - first.y) <= this.closeRadius) {
16469
- this.close();
16470
- return;
16471
- }
16472
- if (Math.hypot(p.x - last.x, p.y - last.y) >= 0.5) this.polygon.push(p);
16473
- this.current = p;
16474
- if ((modifiers.detail ?? 1) >= 2) this.close();
16475
- return;
16476
- }
16477
- this.modifiers = modifiers;
16478
- if (options.tool === "row" || options.tool === "column") {
16479
- this.start = null;
16480
- try {
16481
- this.controller.selectLine(options.tool, options.tool === "row" ? point2.y : point2.x, {
16482
- mode: selectionModeFromModifiers(modifiers, options.mode),
16483
- feather: options.feather ?? 0
16484
- });
16485
- } catch (err) {
16486
- this.error = err instanceof Error ? err.message : String(err);
16487
- }
16488
- return;
16489
- }
16490
- if (options.tool === "type" || options.tool === "type-vertical") {
16491
- this.start = null;
16492
- try {
16493
- this.controller.selectTextShape({
16494
- text: options.typeText || "Type",
16495
- x: point2.x,
16496
- y: point2.y,
16497
- orientation: options.tool === "type-vertical" ? "vertical" : "horizontal",
16498
- style: { fontSize: options.typeSize ?? 72, fontFamily: options.typeFamily || "sans-serif" },
16499
- mode: selectionModeFromModifiers(modifiers, options.mode),
16500
- feather: options.feather ?? 0,
16501
- antiAlias: options.antiAlias
16502
- });
16503
- } catch (err) {
16504
- this.error = err instanceof Error ? err.message : String(err);
16505
- }
16506
- return;
16507
- }
16508
- if (options.tool === "wand" || options.tool === "range") {
16509
- this.start = null;
16510
- const common = {
16511
- sampleMerged: options.sampleMerged,
16512
- sample: options.sample,
16513
- feather: options.feather ?? 0,
16514
- mode: selectionModeFromModifiers(modifiers, options.mode)
16515
- };
16516
- try {
16517
- if (options.tool === "range") {
16518
- const pixels = await this.controller.getSamplePixels(common);
16519
- if (!pixels) throw new Error("The image pixels are not available to sample.");
16520
- const x = Math.min(pixels.width - 1, Math.max(0, Math.floor(point2.x)));
16521
- const y = Math.min(pixels.height - 1, Math.max(0, Math.floor(point2.y)));
16522
- const p = (y * pixels.width + x) * 4;
16523
- const color = [pixels.data[p], pixels.data[p + 1], pixels.data[p + 2]];
16524
- await this.controller.selectColorRange([{ color, at: point2 }], {
16525
- ...common,
16526
- fuzziness: options.fuzziness ?? 40,
16527
- localized: options.localized ? options.localized : void 0,
16528
- invert: options.rangeInvert ?? false
16529
- });
16530
- return;
16531
- }
16532
- await this.controller.magicWand(point2, {
16533
- ...common,
16534
- tolerance: options.tolerance,
16535
- contiguous: options.contiguous,
16536
- antiAlias: options.antiAlias,
16537
- distance: options.distance
16538
- });
16539
- } catch (err) {
16540
- this.error = err instanceof Error ? err.message : String(err);
16541
- }
16542
- return;
16543
- }
16544
- this.start = point2;
16545
- this.current = point2;
16546
- this.points = [point2];
16547
- if (options.tool === "quick") {
16548
- const picked = selectionModeFromModifiers(modifiers, options.mode);
16549
- const base = this.controller.getSelection();
16550
- this.quick = {
16551
- base,
16552
- mode: picked === "replace" && base ? "add" : picked,
16553
- pixels: this.controller.getSamplePixels({ sample: options.quickSampleAll === false ? "active-layer" : "composite" }).catch(() => null),
16554
- running: false,
16555
- dirty: false
16556
- };
16557
- this.growPreview();
16558
- }
16559
- }
16560
- /** Pointer move; returns true when the draft changed (re-render). */
16561
- move(point2, modifiers = {}) {
16562
- if (this.magnetic) {
16563
- this.current = point2;
16564
- this.followEdges(point2);
16565
- return true;
16566
- }
16567
- if (this.polygon) {
16568
- const last = this.polygon[this.polygon.length - 1];
16569
- if (this.pressedAt && (this.freehand || Math.hypot(point2.x - this.pressedAt.x, point2.y - this.pressedAt.y) >= 3)) {
16570
- this.freehand = true;
16571
- if (Math.hypot(point2.x - last.x, point2.y - last.y) >= 0.5) this.polygon.push(point2);
16572
- this.current = null;
16573
- return true;
16574
- }
16575
- this.current = modifiers.shiftKey ? snapTo45(last, point2) : point2;
16576
- return true;
16577
- }
16578
- if (!this.start) return false;
16579
- this.current = point2;
16580
- const drawn = this.getOptions().tool;
16581
- if (drawn === "lasso" || drawn === "quick") {
16582
- const last = this.points[this.points.length - 1];
16583
- if (Math.hypot(point2.x - last.x, point2.y - last.y) < 0.5) return false;
16584
- this.points.push(point2);
16585
- if (drawn === "quick") this.growPreview();
16586
- }
16587
- return true;
16588
- }
16589
- /** Pointer up: commits the shape. Returns true when the selection changed. */
16590
- end(point2, modifiers) {
16591
- if (this.magnetic) return false;
16592
- if (this.polygon) {
16593
- if (point2 && this.freehand) this.move(point2);
16594
- this.pressedAt = null;
16595
- this.freehand = false;
16596
- return false;
16597
- }
16598
- if (!this.start) return false;
16599
- if (point2) this.move(point2);
16600
- const options = this.getOptions();
16601
- const mode = selectionModeFromModifiers(modifiers ?? this.modifiers, options.mode);
16602
- if (options.tool === "object") {
16603
- const rect4 = normalizeRect({ x: this.start.x, y: this.start.y, width: this.current.x - this.start.x, height: this.current.y - this.start.y });
16604
- this.start = null;
16605
- this.current = null;
16606
- this.points = [];
16607
- const target = rect4.width >= 4 && rect4.height >= 4 ? { rect: rect4 } : { point: { x: rect4.x + rect4.width / 2, y: rect4.y + rect4.height / 2 } };
16608
- this.pending = this.controller.selectObject(target, { mode, sampleAllLayers: options.objectSampleAll ?? true, enhanceEdge: options.objectEnhance ?? false }).then(
16609
- () => void 0,
16610
- (err) => {
16611
- this.error = err instanceof Error ? err.message : String(err);
16612
- }
16613
- );
16614
- return false;
16615
- }
16616
- if (options.tool === "quick") {
16617
- const stroke = this.points;
16618
- this.start = null;
16619
- this.current = null;
16620
- this.points = [];
16621
- const strokeMode = this.quick?.mode ?? (mode === "replace" && this.controller.getSelection() ? "add" : mode);
16622
- this.quick = null;
16623
- this.pending = this.controller.quickSelect([stroke], {
16624
- size: options.quickSize ?? 30,
16625
- mode: strokeMode,
16626
- sampleAllLayers: options.quickSampleAll ?? true,
16627
- enhanceEdge: options.quickEnhance ?? false
16628
- }).then(
16629
- () => void 0,
16630
- (err) => {
16631
- this.error = err instanceof Error ? err.message : String(err);
16632
- }
16633
- ).finally(() => this.controller.setSelectionPreview(void 0));
16634
- return false;
16635
- }
16636
- const path = this.draftPath;
16637
- const rect3 = normalizeRect({
16638
- x: this.start.x,
16639
- y: this.start.y,
16640
- width: this.current.x - this.start.x,
16641
- height: this.current.y - this.start.y
16642
- });
16643
- this.start = null;
16644
- this.current = null;
16645
- this.points = [];
16646
- if (!path) return mode === "replace" ? this.controller.deselect() : false;
16647
- const opts = { mode, antiAlias: options.antiAlias, feather: options.feather ?? 0 };
16648
- if (options.tool === "lasso") return path.length >= 3 ? this.controller.selectPolygon(path, opts) : false;
16649
- return options.tool === "ellipse" ? this.controller.selectEllipse(rect3, opts) : this.controller.selectRect(rect3, opts);
16650
- }
16651
- /** Closes a polygonal lasso and selects it. Returns true when the selection changed. */
16652
- close() {
16653
- let polygon = this.polygon;
16654
- const m = this.magnetic;
16655
- if (m) {
16656
- const tail = m.edges && m.fixed.length > 1 ? liveWire(m.edges, m.fixed[m.fixed.length - 1], m.fixed[0], this.corridor(m.fixed[m.fixed.length - 1], m.fixed[0])) : [];
16657
- polygon = simplifyPixelPath([...m.fixed, ...tail.slice(1, -1)]);
16658
- }
16659
- if (!polygon) return false;
16660
- const options = this.getOptions();
16661
- this.cancel();
16662
- if (polygon.length < 3) return false;
16663
- return this.controller.selectPolygon(polygon, {
16664
- mode: selectionModeFromModifiers(this.modifiers, options.mode),
16665
- antiAlias: options.antiAlias,
16666
- feather: options.feather ?? 0
16667
- });
16668
- }
16669
- /**
16670
- * Keys for a polygonal lasso being drawn: Enter closes it, Escape cancels, Backspace / Delete
16671
- * removes the last corner. Returns true when the key was used (the host prevents its default).
16672
- */
16673
- key(key2) {
16674
- if (!this.polygon && !this.magnetic) return false;
16675
- if (key2 === "Enter") this.close();
16676
- else if (key2 === "Escape") this.cancel();
16677
- else if (key2 === "Backspace" || key2 === "Delete") {
16678
- const m = this.magnetic;
16679
- if (m) {
16680
- m.anchors.pop();
16681
- if (!m.anchors.length) this.cancel();
16682
- else {
16683
- m.fixed.length = m.anchors[m.anchors.length - 1] + 1;
16684
- m.live = [];
16685
- if (this.current) this.followEdges(this.current, false);
16686
- }
16687
- } else {
16688
- this.polygon.pop();
16689
- if (!this.polygon.length) this.cancel();
16690
- }
16691
- } else return false;
16692
- return true;
16693
- }
16694
- /** Corridor half-width for a live-wire between two points. */
16695
- corridor(a, b) {
16696
- return Math.max(2 * (this.getOptions().magneticWidth ?? 10), 16, Math.hypot(b.x - a.x, b.y - a.y) / 4);
16697
- }
16698
- async magneticClick(point2, modifiers, options) {
16699
- const m = this.magnetic;
16700
- if (!m) {
16701
- this.modifiers = modifiers;
16702
- const draft = { fixed: [point2], anchors: [0], live: [], edges: null };
16703
- this.magnetic = draft;
16704
- this.current = point2;
16705
- try {
16706
- const pixels = await this.controller.getSamplePixels({ sampleMerged: options.sampleMerged, sample: options.sample });
16707
- if (!pixels) throw new Error("The image pixels are not available to find edges in.");
16708
- if (this.magnetic !== draft) return;
16709
- draft.edges = new EdgeMap(pixels, { contrast: (options.magneticContrast ?? 10) / 100 });
16710
- draft.fixed = [snapToEdge(draft.edges, point2, options.magneticWidth ?? 10)];
16711
- if (this.current) this.followEdges(this.current);
16712
- } catch (err) {
16713
- this.error = err instanceof Error ? err.message : String(err);
16714
- if (this.magnetic === draft) this.cancel();
16715
- }
16716
- return;
16717
- }
16718
- const first = m.fixed[0];
16719
- if (m.anchors.length >= 2 && Math.hypot(point2.x - first.x, point2.y - first.y) <= this.closeRadius) {
16720
- this.close();
16721
- return;
16722
- }
16723
- this.current = point2;
16724
- this.followEdges(point2);
16725
- if (m.live.length > 1) {
16726
- m.fixed.push(...m.live.slice(1));
16727
- m.anchors.push(m.fixed.length - 1);
16728
- m.live = [];
16729
- }
16730
- if ((modifiers.detail ?? 1) >= 2) this.close();
16731
- }
16732
- /** Updates the live path to `point`, fixing anchors along it every `frequency` px (with `place`). */
16733
- followEdges(point2, place = true) {
16734
- const m = this.magnetic;
16735
- const last = () => m.fixed[m.fixed.length - 1];
16736
- if (!m.edges) {
16737
- m.live = [last(), point2];
16738
- return;
16739
- }
16740
- const options = this.getOptions();
16741
- const target = snapToEdge(m.edges, point2, options.magneticWidth ?? 10);
16742
- const spacing = magneticAnchorSpacing(options.magneticFrequency ?? 57);
16743
- for (let guard = 0; guard < 64; guard++) {
16744
- const path = liveWire(m.edges, last(), target, this.corridor(last(), target));
16745
- let length = 0;
16746
- let k = 0;
16747
- for (k = 1; k < path.length; k++) {
16748
- length += Math.hypot(path[k].x - path[k - 1].x, path[k].y - path[k - 1].y);
16749
- if (length >= spacing) break;
16750
- }
16751
- if (!place || k >= path.length - 1) {
16752
- m.live = path;
16753
- return;
16754
- }
16755
- m.fixed.push(...path.slice(1, k + 1));
16756
- m.anchors.push(m.fixed.length - 1);
16757
- }
16758
- m.live = [last(), target];
16759
- }
16760
- /** Abandons a drag (or a polygonal lasso) without changing the selection. */
16761
- cancel() {
16762
- if (this.quick) {
16763
- this.quick = null;
16764
- this.controller.setSelectionPreview(void 0);
16765
- }
16766
- this.start = null;
16767
- this.current = null;
16768
- this.points = [];
16769
- this.polygon = null;
16770
- this.magnetic = null;
16771
- this.pressedAt = null;
16772
- this.freehand = false;
16773
- }
16774
- };
16775
- function handleSelectionShortcut(controller, e, onError) {
16776
- if (!(e.ctrlKey || e.metaKey)) return false;
16777
- const key2 = e.key.toLowerCase();
16778
- const run = (fn) => {
16779
- try {
16780
- fn();
16781
- } catch (err) {
16782
- onError?.(err instanceof Error ? err.message : String(err));
16783
- }
16784
- return true;
16785
- };
16786
- if (key2 === "c") return run(() => controller.copyPixels({ merged: Boolean(e.shiftKey) }));
16787
- if (key2 === "x" && !e.shiftKey) return run(() => controller.cutPixels());
16788
- if (key2 === "v") return run(() => controller.pastePixels(e.shiftKey && e.altKey ? "into" : e.shiftKey ? "in-place" : "new-layer"));
16789
- if (key2 === "d") return run(() => e.shiftKey ? controller.reselect() : controller.deselect());
16790
- if (key2 === "i" && e.shiftKey) return run(() => controller.invertSelection());
16791
- return false;
16792
- }
16793
- function handleMaskViewKey(controller, e) {
16794
- if (e.ctrlKey || e.metaKey || e.altKey || e.key !== "\\") return false;
16795
- const id = controller.getState().activeLayerId;
16796
- const layer = id ? controller.getLayer(id) : null;
16797
- if (!layer?.mask || layer.mask.source) return false;
16798
- const view = controller.getMaskView();
16799
- controller.setMaskView(view?.layerId === layer.id && view.mode === "overlay" ? "off" : "overlay", layer.id);
16800
- return true;
16801
- }
16802
- function handleQuickMaskKey(controller, e) {
16803
- if (e.ctrlKey || e.metaKey || e.altKey || e.key.toLowerCase() !== "q") return false;
16804
- controller.setQuickMaskMode(!controller.quickMaskEditing);
16805
- return true;
16806
- }
16807
-
16808
- // src/image-editor/layers/paint-ui.ts
16809
- var plain2 = { kind: "plain" };
16810
- var BRUSH_OPTION_CONTROLS = [
16811
- { id: "size", label: "Size", section: "layer", min: 1, max: 500, step: 1, default: 20, decimals: 0, track: plain2, unit: "px" },
16812
- { id: "hardness", label: "Hardness", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
16813
- { id: "opacity", label: "Opacity", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
16814
- { id: "flow", label: "Flow", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
16815
- { id: "smoothing", label: "Smoothing", section: "layer", min: 0, max: 0.95, step: 0.01, default: 0, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
16816
- ];
16817
- var MIXER_OPTION_CONTROLS = [
16818
- { id: "wet", label: "Wet", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
16819
- { id: "load", label: "Load", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
16820
- { id: "mix", label: "Mix", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
16821
- ];
16822
- var MIXER_MIXING_OPTIONS = [
16823
- { id: "rgb", label: "RGB", title: "Mix colours as RGB averages" },
16824
- { id: "pigment", label: "Pigment", title: "Mix colours like paint: blue and yellow make green" }
16825
- ];
16826
- var PAINT_TOOLS = [
16827
- { id: "move", label: "Move", shortcut: "V" },
16828
- { id: "brush", label: "Brush", shortcut: "B" },
16829
- { id: "mixer", label: "Mixer", shortcut: "Shift+B" },
16830
- { id: "eraser", label: "Eraser", shortcut: "E" },
16831
- { id: "clone", label: "Clone", shortcut: "S" },
16832
- { id: "heal", label: "Heal", shortcut: "Shift+J" },
16833
- { id: "spot-heal", label: "Spot Heal", shortcut: "J" },
16834
- { id: "smudge", label: "Smudge", shortcut: "" },
16835
- { id: "dodge", label: "Dodge", shortcut: "O" },
16836
- { id: "burn", label: "Burn", shortcut: "Shift+O" },
16837
- { id: "sponge", label: "Sponge", shortcut: "Shift+O" },
16838
- { id: "blur", label: "Blur", shortcut: "" },
16839
- { id: "sharpen", label: "Sharpen", shortcut: "" },
16840
- { id: "history", label: "History", shortcut: "Y" }
16841
- ];
16842
- var isPaintTool = (tool) => PAINT_TOOLS.some((t) => t.id === tool);
16843
- var isRetouchPaintTool = (tool) => tool === "clone" || tool === "heal" || tool === "spot-heal" || tool === "smudge" || tool === "dodge" || tool === "burn" || tool === "sponge" || tool === "blur" || tool === "sharpen" || tool === "history";
16844
- var RETOUCH_OPTION_CONTROLS = [
16845
- { id: "exposure", label: "Exposure", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%", tools: ["dodge", "burn"] },
16846
- { id: "strength", label: "Strength", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%", tools: ["smudge", "blur", "sharpen"] },
16847
- { id: "historySteps", label: "Steps back", section: "layer", min: 0, max: 50, step: 1, default: 0, decimals: 0, track: plain2, unit: "", tools: ["history"] }
16848
- ];
16849
- var CLONE_TRANSFORM_CONTROLS = [
16850
- { id: "width", label: "Source W", section: "layer", min: 1, max: 400, step: 1, default: 100, decimals: 0, track: plain2, unit: "%" },
16851
- { id: "height", label: "Source H", section: "layer", min: 1, max: 400, step: 1, default: 100, decimals: 0, track: plain2, unit: "%" },
16852
- { id: "angle", label: "Source angle", section: "layer", min: -180, max: 180, step: 1, default: 0, decimals: 0, track: plain2, unit: "\xB0" }
16853
- ];
16854
- function cloneTransformView(controller) {
16855
- const t = controller.getState().paint.cloneTransform;
16856
- return {
16857
- width: Math.round(Math.abs(t.scaleX) * 100),
16858
- height: Math.round(Math.abs(t.scaleY) * 100),
16859
- angle: t.rotate,
16860
- flipH: t.scaleX < 0,
16861
- flipV: t.scaleY < 0,
16862
- identity: t.scaleX === 1 && t.scaleY === 1 && t.rotate === 0
16863
- };
16864
- }
16865
- function setCloneTransformValue(controller, id, value) {
16866
- const t = controller.getState().paint.cloneTransform;
16867
- if (id === "width") controller.setCloneTransform({ scaleX: Math.sign(t.scaleX) * (value ?? 100) / 100 });
16868
- else if (id === "height") controller.setCloneTransform({ scaleY: Math.sign(t.scaleY) * (value ?? 100) / 100 });
16869
- else if (id === "angle") controller.setCloneTransform({ rotate: value ?? 0 });
16870
- else if (id === "flipH") controller.setCloneTransform({ scaleX: -t.scaleX });
16871
- else controller.setCloneTransform({ scaleY: -t.scaleY });
16872
- }
16873
- var RETOUCH_CHOICES = [
16874
- {
16875
- id: "sample",
16876
- label: "Sample",
16877
- tools: ["clone", "heal", "spot-heal"],
16878
- options: [
16879
- { id: "current", label: "Current", title: "Sample the layer being retouched" },
16880
- { id: "current-below", label: "Current & Below", title: "Sample this layer and the layers under it" },
16881
- { id: "all", label: "All", title: "Sample every layer (retouch onto an empty layer)" }
16882
- ]
16883
- },
16884
- {
16885
- id: "spotType",
16886
- label: "Type",
16887
- tools: ["spot-heal"],
16888
- options: [
16889
- { id: "proximity-match", label: "Proximity Match", title: "Copy the best matching patch nearby" },
16890
- { id: "content-aware", label: "Content-Aware", title: "Synthesise texture and edges from patches around the spot" },
16891
- { id: "smooth", label: "Smooth", title: "Fill smoothly from the edges" }
16892
- ]
16893
- },
16894
- {
16895
- id: "range",
16896
- label: "Range",
16897
- tools: ["dodge", "burn"],
16898
- options: [
16899
- { id: "shadows", label: "Shadows", title: "Change the dark tones most" },
16900
- { id: "midtones", label: "Midtones", title: "Change the middle tones most" },
16901
- { id: "highlights", label: "Highlights", title: "Change the light tones most" }
16902
- ]
16903
- },
16904
- {
16905
- id: "spongeMode",
16906
- label: "Mode",
16907
- tools: ["sponge"],
16908
- options: [
16909
- { id: "desaturate", label: "Desaturate", title: "Take colour out" },
16910
- { id: "saturate", label: "Saturate", title: "Add colour" }
16911
- ]
16912
- }
16913
- ];
16914
- var RETOUCH_TOGGLES = [
16915
- { id: "aligned", label: "Aligned", tools: ["clone", "heal"] },
16916
- { id: "protectTones", label: "Protect tones", tools: ["dodge", "burn"] },
16917
- { id: "vibrance", label: "Vibrance", tools: ["sponge"] }
16918
- ];
16919
- function setRetouchChoice(controller, id, value) {
16920
- controller.setPaintOptions({ retouch: { [id]: value } });
16921
- }
16922
- function cloneSourceLabel(controller) {
16923
- const cs = controller.getState().paint.cloneSource;
16924
- return cs ? `Source ${cs.x}, ${cs.y}${cs.offset ? ` (offset ${cs.offset.x}, ${cs.offset.y})` : ""}` : "Alt-click to set the source";
16925
- }
16926
- function rgbUnitToHex(c) {
16927
- return `#${c.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("")}`;
16928
- }
16929
- function sizeStep(size) {
16930
- return size < 10 ? 1 : size < 50 ? 5 : size < 100 ? 10 : size < 200 ? 25 : size < 500 ? 50 : 100;
16931
- }
16932
- var isTyping = (t) => {
16933
- const el = t;
16934
- return Boolean(el && (el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT"));
16935
- };
16936
- function handlePaintShortcut(controller, e) {
16937
- if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
16938
- const brush = controller.getState().paint.brush;
16939
- const set = (b) => controller.setPaintOptions({ brush: b });
16940
- const key2 = e.key;
16941
- if (handleMaskViewKey(controller, e)) return true;
16942
- switch (key2.toLowerCase()) {
16943
- case "q":
16944
- controller.setQuickMaskMode(!controller.quickMaskEditing);
16945
- return true;
16946
- case "b":
16947
- controller.setTool(e.shiftKey || key2 === "B" ? "mixer" : "brush");
16948
- return true;
16949
- case "e":
16950
- controller.setTool("eraser");
16951
- return true;
16952
- case "v":
16953
- controller.setTool("move");
16954
- return true;
16955
- case "s":
16956
- controller.setTool("clone");
16957
- return true;
16958
- case "j":
16959
- controller.setTool(e.shiftKey || key2 === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
16960
- return true;
16961
- case "o": {
16962
- const cur = controller.getState().activeTool;
16963
- const next = e.shiftKey || key2 === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
16964
- controller.setTool(next);
16965
- return true;
16966
- }
16967
- case "y":
16968
- controller.setTool("history");
16969
- return true;
16970
- case "d":
16971
- controller.resetPaintColors();
16972
- return true;
16973
- case "x":
16974
- controller.swapPaintColors();
16975
- return true;
16976
- }
16977
- if (key2 === "[" || key2 === "{") {
16978
- if (e.shiftKey || key2 === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
16979
- else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
16980
- return true;
16981
- }
16982
- if (key2 === "]" || key2 === "}") {
16983
- if (e.shiftKey || key2 === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
16984
- else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
16985
- return true;
16986
- }
16987
- if (/^[0-9]$/.test(key2) && isPaintTool(controller.getState().activeTool)) {
16988
- set({ opacity: key2 === "0" ? 1 : Number(key2) / 10 });
16989
- return true;
16990
- }
16991
- return false;
16992
- }
16993
- var PaintGesture = class {
16994
- constructor(controller) {
16995
- this.controller = controller;
16996
- }
16997
- controller;
16998
- stroke = null;
16999
- move = null;
17000
- /** The last refusal (e.g. "layer is locked"), for the editor to show. */
17001
- error = null;
17002
- get active() {
17003
- return Boolean(this.stroke || this.move);
17004
- }
17005
- down(sample) {
17006
- this.error = null;
17007
- const tool = this.controller.getState().activeTool;
17008
- try {
17009
- if (tool === "move") {
17010
- const layer = this.controller.getActiveLayer();
17011
- if (!layer) throw new Error("Select a layer to move in the Layers panel.");
17012
- if (layer.type !== "pixel" && layer.type !== "shape" && layer.type !== "smart") throw new Error(`Only pixel, shape and smart object layers move; "${layer.name}" is a ${layer.type} layer.`);
17013
- if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
17014
- const origin = layer.type === "pixel" ? { ...layer.offset } : layer.type === "smart" ? { ...layer.placement.corners[0] } : (() => {
17015
- const b = shapeGeometryBounds(layer);
17016
- return b ? { x: b.x, y: b.y } : { x: 0, y: 0 };
17017
- })();
17018
- this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin, moved: false };
17019
- return null;
17020
- }
17021
- if ((tool === "clone" || tool === "heal") && sample.altKey) {
17022
- this.controller.setCloneSource({ x: sample.x, y: sample.y });
17023
- return null;
17024
- }
17025
- if (isPaintTool(tool)) {
17026
- this.stroke = this.controller.beginStroke({ tool });
17027
- this.stroke.push([{ x: sample.x, y: sample.y, pressure: sample.pressure }]);
17028
- return null;
17029
- }
17030
- } catch (err) {
17031
- this.error = userErrorMessage(err);
17032
- this.stroke = null;
17033
- this.move = null;
17034
- }
17035
- return this.error;
17036
- }
17037
- /** Feeds pointer samples (all coalesced events of one move, in order). */
17038
- moveTo(samples) {
17039
- if (!samples.length) return;
17040
- if (this.stroke) {
17041
- this.stroke.push(samples);
17042
- return;
17043
- }
17044
- const m = this.move;
17045
- if (m) {
17046
- const last = samples[samples.length - 1];
17047
- const x = m.origin.x + Math.round(last.x - m.start.x);
17048
- const y = m.origin.y + Math.round(last.y - m.start.y);
17049
- try {
17050
- this.controller.offsetLayer(m.id, { x, y }, !m.moved);
17051
- m.moved = true;
17052
- } catch (err) {
17053
- this.error = userErrorMessage(err);
17054
- this.move = null;
17055
- }
17056
- }
17057
- }
17058
- up() {
17059
- if (this.stroke) {
17060
- try {
17061
- this.stroke.end();
17062
- } catch (err) {
17063
- this.error = userErrorMessage(err);
17064
- }
17065
- }
17066
- this.stroke = null;
17067
- this.move = null;
17068
- }
17069
- cancel() {
17070
- this.stroke?.cancel();
17071
- this.stroke = null;
17072
- this.move = null;
17073
- }
17074
- };
17075
-
17076
15977
  // src/image-editor/paint-tools.ts
17077
15978
  var READ3 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
17078
15979
  var WRITE3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };