@pilio/gemini-watermark-remover 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +91 -91
  2. package/README_zh.md +414 -414
  3. package/package.json +10 -1
  4. package/skills/gemini-watermark-remover/SKILL.md +32 -32
  5. package/skills/gemini-watermark-remover/references/usage.md +23 -23
  6. package/src/core/candidateEvaluation.js +182 -0
  7. package/src/core/candidateSelector.js +46 -28
  8. package/src/core/imageWatermarkPipeline.js +127 -0
  9. package/src/core/imageWatermarkPipelineRequest.js +39 -0
  10. package/src/core/pipelineAcceptedExecutor.js +299 -0
  11. package/src/core/pipelineAcceptedExecutorRequest.js +44 -0
  12. package/src/core/pipelineAcceptedFinalizationRequest.js +21 -0
  13. package/src/core/pipelineAlphaStageSpecs.js +573 -0
  14. package/src/core/pipelineAlphaTraceContract.js +56 -0
  15. package/src/core/pipelineAlphaTrial.js +223 -0
  16. package/src/core/pipelineDecisionPath.js +161 -0
  17. package/src/core/pipelineDetectionCandidate.js +128 -0
  18. package/src/core/pipelineFinalization.js +46 -0
  19. package/src/core/pipelineInitialContext.js +52 -0
  20. package/src/core/pipelineInitialSelection.js +61 -0
  21. package/src/core/pipelineLayerContracts.js +163 -0
  22. package/src/core/pipelineMeta.js +201 -0
  23. package/src/core/pipelineMetrics.js +88 -0
  24. package/src/core/pipelinePassState.js +36 -0
  25. package/src/core/pipelineRepairGates.js +124 -0
  26. package/src/core/pipelineRepairStageSpecs.js +491 -0
  27. package/src/core/pipelineRepairTrial.js +156 -0
  28. package/src/core/pipelineResult.js +128 -0
  29. package/src/core/pipelineRuntime.js +689 -0
  30. package/src/core/pipelineRuntimeBootstrap.js +78 -0
  31. package/src/core/pipelineState.js +134 -0
  32. package/src/core/pipelineTimings.js +36 -0
  33. package/src/core/pipelineTrace.js +28 -0
  34. package/src/core/watermarkProcessor.js +1188 -1297
@@ -0,0 +1,163 @@
1
+ export const PIPELINE_LAYER_ORDER = Object.freeze([
2
+ 'detection',
3
+ 'alpha',
4
+ 'repair',
5
+ 'evaluation'
6
+ ]);
7
+
8
+ export const PIPELINE_LAYER_CONTRACTS = Object.freeze({
9
+ detection: Object.freeze({
10
+ layer: 'detection',
11
+ owns: 'candidate-localization-and-evidence',
12
+ inputFields: Object.freeze([
13
+ 'originalImageData',
14
+ 'resolvedConfig',
15
+ 'position',
16
+ 'alpha48',
17
+ 'alpha96',
18
+ 'alpha96Variants',
19
+ 'allowAdaptiveSearch',
20
+ 'alphaGainCandidates',
21
+ 'alphaPriorityGains'
22
+ ]),
23
+ outputFields: Object.freeze([
24
+ 'selectedTrial',
25
+ 'source',
26
+ 'decisionTier',
27
+ 'adaptiveConfidence',
28
+ 'standardSpatialScore',
29
+ 'standardGradientScore'
30
+ ]),
31
+ moduleAnchors: Object.freeze([
32
+ 'pipelineDetectionCandidate',
33
+ 'pipelineInitialContext',
34
+ 'pipelineInitialSelection',
35
+ 'candidateSelector'
36
+ ]),
37
+ testAnchors: Object.freeze([
38
+ 'pipelineDetectionCandidate.test',
39
+ 'pipelineInitialContext.test',
40
+ 'pipelineInitialSelection.test',
41
+ 'candidateEvaluation.test'
42
+ ])
43
+ }),
44
+ alpha: Object.freeze({
45
+ layer: 'alpha',
46
+ owns: 'alpha-map-selection-and-alpha-fit',
47
+ inputFields: Object.freeze([
48
+ 'acceptedPipelineState',
49
+ 'runtimeBootstrap',
50
+ 'alpha96',
51
+ 'getAlphaMap',
52
+ 'alpha96Variants',
53
+ 'alphaGain',
54
+ 'alphaMapSource'
55
+ ]),
56
+ outputFields: Object.freeze([
57
+ 'alphaTrialEvents',
58
+ 'alphaAdjustmentStages',
59
+ 'alphaGain',
60
+ 'alphaMap',
61
+ 'subpixelShift',
62
+ 'pipelineState'
63
+ ]),
64
+ moduleAnchors: Object.freeze([
65
+ 'pipelineAlphaTraceContract',
66
+ 'pipelineAlphaTrial',
67
+ 'pipelineAlphaStageSpecs',
68
+ 'pipelineAcceptedExecutor',
69
+ 'pipelineRuntime'
70
+ ]),
71
+ testAnchors: Object.freeze([
72
+ 'pipelineAlphaTraceContract.test',
73
+ 'pipelineAlphaTrial.test',
74
+ 'pipelineAlphaStageSpecs.test',
75
+ 'pipelineAcceptedExecutor.test',
76
+ 'pipelineRuntime.test'
77
+ ])
78
+ }),
79
+ repair: Object.freeze({
80
+ layer: 'repair',
81
+ owns: 'texture-repair-and-artifact-gated-cleanup',
82
+ inputFields: Object.freeze([
83
+ 'pipelineState',
84
+ 'passState',
85
+ 'cleanupFlags',
86
+ 'repairCleanupConfig',
87
+ 'visualPostProcessingEnabled'
88
+ ]),
89
+ outputFields: Object.freeze([
90
+ 'repairTrial',
91
+ 'passState',
92
+ 'passes',
93
+ 'source',
94
+ 'finalImageData',
95
+ 'finalProcessedSpatialScore',
96
+ 'finalProcessedGradientScore'
97
+ ]),
98
+ moduleAnchors: Object.freeze([
99
+ 'pipelineRepairTrial',
100
+ 'pipelineRepairStageSpecs',
101
+ 'pipelineRepairGates',
102
+ 'pipelineAcceptedExecutor'
103
+ ]),
104
+ testAnchors: Object.freeze([
105
+ 'pipelineRepairTrial.test',
106
+ 'pipelineRepairStageSpecs.test',
107
+ 'pipelineRepairGates.test',
108
+ 'pipelineAcceptedExecutor.test'
109
+ ])
110
+ }),
111
+ evaluation: Object.freeze({
112
+ layer: 'evaluation',
113
+ owns: 'decision-path-and-risk-gated-arbitration',
114
+ inputFields: Object.freeze([
115
+ 'detectionCandidate',
116
+ 'alphaTrial',
117
+ 'repairTrial',
118
+ 'selectedTrial',
119
+ 'alphaTrialEvents',
120
+ 'residualVisibility'
121
+ ]),
122
+ outputFields: Object.freeze([
123
+ 'decisionPath',
124
+ 'evaluationDecision',
125
+ 'blockedGate',
126
+ 'riskFlags',
127
+ 'selectionDebug',
128
+ 'meta'
129
+ ]),
130
+ moduleAnchors: Object.freeze([
131
+ 'pipelineDecisionPath',
132
+ 'candidateEvaluation',
133
+ 'pipelineMeta',
134
+ 'pipelineFinalization',
135
+ 'pipelineResult'
136
+ ]),
137
+ testAnchors: Object.freeze([
138
+ 'pipelineDecisionPath.test',
139
+ 'candidateEvaluation.test',
140
+ 'pipelineMeta.test',
141
+ 'pipelineFinalization.test',
142
+ 'pipelineResult.test'
143
+ ])
144
+ })
145
+ });
146
+
147
+ export function getPipelineLayerContract(layer) {
148
+ return PIPELINE_LAYER_CONTRACTS[layer] ?? null;
149
+ }
150
+
151
+ export function createPipelineLayerContractSummary() {
152
+ return PIPELINE_LAYER_ORDER.map((layer) => {
153
+ const contract = getPipelineLayerContract(layer);
154
+ return {
155
+ layer,
156
+ owns: contract.owns,
157
+ inputCount: contract.inputFields.length,
158
+ outputCount: contract.outputFields.length,
159
+ moduleAnchors: [...contract.moduleAnchors],
160
+ testAnchors: [...contract.testAnchors]
161
+ };
162
+ });
163
+ }
@@ -0,0 +1,201 @@
1
+ import {
2
+ createAcceptedDecisionPath,
3
+ createRejectedDecisionPath
4
+ } from './candidateEvaluation.js';
5
+
6
+ function normalizeMetaPosition(position) {
7
+ if (!position) return null;
8
+
9
+ const { x, y, width, height } = position;
10
+ if (![x, y, width, height].every((value) => Number.isFinite(value))) {
11
+ return null;
12
+ }
13
+
14
+ return { x, y, width, height };
15
+ }
16
+
17
+ function normalizeMetaConfig(config) {
18
+ if (!config) return null;
19
+
20
+ const { logoSize, marginRight, marginBottom } = config;
21
+ if (![logoSize, marginRight, marginBottom].every((value) => Number.isFinite(value))) {
22
+ return null;
23
+ }
24
+
25
+ return {
26
+ logoSize,
27
+ marginRight,
28
+ marginBottom,
29
+ ...(typeof config.alphaVariant === 'string' && config.alphaVariant.length > 0
30
+ ? { alphaVariant: config.alphaVariant }
31
+ : {})
32
+ };
33
+ }
34
+
35
+ export function createWatermarkMeta({
36
+ position = null,
37
+ config = null,
38
+ adaptiveConfidence = null,
39
+ originalSpatialScore = null,
40
+ originalGradientScore = null,
41
+ processedSpatialScore = null,
42
+ processedGradientScore = null,
43
+ suppressionGain = null,
44
+ residualVisibility = null,
45
+ templateWarp = null,
46
+ alphaGain = 1,
47
+ passCount = 0,
48
+ attemptedPassCount = 0,
49
+ passStopReason = null,
50
+ passes = null,
51
+ source = 'standard',
52
+ decisionTier = null,
53
+ applied = true,
54
+ skipReason = null,
55
+ subpixelShift = null,
56
+ selectionDebug = null,
57
+ alphaAdjustmentStages = null,
58
+ alphaMapSource = null,
59
+ decisionPath = null
60
+ } = {}) {
61
+ const normalizedPosition = normalizeMetaPosition(position);
62
+
63
+ return {
64
+ applied,
65
+ skipReason: applied ? null : skipReason,
66
+ size: normalizedPosition ? normalizedPosition.width : null,
67
+ position: normalizedPosition,
68
+ config: normalizeMetaConfig(config),
69
+ detection: {
70
+ adaptiveConfidence,
71
+ originalSpatialScore,
72
+ originalGradientScore,
73
+ processedSpatialScore,
74
+ processedGradientScore,
75
+ suppressionGain,
76
+ residualVisibility
77
+ },
78
+ templateWarp: templateWarp ?? null,
79
+ alphaGain,
80
+ passCount,
81
+ attemptedPassCount,
82
+ passStopReason,
83
+ passes: Array.isArray(passes) ? passes : null,
84
+ // decisionTier is the normalized contract used by UI and attribution.
85
+ // source remains as a verbose execution trace for debugging/tests.
86
+ source,
87
+ decisionTier,
88
+ subpixelShift: subpixelShift ?? null,
89
+ selectionDebug,
90
+ alphaAdjustmentStages: Array.isArray(alphaAdjustmentStages) ? alphaAdjustmentStages : null,
91
+ alphaMapSource: alphaMapSource ?? null,
92
+ decisionPath: decisionPath ?? null
93
+ };
94
+ }
95
+
96
+ export function createAcceptedWatermarkMeta({
97
+ selectedTrial = null,
98
+ selectionSource = null,
99
+ position = null,
100
+ config = null,
101
+ adaptiveConfidence = null,
102
+ originalSpatialScore = null,
103
+ originalGradientScore = null,
104
+ processedSpatialScore = null,
105
+ processedGradientScore = null,
106
+ suppressionGain = null,
107
+ residualVisibility = null,
108
+ templateWarp = null,
109
+ alphaGain = 1,
110
+ passCount = 0,
111
+ attemptedPassCount = 0,
112
+ passStopReason = null,
113
+ passes = null,
114
+ source = 'standard',
115
+ decisionTier = null,
116
+ subpixelShift = null,
117
+ selectionDebug = null,
118
+ alphaAdjustmentStages = null,
119
+ alphaTrialEvents = null,
120
+ alphaMapSource = null
121
+ } = {}) {
122
+ const decisionPath = createAcceptedDecisionPath({
123
+ selectedTrial,
124
+ selectionSource,
125
+ source,
126
+ decisionTier,
127
+ config,
128
+ position,
129
+ adaptiveConfidence,
130
+ alphaGain,
131
+ alphaMapSource,
132
+ templateWarp,
133
+ alphaAdjustmentStages,
134
+ alphaTrialEvents,
135
+ originalSpatialScore,
136
+ originalGradientScore,
137
+ processedSpatialScore,
138
+ processedGradientScore,
139
+ suppressionGain,
140
+ residualVisibility
141
+ });
142
+
143
+ return createWatermarkMeta({
144
+ position,
145
+ config,
146
+ adaptiveConfidence,
147
+ originalSpatialScore,
148
+ originalGradientScore,
149
+ processedSpatialScore,
150
+ processedGradientScore,
151
+ suppressionGain,
152
+ residualVisibility,
153
+ templateWarp,
154
+ alphaGain,
155
+ passCount,
156
+ attemptedPassCount,
157
+ passStopReason,
158
+ passes,
159
+ source,
160
+ decisionTier,
161
+ applied: true,
162
+ subpixelShift,
163
+ alphaAdjustmentStages,
164
+ alphaMapSource,
165
+ selectionDebug,
166
+ decisionPath
167
+ });
168
+ }
169
+
170
+ export function createRejectedWatermarkMeta({
171
+ reason = 'no-watermark-detected',
172
+ source = 'skipped',
173
+ decisionTier = 'insufficient',
174
+ adaptiveConfidence = null,
175
+ originalSpatialScore = null,
176
+ originalGradientScore = null,
177
+ selectionDebug = null
178
+ } = {}) {
179
+ return createWatermarkMeta({
180
+ adaptiveConfidence,
181
+ originalSpatialScore,
182
+ originalGradientScore,
183
+ processedSpatialScore: originalSpatialScore,
184
+ processedGradientScore: originalGradientScore,
185
+ suppressionGain: 0,
186
+ alphaGain: 1,
187
+ source,
188
+ decisionTier,
189
+ applied: false,
190
+ skipReason: reason,
191
+ selectionDebug,
192
+ decisionPath: createRejectedDecisionPath({
193
+ reason,
194
+ source,
195
+ decisionTier,
196
+ originalSpatialScore,
197
+ originalGradientScore,
198
+ adaptiveConfidence
199
+ })
200
+ });
201
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ computeRegionGradientCorrelation,
3
+ computeRegionSpatialCorrelation
4
+ } from './adaptiveDetector.js';
5
+ import { calculateNearBlackRatio } from './candidateSelector.js';
6
+
7
+ const FIRST_PASS_SIGN_FLIP_GRADIENT_THRESHOLD = 0.08;
8
+ const FIRST_PASS_SIGN_FLIP_MIN_GRADIENT_DROP = 0.2;
9
+
10
+ export function shouldStopAfterFirstPass({
11
+ originalSpatialScore,
12
+ originalGradientScore,
13
+ firstPassSpatialScore,
14
+ firstPassGradientScore
15
+ }) {
16
+ if (Math.abs(firstPassSpatialScore) <= 0.25) {
17
+ return true;
18
+ }
19
+
20
+ return originalSpatialScore >= 0 &&
21
+ firstPassSpatialScore < 0 &&
22
+ firstPassGradientScore <= FIRST_PASS_SIGN_FLIP_GRADIENT_THRESHOLD &&
23
+ (originalGradientScore - firstPassGradientScore) >= FIRST_PASS_SIGN_FLIP_MIN_GRADIENT_DROP;
24
+ }
25
+
26
+ export function createRegionCorrelationMetrics({
27
+ imageData,
28
+ alphaMap,
29
+ position,
30
+ includeNearBlackRatio = false
31
+ } = {}) {
32
+ const region = { x: position.x, y: position.y, size: position.width };
33
+ const metrics = {
34
+ spatialScore: computeRegionSpatialCorrelation({
35
+ imageData,
36
+ alphaMap,
37
+ region
38
+ }),
39
+ gradientScore: computeRegionGradientCorrelation({
40
+ imageData,
41
+ alphaMap,
42
+ region
43
+ })
44
+ };
45
+ if (includeNearBlackRatio) {
46
+ metrics.nearBlackRatio = calculateNearBlackRatio(imageData, position);
47
+ }
48
+ return metrics;
49
+ }
50
+
51
+ export function createFirstPassMetrics({
52
+ imageData,
53
+ alphaMap,
54
+ position,
55
+ originalSpatialScore,
56
+ originalGradientScore
57
+ } = {}) {
58
+ const firstPassMetrics = createRegionCorrelationMetrics({
59
+ imageData,
60
+ alphaMap,
61
+ position,
62
+ includeNearBlackRatio: true
63
+ });
64
+ const clearedResidual = shouldStopAfterFirstPass({
65
+ originalSpatialScore,
66
+ originalGradientScore,
67
+ firstPassSpatialScore: firstPassMetrics.spatialScore,
68
+ firstPassGradientScore: firstPassMetrics.gradientScore
69
+ });
70
+
71
+ return {
72
+ spatialScore: firstPassMetrics.spatialScore,
73
+ gradientScore: firstPassMetrics.gradientScore,
74
+ nearBlackRatio: firstPassMetrics.nearBlackRatio,
75
+ clearedResidual,
76
+ passStopReason: clearedResidual ? 'residual-low' : 'single-pass',
77
+ passRecord: {
78
+ index: 1,
79
+ beforeSpatialScore: originalSpatialScore,
80
+ beforeGradientScore: originalGradientScore,
81
+ afterSpatialScore: firstPassMetrics.spatialScore,
82
+ afterGradientScore: firstPassMetrics.gradientScore,
83
+ improvement: Math.abs(originalSpatialScore) - Math.abs(firstPassMetrics.spatialScore),
84
+ gradientDelta: firstPassMetrics.gradientScore - originalGradientScore,
85
+ nearBlackRatio: firstPassMetrics.nearBlackRatio
86
+ }
87
+ };
88
+ }
@@ -0,0 +1,36 @@
1
+ function finiteOrFallback(value, fallback) {
2
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
3
+ }
4
+
5
+ export function createEmptyPipelinePassState() {
6
+ return {
7
+ passCount: 0,
8
+ attemptedPassCount: 0,
9
+ passStopReason: null,
10
+ passes: null
11
+ };
12
+ }
13
+
14
+ export function createFirstPassPipelinePassState({
15
+ firstPassMetrics = null
16
+ } = {}) {
17
+ return {
18
+ passCount: 1,
19
+ attemptedPassCount: 1,
20
+ passStopReason: firstPassMetrics?.passStopReason ?? null,
21
+ passes: [firstPassMetrics?.passRecord]
22
+ };
23
+ }
24
+
25
+ export function applyPipelinePassOutcome({
26
+ current,
27
+ outcome
28
+ } = {}) {
29
+ const passIncrement = Math.max(0, finiteOrFallback(outcome?.passIncrement, 0));
30
+ return {
31
+ passCount: finiteOrFallback(current?.passCount, 0) + passIncrement,
32
+ attemptedPassCount: finiteOrFallback(current?.attemptedPassCount, 0) + passIncrement,
33
+ passStopReason: outcome?.passStopReason ?? current?.passStopReason ?? null,
34
+ passes: current?.passes ?? null
35
+ };
36
+ }
@@ -0,0 +1,124 @@
1
+ const DEFAULT_PREVIEW_EDGE_CLEANUP_MAX_SIZE = 40;
2
+ const DEFAULT_KNOWN_48_EDGE_CLEANUP_MIN_SIZE = 40;
3
+ const DEFAULT_KNOWN_48_EDGE_CLEANUP_MAX_SIZE = 56;
4
+ const DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE = 36;
5
+ const DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE = 2;
6
+
7
+ export function shouldUsePreviewAnchorFastCleanup(
8
+ selectedTrial,
9
+ position,
10
+ { previewEdgeCleanupMaxSize = DEFAULT_PREVIEW_EDGE_CLEANUP_MAX_SIZE } = {}
11
+ ) {
12
+ return selectedTrial?.provenance?.previewAnchor === true &&
13
+ position?.width >= 24 &&
14
+ position?.width <= previewEdgeCleanupMaxSize;
15
+ }
16
+
17
+ export function isKnown48AnchorConfig(
18
+ config,
19
+ {
20
+ known48EdgeCleanupMinSize = DEFAULT_KNOWN_48_EDGE_CLEANUP_MIN_SIZE,
21
+ known48EdgeCleanupMaxSize = DEFAULT_KNOWN_48_EDGE_CLEANUP_MAX_SIZE
22
+ } = {}
23
+ ) {
24
+ if (!config || config.logoSize < known48EdgeCleanupMinSize || config.logoSize > known48EdgeCleanupMaxSize) {
25
+ return false;
26
+ }
27
+
28
+ const marginRight = Number(config.marginRight);
29
+ const marginBottom = Number(config.marginBottom);
30
+ if (!Number.isFinite(marginRight) || !Number.isFinite(marginBottom)) return false;
31
+
32
+ const isCurrentLargeMargin = Math.abs(marginRight - 96) <= 2 && Math.abs(marginBottom - 96) <= 2;
33
+ const isCurrentStandardMargin = marginRight >= 28 && marginRight <= 36 && marginBottom >= 28 && marginBottom <= 36;
34
+ return isCurrentLargeMargin || isCurrentStandardMargin;
35
+ }
36
+
37
+ export function shouldUseKnown48EdgeCleanup({
38
+ selectedTrial,
39
+ position,
40
+ source,
41
+ known48EdgeCleanupMinSize = DEFAULT_KNOWN_48_EDGE_CLEANUP_MIN_SIZE,
42
+ known48EdgeCleanupMaxSize = DEFAULT_KNOWN_48_EDGE_CLEANUP_MAX_SIZE
43
+ } = {}) {
44
+ if (selectedTrial?.provenance?.previewAnchor === true) return false;
45
+ if (position?.width < known48EdgeCleanupMinSize || position?.width > known48EdgeCleanupMaxSize) return false;
46
+ if (!isKnown48AnchorConfig(selectedTrial?.config, {
47
+ known48EdgeCleanupMinSize,
48
+ known48EdgeCleanupMaxSize
49
+ })) return false;
50
+
51
+ const sourceText = String(source || '');
52
+ return sourceText === 'standard' ||
53
+ sourceText.startsWith('standard+gain') ||
54
+ sourceText.includes('catalog') ||
55
+ sourceText.includes('fixed-local');
56
+ }
57
+
58
+ export function isV2SmallAnchorConfig(
59
+ config,
60
+ { v2SmallEdgeCleanupSize = DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE } = {}
61
+ ) {
62
+ if (!config || config.logoSize !== v2SmallEdgeCleanupSize || config.alphaVariant !== 'v2') {
63
+ return false;
64
+ }
65
+
66
+ const marginRight = Number(config.marginRight);
67
+ const marginBottom = Number(config.marginBottom);
68
+ return Number.isFinite(marginRight) &&
69
+ Number.isFinite(marginBottom) &&
70
+ marginRight >= 48 &&
71
+ marginBottom >= 48;
72
+ }
73
+
74
+ export function shouldUseV2SmallEdgeCleanup({
75
+ selectedTrial,
76
+ position,
77
+ source,
78
+ v2SmallEdgeCleanupSize = DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE,
79
+ v2SmallEdgeCleanupSizeTolerance = DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE
80
+ } = {}) {
81
+ if (selectedTrial?.provenance?.previewAnchor === true) return false;
82
+ if (
83
+ position?.width < v2SmallEdgeCleanupSize - v2SmallEdgeCleanupSizeTolerance ||
84
+ position?.width > v2SmallEdgeCleanupSize + v2SmallEdgeCleanupSizeTolerance
85
+ ) {
86
+ return false;
87
+ }
88
+ if (!isV2SmallAnchorConfig(selectedTrial?.config, { v2SmallEdgeCleanupSize })) return false;
89
+ if (selectedTrial?.provenance?.catalogFamily !== 'gemini-v2-small') return false;
90
+
91
+ const sourceText = String(source || '');
92
+ return sourceText.includes('catalog');
93
+ }
94
+
95
+ export function createRepairCleanupFlags({
96
+ selectedTrial,
97
+ position,
98
+ source,
99
+ previewEdgeCleanupMaxSize = DEFAULT_PREVIEW_EDGE_CLEANUP_MAX_SIZE,
100
+ known48EdgeCleanupMinSize = DEFAULT_KNOWN_48_EDGE_CLEANUP_MIN_SIZE,
101
+ known48EdgeCleanupMaxSize = DEFAULT_KNOWN_48_EDGE_CLEANUP_MAX_SIZE,
102
+ v2SmallEdgeCleanupSize = DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE,
103
+ v2SmallEdgeCleanupSizeTolerance = DEFAULT_V2_SMALL_EDGE_CLEANUP_SIZE_TOLERANCE
104
+ } = {}) {
105
+ return {
106
+ usePreviewAnchorFastCleanup: shouldUsePreviewAnchorFastCleanup(selectedTrial, position, {
107
+ previewEdgeCleanupMaxSize
108
+ }),
109
+ useKnown48EdgeCleanup: shouldUseKnown48EdgeCleanup({
110
+ selectedTrial,
111
+ position,
112
+ source,
113
+ known48EdgeCleanupMinSize,
114
+ known48EdgeCleanupMaxSize
115
+ }),
116
+ useV2SmallEdgeCleanup: shouldUseV2SmallEdgeCleanup({
117
+ selectedTrial,
118
+ position,
119
+ source,
120
+ v2SmallEdgeCleanupSize,
121
+ v2SmallEdgeCleanupSizeTolerance
122
+ })
123
+ };
124
+ }