@pixerate/editor 0.207.0 → 0.208.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/dist/animation-editor/index.cjs +60 -59
- package/dist/animation-editor/index.js +6 -5
- package/dist/{brushstrokeRender-7LXCU7PD.js → brushstrokeRender-C3UU6GVC.js} +5 -4
- package/dist/{brushstrokeRender-VO4ZTWVA.cjs → brushstrokeRender-EAJNE6WP.cjs} +12 -11
- package/dist/canvas/index.cjs +8 -8
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-2N2QPO3H.js → chunk-2PBZBRP5.js} +9 -7
- package/dist/{chunk-7NW46N7I.cjs → chunk-3EHPLDFQ.cjs} +54 -52
- package/dist/{chunk-H367YTXI.js → chunk-42TWUPNJ.js} +13 -387
- package/dist/chunk-474IQG74.cjs +384 -0
- package/dist/{chunk-MLC7HUHO.js → chunk-BGOCAUXH.js} +1 -1
- package/dist/chunk-DWYQPL2A.js +2010 -0
- package/dist/{chunk-YXKLVD56.js → chunk-F7YA3MMC.js} +2 -2
- package/dist/{chunk-ZZGPYNS2.js → chunk-G4E6RUPC.js} +1 -1
- package/dist/chunk-HVVR5PZW.js +384 -0
- package/dist/{chunk-FR2Q2LVF.cjs → chunk-I7G3RSUJ.cjs} +35 -35
- package/dist/chunk-K6PXS6DO.cjs +102 -0
- package/dist/{chunk-ZIDUPSP4.js → chunk-KFIOEOCQ.js} +24 -761
- package/dist/{chunk-V7GWZIZK.cjs → chunk-KWNMBMKG.cjs} +23 -21
- package/dist/chunk-L3G23N5Y.js +763 -0
- package/dist/{chunk-GHCXLQ47.js → chunk-NDMXB4PR.js} +2 -2
- package/dist/{chunk-CWM3CUUX.cjs → chunk-OLNIPTB3.cjs} +39 -413
- package/dist/{chunk-EBDCRTZI.cjs → chunk-PQ3H2AAN.cjs} +92 -90
- package/dist/{chunk-BRKVNXDZ.cjs → chunk-QAUIZPYI.cjs} +7 -7
- package/dist/{chunk-UYXL6ZWQ.cjs → chunk-QE7D7TZR.cjs} +494 -1231
- package/dist/chunk-QYLWG22N.cjs +763 -0
- package/dist/{chunk-YQ5C5BVK.cjs → chunk-ROVK6AXX.cjs} +60 -60
- package/dist/{chunk-HGTDMKNE.cjs → chunk-UOGKSVRQ.cjs} +126 -221
- package/dist/{chunk-EL5PAHNP.js → chunk-USQCKRZG.js} +6 -4
- package/dist/{chunk-36OKQDCF.js → chunk-WJ2RBZMM.js} +5 -3
- package/dist/chunk-X4LUGTQX.cjs +2010 -0
- package/dist/{chunk-NQMP3CWF.js → chunk-YBJXCQTR.js} +15 -110
- package/dist/chunk-ZIF4UJJ6.js +102 -0
- package/dist/{chunk-WTW7QRTS.cjs → chunk-ZMHPQ6HP.cjs} +1 -1
- package/dist/image-editor/controller.d.cts +28 -3
- package/dist/image-editor/controller.d.ts +28 -3
- package/dist/image-editor/index.cjs +1726 -3359
- package/dist/image-editor/index.js +241 -1874
- package/dist/image-editor/selection/index.d.cts +1 -0
- package/dist/image-editor/selection/index.d.ts +1 -0
- package/dist/image-editor/selection/segment/focus.d.cts +2 -0
- package/dist/image-editor/selection/segment/focus.d.ts +2 -0
- package/dist/image-editor/selection/segment/grabcut.d.cts +4 -2
- package/dist/image-editor/selection/segment/grabcut.d.ts +4 -2
- package/dist/image-editor/selection/segment/sky.d.cts +2 -0
- package/dist/image-editor/selection/segment/sky.d.ts +2 -0
- package/dist/image-editor/selection/segment/subject.d.cts +2 -1
- package/dist/image-editor/selection/segment/subject.d.ts +2 -1
- package/dist/image-editor/selection/segment/work.d.cts +5 -0
- package/dist/image-editor/selection/segment/work.d.ts +5 -0
- package/dist/image-editor/selection/select-and-mask.d.cts +12 -2
- package/dist/image-editor/selection/select-and-mask.d.ts +12 -2
- package/dist/image-editor/selection/worker.d.cts +126 -0
- package/dist/image-editor/selection/worker.d.ts +126 -0
- package/dist/image-editor/selection-worker.cjs +14 -0
- package/dist/image-editor/selection-worker.d.cts +12 -0
- package/dist/image-editor/selection-worker.d.ts +12 -0
- package/dist/image-editor/selection-worker.js +14 -0
- package/dist/image-editor/types.d.cts +8 -0
- package/dist/image-editor/types.d.ts +8 -0
- package/dist/paint/index.cjs +6 -5
- package/dist/paint/index.js +5 -4
- package/dist/paint-editor/index.cjs +394 -390
- package/dist/paint-editor/index.js +18 -14
- package/dist/raster/index.cjs +22 -18
- package/dist/raster/index.js +29 -25
- package/dist/vector/index.cjs +986 -985
- package/dist/vector/index.js +6 -5
- package/package.json +11 -1
|
@@ -3,6 +3,56 @@ import {
|
|
|
3
3
|
parseOpenTypeFont,
|
|
4
4
|
traceField
|
|
5
5
|
} from "../chunk-5OC22IEO.js";
|
|
6
|
+
import {
|
|
7
|
+
ColorGmm,
|
|
8
|
+
DEFAULT_REFINE_EDGE_PARAMS,
|
|
9
|
+
GAMMA,
|
|
10
|
+
GUIDED_FILTER_EPS,
|
|
11
|
+
GridGraph,
|
|
12
|
+
REFINE_EDIT_KINDS,
|
|
13
|
+
SelectionWorkerClient,
|
|
14
|
+
UNITS,
|
|
15
|
+
WORK_SIZE,
|
|
16
|
+
applyRefineEdits,
|
|
17
|
+
boxBlur,
|
|
18
|
+
boxMean,
|
|
19
|
+
brushStrokeMask,
|
|
20
|
+
decontaminateColors,
|
|
21
|
+
deserializeMask,
|
|
22
|
+
edgeWidth,
|
|
23
|
+
ellipsePoints,
|
|
24
|
+
ellipseSelection,
|
|
25
|
+
findSky,
|
|
26
|
+
findSubject,
|
|
27
|
+
focusArea,
|
|
28
|
+
grabCutLabels,
|
|
29
|
+
guidePlanes,
|
|
30
|
+
guidedFilterColor,
|
|
31
|
+
keepMainPieces,
|
|
32
|
+
normalizeRect,
|
|
33
|
+
normalizeRefineEdgeParams,
|
|
34
|
+
otsuThreshold,
|
|
35
|
+
polygonCoverage,
|
|
36
|
+
polygonCoverageBox,
|
|
37
|
+
polygonSelection,
|
|
38
|
+
quickSelectStroke,
|
|
39
|
+
rectSelection,
|
|
40
|
+
refineCoverage,
|
|
41
|
+
refineEdgeHalo,
|
|
42
|
+
refineSelection,
|
|
43
|
+
runSelectionJob,
|
|
44
|
+
selectObjectIn,
|
|
45
|
+
serializeMask,
|
|
46
|
+
slicSuperpixels,
|
|
47
|
+
smoothnessWeights,
|
|
48
|
+
softMaskToSelection,
|
|
49
|
+
startSelectionWorker,
|
|
50
|
+
superpixelSaliency,
|
|
51
|
+
upscaleRegion,
|
|
52
|
+
workImage,
|
|
53
|
+
workLuma,
|
|
54
|
+
workPlanes
|
|
55
|
+
} from "../chunk-DWYQPL2A.js";
|
|
6
56
|
import {
|
|
7
57
|
ADJUSTMENT_KEYS,
|
|
8
58
|
ADJUSTMENT_KINDS,
|
|
@@ -20,7 +70,6 @@ import {
|
|
|
20
70
|
DEFAULT_MITER_LIMIT,
|
|
21
71
|
DEFAULT_TRANSFORM_VALUES,
|
|
22
72
|
DEVELOP_FILTER_ID,
|
|
23
|
-
EMPTY_DISTANCE,
|
|
24
73
|
FREE_TRANSFORM_MODES,
|
|
25
74
|
HUE_RANGE_LABELS,
|
|
26
75
|
IDENTITY_MATRIX3,
|
|
@@ -39,8 +88,6 @@ import {
|
|
|
39
88
|
PATTERN_BLOCK_KEYS,
|
|
40
89
|
PSD_BLEND_KEYS,
|
|
41
90
|
PsdError,
|
|
42
|
-
SELECTION_MODES,
|
|
43
|
-
SELECTION_MODIFY_OPS,
|
|
44
91
|
SHAPE_OPERATIONS,
|
|
45
92
|
SHAPE_RENDER_MARGIN,
|
|
46
93
|
SMART_RENDER_MARGIN,
|
|
@@ -89,7 +136,6 @@ import {
|
|
|
89
136
|
backgroundLayer,
|
|
90
137
|
blackWhiteGray,
|
|
91
138
|
blurFilter,
|
|
92
|
-
borderCoverage,
|
|
93
139
|
boxPointOf,
|
|
94
140
|
bradfordAdaptation,
|
|
95
141
|
buildLayerMaskPlanes,
|
|
@@ -98,21 +144,16 @@ import {
|
|
|
98
144
|
canvasToImagePoint,
|
|
99
145
|
clampCropToImage,
|
|
100
146
|
colorBalanceChannel,
|
|
101
|
-
combineSelections,
|
|
102
147
|
compileAdjustmentLayer,
|
|
103
148
|
componentPath,
|
|
104
149
|
componentsCoverage,
|
|
105
150
|
compositeDeep,
|
|
106
|
-
computeSelectionBounds,
|
|
107
151
|
contentsBox,
|
|
108
152
|
contentsSurface,
|
|
109
|
-
contractCoverage,
|
|
110
153
|
convertToSmartObject,
|
|
111
154
|
copySmartObject,
|
|
112
155
|
cornersFromMatrix,
|
|
113
|
-
coverageToByte,
|
|
114
156
|
createAdjustmentLayer,
|
|
115
|
-
createSelectionMask,
|
|
116
157
|
createShapeLayer,
|
|
117
158
|
createTypeLayer,
|
|
118
159
|
curveLutFromLevels,
|
|
@@ -122,7 +163,6 @@ import {
|
|
|
122
163
|
decodeMergedImage,
|
|
123
164
|
decodePlanes,
|
|
124
165
|
decodePngDataUrl,
|
|
125
|
-
decodeSelectionRle,
|
|
126
166
|
defaultActiveLayerId,
|
|
127
167
|
defaultAdjustmentParams,
|
|
128
168
|
defaultShapeName,
|
|
@@ -133,39 +173,31 @@ import {
|
|
|
133
173
|
deserializeLayer,
|
|
134
174
|
deserializeLayers,
|
|
135
175
|
deserializePatterns,
|
|
136
|
-
deserializeSelection,
|
|
137
176
|
developFilter,
|
|
138
177
|
developFromAdjustments,
|
|
139
178
|
developTarget,
|
|
140
|
-
distanceTransform,
|
|
141
179
|
documentContents,
|
|
142
180
|
duplicateLayer,
|
|
143
181
|
effectiveLayerMask,
|
|
144
182
|
encodeDeepSurface,
|
|
145
183
|
encodeRle,
|
|
146
|
-
encodeSelectionRle,
|
|
147
184
|
encodeSurface,
|
|
148
185
|
evaluateAdjustmentLayers,
|
|
149
|
-
expandCoverage,
|
|
150
186
|
exportPsd,
|
|
151
|
-
featherCoverage,
|
|
152
187
|
filterMaskCoverage,
|
|
153
188
|
flattenPath,
|
|
154
189
|
flipCorners,
|
|
155
190
|
followLayerMove,
|
|
156
|
-
fullSelection,
|
|
157
191
|
geometryHomography,
|
|
158
192
|
getAnnotationBounds,
|
|
159
193
|
getCropCursor,
|
|
160
194
|
getRotatedSize,
|
|
161
195
|
getViewportMetrics,
|
|
162
196
|
getViewportProjection,
|
|
163
|
-
greyMorphology,
|
|
164
197
|
groupLayers,
|
|
165
198
|
hasResampledLegacyMask,
|
|
166
199
|
hasSurface,
|
|
167
200
|
hasWarp,
|
|
168
|
-
hashMaskContent,
|
|
169
201
|
hexToRgb,
|
|
170
202
|
hitTestAnnotation,
|
|
171
203
|
hitTestCrop,
|
|
@@ -180,7 +212,6 @@ import {
|
|
|
180
212
|
importPsd,
|
|
181
213
|
instancesOf,
|
|
182
214
|
invertMatrix3,
|
|
183
|
-
invertSelection,
|
|
184
215
|
isActiveFilter,
|
|
185
216
|
isActiveVectorMask,
|
|
186
217
|
isAdjustmentKind,
|
|
@@ -191,8 +222,6 @@ import {
|
|
|
191
222
|
isPaintableLayer,
|
|
192
223
|
isQuarterTurnSwapped,
|
|
193
224
|
isRenderedLayer,
|
|
194
|
-
isSelectionEmpty,
|
|
195
|
-
isSelectionMode,
|
|
196
225
|
isSmartSurfaceIncomplete,
|
|
197
226
|
isSpatialFilter,
|
|
198
227
|
isTransformAnchor,
|
|
@@ -220,7 +249,6 @@ import {
|
|
|
220
249
|
matrixFromCorners,
|
|
221
250
|
matrixFromParams,
|
|
222
251
|
mergeShapeComponents,
|
|
223
|
-
modifySelection,
|
|
224
252
|
moveLayer,
|
|
225
253
|
moveSmartFilter,
|
|
226
254
|
mulMatrix3,
|
|
@@ -249,7 +277,6 @@ import {
|
|
|
249
277
|
parseLfx2,
|
|
250
278
|
parseLrfx,
|
|
251
279
|
parsePatternBlock,
|
|
252
|
-
parseSelectionMode,
|
|
253
280
|
parseVersionedDescriptor,
|
|
254
281
|
patchParagraphStyle,
|
|
255
282
|
patchTextStyle,
|
|
@@ -295,7 +322,6 @@ import {
|
|
|
295
322
|
rerender,
|
|
296
323
|
rerenderShape,
|
|
297
324
|
rerenderText,
|
|
298
|
-
resizeSelection,
|
|
299
325
|
resolveExternalAssets,
|
|
300
326
|
resolveVectorMasks,
|
|
301
327
|
rgbToGray,
|
|
@@ -304,21 +330,12 @@ import {
|
|
|
304
330
|
runLayerFiltersFloat,
|
|
305
331
|
sampleLut,
|
|
306
332
|
sampleToneLut,
|
|
307
|
-
selectionArea,
|
|
308
|
-
selectionFromCoverage,
|
|
309
|
-
selectionFromPixels,
|
|
310
|
-
selectionFromRegion,
|
|
311
|
-
selectionFromTiles,
|
|
312
|
-
selectionToCoverage,
|
|
313
|
-
selectionToPixels,
|
|
314
|
-
selectionValueAt,
|
|
315
333
|
serializeContents,
|
|
316
334
|
serializeEffects,
|
|
317
335
|
serializeLayer,
|
|
318
336
|
serializeLayers,
|
|
319
337
|
serializeLayersExternal,
|
|
320
338
|
serializePatterns,
|
|
321
|
-
serializeSelection,
|
|
322
339
|
setLayerEffects,
|
|
323
340
|
setLayerEffectsEnabled,
|
|
324
341
|
setLayerMask,
|
|
@@ -332,10 +349,8 @@ import {
|
|
|
332
349
|
shapeLayers,
|
|
333
350
|
shapeOutline,
|
|
334
351
|
shapeRenderClip,
|
|
335
|
-
shiftCoverage,
|
|
336
352
|
smartContentsFile,
|
|
337
353
|
smartObjectLayers,
|
|
338
|
-
smoothCoverage,
|
|
339
354
|
srgbToLinear,
|
|
340
355
|
tempTintToXy,
|
|
341
356
|
temperatureToKelvin,
|
|
@@ -386,10 +401,47 @@ import {
|
|
|
386
401
|
writePsd,
|
|
387
402
|
xyToTempTint,
|
|
388
403
|
xyToUv
|
|
389
|
-
} from "../chunk-
|
|
404
|
+
} from "../chunk-KFIOEOCQ.js";
|
|
390
405
|
import {
|
|
391
406
|
parseCssColor
|
|
392
407
|
} from "../chunk-3CI7FRMK.js";
|
|
408
|
+
import {
|
|
409
|
+
EMPTY_DISTANCE,
|
|
410
|
+
SELECTION_MODES,
|
|
411
|
+
SELECTION_MODIFY_OPS,
|
|
412
|
+
borderCoverage,
|
|
413
|
+
combineSelections,
|
|
414
|
+
computeSelectionBounds,
|
|
415
|
+
contractCoverage,
|
|
416
|
+
coverageToByte,
|
|
417
|
+
createSelectionMask,
|
|
418
|
+
decodeSelectionRle,
|
|
419
|
+
deserializeSelection,
|
|
420
|
+
distanceTransform,
|
|
421
|
+
encodeSelectionRle,
|
|
422
|
+
expandCoverage,
|
|
423
|
+
featherCoverage,
|
|
424
|
+
fullSelection,
|
|
425
|
+
greyMorphology,
|
|
426
|
+
hashMaskContent,
|
|
427
|
+
invertSelection,
|
|
428
|
+
isSelectionEmpty,
|
|
429
|
+
isSelectionMode,
|
|
430
|
+
modifySelection,
|
|
431
|
+
parseSelectionMode,
|
|
432
|
+
resizeSelection,
|
|
433
|
+
selectionArea,
|
|
434
|
+
selectionFromCoverage,
|
|
435
|
+
selectionFromPixels,
|
|
436
|
+
selectionFromRegion,
|
|
437
|
+
selectionFromTiles,
|
|
438
|
+
selectionToCoverage,
|
|
439
|
+
selectionToPixels,
|
|
440
|
+
selectionValueAt,
|
|
441
|
+
serializeSelection,
|
|
442
|
+
shiftCoverage,
|
|
443
|
+
smoothCoverage
|
|
444
|
+
} from "../chunk-L3G23N5Y.js";
|
|
393
445
|
import {
|
|
394
446
|
EffectRendererPool
|
|
395
447
|
} from "../chunk-EHLOCVWB.js";
|
|
@@ -398,7 +450,6 @@ import {
|
|
|
398
450
|
} from "../chunk-5ED73EC6.js";
|
|
399
451
|
import {
|
|
400
452
|
DEFAULT_GLOBAL_LIGHT,
|
|
401
|
-
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
402
453
|
INTERPOLATIONS,
|
|
403
454
|
LAYER_EFFECT_KINDS,
|
|
404
455
|
LayerEffectError,
|
|
@@ -422,7 +473,6 @@ import {
|
|
|
422
473
|
effectsScale,
|
|
423
474
|
findLayer,
|
|
424
475
|
flattenLayers,
|
|
425
|
-
gaussianBlur,
|
|
426
476
|
hasActiveEffects,
|
|
427
477
|
hashNoise,
|
|
428
478
|
insertLayer,
|
|
@@ -447,7 +497,7 @@ import {
|
|
|
447
497
|
validateLayerEffects,
|
|
448
498
|
warpSurface,
|
|
449
499
|
workingToSamples
|
|
450
|
-
} from "../chunk-
|
|
500
|
+
} from "../chunk-YBJXCQTR.js";
|
|
451
501
|
import {
|
|
452
502
|
applyHomography,
|
|
453
503
|
boxQuad,
|
|
@@ -457,6 +507,10 @@ import {
|
|
|
457
507
|
multiplyHomographies,
|
|
458
508
|
rectToQuad
|
|
459
509
|
} from "../chunk-OHKOFKBY.js";
|
|
510
|
+
import {
|
|
511
|
+
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
512
|
+
gaussianBlur
|
|
513
|
+
} from "../chunk-ZIF4UJJ6.js";
|
|
460
514
|
import {
|
|
461
515
|
DEFAULT_RETOUCH,
|
|
462
516
|
IDENTITY_CLONE_TRANSFORM,
|
|
@@ -495,7 +549,7 @@ import {
|
|
|
495
549
|
spotHealPixels,
|
|
496
550
|
spotHealWindow,
|
|
497
551
|
toneOf
|
|
498
|
-
} from "../chunk-
|
|
552
|
+
} from "../chunk-USQCKRZG.js";
|
|
499
553
|
import {
|
|
500
554
|
BODY_SCATTER,
|
|
501
555
|
DEFAULT_FLOW,
|
|
@@ -586,7 +640,7 @@ import {
|
|
|
586
640
|
wetGpuSupport,
|
|
587
641
|
wetSimdSupported,
|
|
588
642
|
wetSupport
|
|
589
|
-
} from "../chunk-
|
|
643
|
+
} from "../chunk-WJ2RBZMM.js";
|
|
590
644
|
import {
|
|
591
645
|
BUILTIN_PROFILES,
|
|
592
646
|
CmsError,
|
|
@@ -646,8 +700,8 @@ import {
|
|
|
646
700
|
warpMeshParamAt,
|
|
647
701
|
warpMeshRows,
|
|
648
702
|
warpPresetMesh
|
|
649
|
-
} from "../chunk-
|
|
650
|
-
import "../chunk-
|
|
703
|
+
} from "../chunk-NDMXB4PR.js";
|
|
704
|
+
import "../chunk-F7YA3MMC.js";
|
|
651
705
|
import "../chunk-M6DNZNVY.js";
|
|
652
706
|
import {
|
|
653
707
|
bezierPathToSvg
|
|
@@ -758,7 +812,7 @@ import {
|
|
|
758
812
|
tipShape,
|
|
759
813
|
updatePaintGradientStop,
|
|
760
814
|
walkDabs
|
|
761
|
-
} from "../chunk-
|
|
815
|
+
} from "../chunk-2PBZBRP5.js";
|
|
762
816
|
import {
|
|
763
817
|
BRUSHSTROKE_MAX_SCALE,
|
|
764
818
|
BRUSHSTROKE_MIN_SCALE,
|
|
@@ -773,10 +827,6 @@ import {
|
|
|
773
827
|
PIGMENT_LAMBDA_STEP,
|
|
774
828
|
PIGMENT_WAVELENGTHS,
|
|
775
829
|
SHADOW_AMBIENT,
|
|
776
|
-
SurfaceDraft,
|
|
777
|
-
TILE,
|
|
778
|
-
TILE_SHIFT,
|
|
779
|
-
TiledSurface,
|
|
780
830
|
base64ToBytes,
|
|
781
831
|
blendChannel,
|
|
782
832
|
blendImageData,
|
|
@@ -785,18 +835,13 @@ import {
|
|
|
785
835
|
blendRgb,
|
|
786
836
|
bytesToBase64,
|
|
787
837
|
canvasCompositeOperation,
|
|
788
|
-
collectTiles,
|
|
789
|
-
contentBounds,
|
|
790
838
|
convertSurfaceDepth,
|
|
791
839
|
createPigment,
|
|
792
840
|
decodePng,
|
|
793
841
|
deepBytes,
|
|
794
842
|
deepTile,
|
|
795
843
|
depthOf,
|
|
796
|
-
emptySurface,
|
|
797
844
|
encodePng,
|
|
798
|
-
fillRect,
|
|
799
|
-
finishTile,
|
|
800
845
|
isBitDepth,
|
|
801
846
|
isBlendMode,
|
|
802
847
|
isPainterlyBlendMode,
|
|
@@ -809,45 +854,56 @@ import {
|
|
|
809
854
|
mixPigmentRgb,
|
|
810
855
|
mixPigments,
|
|
811
856
|
normalizeBlendMode,
|
|
812
|
-
packRgba,
|
|
813
857
|
parseDataUrl,
|
|
814
858
|
pigment8,
|
|
815
859
|
pngDataUrl,
|
|
816
860
|
proxyOf,
|
|
817
861
|
readDeepRegion,
|
|
818
|
-
readPixel,
|
|
819
|
-
readRegion,
|
|
820
862
|
reflectanceToRgb,
|
|
821
863
|
rgbToReflectance,
|
|
822
864
|
setLum,
|
|
823
865
|
setPigmentColor,
|
|
824
|
-
solidSurface,
|
|
825
866
|
srgbDecode,
|
|
826
867
|
srgbEncode,
|
|
827
868
|
standardBlendMode,
|
|
828
|
-
surfaceBytes,
|
|
829
869
|
surfaceDepth,
|
|
830
870
|
surfaceFromDeepPixels,
|
|
831
|
-
surfaceFromPixels,
|
|
832
|
-
surfacesEqual,
|
|
833
|
-
tileCoord,
|
|
834
|
-
tileKey,
|
|
835
|
-
tileRect,
|
|
836
871
|
tileSamples,
|
|
837
872
|
tileSamples16,
|
|
838
873
|
tileSamples32,
|
|
839
|
-
tilesInRect,
|
|
840
874
|
to16From32,
|
|
841
875
|
to16From8,
|
|
842
876
|
to32From16,
|
|
843
877
|
to32From8,
|
|
844
878
|
to8From16,
|
|
845
879
|
to8From32,
|
|
880
|
+
writeDeepRegion
|
|
881
|
+
} from "../chunk-42TWUPNJ.js";
|
|
882
|
+
import {
|
|
883
|
+
SurfaceDraft,
|
|
884
|
+
TILE,
|
|
885
|
+
TILE_SHIFT,
|
|
886
|
+
TiledSurface,
|
|
887
|
+
collectTiles,
|
|
888
|
+
contentBounds,
|
|
889
|
+
emptySurface,
|
|
890
|
+
fillRect,
|
|
891
|
+
finishTile,
|
|
892
|
+
packRgba,
|
|
893
|
+
readPixel,
|
|
894
|
+
readRegion,
|
|
895
|
+
solidSurface,
|
|
896
|
+
surfaceBytes,
|
|
897
|
+
surfaceFromPixels,
|
|
898
|
+
surfacesEqual,
|
|
899
|
+
tileCoord,
|
|
900
|
+
tileKey,
|
|
901
|
+
tileRect,
|
|
902
|
+
tilesInRect,
|
|
846
903
|
translateSurface,
|
|
847
904
|
uniformTile,
|
|
848
|
-
writeDeepRegion,
|
|
849
905
|
writeRegion
|
|
850
|
-
} from "../chunk-
|
|
906
|
+
} from "../chunk-HVVR5PZW.js";
|
|
851
907
|
import {
|
|
852
908
|
ZlibError,
|
|
853
909
|
adler32,
|
|
@@ -2803,1597 +2859,6 @@ function magneticAnchorSpacing(frequency = 57) {
|
|
|
2803
2859
|
return 8 + (100 - f);
|
|
2804
2860
|
}
|
|
2805
2861
|
|
|
2806
|
-
// src/image-editor/selection/segment/maxflow.ts
|
|
2807
|
-
var TERMINAL = -2;
|
|
2808
|
-
var NONE = -1;
|
|
2809
|
-
var FREE = 0;
|
|
2810
|
-
var SOURCE = 1;
|
|
2811
|
-
var SINK = 2;
|
|
2812
|
-
var GridGraph = class {
|
|
2813
|
-
width;
|
|
2814
|
-
height;
|
|
2815
|
-
/** Residual capacity of the edge leaving node `p` in direction `d`, at `p * 4 + d`. */
|
|
2816
|
-
cap;
|
|
2817
|
-
/** Net terminal capacity: above 0 to the source, below 0 to the sink. */
|
|
2818
|
-
tr;
|
|
2819
|
-
tree;
|
|
2820
|
-
constructor(width, height) {
|
|
2821
|
-
this.width = width;
|
|
2822
|
-
this.height = height;
|
|
2823
|
-
const n = width * height;
|
|
2824
|
-
this.cap = new Float64Array(n * 4);
|
|
2825
|
-
this.tr = new Float64Array(n);
|
|
2826
|
-
this.tree = new Uint8Array(n);
|
|
2827
|
-
}
|
|
2828
|
-
/** Sets the capacity of the edge between `p` and its neighbour in direction `d`, both ways. */
|
|
2829
|
-
setEdge(p, d, capacity) {
|
|
2830
|
-
const q = d === 0 ? p + 1 : p + this.width;
|
|
2831
|
-
this.cap[p * 4 + d] = capacity;
|
|
2832
|
-
this.cap[q * 4 + (d ^ 2)] = capacity;
|
|
2833
|
-
}
|
|
2834
|
-
/** Adds `source` (cost of putting `p` on the sink side) and `sink` (the other way) to `p`. */
|
|
2835
|
-
addTerminal(p, source, sink) {
|
|
2836
|
-
this.tr[p] += source - sink;
|
|
2837
|
-
}
|
|
2838
|
-
/** Whether `p` is on the source side of the minimum cut (after {@link maxflow}). */
|
|
2839
|
-
inSource(p) {
|
|
2840
|
-
return this.tree[p] === SOURCE;
|
|
2841
|
-
}
|
|
2842
|
-
/** Runs max-flow and returns the flow (the cut's cost, less the constant terminal parts). */
|
|
2843
|
-
maxflow() {
|
|
2844
|
-
const { width: w, height: h, cap, tr, tree } = this;
|
|
2845
|
-
const n = w * h;
|
|
2846
|
-
const nb = (p, d) => {
|
|
2847
|
-
const x = p % w;
|
|
2848
|
-
if (d === 0) return x < w - 1 ? p + 1 : -1;
|
|
2849
|
-
if (d === 1) return p + w < n ? p + w : -1;
|
|
2850
|
-
if (d === 2) return x > 0 ? p - 1 : -1;
|
|
2851
|
-
return p - w >= 0 ? p - w : -1;
|
|
2852
|
-
};
|
|
2853
|
-
const parent = new Int8Array(n).fill(NONE);
|
|
2854
|
-
const ts = new Int32Array(n);
|
|
2855
|
-
const dist = new Int32Array(n);
|
|
2856
|
-
const queue = new Int32Array(n + 1);
|
|
2857
|
-
const queued = new Uint8Array(n);
|
|
2858
|
-
let qHead = 0;
|
|
2859
|
-
let qTail = 0;
|
|
2860
|
-
const activate = (p) => {
|
|
2861
|
-
if (queued[p]) return;
|
|
2862
|
-
queued[p] = 1;
|
|
2863
|
-
queue[qTail] = p;
|
|
2864
|
-
qTail = qTail === n ? 0 : qTail + 1;
|
|
2865
|
-
};
|
|
2866
|
-
const nextActive = () => {
|
|
2867
|
-
while (qHead !== qTail) {
|
|
2868
|
-
const p = queue[qHead];
|
|
2869
|
-
qHead = qHead === n ? 0 : qHead + 1;
|
|
2870
|
-
queued[p] = 0;
|
|
2871
|
-
if (tree[p] !== FREE) return p;
|
|
2872
|
-
}
|
|
2873
|
-
return -1;
|
|
2874
|
-
};
|
|
2875
|
-
let orphans = new Int32Array(64);
|
|
2876
|
-
let orphanCount = 0;
|
|
2877
|
-
const orphan = (p) => {
|
|
2878
|
-
parent[p] = NONE;
|
|
2879
|
-
if (orphanCount === orphans.length) {
|
|
2880
|
-
const grown = new Int32Array(orphans.length * 2);
|
|
2881
|
-
grown.set(orphans);
|
|
2882
|
-
orphans = grown;
|
|
2883
|
-
}
|
|
2884
|
-
orphans[orphanCount++] = p;
|
|
2885
|
-
};
|
|
2886
|
-
tree.fill(FREE);
|
|
2887
|
-
for (let p = 0; p < n; p++) {
|
|
2888
|
-
if (tr[p] > 0) tree[p] = SOURCE;
|
|
2889
|
-
else if (tr[p] < 0) tree[p] = SINK;
|
|
2890
|
-
else continue;
|
|
2891
|
-
parent[p] = TERMINAL;
|
|
2892
|
-
dist[p] = 1;
|
|
2893
|
-
activate(p);
|
|
2894
|
-
}
|
|
2895
|
-
let flow = 0;
|
|
2896
|
-
let time = 0;
|
|
2897
|
-
let current = -1;
|
|
2898
|
-
for (; ; ) {
|
|
2899
|
-
if (current < 0 || tree[current] === FREE) {
|
|
2900
|
-
current = nextActive();
|
|
2901
|
-
if (current < 0) break;
|
|
2902
|
-
}
|
|
2903
|
-
const p = current;
|
|
2904
|
-
const side = tree[p];
|
|
2905
|
-
let s = -1;
|
|
2906
|
-
let t = -1;
|
|
2907
|
-
let dir = 0;
|
|
2908
|
-
for (let d = 0; d < 4; d++) {
|
|
2909
|
-
const q = nb(p, d);
|
|
2910
|
-
if (q < 0) continue;
|
|
2911
|
-
if (side === SOURCE ? cap[p * 4 + d] <= 0 : cap[q * 4 + (d ^ 2)] <= 0) continue;
|
|
2912
|
-
if (tree[q] === FREE) {
|
|
2913
|
-
tree[q] = side;
|
|
2914
|
-
parent[q] = d ^ 2;
|
|
2915
|
-
ts[q] = ts[p];
|
|
2916
|
-
dist[q] = dist[p] + 1;
|
|
2917
|
-
activate(q);
|
|
2918
|
-
} else if (tree[q] !== side) {
|
|
2919
|
-
if (side === SOURCE) {
|
|
2920
|
-
s = p;
|
|
2921
|
-
t = q;
|
|
2922
|
-
dir = d;
|
|
2923
|
-
} else {
|
|
2924
|
-
s = q;
|
|
2925
|
-
t = p;
|
|
2926
|
-
dir = d ^ 2;
|
|
2927
|
-
}
|
|
2928
|
-
break;
|
|
2929
|
-
} else if (ts[q] <= ts[p] && dist[q] > dist[p]) {
|
|
2930
|
-
parent[q] = d ^ 2;
|
|
2931
|
-
ts[q] = ts[p];
|
|
2932
|
-
dist[q] = dist[p] + 1;
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
2935
|
-
if (s < 0) {
|
|
2936
|
-
current = -1;
|
|
2937
|
-
continue;
|
|
2938
|
-
}
|
|
2939
|
-
time++;
|
|
2940
|
-
let f = cap[s * 4 + dir];
|
|
2941
|
-
for (let x = s; ; ) {
|
|
2942
|
-
const a = parent[x];
|
|
2943
|
-
if (a === TERMINAL) {
|
|
2944
|
-
f = Math.min(f, tr[x]);
|
|
2945
|
-
break;
|
|
2946
|
-
}
|
|
2947
|
-
const up = nb(x, a);
|
|
2948
|
-
f = Math.min(f, cap[up * 4 + (a ^ 2)]);
|
|
2949
|
-
x = up;
|
|
2950
|
-
}
|
|
2951
|
-
for (let x = t; ; ) {
|
|
2952
|
-
const a = parent[x];
|
|
2953
|
-
if (a === TERMINAL) {
|
|
2954
|
-
f = Math.min(f, -tr[x]);
|
|
2955
|
-
break;
|
|
2956
|
-
}
|
|
2957
|
-
f = Math.min(f, cap[x * 4 + a]);
|
|
2958
|
-
x = nb(x, a);
|
|
2959
|
-
}
|
|
2960
|
-
cap[s * 4 + dir] -= f;
|
|
2961
|
-
cap[t * 4 + (dir ^ 2)] += f;
|
|
2962
|
-
for (let x = s; ; ) {
|
|
2963
|
-
const a = parent[x];
|
|
2964
|
-
if (a === TERMINAL) {
|
|
2965
|
-
tr[x] -= f;
|
|
2966
|
-
if (tr[x] <= 0) orphan(x);
|
|
2967
|
-
break;
|
|
2968
|
-
}
|
|
2969
|
-
const up = nb(x, a);
|
|
2970
|
-
cap[up * 4 + (a ^ 2)] -= f;
|
|
2971
|
-
cap[x * 4 + a] += f;
|
|
2972
|
-
if (cap[up * 4 + (a ^ 2)] <= 0) orphan(x);
|
|
2973
|
-
x = up;
|
|
2974
|
-
}
|
|
2975
|
-
for (let x = t; ; ) {
|
|
2976
|
-
const a = parent[x];
|
|
2977
|
-
if (a === TERMINAL) {
|
|
2978
|
-
tr[x] += f;
|
|
2979
|
-
if (tr[x] >= 0) orphan(x);
|
|
2980
|
-
break;
|
|
2981
|
-
}
|
|
2982
|
-
const up = nb(x, a);
|
|
2983
|
-
cap[x * 4 + a] -= f;
|
|
2984
|
-
cap[up * 4 + (a ^ 2)] += f;
|
|
2985
|
-
if (cap[x * 4 + a] <= 0) orphan(x);
|
|
2986
|
-
x = up;
|
|
2987
|
-
}
|
|
2988
|
-
flow += f;
|
|
2989
|
-
while (orphanCount) {
|
|
2990
|
-
const o = orphans[--orphanCount];
|
|
2991
|
-
const oSide = tree[o];
|
|
2992
|
-
let best = NONE;
|
|
2993
|
-
let bestDist = Infinity;
|
|
2994
|
-
for (let d = 0; d < 4; d++) {
|
|
2995
|
-
const q = nb(o, d);
|
|
2996
|
-
if (q < 0 || tree[q] !== oSide) continue;
|
|
2997
|
-
if (oSide === SOURCE ? cap[q * 4 + (d ^ 2)] <= 0 : cap[o * 4 + d] <= 0) continue;
|
|
2998
|
-
let k = 0;
|
|
2999
|
-
let x = q;
|
|
3000
|
-
for (; ; ) {
|
|
3001
|
-
if (ts[x] === time) {
|
|
3002
|
-
k += dist[x];
|
|
3003
|
-
break;
|
|
3004
|
-
}
|
|
3005
|
-
const a = parent[x];
|
|
3006
|
-
k++;
|
|
3007
|
-
if (a === TERMINAL) {
|
|
3008
|
-
ts[x] = time;
|
|
3009
|
-
dist[x] = 1;
|
|
3010
|
-
break;
|
|
3011
|
-
}
|
|
3012
|
-
if (a === NONE) {
|
|
3013
|
-
k = Infinity;
|
|
3014
|
-
break;
|
|
3015
|
-
}
|
|
3016
|
-
x = nb(x, a);
|
|
3017
|
-
}
|
|
3018
|
-
if (k === Infinity) continue;
|
|
3019
|
-
if (k < bestDist) {
|
|
3020
|
-
bestDist = k;
|
|
3021
|
-
best = d;
|
|
3022
|
-
}
|
|
3023
|
-
for (let y = q, dd = k; ts[y] !== time; y = nb(y, parent[y]), dd--) {
|
|
3024
|
-
ts[y] = time;
|
|
3025
|
-
dist[y] = dd;
|
|
3026
|
-
}
|
|
3027
|
-
}
|
|
3028
|
-
if (best !== NONE) {
|
|
3029
|
-
parent[o] = best;
|
|
3030
|
-
ts[o] = time;
|
|
3031
|
-
dist[o] = bestDist + 1;
|
|
3032
|
-
continue;
|
|
3033
|
-
}
|
|
3034
|
-
for (let d = 0; d < 4; d++) {
|
|
3035
|
-
const q = nb(o, d);
|
|
3036
|
-
if (q < 0 || tree[q] !== oSide) continue;
|
|
3037
|
-
if (oSide === SOURCE ? cap[q * 4 + (d ^ 2)] > 0 : cap[o * 4 + d] > 0) activate(q);
|
|
3038
|
-
const a = parent[q];
|
|
3039
|
-
if (a !== TERMINAL && a !== NONE && nb(q, a) === o) orphan(q);
|
|
3040
|
-
}
|
|
3041
|
-
tree[o] = FREE;
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
return flow;
|
|
3045
|
-
}
|
|
3046
|
-
};
|
|
3047
|
-
|
|
3048
|
-
// src/image-editor/selection/segment/work.ts
|
|
3049
|
-
var WORK_SIZE = 512;
|
|
3050
|
-
var workCache = /* @__PURE__ */ new WeakMap();
|
|
3051
|
-
function workImage(pixels, maxSide = WORK_SIZE) {
|
|
3052
|
-
const sizes = workCache.get(pixels.data);
|
|
3053
|
-
const cached = sizes?.get(maxSide);
|
|
3054
|
-
if (cached && cached.sourceWidth === pixels.width && cached.sourceHeight === pixels.height) return cached;
|
|
3055
|
-
const { width: W, height: H, data } = pixels;
|
|
3056
|
-
const scale = Math.min(1, maxSide / Math.max(W, H));
|
|
3057
|
-
const w = Math.max(1, Math.round(W * scale));
|
|
3058
|
-
const h = Math.max(1, Math.round(H * scale));
|
|
3059
|
-
const sum = new Float64Array(w * h * 3);
|
|
3060
|
-
const count = new Float64Array(w * h);
|
|
3061
|
-
const xs = new Int32Array(W);
|
|
3062
|
-
for (let x = 0; x < W; x++) xs[x] = Math.min(w - 1, Math.floor(x * w / W));
|
|
3063
|
-
for (let y = 0; y < H; y++) {
|
|
3064
|
-
const row = Math.min(h - 1, Math.floor(y * h / H)) * w;
|
|
3065
|
-
for (let x = 0, p = y * W * 4; x < W; x++, p += 4) {
|
|
3066
|
-
const o = row + xs[x];
|
|
3067
|
-
const a = data[p + 3] / 255;
|
|
3068
|
-
const k = o * 3;
|
|
3069
|
-
sum[k] += data[p] * a + 255 * (1 - a);
|
|
3070
|
-
sum[k + 1] += data[p + 1] * a + 255 * (1 - a);
|
|
3071
|
-
sum[k + 2] += data[p + 2] * a + 255 * (1 - a);
|
|
3072
|
-
count[o]++;
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
const rgb = new Float32Array(w * h * 3);
|
|
3076
|
-
for (let i = 0; i < w * h; i++) {
|
|
3077
|
-
const c = count[i] || 1;
|
|
3078
|
-
rgb[i * 3] = sum[i * 3] / c;
|
|
3079
|
-
rgb[i * 3 + 1] = sum[i * 3 + 1] / c;
|
|
3080
|
-
rgb[i * 3 + 2] = sum[i * 3 + 2] / c;
|
|
3081
|
-
}
|
|
3082
|
-
const work = { sourceWidth: W, sourceHeight: H, width: w, height: h, scale: w / W, rgb };
|
|
3083
|
-
if (sizes) sizes.set(maxSide, work);
|
|
3084
|
-
else workCache.set(pixels.data, /* @__PURE__ */ new Map([[maxSide, work]]));
|
|
3085
|
-
return work;
|
|
3086
|
-
}
|
|
3087
|
-
var GAMMA = 50;
|
|
3088
|
-
var UNITS = 64;
|
|
3089
|
-
function smoothnessWeights(work, roi) {
|
|
3090
|
-
const { width: w, rgb } = work;
|
|
3091
|
-
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3092
|
-
const right = new Float64Array(rw * rh);
|
|
3093
|
-
const down = new Float64Array(rw * rh);
|
|
3094
|
-
let sumSq = 0;
|
|
3095
|
-
let pairs = 0;
|
|
3096
|
-
for (let y = 0; y < rh; y++) {
|
|
3097
|
-
for (let x = 0; x < rw; x++) {
|
|
3098
|
-
const p = y * rw + x;
|
|
3099
|
-
const c = ((ry + y) * w + rx + x) * 3;
|
|
3100
|
-
if (x + 1 < rw) {
|
|
3101
|
-
right[p] = (rgb[c] - rgb[c + 3]) ** 2 + (rgb[c + 1] - rgb[c + 4]) ** 2 + (rgb[c + 2] - rgb[c + 5]) ** 2;
|
|
3102
|
-
sumSq += right[p];
|
|
3103
|
-
pairs++;
|
|
3104
|
-
}
|
|
3105
|
-
if (y + 1 < rh) {
|
|
3106
|
-
const k = c + w * 3;
|
|
3107
|
-
down[p] = (rgb[c] - rgb[k]) ** 2 + (rgb[c + 1] - rgb[k + 1]) ** 2 + (rgb[c + 2] - rgb[k + 2]) ** 2;
|
|
3108
|
-
sumSq += down[p];
|
|
3109
|
-
pairs++;
|
|
3110
|
-
}
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
const beta = 1 / (2 * Math.max(1e-6, sumSq / Math.max(1, pairs)));
|
|
3114
|
-
for (let p = 0; p < right.length; p++) {
|
|
3115
|
-
right[p] = Math.round(GAMMA * UNITS * Math.exp(-beta * right[p]));
|
|
3116
|
-
down[p] = Math.round(GAMMA * UNITS * Math.exp(-beta * down[p]));
|
|
3117
|
-
}
|
|
3118
|
-
return { right, down };
|
|
3119
|
-
}
|
|
3120
|
-
function boxBlur(src, w, h, passes) {
|
|
3121
|
-
let a = src;
|
|
3122
|
-
for (let k = 0; k < passes; k++) {
|
|
3123
|
-
const b = new Float32Array(a.length);
|
|
3124
|
-
for (let y = 0; y < h; y++) {
|
|
3125
|
-
for (let x = 0; x < w; x++) {
|
|
3126
|
-
let s = 0;
|
|
3127
|
-
let c = 0;
|
|
3128
|
-
for (let j = Math.max(0, y - 1); j <= Math.min(h - 1, y + 1); j++) {
|
|
3129
|
-
for (let i = Math.max(0, x - 1); i <= Math.min(w - 1, x + 1); i++) {
|
|
3130
|
-
s += a[j * w + i];
|
|
3131
|
-
c++;
|
|
3132
|
-
}
|
|
3133
|
-
}
|
|
3134
|
-
b[y * w + x] = s / c;
|
|
3135
|
-
}
|
|
3136
|
-
}
|
|
3137
|
-
a = b;
|
|
3138
|
-
}
|
|
3139
|
-
return a;
|
|
3140
|
-
}
|
|
3141
|
-
function upscaleRegion(work, pixels, roi, label, enhance) {
|
|
3142
|
-
const { sourceWidth: W, sourceHeight: H, scale, rgb, width: ww } = work;
|
|
3143
|
-
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3144
|
-
let soft = Float32Array.from(label);
|
|
3145
|
-
if (enhance) {
|
|
3146
|
-
soft = boxBlur(soft, rw, rh, 2);
|
|
3147
|
-
for (let i = 0; i < soft.length; i++) soft[i] = Math.min(1, Math.max(0, (soft[i] - 0.5) * 3 + 0.5));
|
|
3148
|
-
}
|
|
3149
|
-
const X0 = Math.max(0, Math.floor(rx / scale));
|
|
3150
|
-
const Y0 = Math.max(0, Math.floor(ry / scale));
|
|
3151
|
-
const X1 = Math.min(W, Math.ceil((rx + rw) / scale));
|
|
3152
|
-
const Y1 = Math.min(H, Math.ceil((ry + rh) / scale));
|
|
3153
|
-
const RW = X1 - X0;
|
|
3154
|
-
const RH = Y1 - Y0;
|
|
3155
|
-
const out = new Uint8Array(RW * RH);
|
|
3156
|
-
const at = (x, y) => soft[Math.min(rh - 1, Math.max(0, y)) * rw + Math.min(rw - 1, Math.max(0, x))];
|
|
3157
|
-
const data = pixels.data;
|
|
3158
|
-
const colOf = new Int32Array(RW);
|
|
3159
|
-
const fxOf = new Float32Array(RW);
|
|
3160
|
-
for (let X = 0; X < RW; X++) {
|
|
3161
|
-
const u = (X0 + X + 0.5) * scale - 0.5 - rx;
|
|
3162
|
-
colOf[X] = Math.floor(u);
|
|
3163
|
-
fxOf[X] = u - colOf[X];
|
|
3164
|
-
}
|
|
3165
|
-
const cellState = (i, j) => {
|
|
3166
|
-
const a = at(i, j), b = at(i + 1, j), c = at(i, j + 1), d = at(i + 1, j + 1);
|
|
3167
|
-
if (a === 1 && b === 1 && c === 1 && d === 1) return 1;
|
|
3168
|
-
if (a === 0 && b === 0 && c === 0 && d === 0) return 0;
|
|
3169
|
-
return 2;
|
|
3170
|
-
};
|
|
3171
|
-
for (let Y = 0; Y < RH; Y++) {
|
|
3172
|
-
const v = (Y0 + Y + 0.5) * scale - 0.5 - ry;
|
|
3173
|
-
const j = Math.floor(v);
|
|
3174
|
-
const fy = v - j;
|
|
3175
|
-
for (let X = 0; X < RW; X++) {
|
|
3176
|
-
const i = colOf[X];
|
|
3177
|
-
const state = cellState(i, j);
|
|
3178
|
-
if (state !== 2) {
|
|
3179
|
-
let end = X + 1;
|
|
3180
|
-
while (end < RW && colOf[end] === i) end++;
|
|
3181
|
-
if (state === 1) out.fill(255, Y * RW + X, Y * RW + end);
|
|
3182
|
-
X = end - 1;
|
|
3183
|
-
continue;
|
|
3184
|
-
}
|
|
3185
|
-
const fx = fxOf[X];
|
|
3186
|
-
const m = (at(i, j) * (1 - fx) + at(i + 1, j) * fx) * (1 - fy) + (at(i, j + 1) * (1 - fx) + at(i + 1, j + 1) * fx) * fy;
|
|
3187
|
-
if (m <= 1e-3 || m >= 0.999) {
|
|
3188
|
-
out[Y * RW + X] = m >= 0.999 ? 255 : 0;
|
|
3189
|
-
continue;
|
|
3190
|
-
}
|
|
3191
|
-
let fr = 0, fg = 0, fb = 0, fn = 0, br = 0, bg = 0, bb = 0, bn = 0;
|
|
3192
|
-
for (let jj = j - 1; jj <= j + 2; jj++) {
|
|
3193
|
-
for (let ii = i - 1; ii <= i + 2; ii++) {
|
|
3194
|
-
if (ii < 0 || jj < 0 || ii >= rw || jj >= rh) continue;
|
|
3195
|
-
const k = ((ry + jj) * ww + rx + ii) * 3;
|
|
3196
|
-
if (soft[jj * rw + ii] >= 0.5) {
|
|
3197
|
-
fr += rgb[k];
|
|
3198
|
-
fg += rgb[k + 1];
|
|
3199
|
-
fb += rgb[k + 2];
|
|
3200
|
-
fn++;
|
|
3201
|
-
} else {
|
|
3202
|
-
br += rgb[k];
|
|
3203
|
-
bg += rgb[k + 1];
|
|
3204
|
-
bb += rgb[k + 2];
|
|
3205
|
-
bn++;
|
|
3206
|
-
}
|
|
3207
|
-
}
|
|
3208
|
-
}
|
|
3209
|
-
let alpha = m;
|
|
3210
|
-
if (fn && bn) {
|
|
3211
|
-
fr /= fn;
|
|
3212
|
-
fg /= fn;
|
|
3213
|
-
fb /= fn;
|
|
3214
|
-
br /= bn;
|
|
3215
|
-
bg /= bn;
|
|
3216
|
-
bb /= bn;
|
|
3217
|
-
const dr = fr - br, dg = fg - bg, db = fb - bb;
|
|
3218
|
-
const len2 = dr * dr + dg * dg + db * db;
|
|
3219
|
-
if (len2 > 400) {
|
|
3220
|
-
const p = ((Y0 + Y) * W + X0 + X) * 4;
|
|
3221
|
-
const a = data[p + 3] / 255;
|
|
3222
|
-
const cr = data[p] * a + 255 * (1 - a) - br;
|
|
3223
|
-
const cg = data[p + 1] * a + 255 * (1 - a) - bg;
|
|
3224
|
-
const cb = data[p + 2] * a + 255 * (1 - a) - bb;
|
|
3225
|
-
const proj = Math.min(1, Math.max(0, (cr * dr + cg * dg + cb * db) / len2));
|
|
3226
|
-
alpha = Math.min(1, Math.max(0, proj * 0.75 + m * 0.25));
|
|
3227
|
-
} else alpha = Math.min(1, Math.max(0, (m - 0.5) * 2 + 0.5));
|
|
3228
|
-
}
|
|
3229
|
-
out[Y * RW + X] = Math.round(alpha * 255);
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
return selectionFromRegion(W, H, { x: X0, y: Y0, width: RW, height: RH }, out);
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
// src/image-editor/selection/segment/quick.ts
|
|
3236
|
-
var BINS = 16;
|
|
3237
|
-
var binOf = (rgb, i) => (Math.min(BINS - 1, rgb[i * 3] >> 4) * BINS + Math.min(BINS - 1, rgb[i * 3 + 1] >> 4)) * BINS + Math.min(BINS - 1, rgb[i * 3 + 2] >> 4);
|
|
3238
|
-
function costTable(hist, total) {
|
|
3239
|
-
let a = hist;
|
|
3240
|
-
for (const stride of [1, BINS, BINS * BINS]) {
|
|
3241
|
-
const b = new Float64Array(a.length);
|
|
3242
|
-
for (let i = 0; i < a.length; i++) {
|
|
3243
|
-
const pos = Math.floor(i / stride) % BINS;
|
|
3244
|
-
const lo = pos > 0 ? a[i - stride] : a[i];
|
|
3245
|
-
const hi = pos < BINS - 1 ? a[i + stride] : a[i];
|
|
3246
|
-
b[i] = (lo + 2 * a[i] + hi) / 4;
|
|
3247
|
-
}
|
|
3248
|
-
a = b;
|
|
3249
|
-
}
|
|
3250
|
-
const out = new Float64Array(a.length);
|
|
3251
|
-
const floor = 1e-4;
|
|
3252
|
-
const denom = Math.max(1, total) * (1 + floor * a.length);
|
|
3253
|
-
for (let i = 0; i < a.length; i++) out[i] = -Math.log((a[i] + floor * Math.max(1, total)) / denom);
|
|
3254
|
-
return out;
|
|
3255
|
-
}
|
|
3256
|
-
function strokePixels(points, r, w, h) {
|
|
3257
|
-
const mask = new Uint8Array(w * h);
|
|
3258
|
-
const list = [];
|
|
3259
|
-
const add2 = (g) => {
|
|
3260
|
-
if (!mask[g]) {
|
|
3261
|
-
mask[g] = 1;
|
|
3262
|
-
list.push(g);
|
|
3263
|
-
}
|
|
3264
|
-
};
|
|
3265
|
-
const segs = points.length === 1 ? [[points[0], points[0]]] : points.slice(1).map((p, i) => [points[i], p]);
|
|
3266
|
-
for (const [a, b] of segs) {
|
|
3267
|
-
const x0 = Math.max(0, Math.floor(Math.min(a.x, b.x) - r));
|
|
3268
|
-
const x1 = Math.min(w - 1, Math.ceil(Math.max(a.x, b.x) + r));
|
|
3269
|
-
const y0 = Math.max(0, Math.floor(Math.min(a.y, b.y) - r));
|
|
3270
|
-
const y1 = Math.min(h - 1, Math.ceil(Math.max(a.y, b.y) + r));
|
|
3271
|
-
const dx = b.x - a.x;
|
|
3272
|
-
const dy = b.y - a.y;
|
|
3273
|
-
const len2 = dx * dx + dy * dy;
|
|
3274
|
-
for (let y = y0; y <= y1; y++) {
|
|
3275
|
-
for (let x = x0; x <= x1; x++) {
|
|
3276
|
-
const px2 = x + 0.5 - a.x;
|
|
3277
|
-
const py = y + 0.5 - a.y;
|
|
3278
|
-
const t = len2 > 0 ? Math.min(1, Math.max(0, (px2 * dx + py * dy) / len2)) : 0;
|
|
3279
|
-
if ((px2 - t * dx) ** 2 + (py - t * dy) ** 2 <= r * r) add2(y * w + x);
|
|
3280
|
-
}
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
if (!list.length) {
|
|
3284
|
-
for (const p of points) add2(Math.min(h - 1, Math.max(0, Math.floor(p.y))) * w + Math.min(w - 1, Math.max(0, Math.floor(p.x))));
|
|
3285
|
-
}
|
|
3286
|
-
return { mask, list };
|
|
3287
|
-
}
|
|
3288
|
-
function segmentStroke(work, stroke, grown, start) {
|
|
3289
|
-
const { width: w, height: h, rgb } = work;
|
|
3290
|
-
let roi = start;
|
|
3291
|
-
for (; ; ) {
|
|
3292
|
-
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3293
|
-
const n = rw * rh;
|
|
3294
|
-
const fgHist = new Float64Array(BINS ** 3);
|
|
3295
|
-
const bgHist = new Float64Array(BINS ** 3);
|
|
3296
|
-
let fgN = 0;
|
|
3297
|
-
let bgN = 0;
|
|
3298
|
-
const atImageEdge = { l: rx === 0, t: ry === 0, r: rx + rw === w, b: ry + rh === h };
|
|
3299
|
-
for (let y = 0; y < rh; y++) {
|
|
3300
|
-
for (let x = 0; x < rw; x++) {
|
|
3301
|
-
const g = (ry + y) * w + (rx + x);
|
|
3302
|
-
const bin = binOf(rgb, g);
|
|
3303
|
-
bgHist[bin]++;
|
|
3304
|
-
bgN++;
|
|
3305
|
-
if (stroke.mask[g] || grown?.[g]) {
|
|
3306
|
-
fgHist[bin]++;
|
|
3307
|
-
fgN++;
|
|
3308
|
-
}
|
|
3309
|
-
}
|
|
3310
|
-
}
|
|
3311
|
-
const fgCost = costTable(fgHist, fgN);
|
|
3312
|
-
const bgCost = costTable(bgHist, bgN);
|
|
3313
|
-
const { right, down } = smoothnessWeights(work, roi);
|
|
3314
|
-
const graph = new GridGraph(rw, rh);
|
|
3315
|
-
const HARD = 1e9;
|
|
3316
|
-
for (let y = 0; y < rh; y++) {
|
|
3317
|
-
for (let x = 0; x < rw; x++) {
|
|
3318
|
-
const p = y * rw + x;
|
|
3319
|
-
const g = (ry + y) * w + rx + x;
|
|
3320
|
-
if (stroke.mask[g]) graph.addTerminal(p, HARD, 0);
|
|
3321
|
-
else {
|
|
3322
|
-
const bin = binOf(rgb, g);
|
|
3323
|
-
graph.addTerminal(p, Math.round(bgCost[bin] * UNITS), Math.round(fgCost[bin] * UNITS));
|
|
3324
|
-
}
|
|
3325
|
-
if (x + 1 < rw) graph.setEdge(p, 0, right[p]);
|
|
3326
|
-
if (y + 1 < rh) graph.setEdge(p, 1, down[p]);
|
|
3327
|
-
}
|
|
3328
|
-
}
|
|
3329
|
-
graph.maxflow();
|
|
3330
|
-
const label = new Uint8Array(n);
|
|
3331
|
-
const stack = [];
|
|
3332
|
-
for (const g of stroke.list) {
|
|
3333
|
-
const x = g % w - rx;
|
|
3334
|
-
const y = Math.floor(g / w) - ry;
|
|
3335
|
-
if (x < 0 || y < 0 || x >= rw || y >= rh) continue;
|
|
3336
|
-
const p = y * rw + x;
|
|
3337
|
-
if (!label[p]) {
|
|
3338
|
-
label[p] = 1;
|
|
3339
|
-
stack.push(p);
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
while (stack.length) {
|
|
3343
|
-
const p = stack.pop();
|
|
3344
|
-
const x = p % rw;
|
|
3345
|
-
for (const q of [x + 1 < rw ? p + 1 : -1, x > 0 ? p - 1 : -1, p + rw < n ? p + rw : -1, p - rw]) {
|
|
3346
|
-
if (q >= 0 && !label[q] && graph.inSource(q)) {
|
|
3347
|
-
label[q] = 1;
|
|
3348
|
-
stack.push(q);
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
let touches = false;
|
|
3353
|
-
for (let y = 0; y < rh; y++) {
|
|
3354
|
-
for (let x = 0; x < rw; x++) {
|
|
3355
|
-
const p = y * rw + x;
|
|
3356
|
-
if (!label[p]) continue;
|
|
3357
|
-
if (!atImageEdge.l && x === 0 || !atImageEdge.t && y === 0 || !atImageEdge.r && x === rw - 1 || !atImageEdge.b && y === rh - 1) touches = true;
|
|
3358
|
-
}
|
|
3359
|
-
}
|
|
3360
|
-
if (!touches || atImageEdge.l && atImageEdge.t && atImageEdge.r && atImageEdge.b) return { roi, label };
|
|
3361
|
-
const nx = Math.max(0, rx - Math.ceil(rw / 2));
|
|
3362
|
-
const ny = Math.max(0, ry - Math.ceil(rh / 2));
|
|
3363
|
-
roi = { x: nx, y: ny, width: Math.min(w, rx + rw + Math.ceil(rw / 2)) - nx, height: Math.min(h, ry + rh + Math.ceil(rh / 2)) - ny };
|
|
3364
|
-
if (roi.width * roi.height * 2.5 > w * h) roi = { x: 0, y: 0, width: w, height: h };
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
function quickSelectStroke(pixels, stroke, options = {}) {
|
|
3368
|
-
if (!stroke.length) throw new Error("A stroke needs at least one point.");
|
|
3369
|
-
const work = workImage(pixels);
|
|
3370
|
-
const { width: w, height: h, scale } = work;
|
|
3371
|
-
const r = Math.max(0.5, (options.size ?? 30) / 2 * scale);
|
|
3372
|
-
const pts = stroke.map((p) => ({ x: p.x * scale, y: p.y * scale }));
|
|
3373
|
-
const seeds = strokePixels(pts, r, w, h);
|
|
3374
|
-
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
3375
|
-
for (const p of pts) {
|
|
3376
|
-
x0 = Math.min(x0, p.x);
|
|
3377
|
-
y0 = Math.min(y0, p.y);
|
|
3378
|
-
x1 = Math.max(x1, p.x);
|
|
3379
|
-
y1 = Math.max(y1, p.y);
|
|
3380
|
-
}
|
|
3381
|
-
const margin = Math.max(24, 4 * r);
|
|
3382
|
-
const rx = Math.max(0, Math.floor(x0 - margin));
|
|
3383
|
-
const ry = Math.max(0, Math.floor(y0 - margin));
|
|
3384
|
-
const roi = { x: rx, y: ry, width: Math.min(w, Math.ceil(x1 + margin)) - rx, height: Math.min(h, Math.ceil(y1 + margin)) - ry };
|
|
3385
|
-
let grown = null;
|
|
3386
|
-
const sel = options.grow ? options.selection : null;
|
|
3387
|
-
if (sel?.bounds) {
|
|
3388
|
-
grown = new Uint8Array(w * h);
|
|
3389
|
-
const b = sel.bounds;
|
|
3390
|
-
for (let y = Math.max(0, Math.floor(b.y * scale)); y < Math.min(h, Math.ceil((b.y + b.height) * scale)); y++) {
|
|
3391
|
-
for (let x = Math.max(0, Math.floor(b.x * scale)); x < Math.min(w, Math.ceil((b.x + b.width) * scale)); x++) {
|
|
3392
|
-
if (selectionValueAt(sel, Math.min(sel.width - 1, Math.floor((x + 0.5) / scale)), Math.min(sel.height - 1, Math.floor((y + 0.5) / scale))) >= 0.5) grown[y * w + x] = 1;
|
|
3393
|
-
}
|
|
3394
|
-
}
|
|
3395
|
-
}
|
|
3396
|
-
const { roi: done, label } = segmentStroke(work, seeds, grown, roi);
|
|
3397
|
-
return upscaleRegion(work, pixels, done, label, options.enhanceEdge ?? false);
|
|
3398
|
-
}
|
|
3399
|
-
|
|
3400
|
-
// src/image-editor/selection/segment/grabcut.ts
|
|
3401
|
-
var K = 5;
|
|
3402
|
-
var ColorGmm = class {
|
|
3403
|
-
logWeight = new Float64Array(K);
|
|
3404
|
-
mean = new Float64Array(K * 3);
|
|
3405
|
-
/** Inverse covariances, row-major 3 × 3. */
|
|
3406
|
-
inv = new Float64Array(K * 9);
|
|
3407
|
-
/** ln(weight) − ½ ln((2π)³ |Σ|) per component; −∞ for an empty one. */
|
|
3408
|
-
logNorm = new Float64Array(K).fill(-Infinity);
|
|
3409
|
-
/** Fits the components to the pixels `ids` of `rgb`, each in component `comp[id]`. */
|
|
3410
|
-
fit(rgb, ids, comp) {
|
|
3411
|
-
const n = new Float64Array(K);
|
|
3412
|
-
const sum = new Float64Array(K * 3);
|
|
3413
|
-
const prod = new Float64Array(K * 9);
|
|
3414
|
-
for (let t = 0; t < ids.length; t++) {
|
|
3415
|
-
const i = ids[t];
|
|
3416
|
-
const k = comp[i];
|
|
3417
|
-
const c = [rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]];
|
|
3418
|
-
n[k]++;
|
|
3419
|
-
for (let a = 0; a < 3; a++) {
|
|
3420
|
-
sum[k * 3 + a] += c[a];
|
|
3421
|
-
for (let b = 0; b < 3; b++) prod[k * 9 + a * 3 + b] += c[a] * c[b];
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
const total = ids.length || 1;
|
|
3425
|
-
for (let k = 0; k < K; k++) {
|
|
3426
|
-
if (!n[k]) {
|
|
3427
|
-
this.logNorm[k] = -Infinity;
|
|
3428
|
-
continue;
|
|
3429
|
-
}
|
|
3430
|
-
const m = [sum[k * 3] / n[k], sum[k * 3 + 1] / n[k], sum[k * 3 + 2] / n[k]];
|
|
3431
|
-
const cov = new Float64Array(9);
|
|
3432
|
-
for (let a = 0; a < 3; a++) {
|
|
3433
|
-
for (let b = 0; b < 3; b++) cov[a * 3 + b] = prod[k * 9 + a * 3 + b] / n[k] - m[a] * m[b];
|
|
3434
|
-
cov[a * 4] += 4;
|
|
3435
|
-
}
|
|
3436
|
-
const det = cov[0] * (cov[4] * cov[8] - cov[5] * cov[7]) - cov[1] * (cov[3] * cov[8] - cov[5] * cov[6]) + cov[2] * (cov[3] * cov[7] - cov[4] * cov[6]);
|
|
3437
|
-
const d = Math.max(det, 1e-6);
|
|
3438
|
-
const inv = [
|
|
3439
|
-
(cov[4] * cov[8] - cov[5] * cov[7]) / d,
|
|
3440
|
-
(cov[2] * cov[7] - cov[1] * cov[8]) / d,
|
|
3441
|
-
(cov[1] * cov[5] - cov[2] * cov[4]) / d,
|
|
3442
|
-
(cov[5] * cov[6] - cov[3] * cov[8]) / d,
|
|
3443
|
-
(cov[0] * cov[8] - cov[2] * cov[6]) / d,
|
|
3444
|
-
(cov[2] * cov[3] - cov[0] * cov[5]) / d,
|
|
3445
|
-
(cov[3] * cov[7] - cov[4] * cov[6]) / d,
|
|
3446
|
-
(cov[1] * cov[6] - cov[0] * cov[7]) / d,
|
|
3447
|
-
(cov[0] * cov[4] - cov[1] * cov[3]) / d
|
|
3448
|
-
];
|
|
3449
|
-
this.mean.set(m, k * 3);
|
|
3450
|
-
this.inv.set(inv, k * 9);
|
|
3451
|
-
this.logWeight[k] = Math.log(n[k] / total);
|
|
3452
|
-
this.logNorm[k] = this.logWeight[k] - 0.5 * Math.log(Math.pow(2 * Math.PI, 3) * d);
|
|
3453
|
-
}
|
|
3454
|
-
}
|
|
3455
|
-
/** ln p(colour | component k), up to the shared constant. */
|
|
3456
|
-
componentLog(k, r, g, b) {
|
|
3457
|
-
const ln = this.logNorm[k];
|
|
3458
|
-
if (ln === -Infinity) return -Infinity;
|
|
3459
|
-
const dr = r - this.mean[k * 3];
|
|
3460
|
-
const dg = g - this.mean[k * 3 + 1];
|
|
3461
|
-
const db = b - this.mean[k * 3 + 2];
|
|
3462
|
-
const v = this.inv;
|
|
3463
|
-
const o = k * 9;
|
|
3464
|
-
const q = dr * (v[o] * dr + v[o + 1] * dg + v[o + 2] * db) + dg * (v[o + 3] * dr + v[o + 4] * dg + v[o + 5] * db) + db * (v[o + 6] * dr + v[o + 7] * dg + v[o + 8] * db);
|
|
3465
|
-
return ln - 0.5 * q;
|
|
3466
|
-
}
|
|
3467
|
-
/** −ln p(colour) under the mixture (large for colours the model hasn't seen). */
|
|
3468
|
-
cost(r, g, b) {
|
|
3469
|
-
let best = -Infinity;
|
|
3470
|
-
const logs = new Array(K);
|
|
3471
|
-
for (let k = 0; k < K; k++) {
|
|
3472
|
-
logs[k] = this.componentLog(k, r, g, b);
|
|
3473
|
-
if (logs[k] > best) best = logs[k];
|
|
3474
|
-
}
|
|
3475
|
-
if (best === -Infinity) return 50;
|
|
3476
|
-
let s = 0;
|
|
3477
|
-
for (let k = 0; k < K; k++) if (logs[k] > -Infinity) s += Math.exp(logs[k] - best);
|
|
3478
|
-
return Math.min(50, -(best + Math.log(s)));
|
|
3479
|
-
}
|
|
3480
|
-
/** The component most likely to have produced the colour. */
|
|
3481
|
-
nearest(r, g, b) {
|
|
3482
|
-
let best = 0;
|
|
3483
|
-
let bestLog = -Infinity;
|
|
3484
|
-
for (let k = 0; k < K; k++) {
|
|
3485
|
-
const l = this.componentLog(k, r, g, b);
|
|
3486
|
-
if (l > bestLog) {
|
|
3487
|
-
bestLog = l;
|
|
3488
|
-
best = k;
|
|
3489
|
-
}
|
|
3490
|
-
}
|
|
3491
|
-
return best;
|
|
3492
|
-
}
|
|
3493
|
-
};
|
|
3494
|
-
function initialComponents(rgb, ids, comp) {
|
|
3495
|
-
if (!ids.length) return;
|
|
3496
|
-
const step = Math.max(1, Math.floor(ids.length / 6e3));
|
|
3497
|
-
const sample = [];
|
|
3498
|
-
for (let t = 0; t < ids.length; t += step) sample.push(ids[t]);
|
|
3499
|
-
const centres = [];
|
|
3500
|
-
const dist2 = (i, c) => (rgb[i * 3] - c[0]) ** 2 + (rgb[i * 3 + 1] - c[1]) ** 2 + (rgb[i * 3 + 2] - c[2]) ** 2;
|
|
3501
|
-
centres.push([rgb[sample[0] * 3], rgb[sample[0] * 3 + 1], rgb[sample[0] * 3 + 2]]);
|
|
3502
|
-
const near = sample.map((i) => dist2(i, centres[0]));
|
|
3503
|
-
while (centres.length < K) {
|
|
3504
|
-
let far = 0;
|
|
3505
|
-
for (let t = 1; t < sample.length; t++) if (near[t] > near[far]) far = t;
|
|
3506
|
-
const i = sample[far];
|
|
3507
|
-
const c = [rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]];
|
|
3508
|
-
centres.push(c);
|
|
3509
|
-
for (let t = 0; t < sample.length; t++) near[t] = Math.min(near[t], dist2(sample[t], c));
|
|
3510
|
-
}
|
|
3511
|
-
const closest = (i) => {
|
|
3512
|
-
let best = 0;
|
|
3513
|
-
for (let k = 1; k < K; k++) if (dist2(i, centres[k]) < dist2(i, centres[best])) best = k;
|
|
3514
|
-
return best;
|
|
3515
|
-
};
|
|
3516
|
-
for (let round6 = 0; round6 < 6; round6++) {
|
|
3517
|
-
const sum = Array.from({ length: K }, () => [0, 0, 0, 0]);
|
|
3518
|
-
for (const i of sample) {
|
|
3519
|
-
const s = sum[closest(i)];
|
|
3520
|
-
s[0] += rgb[i * 3];
|
|
3521
|
-
s[1] += rgb[i * 3 + 1];
|
|
3522
|
-
s[2] += rgb[i * 3 + 2];
|
|
3523
|
-
s[3]++;
|
|
3524
|
-
}
|
|
3525
|
-
for (let k = 0; k < K; k++) if (sum[k][3]) centres[k] = [sum[k][0] / sum[k][3], sum[k][1] / sum[k][3], sum[k][2] / sum[k][3]];
|
|
3526
|
-
}
|
|
3527
|
-
for (const i of ids) comp[i] = closest(i);
|
|
3528
|
-
}
|
|
3529
|
-
function grabCutLabels(work, setup, iterations) {
|
|
3530
|
-
const { width: w, rgb } = work;
|
|
3531
|
-
const { roi, hardBg, hardFg } = setup;
|
|
3532
|
-
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3533
|
-
const n = rw * rh;
|
|
3534
|
-
const label = setup.initial.slice();
|
|
3535
|
-
const global = new Int32Array(n);
|
|
3536
|
-
for (let y = 0; y < rh; y++) for (let x = 0; x < rw; x++) global[y * rw + x] = (ry + y) * w + rx + x;
|
|
3537
|
-
const comp = new Uint8Array(w * work.height);
|
|
3538
|
-
const fg = new ColorGmm();
|
|
3539
|
-
const bg = new ColorGmm();
|
|
3540
|
-
const { right, down } = smoothnessWeights(work, roi);
|
|
3541
|
-
const HARD = 1e9;
|
|
3542
|
-
for (let iter = 0; iter < iterations; iter++) {
|
|
3543
|
-
const fgIds = [];
|
|
3544
|
-
const bgIds = [];
|
|
3545
|
-
for (let p = 0; p < n; p++) (label[p] ? fgIds : bgIds).push(global[p]);
|
|
3546
|
-
if (!fgIds.length) return label;
|
|
3547
|
-
if (iter === 0) {
|
|
3548
|
-
initialComponents(rgb, fgIds, comp);
|
|
3549
|
-
initialComponents(rgb, bgIds, comp);
|
|
3550
|
-
} else {
|
|
3551
|
-
for (const i of fgIds) comp[i] = fg.nearest(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
|
3552
|
-
for (const i of bgIds) comp[i] = bg.nearest(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
|
3553
|
-
}
|
|
3554
|
-
fg.fit(rgb, fgIds, comp);
|
|
3555
|
-
bg.fit(rgb, bgIds, comp);
|
|
3556
|
-
const graph = new GridGraph(rw, rh);
|
|
3557
|
-
for (let p = 0; p < n; p++) {
|
|
3558
|
-
if (hardBg[p]) graph.addTerminal(p, 0, HARD);
|
|
3559
|
-
else if (hardFg?.[p]) graph.addTerminal(p, HARD, 0);
|
|
3560
|
-
else {
|
|
3561
|
-
const i = global[p] * 3;
|
|
3562
|
-
graph.addTerminal(p, Math.round(bg.cost(rgb[i], rgb[i + 1], rgb[i + 2]) * UNITS), Math.round(fg.cost(rgb[i], rgb[i + 1], rgb[i + 2]) * UNITS));
|
|
3563
|
-
}
|
|
3564
|
-
if (p % rw + 1 < rw) graph.setEdge(p, 0, right[p]);
|
|
3565
|
-
if (p + rw < n) graph.setEdge(p, 1, down[p]);
|
|
3566
|
-
}
|
|
3567
|
-
graph.maxflow();
|
|
3568
|
-
let changed = 0;
|
|
3569
|
-
for (let p = 0; p < n; p++) {
|
|
3570
|
-
const v = graph.inSource(p) ? 1 : 0;
|
|
3571
|
-
if (v !== label[p]) changed++;
|
|
3572
|
-
label[p] = v;
|
|
3573
|
-
}
|
|
3574
|
-
if (changed <= n * 1e-3) break;
|
|
3575
|
-
}
|
|
3576
|
-
return label;
|
|
3577
|
-
}
|
|
3578
|
-
function keepMainPieces(label, rw, rh, seed) {
|
|
3579
|
-
const n = rw * rh;
|
|
3580
|
-
const piece = new Int32Array(n).fill(-1);
|
|
3581
|
-
const sizes = [];
|
|
3582
|
-
const stack = [];
|
|
3583
|
-
for (let s = 0; s < n; s++) {
|
|
3584
|
-
if (!label[s] || piece[s] >= 0) continue;
|
|
3585
|
-
const id = sizes.length;
|
|
3586
|
-
let size = 0;
|
|
3587
|
-
piece[s] = id;
|
|
3588
|
-
stack.push(s);
|
|
3589
|
-
while (stack.length) {
|
|
3590
|
-
const p = stack.pop();
|
|
3591
|
-
size++;
|
|
3592
|
-
const x = p % rw;
|
|
3593
|
-
for (const q of [x + 1 < rw ? p + 1 : -1, x > 0 ? p - 1 : -1, p + rw < n ? p + rw : -1, p - rw]) {
|
|
3594
|
-
if (q >= 0 && label[q] && piece[q] < 0) {
|
|
3595
|
-
piece[q] = id;
|
|
3596
|
-
stack.push(q);
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
3600
|
-
sizes.push(size);
|
|
3601
|
-
}
|
|
3602
|
-
if (!sizes.length) return;
|
|
3603
|
-
const largest = Math.max(...sizes);
|
|
3604
|
-
const keep = (id) => seed !== null && piece[seed] >= 0 ? id === piece[seed] : sizes[id] * 5 >= largest;
|
|
3605
|
-
for (let p = 0; p < n; p++) if (label[p] && !keep(piece[p])) label[p] = 0;
|
|
3606
|
-
}
|
|
3607
|
-
function selectObjectIn(pixels, target, options = {}) {
|
|
3608
|
-
const work = workImage(pixels);
|
|
3609
|
-
const { width: w, height: h, scale } = work;
|
|
3610
|
-
const iterations = Math.max(1, Math.round(options.iterations ?? 5));
|
|
3611
|
-
const enhance = options.enhanceEdge ?? false;
|
|
3612
|
-
if ("rect" in target) {
|
|
3613
|
-
const r = target.rect;
|
|
3614
|
-
const bx0 = Math.max(0, Math.floor(r.x * scale));
|
|
3615
|
-
const by0 = Math.max(0, Math.floor(r.y * scale));
|
|
3616
|
-
const bx1 = Math.min(w, Math.ceil((r.x + r.width) * scale));
|
|
3617
|
-
const by1 = Math.min(h, Math.ceil((r.y + r.height) * scale));
|
|
3618
|
-
if (bx1 - bx0 < 2 || by1 - by0 < 2) return createSelectionMask(pixels.width, pixels.height);
|
|
3619
|
-
const m = Math.max(8, Math.round(0.25 * Math.max(bx1 - bx0, by1 - by0)));
|
|
3620
|
-
const rx = Math.max(0, bx0 - m);
|
|
3621
|
-
const ry = Math.max(0, by0 - m);
|
|
3622
|
-
const roi = { x: rx, y: ry, width: Math.min(w, bx1 + m) - rx, height: Math.min(h, by1 + m) - ry };
|
|
3623
|
-
const n = roi.width * roi.height;
|
|
3624
|
-
const initial = new Uint8Array(n);
|
|
3625
|
-
const hardBg = new Uint8Array(n);
|
|
3626
|
-
for (let y = 0; y < roi.height; y++) {
|
|
3627
|
-
for (let x = 0; x < roi.width; x++) {
|
|
3628
|
-
const gx = rx + x;
|
|
3629
|
-
const gy = ry + y;
|
|
3630
|
-
const inside = gx >= bx0 && gx < bx1 && gy >= by0 && gy < by1;
|
|
3631
|
-
initial[y * roi.width + x] = inside ? 1 : 0;
|
|
3632
|
-
hardBg[y * roi.width + x] = inside ? 0 : 1;
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg: null }, iterations);
|
|
3636
|
-
keepMainPieces(label, roi.width, roi.height, null);
|
|
3637
|
-
return upscaleRegion(work, pixels, roi, label, enhance);
|
|
3638
|
-
}
|
|
3639
|
-
const px2 = Math.min(w - 1, Math.max(0, Math.floor(target.point.x * scale)));
|
|
3640
|
-
const py = Math.min(h - 1, Math.max(0, Math.floor(target.point.y * scale)));
|
|
3641
|
-
let half = Math.max(24, Math.round(0.3 * Math.max(w, h)));
|
|
3642
|
-
for (; ; ) {
|
|
3643
|
-
const rx = Math.max(0, px2 - half);
|
|
3644
|
-
const ry = Math.max(0, py - half);
|
|
3645
|
-
const roi = { x: rx, y: ry, width: Math.min(w, px2 + half + 1) - rx, height: Math.min(h, py + half + 1) - ry };
|
|
3646
|
-
const whole = roi.width === w && roi.height === h;
|
|
3647
|
-
const band = Math.max(4, Math.round(0.15 * half));
|
|
3648
|
-
const n = roi.width * roi.height;
|
|
3649
|
-
const initial = new Uint8Array(n);
|
|
3650
|
-
const hardBg = new Uint8Array(n);
|
|
3651
|
-
const hardFg = new Uint8Array(n);
|
|
3652
|
-
const inBand = (x, y) => rx > 0 && x < band || ry > 0 && y < band || rx + roi.width < w && x >= roi.width - band || ry + roi.height < h && y >= roi.height - band;
|
|
3653
|
-
for (let y = 0; y < roi.height; y++) {
|
|
3654
|
-
for (let x = 0; x < roi.width; x++) {
|
|
3655
|
-
const p = y * roi.width + x;
|
|
3656
|
-
if (inBand(x, y)) hardBg[p] = 1;
|
|
3657
|
-
else initial[p] = 1;
|
|
3658
|
-
if (Math.hypot(rx + x - px2, ry + y - py) <= 1.5) hardFg[p] = 1;
|
|
3659
|
-
}
|
|
3660
|
-
}
|
|
3661
|
-
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, iterations);
|
|
3662
|
-
keepMainPieces(label, roi.width, roi.height, (py - ry) * roi.width + (px2 - rx));
|
|
3663
|
-
let touches = false;
|
|
3664
|
-
for (let y = 0; y < roi.height && !touches; y++) {
|
|
3665
|
-
for (let x = 0; x < roi.width; x++) {
|
|
3666
|
-
if (label[y * roi.width + x] && (inBand(x - 1, y) || inBand(x + 1, y) || inBand(x, y - 1) || inBand(x, y + 1))) {
|
|
3667
|
-
touches = true;
|
|
3668
|
-
break;
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
if (!touches || whole) return upscaleRegion(work, pixels, roi, label, enhance);
|
|
3673
|
-
half = Math.round(half * 1.6);
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3676
|
-
|
|
3677
|
-
// src/image-editor/selection/segment/subject.ts
|
|
3678
|
-
function toLab(r, g, b) {
|
|
3679
|
-
const lin = (v) => {
|
|
3680
|
-
v /= 255;
|
|
3681
|
-
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
3682
|
-
};
|
|
3683
|
-
const R = lin(r);
|
|
3684
|
-
const G = lin(g);
|
|
3685
|
-
const B = lin(b);
|
|
3686
|
-
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
3687
|
-
const x = f((0.4124 * R + 0.3576 * G + 0.1805 * B) / 0.95047);
|
|
3688
|
-
const y = f(0.2126 * R + 0.7152 * G + 0.0722 * B);
|
|
3689
|
-
const z = f((0.0193 * R + 0.1192 * G + 0.9505 * B) / 1.08883);
|
|
3690
|
-
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
3691
|
-
}
|
|
3692
|
-
function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
|
|
3693
|
-
const { width: w, height: h, rgb } = work;
|
|
3694
|
-
const n = w * h;
|
|
3695
|
-
const lab2 = new Float32Array(n * 3);
|
|
3696
|
-
for (let i = 0; i < n; i++) lab2.set(toLab(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]), i * 3);
|
|
3697
|
-
const S = Math.max(2, Math.round(Math.sqrt(n / target)));
|
|
3698
|
-
const seeds = [];
|
|
3699
|
-
for (let y = Math.floor(S / 2); y < h; y += S) {
|
|
3700
|
-
for (let x = Math.floor(S / 2); x < w; x += S) {
|
|
3701
|
-
const i = y * w + x;
|
|
3702
|
-
seeds.push([lab2[i * 3], lab2[i * 3 + 1], lab2[i * 3 + 2], x, y]);
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
3705
|
-
const k = seeds.length;
|
|
3706
|
-
const label = new Int32Array(n).fill(-1);
|
|
3707
|
-
const best = new Float64Array(n);
|
|
3708
|
-
const m2 = (compactness / S) ** 2;
|
|
3709
|
-
for (let round6 = 0; round6 < rounds; round6++) {
|
|
3710
|
-
best.fill(Infinity);
|
|
3711
|
-
for (let c = 0; c < k; c++) {
|
|
3712
|
-
const [L, A, B, cx, cy] = seeds[c];
|
|
3713
|
-
const x0 = Math.max(0, Math.floor(cx - S));
|
|
3714
|
-
const x1 = Math.min(w - 1, Math.ceil(cx + S));
|
|
3715
|
-
const y0 = Math.max(0, Math.floor(cy - S));
|
|
3716
|
-
const y1 = Math.min(h - 1, Math.ceil(cy + S));
|
|
3717
|
-
for (let y = y0; y <= y1; y++) {
|
|
3718
|
-
for (let x = x0; x <= x1; x++) {
|
|
3719
|
-
const i = y * w + x;
|
|
3720
|
-
const d = (lab2[i * 3] - L) ** 2 + (lab2[i * 3 + 1] - A) ** 2 + (lab2[i * 3 + 2] - B) ** 2 + m2 * ((x - cx) ** 2 + (y - cy) ** 2);
|
|
3721
|
-
if (d < best[i]) {
|
|
3722
|
-
best[i] = d;
|
|
3723
|
-
label[i] = c;
|
|
3724
|
-
}
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
}
|
|
3728
|
-
const sum = new Float64Array(k * 6);
|
|
3729
|
-
for (let i = 0; i < n; i++) {
|
|
3730
|
-
const c = label[i];
|
|
3731
|
-
sum[c * 6] += lab2[i * 3];
|
|
3732
|
-
sum[c * 6 + 1] += lab2[i * 3 + 1];
|
|
3733
|
-
sum[c * 6 + 2] += lab2[i * 3 + 2];
|
|
3734
|
-
sum[c * 6 + 3] += i % w;
|
|
3735
|
-
sum[c * 6 + 4] += Math.floor(i / w);
|
|
3736
|
-
sum[c * 6 + 5]++;
|
|
3737
|
-
}
|
|
3738
|
-
for (let c = 0; c < k; c++) {
|
|
3739
|
-
const s = sum[c * 6 + 5];
|
|
3740
|
-
if (s) seeds[c] = [sum[c * 6] / s, sum[c * 6 + 1] / s, sum[c * 6 + 2] / s, sum[c * 6 + 3] / s, sum[c * 6 + 4] / s];
|
|
3741
|
-
}
|
|
3742
|
-
}
|
|
3743
|
-
const remap = new Int32Array(k).fill(-1);
|
|
3744
|
-
let count = 0;
|
|
3745
|
-
for (let i = 0; i < n; i++) if (remap[label[i]] < 0) remap[label[i]] = count++;
|
|
3746
|
-
const meanLab = new Float64Array(count * 3);
|
|
3747
|
-
const centre = new Float64Array(count * 2);
|
|
3748
|
-
const size = new Int32Array(count);
|
|
3749
|
-
for (let i = 0; i < n; i++) {
|
|
3750
|
-
const c = label[i] = remap[label[i]];
|
|
3751
|
-
meanLab[c * 3] += lab2[i * 3];
|
|
3752
|
-
meanLab[c * 3 + 1] += lab2[i * 3 + 1];
|
|
3753
|
-
meanLab[c * 3 + 2] += lab2[i * 3 + 2];
|
|
3754
|
-
centre[c * 2] += i % w;
|
|
3755
|
-
centre[c * 2 + 1] += Math.floor(i / w);
|
|
3756
|
-
size[c]++;
|
|
3757
|
-
}
|
|
3758
|
-
for (let c = 0; c < count; c++) {
|
|
3759
|
-
for (let j = 0; j < 3; j++) meanLab[c * 3 + j] /= size[c];
|
|
3760
|
-
centre[c * 2] /= size[c];
|
|
3761
|
-
centre[c * 2 + 1] /= size[c];
|
|
3762
|
-
}
|
|
3763
|
-
return { label, count, lab: meanLab, centre, size };
|
|
3764
|
-
}
|
|
3765
|
-
function superpixelSaliency(sp, width, height) {
|
|
3766
|
-
const { count, lab: lab2, centre, size, label } = sp;
|
|
3767
|
-
const dist = (a, b) => Math.hypot(lab2[a * 3] - lab2[b * 3], lab2[a * 3 + 1] - lab2[b * 3 + 1], lab2[a * 3 + 2] - lab2[b * 3 + 2]);
|
|
3768
|
-
const onBorder = new Uint8Array(count);
|
|
3769
|
-
for (let x = 0; x < width; x++) {
|
|
3770
|
-
onBorder[label[x]] = 1;
|
|
3771
|
-
onBorder[label[(height - 1) * width + x]] = 1;
|
|
3772
|
-
}
|
|
3773
|
-
for (let y = 0; y < height; y++) {
|
|
3774
|
-
onBorder[label[y * width]] = 1;
|
|
3775
|
-
onBorder[label[y * width + width - 1]] = 1;
|
|
3776
|
-
}
|
|
3777
|
-
const border = [];
|
|
3778
|
-
for (let c = 0; c < count; c++) if (onBorder[c]) border.push(c);
|
|
3779
|
-
const diag = Math.hypot(width, height);
|
|
3780
|
-
const unlikeBorder = new Float64Array(count);
|
|
3781
|
-
const contrast = new Float64Array(count);
|
|
3782
|
-
const centreBias = new Float64Array(count);
|
|
3783
|
-
for (let i = 0; i < count; i++) {
|
|
3784
|
-
const ds = border.map((j) => dist(i, j)).sort((a, b) => a - b);
|
|
3785
|
-
unlikeBorder[i] = ds.length ? ds[Math.floor(ds.length * 0.25)] : 0;
|
|
3786
|
-
let s = 0;
|
|
3787
|
-
let wsum = 0;
|
|
3788
|
-
for (let j = 0; j < count; j++) {
|
|
3789
|
-
if (j === i) continue;
|
|
3790
|
-
const dx = (centre[i * 2] - centre[j * 2]) / diag;
|
|
3791
|
-
const dy = (centre[i * 2 + 1] - centre[j * 2 + 1]) / diag;
|
|
3792
|
-
const wgt = size[j] * Math.exp(-(dx * dx + dy * dy) / (2 * 0.25 * 0.25));
|
|
3793
|
-
s += wgt * dist(i, j);
|
|
3794
|
-
wsum += wgt;
|
|
3795
|
-
}
|
|
3796
|
-
contrast[i] = wsum ? s / wsum : 0;
|
|
3797
|
-
const cx = centre[i * 2] / width - 0.5;
|
|
3798
|
-
const cy = centre[i * 2 + 1] / height - 0.5;
|
|
3799
|
-
centreBias[i] = 0.4 + 0.6 * Math.exp(-(cx * cx + cy * cy) / (2 * 0.3 * 0.3));
|
|
3800
|
-
}
|
|
3801
|
-
let farthest = 0;
|
|
3802
|
-
for (const v of unlikeBorder) farthest = Math.max(farthest, v);
|
|
3803
|
-
if (farthest < 6) return new Float64Array(count);
|
|
3804
|
-
const norm = (a) => {
|
|
3805
|
-
let max = 0;
|
|
3806
|
-
for (const v of a) max = Math.max(max, v);
|
|
3807
|
-
if (max > 0) for (let i = 0; i < a.length; i++) a[i] /= max;
|
|
3808
|
-
};
|
|
3809
|
-
norm(unlikeBorder);
|
|
3810
|
-
norm(contrast);
|
|
3811
|
-
const out = new Float64Array(count);
|
|
3812
|
-
for (let i = 0; i < count; i++) out[i] = unlikeBorder[i] * (0.3 + 0.7 * contrast[i]) * centreBias[i];
|
|
3813
|
-
norm(out);
|
|
3814
|
-
return out;
|
|
3815
|
-
}
|
|
3816
|
-
function otsuThreshold(values, weights) {
|
|
3817
|
-
const BINS2 = 64;
|
|
3818
|
-
const hist = new Float64Array(BINS2);
|
|
3819
|
-
let total = 0;
|
|
3820
|
-
for (let i = 0; i < values.length; i++) {
|
|
3821
|
-
hist[Math.min(BINS2 - 1, Math.floor(values[i] * BINS2))] += weights[i];
|
|
3822
|
-
total += weights[i];
|
|
3823
|
-
}
|
|
3824
|
-
let sumAll = 0;
|
|
3825
|
-
for (let b = 0; b < BINS2; b++) sumAll += b * hist[b];
|
|
3826
|
-
let wB = 0;
|
|
3827
|
-
let sumB = 0;
|
|
3828
|
-
let best = 0;
|
|
3829
|
-
let bestT = 0.5;
|
|
3830
|
-
for (let b = 0; b < BINS2 - 1; b++) {
|
|
3831
|
-
wB += hist[b];
|
|
3832
|
-
if (!wB) continue;
|
|
3833
|
-
const wF = total - wB;
|
|
3834
|
-
if (!wF) break;
|
|
3835
|
-
sumB += b * hist[b];
|
|
3836
|
-
const between = wB * wF * (sumB / wB - (sumAll - sumB) / wF) ** 2;
|
|
3837
|
-
if (between > best) {
|
|
3838
|
-
best = between;
|
|
3839
|
-
bestT = (b + 1) / BINS2;
|
|
3840
|
-
}
|
|
3841
|
-
}
|
|
3842
|
-
return bestT;
|
|
3843
|
-
}
|
|
3844
|
-
function findSubject(pixels, options = {}) {
|
|
3845
|
-
const work = workImage(pixels);
|
|
3846
|
-
const { width: w, height: h } = work;
|
|
3847
|
-
const sp = slicSuperpixels(work);
|
|
3848
|
-
const sal = superpixelSaliency(sp, w, h);
|
|
3849
|
-
const t = otsuThreshold(sal, sp.size);
|
|
3850
|
-
const n = w * h;
|
|
3851
|
-
const initial = new Uint8Array(n);
|
|
3852
|
-
const hardBg = new Uint8Array(n);
|
|
3853
|
-
const hardFg = new Uint8Array(n);
|
|
3854
|
-
const strong = t + 0.6 * (1 - t);
|
|
3855
|
-
for (let i = 0; i < n; i++) {
|
|
3856
|
-
const s = sal[sp.label[i]];
|
|
3857
|
-
if (s >= t) initial[i] = 1;
|
|
3858
|
-
if (s >= strong) hardFg[i] = 1;
|
|
3859
|
-
}
|
|
3860
|
-
for (let x = 0; x < w; x++) {
|
|
3861
|
-
for (const y of [0, h - 1]) if (sal[sp.label[y * w + x]] < t * 0.5) hardBg[y * w + x] = 1;
|
|
3862
|
-
}
|
|
3863
|
-
for (let y = 0; y < h; y++) {
|
|
3864
|
-
for (const x of [0, w - 1]) if (sal[sp.label[y * w + x]] < t * 0.5) hardBg[y * w + x] = 1;
|
|
3865
|
-
}
|
|
3866
|
-
const empty = (reason) => ({ selection: createSelectionMask(pixels.width, pixels.height), reason });
|
|
3867
|
-
let guessed = 0;
|
|
3868
|
-
for (let i = 0; i < n; i++) guessed += initial[i];
|
|
3869
|
-
if (!guessed || guessed > n * 0.95) return empty("Nothing stands out from the background.");
|
|
3870
|
-
const roi = { x: 0, y: 0, width: w, height: h };
|
|
3871
|
-
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, 5);
|
|
3872
|
-
keepMainPieces(label, w, h, null);
|
|
3873
|
-
let kept = 0;
|
|
3874
|
-
for (let i = 0; i < n; i++) kept += label[i];
|
|
3875
|
-
if (kept < n * 2e-3) return empty("Nothing stands out from the background.");
|
|
3876
|
-
if (kept > n * 0.97) return empty("The whole image looks like one subject; select it with select_all instead.");
|
|
3877
|
-
return { selection: upscaleRegion(work, pixels, roi, label, options.enhanceEdge ?? false) };
|
|
3878
|
-
}
|
|
3879
|
-
|
|
3880
|
-
// src/image-editor/selection/refine.ts
|
|
3881
|
-
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
3882
|
-
radius: 0,
|
|
3883
|
-
smartRadius: false,
|
|
3884
|
-
smooth: 0,
|
|
3885
|
-
feather: 0,
|
|
3886
|
-
contrast: 0,
|
|
3887
|
-
shiftEdge: 0
|
|
3888
|
-
};
|
|
3889
|
-
var GUIDED_FILTER_EPS = 1e-4;
|
|
3890
|
-
var fin = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
3891
|
-
function normalizeRefineEdgeParams(p = {}) {
|
|
3892
|
-
return {
|
|
3893
|
-
radius: Math.min(250, Math.max(0, fin(p.radius, 0))),
|
|
3894
|
-
smartRadius: p.smartRadius === true,
|
|
3895
|
-
smooth: Math.min(100, Math.max(0, fin(p.smooth, 0))),
|
|
3896
|
-
feather: Math.min(1e3, Math.max(0, fin(p.feather, 0))),
|
|
3897
|
-
contrast: Math.min(100, Math.max(0, fin(p.contrast, 0))),
|
|
3898
|
-
shiftEdge: Math.min(100, Math.max(-100, fin(p.shiftEdge, 0)))
|
|
3899
|
-
};
|
|
3900
|
-
}
|
|
3901
|
-
var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
3902
|
-
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
3903
|
-
var featherSigma = (p) => p.feather / 2;
|
|
3904
|
-
function shiftAmount(p) {
|
|
3905
|
-
const px2 = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
3906
|
-
return Number.isFinite(px2) ? Math.min(64, Math.max(-64, px2)) : 0;
|
|
3907
|
-
}
|
|
3908
|
-
function refineEdgeHalo(params = {}) {
|
|
3909
|
-
const p = normalizeRefineEdgeParams(params);
|
|
3910
|
-
const g = gfRadius(p);
|
|
3911
|
-
const edge = p.radius > 0 ? Math.max(Math.ceil(p.radius), 2 * g + (p.smartRadius ? g + 1 : 0)) : 0;
|
|
3912
|
-
return edge + Math.ceil(3 * smoothSigma(p)) + Math.ceil(3 * featherSigma(p)) + Math.ceil(Math.abs(shiftAmount(p))) + 2;
|
|
3913
|
-
}
|
|
3914
|
-
function boxMean(src, w, h, r) {
|
|
3915
|
-
const tmp = new Float32Array(w * h);
|
|
3916
|
-
const pre = new Float64Array(Math.max(w, h) + 1);
|
|
3917
|
-
for (let y = 0; y < h; y++) {
|
|
3918
|
-
const row = y * w;
|
|
3919
|
-
for (let x = 0; x < w; x++) pre[x + 1] = pre[x] + src[row + x];
|
|
3920
|
-
for (let x = 0; x < w; x++) {
|
|
3921
|
-
const lo = Math.max(0, x - r);
|
|
3922
|
-
const hi = Math.min(w, x + r + 1);
|
|
3923
|
-
tmp[row + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
3924
|
-
}
|
|
3925
|
-
}
|
|
3926
|
-
const out = new Float32Array(w * h);
|
|
3927
|
-
for (let x = 0; x < w; x++) {
|
|
3928
|
-
for (let y = 0; y < h; y++) pre[y + 1] = pre[y] + tmp[y * w + x];
|
|
3929
|
-
for (let y = 0; y < h; y++) {
|
|
3930
|
-
const lo = Math.max(0, y - r);
|
|
3931
|
-
const hi = Math.min(h, y + r + 1);
|
|
3932
|
-
out[y * w + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
3933
|
-
}
|
|
3934
|
-
}
|
|
3935
|
-
return out;
|
|
3936
|
-
}
|
|
3937
|
-
function guidedFilterColor(guide, p, w, h, r, eps = GUIDED_FILTER_EPS) {
|
|
3938
|
-
const n = w * h;
|
|
3939
|
-
const [c0, c1, c2] = guide;
|
|
3940
|
-
const prod = (a, b2) => {
|
|
3941
|
-
const o = new Float32Array(n);
|
|
3942
|
-
for (let i = 0; i < n; i++) o[i] = a[i] * b2[i];
|
|
3943
|
-
return o;
|
|
3944
|
-
};
|
|
3945
|
-
const m0 = boxMean(c0, w, h, r);
|
|
3946
|
-
const m1 = boxMean(c1, w, h, r);
|
|
3947
|
-
const m2 = boxMean(c2, w, h, r);
|
|
3948
|
-
const mp = boxMean(p, w, h, r);
|
|
3949
|
-
const mip0 = boxMean(prod(c0, p), w, h, r);
|
|
3950
|
-
const mip1 = boxMean(prod(c1, p), w, h, r);
|
|
3951
|
-
const mip2 = boxMean(prod(c2, p), w, h, r);
|
|
3952
|
-
const s00 = boxMean(prod(c0, c0), w, h, r);
|
|
3953
|
-
const s01 = boxMean(prod(c0, c1), w, h, r);
|
|
3954
|
-
const s02 = boxMean(prod(c0, c2), w, h, r);
|
|
3955
|
-
const s11 = boxMean(prod(c1, c1), w, h, r);
|
|
3956
|
-
const s12 = boxMean(prod(c1, c2), w, h, r);
|
|
3957
|
-
const s22 = boxMean(prod(c2, c2), w, h, r);
|
|
3958
|
-
const a0 = new Float32Array(n);
|
|
3959
|
-
const a1 = new Float32Array(n);
|
|
3960
|
-
const a2 = new Float32Array(n);
|
|
3961
|
-
const b = new Float32Array(n);
|
|
3962
|
-
for (let i = 0; i < n; i++) {
|
|
3963
|
-
const u0 = m0[i], u1 = m1[i], u2 = m2[i], up = mp[i];
|
|
3964
|
-
const rr = s00[i] - u0 * u0 + eps;
|
|
3965
|
-
const rg = s01[i] - u0 * u1;
|
|
3966
|
-
const rb = s02[i] - u0 * u2;
|
|
3967
|
-
const gg = s11[i] - u1 * u1 + eps;
|
|
3968
|
-
const gb = s12[i] - u1 * u2;
|
|
3969
|
-
const bb = s22[i] - u2 * u2 + eps;
|
|
3970
|
-
const v0 = mip0[i] - u0 * up;
|
|
3971
|
-
const v1 = mip1[i] - u1 * up;
|
|
3972
|
-
const v2 = mip2[i] - u2 * up;
|
|
3973
|
-
const i00 = gg * bb - gb * gb;
|
|
3974
|
-
const i01 = gb * rb - rg * bb;
|
|
3975
|
-
const i02 = rg * gb - gg * rb;
|
|
3976
|
-
const i11 = rr * bb - rb * rb;
|
|
3977
|
-
const i12 = rb * rg - rr * gb;
|
|
3978
|
-
const i22 = rr * gg - rg * rg;
|
|
3979
|
-
const det = rr * i00 + rg * i01 + rb * i02;
|
|
3980
|
-
let x0 = 0, x1 = 0, x2 = 0;
|
|
3981
|
-
if (Math.abs(det) >= 1e-30) {
|
|
3982
|
-
x0 = (i00 * v0 + i01 * v1 + i02 * v2) / det;
|
|
3983
|
-
x1 = (i01 * v0 + i11 * v1 + i12 * v2) / det;
|
|
3984
|
-
x2 = (i02 * v0 + i12 * v1 + i22 * v2) / det;
|
|
3985
|
-
}
|
|
3986
|
-
a0[i] = x0;
|
|
3987
|
-
a1[i] = x1;
|
|
3988
|
-
a2[i] = x2;
|
|
3989
|
-
b[i] = up - x0 * u0 - x1 * u1 - x2 * u2;
|
|
3990
|
-
}
|
|
3991
|
-
const ma0 = boxMean(a0, w, h, r);
|
|
3992
|
-
const ma1 = boxMean(a1, w, h, r);
|
|
3993
|
-
const ma2 = boxMean(a2, w, h, r);
|
|
3994
|
-
const mb = boxMean(b, w, h, r);
|
|
3995
|
-
const out = new Float32Array(n);
|
|
3996
|
-
for (let i = 0; i < n; i++) out[i] = ma0[i] * c0[i] + ma1[i] * c1[i] + ma2[i] * c2[i] + mb[i];
|
|
3997
|
-
return out;
|
|
3998
|
-
}
|
|
3999
|
-
function edgeWidth(guide, w, h, r) {
|
|
4000
|
-
const n = w * h;
|
|
4001
|
-
const y = new Float32Array(n);
|
|
4002
|
-
for (let i = 0; i < n; i++) y[i] = 0.299 * guide[0][i] + 0.587 * guide[1][i] + 0.114 * guide[2][i];
|
|
4003
|
-
const g = new Float32Array(n);
|
|
4004
|
-
for (let yy = 0; yy < h; yy++) {
|
|
4005
|
-
for (let xx = 0; xx < w; xx++) {
|
|
4006
|
-
const gx = (y[yy * w + Math.min(w - 1, xx + 1)] - y[yy * w + Math.max(0, xx - 1)]) / 2;
|
|
4007
|
-
const gy = (y[Math.min(h - 1, yy + 1) * w + xx] - y[Math.max(0, yy - 1) * w + xx]) / 2;
|
|
4008
|
-
g[yy * w + xx] = Math.sqrt(gx * gx + gy * gy);
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
const gm = greyMorphology(g, w, h, r, true);
|
|
4012
|
-
const hi = greyMorphology(y, w, h, r, true);
|
|
4013
|
-
const lo = greyMorphology(y, w, h, r, false);
|
|
4014
|
-
const out = new Float32Array(n);
|
|
4015
|
-
for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
|
|
4016
|
-
return out;
|
|
4017
|
-
}
|
|
4018
|
-
function refineCoverage(guide, m, w, h, params = {}, band = null) {
|
|
4019
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4020
|
-
let a = Float32Array.from(m);
|
|
4021
|
-
if ((p.radius > 0 || band) && guide) {
|
|
4022
|
-
const n = w * h;
|
|
4023
|
-
const boundary = new Uint8Array(n);
|
|
4024
|
-
let any = false;
|
|
4025
|
-
for (let y = 0; y < h; y++) {
|
|
4026
|
-
for (let x = 0; x < w; x++) {
|
|
4027
|
-
const i = y * w + x;
|
|
4028
|
-
const v = m[i] >= 0.5;
|
|
4029
|
-
const diff = x > 0 && m[i - 1] >= 0.5 !== v || x + 1 < w && m[i + 1] >= 0.5 !== v || y > 0 && m[i - w] >= 0.5 !== v || y + 1 < h && m[i + w] >= 0.5 !== v;
|
|
4030
|
-
if (diff) {
|
|
4031
|
-
boundary[i] = 1;
|
|
4032
|
-
any = true;
|
|
4033
|
-
}
|
|
4034
|
-
}
|
|
4035
|
-
}
|
|
4036
|
-
if (any || band) {
|
|
4037
|
-
const dist = distanceTransform(boundary, w, h);
|
|
4038
|
-
const r = band ? Math.max(4, gfRadius(p)) : gfRadius(p);
|
|
4039
|
-
const q = guidedFilterColor(guide, m, w, h, r);
|
|
4040
|
-
const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
|
|
4041
|
-
for (let i = 0; i < n; i++) {
|
|
4042
|
-
const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
|
|
4043
|
-
const edge = p.radius > 0 ? Math.min(1, Math.max(0, reff + 1 - dist[i])) : 0;
|
|
4044
|
-
const wgt = band ? Math.max(edge, Math.min(1, Math.max(0, band[i]))) : edge;
|
|
4045
|
-
if (wgt > 0) {
|
|
4046
|
-
const qi = Math.min(1, Math.max(0, q[i]));
|
|
4047
|
-
a[i] = m[i] + (qi - m[i]) * wgt;
|
|
4048
|
-
}
|
|
4049
|
-
}
|
|
4050
|
-
}
|
|
4051
|
-
}
|
|
4052
|
-
const ss = smoothSigma(p);
|
|
4053
|
-
if (ss >= 0.3) {
|
|
4054
|
-
a = gaussianBlur(a, w, h, ss, "clamp");
|
|
4055
|
-
const gain = 1 + ss;
|
|
4056
|
-
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4057
|
-
}
|
|
4058
|
-
a = gaussianBlur(a, w, h, featherSigma(p), "clamp");
|
|
4059
|
-
if (p.contrast > 0) {
|
|
4060
|
-
const gain = 1 / (1 - 0.99 * p.contrast / 100);
|
|
4061
|
-
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4062
|
-
}
|
|
4063
|
-
const amount = shiftAmount(p);
|
|
4064
|
-
if (Math.abs(amount) >= 1e-4) a = shiftCoverage(a, w, h, amount);
|
|
4065
|
-
return a;
|
|
4066
|
-
}
|
|
4067
|
-
function guidePlanes(pixels, win) {
|
|
4068
|
-
const box = win ?? { x: 0, y: 0, width: pixels.width, height: pixels.height };
|
|
4069
|
-
const n = box.width * box.height;
|
|
4070
|
-
const r = new Float32Array(n);
|
|
4071
|
-
const g = new Float32Array(n);
|
|
4072
|
-
const b = new Float32Array(n);
|
|
4073
|
-
const d = pixels.data;
|
|
4074
|
-
for (let y = 0; y < box.height; y++) {
|
|
4075
|
-
for (let x = 0; x < box.width; x++) {
|
|
4076
|
-
const i = y * box.width + x;
|
|
4077
|
-
const p = ((box.y + y) * pixels.width + box.x + x) * 4;
|
|
4078
|
-
r[i] = d[p] / 255;
|
|
4079
|
-
g[i] = d[p + 1] / 255;
|
|
4080
|
-
b[i] = d[p + 2] / 255;
|
|
4081
|
-
}
|
|
4082
|
-
}
|
|
4083
|
-
return [r, g, b];
|
|
4084
|
-
}
|
|
4085
|
-
function refineSelection(pixels, mask, params = {}, refineBrush = null) {
|
|
4086
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4087
|
-
if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
|
|
4088
|
-
throw new Error("Refine Edge needs pixels the size of the selection");
|
|
4089
|
-
}
|
|
4090
|
-
if (!mask.bounds) return mask;
|
|
4091
|
-
const brush = refineBrush?.bounds ? refineBrush : null;
|
|
4092
|
-
const halo = refineEdgeHalo(p) + (brush ? 2 * Math.max(4, gfRadius(p)) : 0);
|
|
4093
|
-
const b = brush ? {
|
|
4094
|
-
x: Math.min(mask.bounds.x, brush.bounds.x),
|
|
4095
|
-
y: Math.min(mask.bounds.y, brush.bounds.y),
|
|
4096
|
-
r: Math.max(mask.bounds.x + mask.bounds.width, brush.bounds.x + brush.bounds.width),
|
|
4097
|
-
btm: Math.max(mask.bounds.y + mask.bounds.height, brush.bounds.y + brush.bounds.height)
|
|
4098
|
-
} : { x: mask.bounds.x, y: mask.bounds.y, r: mask.bounds.x + mask.bounds.width, btm: mask.bounds.y + mask.bounds.height };
|
|
4099
|
-
const x0 = Math.max(0, b.x - halo);
|
|
4100
|
-
const y0 = Math.max(0, b.y - halo);
|
|
4101
|
-
const x1 = Math.min(mask.width, b.r + halo);
|
|
4102
|
-
const y1 = Math.min(mask.height, b.btm + halo);
|
|
4103
|
-
const ww = x1 - x0;
|
|
4104
|
-
const wh = y1 - y0;
|
|
4105
|
-
const m = new Float32Array(ww * wh);
|
|
4106
|
-
for (let y = 0; y < wh; y++) {
|
|
4107
|
-
const src = (y0 + y) * mask.width + x0;
|
|
4108
|
-
for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
|
|
4109
|
-
}
|
|
4110
|
-
const guide = pixels && (p.radius > 0 || brush) ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
4111
|
-
let band = null;
|
|
4112
|
-
if (brush) {
|
|
4113
|
-
band = new Float32Array(ww * wh);
|
|
4114
|
-
const bd = brush.data;
|
|
4115
|
-
for (let y = 0; y < wh; y++) {
|
|
4116
|
-
const src = (y0 + y) * mask.width + x0;
|
|
4117
|
-
for (let x = 0; x < ww; x++) band[y * ww + x] = bd[src + x] / 255;
|
|
4118
|
-
}
|
|
4119
|
-
}
|
|
4120
|
-
const refined = refineCoverage(guide, m, ww, wh, p, band);
|
|
4121
|
-
const out = new Uint8Array(mask.width * mask.height);
|
|
4122
|
-
for (let y = 0; y < wh; y++) {
|
|
4123
|
-
const dst = (y0 + y) * mask.width + x0;
|
|
4124
|
-
for (let x = 0; x < ww; x++) out[dst + x] = coverageToByte(refined[y * ww + x]);
|
|
4125
|
-
}
|
|
4126
|
-
return createSelectionMask(mask.width, mask.height, out);
|
|
4127
|
-
}
|
|
4128
|
-
function decontaminateColors(rgba, coverage, w, h, amount, radius = 8) {
|
|
4129
|
-
const out = Uint8ClampedArray.from(rgba);
|
|
4130
|
-
const n = w * h;
|
|
4131
|
-
const k = Math.min(1, Math.max(0, amount));
|
|
4132
|
-
if (k === 0) return out;
|
|
4133
|
-
const r = Math.max(2, Math.round(radius));
|
|
4134
|
-
const fgW = new Float32Array(n);
|
|
4135
|
-
const bgW = new Float32Array(n);
|
|
4136
|
-
for (let i = 0; i < n; i++) {
|
|
4137
|
-
fgW[i] = coverage[i] >= 250 ? 1 : 0;
|
|
4138
|
-
bgW[i] = coverage[i] <= 5 ? 1 : 0;
|
|
4139
|
-
}
|
|
4140
|
-
const meanOf = (weights, radiusPx) => {
|
|
4141
|
-
const wm = boxMean(weights, w, h, radiusPx);
|
|
4142
|
-
const planes = [0, 1, 2].map((c) => {
|
|
4143
|
-
const v = new Float32Array(n);
|
|
4144
|
-
for (let i = 0; i < n; i++) v[i] = rgba[i * 4 + c] * weights[i];
|
|
4145
|
-
const m = boxMean(v, w, h, radiusPx);
|
|
4146
|
-
for (let i = 0; i < n; i++) m[i] = wm[i] > 1e-6 ? m[i] / wm[i] : NaN;
|
|
4147
|
-
return m;
|
|
4148
|
-
});
|
|
4149
|
-
return { weight: wm, planes };
|
|
4150
|
-
};
|
|
4151
|
-
const fg = meanOf(fgW, r);
|
|
4152
|
-
const fgWide = meanOf(fgW, r * 3);
|
|
4153
|
-
const bg = meanOf(bgW, r);
|
|
4154
|
-
for (let i = 0; i < n; i++) {
|
|
4155
|
-
const c = coverage[i];
|
|
4156
|
-
if (c <= 5 || c >= 250) continue;
|
|
4157
|
-
const a = c / 255;
|
|
4158
|
-
for (let ch = 0; ch < 3; ch++) {
|
|
4159
|
-
const C = rgba[i * 4 + ch];
|
|
4160
|
-
const B = bg.planes[ch][i];
|
|
4161
|
-
const Fm = Number.isNaN(fg.planes[ch][i]) ? fgWide.planes[ch][i] : fg.planes[ch][i];
|
|
4162
|
-
let F;
|
|
4163
|
-
if (a >= 0.5 && !Number.isNaN(B)) F = (C - (1 - a) * B) / a;
|
|
4164
|
-
else if (!Number.isNaN(Fm)) F = Fm;
|
|
4165
|
-
else F = C;
|
|
4166
|
-
out[i * 4 + ch] = Math.round(C + k * (Math.min(255, Math.max(0, F)) - C));
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
return out;
|
|
4170
|
-
}
|
|
4171
|
-
|
|
4172
|
-
// src/image-editor/selection/segment/guided.ts
|
|
4173
|
-
function workPlanes(work) {
|
|
4174
|
-
const n = work.width * work.height;
|
|
4175
|
-
const planes = [new Float32Array(n), new Float32Array(n), new Float32Array(n)];
|
|
4176
|
-
for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] = work.rgb[i * 3 + c] / 255;
|
|
4177
|
-
return planes;
|
|
4178
|
-
}
|
|
4179
|
-
function workLuma(work) {
|
|
4180
|
-
const n = work.width * work.height;
|
|
4181
|
-
const out = new Float32Array(n);
|
|
4182
|
-
const { rgb } = work;
|
|
4183
|
-
for (let i = 0; i < n; i++) out[i] = (0.299 * rgb[i * 3] + 0.587 * rgb[i * 3 + 1] + 0.114 * rgb[i * 3 + 2]) / 255;
|
|
4184
|
-
return out;
|
|
4185
|
-
}
|
|
4186
|
-
function softMaskToSelection(work, mask) {
|
|
4187
|
-
const { sourceWidth: W, sourceHeight: H, width: w, height: h, scale } = work;
|
|
4188
|
-
const out = new Uint8Array(W * H);
|
|
4189
|
-
const col = new Int32Array(W);
|
|
4190
|
-
const fx = new Float32Array(W);
|
|
4191
|
-
for (let X = 0; X < W; X++) {
|
|
4192
|
-
const u = Math.min(w - 1, Math.max(0, (X + 0.5) * scale - 0.5));
|
|
4193
|
-
col[X] = Math.min(w - 2, Math.floor(u));
|
|
4194
|
-
fx[X] = u - col[X];
|
|
4195
|
-
if (w === 1) {
|
|
4196
|
-
col[X] = 0;
|
|
4197
|
-
fx[X] = 0;
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
for (let Y = 0; Y < H; Y++) {
|
|
4201
|
-
const v = Math.min(h - 1, Math.max(0, (Y + 0.5) * scale - 0.5));
|
|
4202
|
-
const j = h === 1 ? 0 : Math.min(h - 2, Math.floor(v));
|
|
4203
|
-
const fy = h === 1 ? 0 : v - j;
|
|
4204
|
-
const r0 = j * w;
|
|
4205
|
-
const r1 = h === 1 ? r0 : r0 + w;
|
|
4206
|
-
for (let X = 0; X < W; X++) {
|
|
4207
|
-
const i = col[X];
|
|
4208
|
-
const i1 = w === 1 ? i : i + 1;
|
|
4209
|
-
const t = fx[X];
|
|
4210
|
-
const m = (mask[r0 + i] * (1 - t) + mask[r0 + i1] * t) * (1 - fy) + (mask[r1 + i] * (1 - t) + mask[r1 + i1] * t) * fy;
|
|
4211
|
-
out[Y * W + X] = Math.round(Math.min(1, Math.max(0, m)) * 255);
|
|
4212
|
-
}
|
|
4213
|
-
}
|
|
4214
|
-
return selectionFromRegion(W, H, { x: 0, y: 0, width: W, height: H }, out);
|
|
4215
|
-
}
|
|
4216
|
-
|
|
4217
|
-
// src/image-editor/selection/segment/focus.ts
|
|
4218
|
-
var FOCUS_SIZE = 1024;
|
|
4219
|
-
function estimateNoise(luma2, w, h) {
|
|
4220
|
-
if (w < 3 || h < 3) return 0;
|
|
4221
|
-
let sum = 0;
|
|
4222
|
-
for (let y = 1; y < h - 1; y++) {
|
|
4223
|
-
for (let x = 1; x < w - 1; x++) {
|
|
4224
|
-
const i = y * w + x;
|
|
4225
|
-
const v = luma2[i - w - 1] - 2 * luma2[i - w] + luma2[i - w + 1] - 2 * luma2[i - 1] + 4 * luma2[i] - 2 * luma2[i + 1] + luma2[i + w - 1] - 2 * luma2[i + w] + luma2[i + w + 1];
|
|
4226
|
-
sum += Math.abs(v);
|
|
4227
|
-
}
|
|
4228
|
-
}
|
|
4229
|
-
return Math.sqrt(Math.PI / 2) * sum / (6 * (w - 2) * (h - 2));
|
|
4230
|
-
}
|
|
4231
|
-
function focusArea(pixels, options = {}) {
|
|
4232
|
-
const work = workImage(pixels, FOCUS_SIZE);
|
|
4233
|
-
const { width: w, height: h, scale } = work;
|
|
4234
|
-
const n = w * h;
|
|
4235
|
-
const luma2 = workLuma(work);
|
|
4236
|
-
const sigma = estimateNoise(luma2, w, h);
|
|
4237
|
-
const noise = Math.min(100, Math.max(0, options.noise ?? 0));
|
|
4238
|
-
const floor = 2 * sigma * (1 + noise / 25);
|
|
4239
|
-
const detail = new Float32Array(n);
|
|
4240
|
-
for (let y = 1; y < h - 1; y++) {
|
|
4241
|
-
for (let x = 1; x < w - 1; x++) {
|
|
4242
|
-
const i = y * w + x;
|
|
4243
|
-
const lap = Math.abs(4 * luma2[i] - luma2[i - 1] - luma2[i + 1] - luma2[i - w] - luma2[i + w]);
|
|
4244
|
-
const d = Math.max(0, lap - floor);
|
|
4245
|
-
detail[i] = d * d;
|
|
4246
|
-
}
|
|
4247
|
-
}
|
|
4248
|
-
const r = Math.max(2, Math.round(0.012 * Math.max(w, h)));
|
|
4249
|
-
const energy = boxMean(detail, w, h, r);
|
|
4250
|
-
const sharp = new Float32Array(n);
|
|
4251
|
-
for (let i = 0; i < n; i++) sharp[i] = Math.log1p(Math.sqrt(energy[i]) * 400);
|
|
4252
|
-
const sorted = Float32Array.from(sharp).sort();
|
|
4253
|
-
const top = sorted[Math.floor(n * 0.995)] || 1;
|
|
4254
|
-
for (let i = 0; i < n; i++) sharp[i] = Math.min(1, sharp[i] / top);
|
|
4255
|
-
const ones = new Float32Array(n).fill(1);
|
|
4256
|
-
const otsu = otsuThreshold(sharp, ones);
|
|
4257
|
-
const range = Math.min(100, Math.max(0, options.range ?? 50));
|
|
4258
|
-
const t = Math.min(0.98, otsu * Math.pow(2, (50 - range) / 25));
|
|
4259
|
-
let mask = new Float32Array(n);
|
|
4260
|
-
for (let i = 0; i < n; i++) mask[i] = sharp[i] >= t ? 1 : 0;
|
|
4261
|
-
const c = 2 * r;
|
|
4262
|
-
const grown = boxMean(mask, w, h, c);
|
|
4263
|
-
for (let i = 0; i < n; i++) grown[i] = grown[i] > 1e-3 ? 1 : 0;
|
|
4264
|
-
const shrunk = boxMean(grown, w, h, c);
|
|
4265
|
-
for (let i = 0; i < n; i++) mask[i] = shrunk[i] >= 0.999 ? 1 : 0;
|
|
4266
|
-
const pulled = boxMean(mask, w, h, Math.max(1, Math.round(r / 2)));
|
|
4267
|
-
for (let i = 0; i < n; i++) mask[i] = pulled[i] >= 0.999 ? 1 : 0;
|
|
4268
|
-
const softness = Math.max(0, options.softness ?? 4) * scale;
|
|
4269
|
-
if (softness > 0) {
|
|
4270
|
-
const fitted = guidedFilterColor(workPlanes(work), mask, w, h, Math.max(1, Math.round(softness * 2)), 1e-3);
|
|
4271
|
-
for (let i = 0; i < n; i++) mask[i] = Math.min(1, Math.max(0, fitted[i]));
|
|
4272
|
-
}
|
|
4273
|
-
return { selection: softMaskToSelection(work, mask), noiseLevel: Math.round(sigma * 255 * 10) / 10 };
|
|
4274
|
-
}
|
|
4275
|
-
|
|
4276
|
-
// src/image-editor/selection/segment/sky.ts
|
|
4277
|
-
var SKY_SIZE = 768;
|
|
4278
|
-
function lab(r, g, b) {
|
|
4279
|
-
const lin = (v) => {
|
|
4280
|
-
v /= 255;
|
|
4281
|
-
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
4282
|
-
};
|
|
4283
|
-
const R = lin(r);
|
|
4284
|
-
const G = lin(g);
|
|
4285
|
-
const B = lin(b);
|
|
4286
|
-
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
4287
|
-
const x = f((0.4124 * R + 0.3576 * G + 0.1805 * B) / 0.95047);
|
|
4288
|
-
const y = f(0.2126 * R + 0.7152 * G + 0.0722 * B);
|
|
4289
|
-
const z = f((0.0193 * R + 0.1192 * G + 0.9505 * B) / 1.08883);
|
|
4290
|
-
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
4291
|
-
}
|
|
4292
|
-
var sigmoid = (v) => 1 / (1 + Math.exp(-v));
|
|
4293
|
-
function findSky(pixels, options = {}) {
|
|
4294
|
-
const work = workImage(pixels, SKY_SIZE);
|
|
4295
|
-
const { width: w, height: h, rgb, scale } = work;
|
|
4296
|
-
const n = w * h;
|
|
4297
|
-
const L = new Float32Array(n);
|
|
4298
|
-
const A = new Float32Array(n);
|
|
4299
|
-
const B = new Float32Array(n);
|
|
4300
|
-
for (let i = 0; i < n; i++) {
|
|
4301
|
-
const [l, a, b] = lab(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
|
4302
|
-
L[i] = l;
|
|
4303
|
-
A[i] = a;
|
|
4304
|
-
B[i] = b;
|
|
4305
|
-
}
|
|
4306
|
-
const luma2 = boxMean(workLuma(work), w, h, 1);
|
|
4307
|
-
const grad = new Float32Array(n);
|
|
4308
|
-
for (let y = 0; y < h; y++) {
|
|
4309
|
-
for (let x = 0; x < w; x++) {
|
|
4310
|
-
const i = y * w + x;
|
|
4311
|
-
const gx = x + 1 < w ? luma2[i + 1] - luma2[i] : 0;
|
|
4312
|
-
const gy = y + 1 < h ? luma2[i + w] - luma2[i] : 0;
|
|
4313
|
-
grad[i] = Math.hypot(gx, gy);
|
|
4314
|
-
}
|
|
4315
|
-
}
|
|
4316
|
-
const roughR = Math.max(1, Math.round(6e-3 * Math.max(w, h)));
|
|
4317
|
-
const rough = boxMean(grad, w, h, roughR);
|
|
4318
|
-
const score = new Float32Array(n);
|
|
4319
|
-
for (let i = 0; i < n; i++) {
|
|
4320
|
-
const chroma = Math.hypot(A[i], B[i]);
|
|
4321
|
-
const blue = sigmoid((-B[i] - 6) / 4) * sigmoid((L[i] - 35) / 6);
|
|
4322
|
-
const grey = sigmoid((L[i] - 68) / 5) * sigmoid((18 - chroma) / 4);
|
|
4323
|
-
const smooth = Math.exp(-rough[i] / 0.02);
|
|
4324
|
-
const y = Math.floor(i / w) / h;
|
|
4325
|
-
const high = 1 - 0.35 * y;
|
|
4326
|
-
score[i] = Math.max(blue, grey) * smooth * high;
|
|
4327
|
-
}
|
|
4328
|
-
const threshold = Math.min(100, Math.max(0, options.threshold ?? 50));
|
|
4329
|
-
const t = 0.55 - (threshold - 50) * 6e-3;
|
|
4330
|
-
const sky = new Uint8Array(n);
|
|
4331
|
-
const stack = [];
|
|
4332
|
-
const seedRows = Math.max(1, Math.round(h * 0.04));
|
|
4333
|
-
for (let y = 0; y < seedRows; y++) {
|
|
4334
|
-
for (let x = 0; x < w; x++) {
|
|
4335
|
-
const i = y * w + x;
|
|
4336
|
-
if (score[i] >= t) {
|
|
4337
|
-
sky[i] = 1;
|
|
4338
|
-
stack.push(i);
|
|
4339
|
-
}
|
|
4340
|
-
}
|
|
4341
|
-
}
|
|
4342
|
-
if (!stack.length) return { selection: createSelectionMask(pixels.width, pixels.height), reason: "No sky was found along the top of the image." };
|
|
4343
|
-
const low = t * 0.7;
|
|
4344
|
-
while (stack.length) {
|
|
4345
|
-
const p = stack.pop();
|
|
4346
|
-
const x = p % w;
|
|
4347
|
-
for (const q of [x + 1 < w ? p + 1 : -1, x > 0 ? p - 1 : -1, p + w < n ? p + w : -1, p - w]) {
|
|
4348
|
-
if (q < 0 || sky[q] || score[q] < low) continue;
|
|
4349
|
-
if (Math.hypot(L[q] - L[p], A[q] - A[p], B[q] - B[p]) > 6) continue;
|
|
4350
|
-
sky[q] = 1;
|
|
4351
|
-
stack.push(q);
|
|
4352
|
-
}
|
|
4353
|
-
}
|
|
4354
|
-
let count = 0;
|
|
4355
|
-
const mean = [0, 0, 0];
|
|
4356
|
-
for (let i = 0; i < n; i++) {
|
|
4357
|
-
if (!sky[i]) continue;
|
|
4358
|
-
mean[0] += L[i];
|
|
4359
|
-
mean[1] += A[i];
|
|
4360
|
-
mean[2] += B[i];
|
|
4361
|
-
count++;
|
|
4362
|
-
}
|
|
4363
|
-
for (let k = 0; k < 3; k++) mean[k] /= count;
|
|
4364
|
-
let spread = 0;
|
|
4365
|
-
for (let i = 0; i < n; i++) if (sky[i]) spread += (L[i] - mean[0]) ** 2 + (A[i] - mean[1]) ** 2 + (B[i] - mean[2]) ** 2;
|
|
4366
|
-
const limit = Math.max(8, 2.5 * Math.sqrt(spread / count));
|
|
4367
|
-
for (let i = 0; i < n; i++) {
|
|
4368
|
-
if (sky[i] || score[i] < t) continue;
|
|
4369
|
-
if (Math.hypot(L[i] - mean[0], A[i] - mean[1], B[i] - mean[2]) <= limit) sky[i] = 1;
|
|
4370
|
-
}
|
|
4371
|
-
let front = [];
|
|
4372
|
-
for (let i = 0; i < n; i++) if (sky[i]) front.push(i);
|
|
4373
|
-
for (let step = 0; step <= roughR + 1 && front.length; step++) {
|
|
4374
|
-
const next = [];
|
|
4375
|
-
for (const p of front) {
|
|
4376
|
-
const x = p % w;
|
|
4377
|
-
for (const q of [x + 1 < w ? p + 1 : -1, x > 0 ? p - 1 : -1, p + w < n ? p + w : -1, p - w]) {
|
|
4378
|
-
if (q < 0 || sky[q]) continue;
|
|
4379
|
-
if (Math.hypot(L[q] - mean[0], A[q] - mean[1], B[q] - mean[2]) > limit) continue;
|
|
4380
|
-
sky[q] = 1;
|
|
4381
|
-
next.push(q);
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
front = next;
|
|
4385
|
-
}
|
|
4386
|
-
if (count < n * 5e-3) return { selection: createSelectionMask(pixels.width, pixels.height), reason: "No sky was found along the top of the image." };
|
|
4387
|
-
let mask = Float32Array.from(sky);
|
|
4388
|
-
const softness = Math.max(0, options.softness ?? 4) * scale;
|
|
4389
|
-
if (softness > 0) {
|
|
4390
|
-
const fitted = guidedFilterColor(workPlanes(work), mask, w, h, Math.max(1, Math.round(softness * 2)), 1e-3);
|
|
4391
|
-
mask = new Float32Array(n);
|
|
4392
|
-
for (let i = 0; i < n; i++) mask[i] = Math.min(1, Math.max(0, fitted[i]));
|
|
4393
|
-
}
|
|
4394
|
-
return { selection: softMaskToSelection(work, mask) };
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
2862
|
// src/image-editor/layers/shape-type-ui.ts
|
|
4398
2863
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
4399
2864
|
kind: "rect",
|
|
@@ -4843,123 +3308,6 @@ var TypeToolGesture = class {
|
|
|
4843
3308
|
}
|
|
4844
3309
|
};
|
|
4845
3310
|
|
|
4846
|
-
// src/image-editor/selection/shapes.ts
|
|
4847
|
-
function polygonCoverage(points, width, height, antiAlias = true) {
|
|
4848
|
-
const { box, coverage } = polygonCoverageBox(points, width, height, antiAlias);
|
|
4849
|
-
if (!box || box.width === width && box.height === height) return box ? coverage : new Float32Array(width * height);
|
|
4850
|
-
const m = new Float32Array(width * height);
|
|
4851
|
-
for (let y = 0; y < box.height; y++) m.set(coverage.subarray(y * box.width, (y + 1) * box.width), (box.y + y) * width + box.x);
|
|
4852
|
-
return m;
|
|
4853
|
-
}
|
|
4854
|
-
function polygonCoverageBox(points, width, height, antiAlias = true) {
|
|
4855
|
-
const none = { box: null, coverage: new Float32Array(0) };
|
|
4856
|
-
if (points.length < 3 || width <= 0 || height <= 0) return none;
|
|
4857
|
-
let minX = Infinity;
|
|
4858
|
-
let minY = Infinity;
|
|
4859
|
-
let maxX = -Infinity;
|
|
4860
|
-
let maxY = -Infinity;
|
|
4861
|
-
for (const p of points) {
|
|
4862
|
-
if (!Number.isFinite(p.x) || !Number.isFinite(p.y)) continue;
|
|
4863
|
-
minX = Math.min(minX, p.x);
|
|
4864
|
-
minY = Math.min(minY, p.y);
|
|
4865
|
-
maxX = Math.max(maxX, p.x);
|
|
4866
|
-
maxY = Math.max(maxY, p.y);
|
|
4867
|
-
}
|
|
4868
|
-
const bx0 = Math.max(0, Math.floor(minX));
|
|
4869
|
-
const by0 = Math.max(0, Math.floor(minY));
|
|
4870
|
-
const bx1 = Math.min(width, Math.ceil(maxX));
|
|
4871
|
-
const by1 = Math.min(height, Math.ceil(maxY));
|
|
4872
|
-
if (!(bx1 > bx0 && by1 > by0)) return none;
|
|
4873
|
-
const bw = bx1 - bx0;
|
|
4874
|
-
const m = new Float32Array(bw * (by1 - by0));
|
|
4875
|
-
const sub2 = antiAlias ? 4 : 1;
|
|
4876
|
-
const edges = [];
|
|
4877
|
-
for (let i = 0; i < points.length; i++) {
|
|
4878
|
-
const a = points[i];
|
|
4879
|
-
const b = points[(i + 1) % points.length];
|
|
4880
|
-
if (![a.x, a.y, b.x, b.y].every(Number.isFinite)) continue;
|
|
4881
|
-
if (a.y < b.y) edges.push({ top: a.y, bottom: b.y, ax: a.x, ay: a.y, bx: b.x, by: b.y });
|
|
4882
|
-
else if (b.y < a.y) edges.push({ top: b.y, bottom: a.y, ax: a.x, ay: a.y, bx: b.x, by: b.y });
|
|
4883
|
-
}
|
|
4884
|
-
edges.sort((p, q) => p.top - q.top);
|
|
4885
|
-
let next = 0;
|
|
4886
|
-
let active = [];
|
|
4887
|
-
const xs = [];
|
|
4888
|
-
for (let py = by0; py < by1; py++) {
|
|
4889
|
-
const row = (py - by0) * bw - bx0;
|
|
4890
|
-
for (let s = 0; s < sub2; s++) {
|
|
4891
|
-
const y = py + (s + 0.5) / sub2;
|
|
4892
|
-
while (next < edges.length && edges[next].top <= y) active.push(edges[next++]);
|
|
4893
|
-
if (active.length) active = active.filter((e) => e.bottom > y);
|
|
4894
|
-
if (!active.length) continue;
|
|
4895
|
-
xs.length = 0;
|
|
4896
|
-
for (const e of active) {
|
|
4897
|
-
if (e.top <= y) xs.push(e.ax + (y - e.ay) / (e.by - e.ay) * (e.bx - e.ax));
|
|
4898
|
-
}
|
|
4899
|
-
xs.sort((a, b) => a - b);
|
|
4900
|
-
for (let k = 0; k + 1 < xs.length; k += 2) {
|
|
4901
|
-
const x0 = Math.max(0, xs[k]);
|
|
4902
|
-
const x1 = Math.min(width, xs[k + 1]);
|
|
4903
|
-
if (x1 <= x0) continue;
|
|
4904
|
-
const c0 = Math.floor(x0);
|
|
4905
|
-
const c1 = Math.min(width, Math.ceil(x1));
|
|
4906
|
-
for (let cx = c0; cx < c1; cx++) {
|
|
4907
|
-
let cov;
|
|
4908
|
-
if (antiAlias) cov = Math.min(cx + 1, x1) - Math.max(cx, x0);
|
|
4909
|
-
else {
|
|
4910
|
-
const c = cx + 0.5;
|
|
4911
|
-
cov = c >= x0 && c < x1 ? 1 : 0;
|
|
4912
|
-
}
|
|
4913
|
-
if (cov > 0) m[row + cx] += cov / sub2;
|
|
4914
|
-
}
|
|
4915
|
-
}
|
|
4916
|
-
}
|
|
4917
|
-
}
|
|
4918
|
-
for (let i = 0; i < m.length; i++) if (m[i] > 1) m[i] = 1;
|
|
4919
|
-
return { box: { x: bx0, y: by0, width: bw, height: by1 - by0 }, coverage: m };
|
|
4920
|
-
}
|
|
4921
|
-
function polygonSelection(width, height, points, options = {}) {
|
|
4922
|
-
const { box, coverage } = polygonCoverageBox(points, width, height, options.antiAlias ?? true);
|
|
4923
|
-
if (!box) return createSelectionMask(width, height);
|
|
4924
|
-
const out = new Uint8Array(coverage.length);
|
|
4925
|
-
for (let i = 0; i < out.length; i++) out[i] = coverageToByte(coverage[i]);
|
|
4926
|
-
return selectionFromRegion(width, height, box, out);
|
|
4927
|
-
}
|
|
4928
|
-
function normalizeRect(rect) {
|
|
4929
|
-
const x = rect.width < 0 ? rect.x + rect.width : rect.x;
|
|
4930
|
-
const y = rect.height < 0 ? rect.y + rect.height : rect.y;
|
|
4931
|
-
return { x, y, width: Math.abs(rect.width), height: Math.abs(rect.height) };
|
|
4932
|
-
}
|
|
4933
|
-
function rectSelection(width, height, rect, options = {}) {
|
|
4934
|
-
const r = normalizeRect(rect);
|
|
4935
|
-
const pts = [
|
|
4936
|
-
{ x: r.x, y: r.y },
|
|
4937
|
-
{ x: r.x + r.width, y: r.y },
|
|
4938
|
-
{ x: r.x + r.width, y: r.y + r.height },
|
|
4939
|
-
{ x: r.x, y: r.y + r.height }
|
|
4940
|
-
];
|
|
4941
|
-
return polygonSelection(width, height, pts, options);
|
|
4942
|
-
}
|
|
4943
|
-
function ellipsePoints(rect) {
|
|
4944
|
-
const r = normalizeRect(rect);
|
|
4945
|
-
const rx = r.width / 2;
|
|
4946
|
-
const ry = r.height / 2;
|
|
4947
|
-
const cx = r.x + rx;
|
|
4948
|
-
const cy = r.y + ry;
|
|
4949
|
-
const rmax = Math.max(rx, ry, 0.5);
|
|
4950
|
-
const step = Math.sqrt(0.4 / rmax);
|
|
4951
|
-
const n = Math.min(4096, Math.max(16, Math.ceil(2 * Math.PI / step)));
|
|
4952
|
-
const pts = [];
|
|
4953
|
-
for (let i = 0; i < n; i++) {
|
|
4954
|
-
const t = i / n * 2 * Math.PI;
|
|
4955
|
-
pts.push({ x: cx + rx * Math.cos(t), y: cy + ry * Math.sin(t) });
|
|
4956
|
-
}
|
|
4957
|
-
return pts;
|
|
4958
|
-
}
|
|
4959
|
-
function ellipseSelection(width, height, rect, options = {}) {
|
|
4960
|
-
return polygonSelection(width, height, ellipsePoints(rect), options);
|
|
4961
|
-
}
|
|
4962
|
-
|
|
4963
3311
|
// src/image-editor/selection/wand.ts
|
|
4964
3312
|
function checkPixels(p) {
|
|
4965
3313
|
const n = p.width * p.height;
|
|
@@ -5319,78 +3667,6 @@ function segmentsToSelection(width, height, results) {
|
|
|
5319
3667
|
return out;
|
|
5320
3668
|
}
|
|
5321
3669
|
|
|
5322
|
-
// src/image-editor/selection/refine-edits.ts
|
|
5323
|
-
var REFINE_EDIT_KINDS = ["quick-select", "brush", "refine-brush", "lasso", "object"];
|
|
5324
|
-
function brushStrokeMask(width, height, points, size, hardness = 1) {
|
|
5325
|
-
const r = Math.max(0.5, size / 2);
|
|
5326
|
-
const solid = r * Math.min(1, Math.max(0, hardness));
|
|
5327
|
-
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
5328
|
-
for (const p of points) {
|
|
5329
|
-
x0 = Math.min(x0, p.x);
|
|
5330
|
-
y0 = Math.min(y0, p.y);
|
|
5331
|
-
x1 = Math.max(x1, p.x);
|
|
5332
|
-
y1 = Math.max(y1, p.y);
|
|
5333
|
-
}
|
|
5334
|
-
const bx = Math.max(0, Math.floor(x0 - r - 1));
|
|
5335
|
-
const by = Math.max(0, Math.floor(y0 - r - 1));
|
|
5336
|
-
const bw = Math.min(width, Math.ceil(x1 + r + 1)) - bx;
|
|
5337
|
-
const bh = Math.min(height, Math.ceil(y1 + r + 1)) - by;
|
|
5338
|
-
if (!(bw > 0 && bh > 0)) return createSelectionMask(width, height);
|
|
5339
|
-
const out = new Uint8Array(bw * bh);
|
|
5340
|
-
const segs = points.length === 1 ? [[points[0], points[0]]] : points.slice(1).map((p, i) => [points[i], p]);
|
|
5341
|
-
for (let y = 0; y < bh; y++) {
|
|
5342
|
-
for (let x = 0; x < bw; x++) {
|
|
5343
|
-
const px2 = bx + x + 0.5;
|
|
5344
|
-
const py = by + y + 0.5;
|
|
5345
|
-
let d = Infinity;
|
|
5346
|
-
for (const [a, b] of segs) {
|
|
5347
|
-
const dx = b.x - a.x;
|
|
5348
|
-
const dy = b.y - a.y;
|
|
5349
|
-
const len2 = dx * dx + dy * dy;
|
|
5350
|
-
const t = len2 > 0 ? Math.min(1, Math.max(0, ((px2 - a.x) * dx + (py - a.y) * dy) / len2)) : 0;
|
|
5351
|
-
d = Math.min(d, Math.hypot(px2 - a.x - t * dx, py - a.y - t * dy));
|
|
5352
|
-
}
|
|
5353
|
-
let v = d <= solid ? 1 : solid >= r ? 0 : 1 - (d - solid) / (r - solid);
|
|
5354
|
-
v = Math.min(v, Math.min(1, Math.max(0, r + 0.5 - d)));
|
|
5355
|
-
out[y * bw + x] = Math.round(Math.max(0, v) * 255);
|
|
5356
|
-
}
|
|
5357
|
-
}
|
|
5358
|
-
return selectionFromRegion(width, height, { x: bx, y: by, width: bw, height: bh }, out);
|
|
5359
|
-
}
|
|
5360
|
-
function applyRefineEdits(pixels, selection, edits, width, height) {
|
|
5361
|
-
let sel = selection?.bounds ? selection : null;
|
|
5362
|
-
let band = null;
|
|
5363
|
-
for (const e of edits) {
|
|
5364
|
-
if (e.kind === "object") {
|
|
5365
|
-
if (!pixels) throw new Error("Object Selection needs the image pixels.");
|
|
5366
|
-
if (!e.rect === !e.point) throw new Error("An object edit takes rect (a box around the object) or point (on it), not both.");
|
|
5367
|
-
const region2 = selectObjectIn(pixels, e.rect ? { rect: e.rect } : { point: e.point });
|
|
5368
|
-
sel = combineSelections(sel, region2, e.mode ?? "add");
|
|
5369
|
-
continue;
|
|
5370
|
-
}
|
|
5371
|
-
if (!e.points.length) throw new Error(`A ${e.kind} stroke needs at least one point.`);
|
|
5372
|
-
if (e.kind === "lasso") {
|
|
5373
|
-
if (e.points.length < 3) throw new Error("A lasso edit needs at least 3 points.");
|
|
5374
|
-
sel = combineSelections(sel, polygonSelection(width, height, e.points, {}), e.mode ?? "add");
|
|
5375
|
-
continue;
|
|
5376
|
-
}
|
|
5377
|
-
if (e.kind === "refine-brush") {
|
|
5378
|
-
band = combineSelections(band, brushStrokeMask(width, height, e.points, e.size ?? 40), "add");
|
|
5379
|
-
continue;
|
|
5380
|
-
}
|
|
5381
|
-
const mode = e.mode ?? "add";
|
|
5382
|
-
let region;
|
|
5383
|
-
if (e.kind === "quick-select") {
|
|
5384
|
-
if (!pixels) throw new Error("Quick Selection strokes need the image pixels.");
|
|
5385
|
-
region = quickSelectStroke(pixels, e.points, { size: e.size, selection: sel, grow: mode === "add" });
|
|
5386
|
-
} else {
|
|
5387
|
-
region = brushStrokeMask(width, height, e.points, e.size ?? 20, e.hardness ?? 1);
|
|
5388
|
-
}
|
|
5389
|
-
sel = combineSelections(sel, region, mode);
|
|
5390
|
-
}
|
|
5391
|
-
return { selection: sel, refineBrush: band };
|
|
5392
|
-
}
|
|
5393
|
-
|
|
5394
3670
|
// src/image-editor/selection/stroke.ts
|
|
5395
3671
|
function selectionRing(sel, width, location) {
|
|
5396
3672
|
const half = location === "center" ? width / 2 : width;
|
|
@@ -6195,10 +4471,10 @@ function colorModePlanes(doc, mode) {
|
|
|
6195
4471
|
}
|
|
6196
4472
|
const t = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
|
|
6197
4473
|
return (rgba, n) => {
|
|
6198
|
-
const
|
|
6199
|
-
t.convert8(rgba, 4,
|
|
4474
|
+
const lab = new Uint8Array(n * 4);
|
|
4475
|
+
t.convert8(rgba, 4, lab, 4, false);
|
|
6200
4476
|
const planes = [0, 1, 2].map(() => new Uint8Array(n));
|
|
6201
|
-
for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] =
|
|
4477
|
+
for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] = lab[i * 4 + c];
|
|
6202
4478
|
return planes;
|
|
6203
4479
|
};
|
|
6204
4480
|
}
|
|
@@ -6826,6 +5102,7 @@ var ImageEditorController = class {
|
|
|
6826
5102
|
compositor = new LayerCompositor();
|
|
6827
5103
|
/** The segmentation provider behind `selectSubject` (see `setSegmentationProvider`). */
|
|
6828
5104
|
segmentation = null;
|
|
5105
|
+
selectionWorker = null;
|
|
6829
5106
|
/** The effects provider slot (see `setEffectsProvider`). */
|
|
6830
5107
|
effects = new EffectsSlot();
|
|
6831
5108
|
/** Renders provider effects: effect smart filter results and effect layers. */
|
|
@@ -6836,6 +5113,7 @@ var ImageEditorController = class {
|
|
|
6836
5113
|
this.effectsEngine.subscribe(() => this.queueEffectRefresh("incomplete"));
|
|
6837
5114
|
if (options.effects) this.effects.set(options.effects);
|
|
6838
5115
|
if (options.segmentation) this.segmentation = options.segmentation;
|
|
5116
|
+
if (options.selectionWorker) this.selectionWorker = new SelectionWorkerClient(options.selectionWorker);
|
|
6839
5117
|
this.maxHistoryDepth = options.maxHistoryDepth ?? 50;
|
|
6840
5118
|
if (options.textShaper !== void 0) this.textShaperValue = options.textShaper;
|
|
6841
5119
|
this.maxHistoryBytes = options.maxHistoryBytes ?? 512 * 1024 * 1024;
|
|
@@ -7025,6 +5303,30 @@ var ImageEditorController = class {
|
|
|
7025
5303
|
get segmentationProvider() {
|
|
7026
5304
|
return this.segmentation;
|
|
7027
5305
|
}
|
|
5306
|
+
/**
|
|
5307
|
+
* Runs the long selections in a Web Worker made by `factory` (or with `null`, on the page again).
|
|
5308
|
+
* A worker that is cancelled or fails is replaced by a fresh one on the next job.
|
|
5309
|
+
*/
|
|
5310
|
+
setSelectionWorker(factory) {
|
|
5311
|
+
this.selectionWorker?.terminate();
|
|
5312
|
+
this.selectionWorker = factory ? new SelectionWorkerClient(factory) : null;
|
|
5313
|
+
}
|
|
5314
|
+
/** Whether the long selections run in a worker. */
|
|
5315
|
+
get hasSelectionWorker() {
|
|
5316
|
+
return this.selectionWorker !== null;
|
|
5317
|
+
}
|
|
5318
|
+
/**
|
|
5319
|
+
* Runs a long selection job on `pixels`: in the worker when there is one, else here (which
|
|
5320
|
+
* blocks the page until it is done). `signal` cancels it (rejecting with an `AbortError`);
|
|
5321
|
+
* `onProgress` hears its stages. The document is not changed.
|
|
5322
|
+
*/
|
|
5323
|
+
async runSelectionJob(job, pixels, options = {}) {
|
|
5324
|
+
options.signal?.throwIfAborted();
|
|
5325
|
+
if (this.selectionWorker) return this.selectionWorker.run(job, pixels, options);
|
|
5326
|
+
const result = runSelectionJob(job, pixels, options.onProgress);
|
|
5327
|
+
options.signal?.throwIfAborted();
|
|
5328
|
+
return result;
|
|
5329
|
+
}
|
|
7028
5330
|
/** Registers (or with `null` removes) the segmentation provider. Not an edit: no undo step. */
|
|
7029
5331
|
setSegmentationProvider(provider) {
|
|
7030
5332
|
if (this.segmentation === provider) return;
|
|
@@ -10195,11 +8497,22 @@ var ImageEditorController = class {
|
|
|
10195
8497
|
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
10196
8498
|
const mode = options.mode ?? "replace";
|
|
10197
8499
|
let sel = this.state.selection ?? null;
|
|
10198
|
-
strokes.
|
|
8500
|
+
for (let i = 0; i < strokes.length; i++) {
|
|
10199
8501
|
const m = mode === "replace" && i > 0 ? "add" : mode;
|
|
10200
|
-
const
|
|
10201
|
-
|
|
10202
|
-
|
|
8502
|
+
const job = {
|
|
8503
|
+
op: "quick",
|
|
8504
|
+
stroke: strokes[i],
|
|
8505
|
+
size: options.size,
|
|
8506
|
+
grow: m === "add",
|
|
8507
|
+
enhanceEdge: options.enhanceEdge,
|
|
8508
|
+
selection: m === "add" && sel?.bounds ? serializeMask(sel) : null
|
|
8509
|
+
};
|
|
8510
|
+
const { mask } = await this.runSelectionJob(job, pixels, {
|
|
8511
|
+
signal: options.signal,
|
|
8512
|
+
onProgress: options.onProgress && ((f, stage) => options.onProgress((i + f) / strokes.length, stage))
|
|
8513
|
+
});
|
|
8514
|
+
sel = combineSelections(sel, mask, m);
|
|
8515
|
+
}
|
|
10203
8516
|
this.setSelection(sel, "replace");
|
|
10204
8517
|
return this.state.selection ?? null;
|
|
10205
8518
|
}
|
|
@@ -10268,7 +8581,7 @@ var ImageEditorController = class {
|
|
|
10268
8581
|
}
|
|
10269
8582
|
}
|
|
10270
8583
|
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
10271
|
-
const region =
|
|
8584
|
+
const { mask: region } = await this.runSelectionJob({ op: "object", target, enhanceEdge: options.enhanceEdge }, pixels, options);
|
|
10272
8585
|
this.setSelection(region, options.mode ?? "replace");
|
|
10273
8586
|
return { selection: this.state.selection ?? null, method: "grabcut" };
|
|
10274
8587
|
}
|
|
@@ -10279,7 +8592,11 @@ var ImageEditorController = class {
|
|
|
10279
8592
|
*/
|
|
10280
8593
|
async selectFocusArea(options = {}) {
|
|
10281
8594
|
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
10282
|
-
const { selection, noiseLevel } =
|
|
8595
|
+
const { mask: selection, noiseLevel = 0 } = await this.runSelectionJob(
|
|
8596
|
+
{ op: "focus", options: { range: options.range, noise: options.noise, softness: options.softness } },
|
|
8597
|
+
pixels,
|
|
8598
|
+
options
|
|
8599
|
+
);
|
|
10283
8600
|
this.setSelection(selection, options.mode ?? "replace");
|
|
10284
8601
|
return { selection: this.state.selection ?? null, noiseLevel };
|
|
10285
8602
|
}
|
|
@@ -10298,7 +8615,7 @@ var ImageEditorController = class {
|
|
|
10298
8615
|
return { selection: this.state.selection ?? null, method: "model" };
|
|
10299
8616
|
}
|
|
10300
8617
|
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
10301
|
-
const { selection, reason } =
|
|
8618
|
+
const { mask: selection, reason } = await this.runSelectionJob({ op: "sky", options: { threshold: options.threshold, softness: options.softness } }, pixels, options);
|
|
10302
8619
|
if (!selection.bounds) return { selection: this.state.selection ?? null, method: "classical", note: reason };
|
|
10303
8620
|
this.setSelection(selection, options.mode ?? "replace");
|
|
10304
8621
|
return { selection: this.state.selection ?? null, method: "classical" };
|
|
@@ -10581,7 +8898,7 @@ var ImageEditorController = class {
|
|
|
10581
8898
|
if (!provider) {
|
|
10582
8899
|
const pixels = await this.requirePixels({});
|
|
10583
8900
|
options.signal?.throwIfAborted();
|
|
10584
|
-
const { selection, reason } =
|
|
8901
|
+
const { mask: selection, reason } = await this.runSelectionJob({ op: "subject", enhanceEdge: options.enhanceEdge }, pixels, options);
|
|
10585
8902
|
if (selection.bounds) this.shapeCommand(selection, options);
|
|
10586
8903
|
const unused = target.trim() ? ` "${target.trim()}" was not used to choose it.` : "";
|
|
10587
8904
|
return {
|
|
@@ -11904,7 +10221,7 @@ var ImageEditorController = class {
|
|
|
11904
10221
|
}
|
|
11905
10222
|
if (src.colorSpace !== "rgb") throw new LayerOperationError("Colours are given as #rrggbb, so the source profile must be RGB.");
|
|
11906
10223
|
const t = Transform.create(src, dst, { intent: options.intent ?? "relative", bpc: options.bpc ?? true });
|
|
11907
|
-
const
|
|
10224
|
+
const toLab = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
|
|
11908
10225
|
return colors.map((input) => {
|
|
11909
10226
|
const m = /^#?([0-9a-f]{6})$/i.exec(input.trim());
|
|
11910
10227
|
if (!m) throw new LayerOperationError(`Invalid colour "${input}"; use #rrggbb.`);
|
|
@@ -11914,9 +10231,9 @@ var ImageEditorController = class {
|
|
|
11914
10231
|
t.eval(rgb, out);
|
|
11915
10232
|
const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
|
|
11916
10233
|
const l = new Float32Array(3);
|
|
11917
|
-
|
|
11918
|
-
const
|
|
11919
|
-
const res = { input, values, lab
|
|
10234
|
+
toLab.eval(rgb, l);
|
|
10235
|
+
const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
|
|
10236
|
+
const res = { input, values, lab };
|
|
11920
10237
|
if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
11921
10238
|
return res;
|
|
11922
10239
|
});
|
|
@@ -13949,7 +12266,7 @@ function createImageSelectionTools(controller) {
|
|
|
13949
12266
|
additionalProperties: false
|
|
13950
12267
|
},
|
|
13951
12268
|
annotations: writeAnnotations("Quick Selection"),
|
|
13952
|
-
handler: async (args) => {
|
|
12269
|
+
handler: async (args, context) => {
|
|
13953
12270
|
const strokes = Array.isArray(args.strokes) ? args.strokes.map((s) => Array.isArray(s) ? s.map((p) => ({ x: num2(p?.x, NaN), y: num2(p?.y, NaN) })) : []) : [];
|
|
13954
12271
|
if (!strokes.length || strokes.some((s) => !s.length || !s.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y)))) {
|
|
13955
12272
|
throw new Error("strokes needs at least one stroke of { x, y } points in natural image pixels.");
|
|
@@ -13958,7 +12275,8 @@ function createImageSelectionTools(controller) {
|
|
|
13958
12275
|
size: args.size === void 0 ? void 0 : num2(args.size, 30),
|
|
13959
12276
|
mode: parseSelectionMode(args.mode),
|
|
13960
12277
|
sampleAllLayers: args.sampleAllLayers,
|
|
13961
|
-
enhanceEdge: args.enhanceEdge
|
|
12278
|
+
enhanceEdge: args.enhanceEdge,
|
|
12279
|
+
signal: context?.signal
|
|
13962
12280
|
});
|
|
13963
12281
|
return summary(controller);
|
|
13964
12282
|
}
|
|
@@ -13986,7 +12304,7 @@ function createImageSelectionTools(controller) {
|
|
|
13986
12304
|
additionalProperties: false
|
|
13987
12305
|
},
|
|
13988
12306
|
annotations: { ...writeAnnotations("Object Selection"), openWorldHint: true },
|
|
13989
|
-
handler: async (args) => {
|
|
12307
|
+
handler: async (args, context) => {
|
|
13990
12308
|
const rect = args.rect;
|
|
13991
12309
|
const point = args.point;
|
|
13992
12310
|
if (!rect && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
|
|
@@ -13995,7 +12313,8 @@ function createImageSelectionTools(controller) {
|
|
|
13995
12313
|
mode: parseSelectionMode(args.mode),
|
|
13996
12314
|
sampleAllLayers: args.sampleAllLayers,
|
|
13997
12315
|
enhanceEdge: args.enhanceEdge,
|
|
13998
|
-
useModel: args.useModel
|
|
12316
|
+
useModel: args.useModel,
|
|
12317
|
+
signal: context?.signal
|
|
13999
12318
|
};
|
|
14000
12319
|
const { method } = rect ? await controller.selectObject({ rect: { x: num2(rect.x, NaN), y: num2(rect.y, NaN), width: num2(rect.width, NaN), height: num2(rect.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
|
|
14001
12320
|
return { ...summary(controller), method };
|
|
@@ -14017,13 +12336,14 @@ function createImageSelectionTools(controller) {
|
|
|
14017
12336
|
additionalProperties: false
|
|
14018
12337
|
},
|
|
14019
12338
|
annotations: writeAnnotations("Select the focus area", true),
|
|
14020
|
-
handler: async (args) => {
|
|
12339
|
+
handler: async (args, context) => {
|
|
14021
12340
|
const { noiseLevel } = await controller.selectFocusArea({
|
|
14022
12341
|
range: args.range === void 0 ? void 0 : num2(args.range, 50),
|
|
14023
12342
|
noise: args.noise === void 0 ? void 0 : num2(args.noise, 0),
|
|
14024
12343
|
softness: args.softness === void 0 ? void 0 : num2(args.softness, 4),
|
|
14025
12344
|
mode: parseSelectionMode(args.mode),
|
|
14026
|
-
sampleAllLayers: args.sampleAllLayers
|
|
12345
|
+
sampleAllLayers: args.sampleAllLayers,
|
|
12346
|
+
signal: context?.signal
|
|
14027
12347
|
});
|
|
14028
12348
|
return { ...summary(controller), noiseLevel };
|
|
14029
12349
|
}
|
|
@@ -14044,13 +12364,14 @@ function createImageSelectionTools(controller) {
|
|
|
14044
12364
|
additionalProperties: false
|
|
14045
12365
|
},
|
|
14046
12366
|
annotations: { ...writeAnnotations("Select the sky", true), openWorldHint: true },
|
|
14047
|
-
handler: async (args) => {
|
|
12367
|
+
handler: async (args, context) => {
|
|
14048
12368
|
const { note, method } = await controller.selectSky({
|
|
14049
12369
|
threshold: args.threshold === void 0 ? void 0 : num2(args.threshold, 50),
|
|
14050
12370
|
softness: args.softness === void 0 ? void 0 : num2(args.softness, 4),
|
|
14051
12371
|
mode: parseSelectionMode(args.mode),
|
|
14052
12372
|
sampleAllLayers: args.sampleAllLayers,
|
|
14053
|
-
useModel: args.useModel
|
|
12373
|
+
useModel: args.useModel,
|
|
12374
|
+
signal: context?.signal
|
|
14054
12375
|
});
|
|
14055
12376
|
return { ...summary(controller), method, ...note ? { note } : {} };
|
|
14056
12377
|
}
|
|
@@ -17062,7 +15383,7 @@ function checkSource(controller, source) {
|
|
|
17062
15383
|
}
|
|
17063
15384
|
return p;
|
|
17064
15385
|
}
|
|
17065
|
-
var
|
|
15386
|
+
var SOURCE = {
|
|
17066
15387
|
type: "object",
|
|
17067
15388
|
description: "Source point in document pixels (an Alt-click). Given, it replaces the clone source and the offset starts afresh at this stroke; missing, the current clone source is used (set_clone_source).",
|
|
17068
15389
|
properties: { x: { type: "number", description: "Source x in px." }, y: { type: "number", description: "Source y in px." } },
|
|
@@ -17086,7 +15407,7 @@ function createImageRetouchTools(controller) {
|
|
|
17086
15407
|
type: "object",
|
|
17087
15408
|
properties: {
|
|
17088
15409
|
points: POINTS,
|
|
17089
|
-
source:
|
|
15410
|
+
source: SOURCE,
|
|
17090
15411
|
aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
|
|
17091
15412
|
sample: SAMPLE,
|
|
17092
15413
|
brush: BRUSH2,
|
|
@@ -17106,7 +15427,7 @@ function createImageRetouchTools(controller) {
|
|
|
17106
15427
|
type: "object",
|
|
17107
15428
|
properties: {
|
|
17108
15429
|
points: POINTS,
|
|
17109
|
-
source:
|
|
15430
|
+
source: SOURCE,
|
|
17110
15431
|
aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
|
|
17111
15432
|
sample: SAMPLE,
|
|
17112
15433
|
brush: BRUSH2,
|
|
@@ -19247,6 +17568,9 @@ var SelectAndMaskSession = class {
|
|
|
19247
17568
|
note = null;
|
|
19248
17569
|
/** Bumped each time the preview changes, so a UI can redraw. */
|
|
19249
17570
|
revision = 0;
|
|
17571
|
+
/** True while the worker computes a preview. */
|
|
17572
|
+
computing = false;
|
|
17573
|
+
dirty = false;
|
|
19250
17574
|
get settings() {
|
|
19251
17575
|
return this.settingsValue;
|
|
19252
17576
|
}
|
|
@@ -19303,13 +17627,20 @@ var SelectAndMaskSession = class {
|
|
|
19303
17627
|
this.previewPixels = scalePixels(px2, w, h);
|
|
19304
17628
|
this.baseMask = this.base?.bounds ? s === 1 ? this.base : resizeSelection(this.base, w, h) : null;
|
|
19305
17629
|
}
|
|
19306
|
-
/**
|
|
17630
|
+
/**
|
|
17631
|
+
* Recomputes the preview mask from the base selection, the strokes and the settings: at once on
|
|
17632
|
+
* the page, or in the worker, where one job runs at a time and the newest state follows it.
|
|
17633
|
+
*/
|
|
19307
17634
|
recompute() {
|
|
19308
17635
|
const px2 = this.previewPixels;
|
|
19309
17636
|
if (!px2) return;
|
|
17637
|
+
this.note = this.settingsValue.refineMode === "object" ? "Object aware refinement asks the segmentation model on OK; the preview refines by colour." : null;
|
|
17638
|
+
if (this.controller.hasSelectionWorker) {
|
|
17639
|
+
void this.recomputeInWorker();
|
|
17640
|
+
return;
|
|
17641
|
+
}
|
|
19310
17642
|
const { scale } = this.previewSize;
|
|
19311
17643
|
const edits = [...this.editsList, ...this.live ? [this.live] : []].map((e) => scaleEdit(e, scale));
|
|
19312
|
-
this.note = this.settingsValue.refineMode === "object" ? "Object aware refinement asks the segmentation model on OK; the preview refines by colour." : null;
|
|
19313
17644
|
try {
|
|
19314
17645
|
const { selection, refineBrush } = applyRefineEdits(px2, this.baseMask, edits, px2.width, px2.height);
|
|
19315
17646
|
this.previewMask = selection?.bounds ? refineSelection(px2, selection, scaleParams(this.settingsValue.params, scale), refineBrush) : null;
|
|
@@ -19320,6 +17651,37 @@ var SelectAndMaskSession = class {
|
|
|
19320
17651
|
}
|
|
19321
17652
|
this.emit();
|
|
19322
17653
|
}
|
|
17654
|
+
async recomputeInWorker() {
|
|
17655
|
+
if (this.computing) {
|
|
17656
|
+
this.dirty = true;
|
|
17657
|
+
return;
|
|
17658
|
+
}
|
|
17659
|
+
this.computing = true;
|
|
17660
|
+
do {
|
|
17661
|
+
this.dirty = false;
|
|
17662
|
+
const px2 = this.previewPixels;
|
|
17663
|
+
const { scale } = this.previewSize;
|
|
17664
|
+
const edits = [...this.editsList, ...this.live ? [this.live] : []].map((e) => scaleEdit(e, scale));
|
|
17665
|
+
try {
|
|
17666
|
+
const { mask } = await this.controller.runSelectionJob(
|
|
17667
|
+
{ op: "refine-preview", base: this.baseMask ? serializeMask(this.baseMask) : null, edits, params: scaleParams(this.settingsValue.params, scale) },
|
|
17668
|
+
px2
|
|
17669
|
+
);
|
|
17670
|
+
this.previewMask = mask.bounds ? mask : null;
|
|
17671
|
+
this.error = null;
|
|
17672
|
+
} catch (err) {
|
|
17673
|
+
this.previewMask = null;
|
|
17674
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
17675
|
+
}
|
|
17676
|
+
this.emit();
|
|
17677
|
+
} while (this.dirty);
|
|
17678
|
+
this.computing = false;
|
|
17679
|
+
this.emit();
|
|
17680
|
+
}
|
|
17681
|
+
/** Resolves once no preview is being computed (immediately without a worker). */
|
|
17682
|
+
async idle() {
|
|
17683
|
+
while (this.computing) await new Promise((r) => setTimeout(r, 5));
|
|
17684
|
+
}
|
|
19323
17685
|
/** Coverage 0..1 of the preview at (x, y) in preview pixels (for tests and hover readouts). */
|
|
19324
17686
|
coverageAt(x, y) {
|
|
19325
17687
|
const m = this.settingsValue.showOriginal ? this.baseMask : this.previewMask;
|
|
@@ -21396,6 +19758,7 @@ export {
|
|
|
21396
19758
|
SUPPORTED_ADJUSTMENT_KEYS,
|
|
21397
19759
|
SelectAndMaskSession,
|
|
21398
19760
|
SelectionGesture,
|
|
19761
|
+
SelectionWorkerClient,
|
|
21399
19762
|
ShapeDrawGesture,
|
|
21400
19763
|
SmudgeTip,
|
|
21401
19764
|
StrokeCoverage,
|
|
@@ -21645,6 +20008,7 @@ export {
|
|
|
21645
20008
|
deserializeEffects,
|
|
21646
20009
|
deserializeLayer,
|
|
21647
20010
|
deserializeLayers,
|
|
20011
|
+
deserializeMask,
|
|
21648
20012
|
deserializePatterns,
|
|
21649
20013
|
deserializeSelection,
|
|
21650
20014
|
developFilter,
|
|
@@ -22066,6 +20430,7 @@ export {
|
|
|
22066
20430
|
runLayerFilters,
|
|
22067
20431
|
runLayerFiltersFloat,
|
|
22068
20432
|
runLayerMenuAction,
|
|
20433
|
+
runSelectionJob,
|
|
22069
20434
|
sameCanvasViewport,
|
|
22070
20435
|
sampleLut,
|
|
22071
20436
|
sampleToneLut,
|
|
@@ -22099,6 +20464,7 @@ export {
|
|
|
22099
20464
|
serializeLayer,
|
|
22100
20465
|
serializeLayers,
|
|
22101
20466
|
serializeLayersExternal,
|
|
20467
|
+
serializeMask,
|
|
22102
20468
|
serializePatterns,
|
|
22103
20469
|
serializeSelection,
|
|
22104
20470
|
setCloneTransformValue,
|
|
@@ -22150,6 +20516,7 @@ export {
|
|
|
22150
20516
|
srgbToLinear,
|
|
22151
20517
|
standardBlendMode,
|
|
22152
20518
|
startMarchingAnts,
|
|
20519
|
+
startSelectionWorker,
|
|
22153
20520
|
stepSlotTs,
|
|
22154
20521
|
strokeGrainCatch,
|
|
22155
20522
|
styleAt,
|