@pixerate/editor 0.221.0 → 0.222.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/image-editor/edit-tools.d.cts +1 -1
- package/dist/image-editor/edit-tools.d.ts +1 -1
- package/dist/image-editor/index.cjs +52 -4
- package/dist/image-editor/index.js +52 -4
- package/dist/image-editor/selection/inspect.d.cts +1 -1
- package/dist/image-editor/selection/inspect.d.ts +1 -1
- package/package.json +1 -1
- package/skills/README.md +43 -9
- package/skills/WORKFLOW_TEMPLATE.md +50 -0
- package/skills/pixerate-image-editor/SKILL.md +32 -76
- package/skills/pixerate-image-editor/references/free-transform.md +11 -0
- package/skills/pixerate-image-editor/references/layer-effects.md +12 -0
- package/skills/pixerate-image-editor/references/smart-objects.md +9 -0
- package/skills/pixerate-image-photo-correction/SKILL.md +129 -0
- package/skills/pixerate-image-photo-correction/evals/crop-for-instagram.json +19 -0
- package/skills/pixerate-image-photo-correction/evals/lift-shadows-keep-sky.json +20 -0
- package/skills/pixerate-image-photo-correction/evals/neutralise-cast.json +25 -0
- package/skills/pixerate-image-portrait-retouch/SKILL.md +115 -0
- package/skills/pixerate-image-portrait-retouch/evals/brighten-eyes.json +25 -0
- package/skills/pixerate-image-portrait-retouch/evals/remove-blemishes.json +23 -0
- package/skills/pixerate-image-portrait-retouch/evals/remove-stray-hair.json +18 -0
- package/skills/pixerate-image-product-photo/SKILL.md +126 -0
- package/skills/pixerate-image-product-photo/evals/grounding-shadow.json +19 -0
- package/skills/pixerate-image-product-photo/evals/square-crop.json +19 -0
- package/skills/pixerate-image-product-photo/evals/white-background.json +23 -0
- package/skills/pixerate-image-selection-masking/evals/local-adjustment.json +22 -0
- package/skills/pixerate-image-selection-masking/evals/mask-out-background.json +24 -0
- package/skills/pixerate-image-selection-masking/evals/select-one-of-two.json +22 -0
- package/skills/pixerate-spreadsheet/SKILL.md +7 -0
- package/skills/pixerate-spreadsheet-clean-import/SKILL.md +154 -0
- package/skills/pixerate-spreadsheet-clean-import/evals/clean-in-place.json +43 -0
- package/skills/pixerate-spreadsheet-clean-import/evals/dedupe-keep-latest.json +25 -0
- package/skills/pixerate-spreadsheet-clean-import/evals/fix-types-only.json +24 -0
- package/skills/pixerate-video-editor/SKILL.md +8 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-image-photo-correction
|
|
3
|
+
description: The global photo correction workflow in the Pixerate image editor - straightening and cropping, then exposure and white balance, black and white points, contrast and tone curves, colour and saturation, local fixes and finally a look, each checked in numbers (histogram, clipping, neutral greys) before the next. Use when asked to fix, correct, enhance, edit, brighten, darken, warm up, cool down or colour-correct a photo, remove a colour cast, recover shadows or highlights, add contrast or punch, straighten a horizon or crop to a ratio, or when set_adjustments, add_adjustment_layer, get_histogram or measure_region are available. Load with pixerate-image-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-image-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Correcting a photo
|
|
10
|
+
|
|
11
|
+
A correction is a sequence where each step changes what the next one sees, so the order
|
|
12
|
+
matters: frame, then light, then colour, then local, then style. The base controls
|
|
13
|
+
(`set_adjustments`) edit the Background's develop filter; adjustment layers stack above it and
|
|
14
|
+
can be masked, faded and blended later. Judge with numbers as well as eyes: `measure_region`
|
|
15
|
+
measures any rectangle of the composited image without a render (mean colour, cast, percentiles,
|
|
16
|
+
clipping), and `get_histogram` summarises the last `render_preview`.
|
|
17
|
+
|
|
18
|
+
This skill is the method. `pixerate-image-editor` has the full tool reference and control ids.
|
|
19
|
+
|
|
20
|
+
## Goal and quality bar
|
|
21
|
+
|
|
22
|
+
- Nothing important clips: under about 0.5 % of pixels at pure black or white per channel on a
|
|
23
|
+
normal photo (`clipping` in `measure_region` or `get_histogram`), unless a light source or a
|
|
24
|
+
specular highlight is meant to.
|
|
25
|
+
- Things that should be neutral are: a white wall, a grey card or clouds within about ±4 levels
|
|
26
|
+
of cast (`cast.redBlue`, `cast.greenMagenta`).
|
|
27
|
+
- The tones span the range the scene has: shadows near but above 0, highlights near but below
|
|
28
|
+
255 (`stats.luminance.p1` / `p99`), midtones where the subject reads well.
|
|
29
|
+
- Every correction is still adjustable: base sliders for global light and white balance, layers
|
|
30
|
+
for the rest, named by intent. Nothing is merged or flattened unless asked.
|
|
31
|
+
|
|
32
|
+
## Inputs to confirm
|
|
33
|
+
|
|
34
|
+
- **The intent:** a faithful correction (the default) or a look (warm, moody, high contrast).
|
|
35
|
+
- **A neutral reference** if there is one: a grey card, white paper, a wall the user knows is
|
|
36
|
+
white. Ask when the scene has none and the "right" colour is a matter of taste.
|
|
37
|
+
- **Output ratio** if the user mentions a destination (Instagram 4:5, a print size, 16:9).
|
|
38
|
+
|
|
39
|
+
## Working method
|
|
40
|
+
|
|
41
|
+
1. **Read.** `get_image_info` (size, crop, rotation), `get_adjustments` (what is already set),
|
|
42
|
+
`list_layers`, then `measure_region` with no rectangle (the whole frame) and on the subject.
|
|
43
|
+
**Check:** you know where the image clips, its overall cast and where the subject is.
|
|
44
|
+
2. **Straighten and crop first**, so every later judgement is on the final frame. Level a
|
|
45
|
+
horizon or vertical with `set_rotation` (small angles, -3..3), then `set_crop` inside the
|
|
46
|
+
transparent corners at the asked ratio; keep the subject's space to look or move into.
|
|
47
|
+
**Check:** `get_image_info` `crop` has the ratio you meant and contains the subject.
|
|
48
|
+
3. **Exposure and white balance** with one `set_adjustments` call (`exposure`, `temperature`,
|
|
49
|
+
`tint`, with a `label`). Set white balance from the neutral reference: `measure_region` on
|
|
50
|
+
it, move `temperature` against `cast.redBlue` and `tint` against `cast.greenMagenta`, measure
|
|
51
|
+
again. For a precise fix, a `levels` layer with per-channel `inWhite` scales each channel so
|
|
52
|
+
the reference comes out grey (scale each channel to the same level).
|
|
53
|
+
**Check:** the reference's `cast` is within ±4; the frame does not clip.
|
|
54
|
+
4. **Black and white points, then contrast.** A `levels` layer with `master.inBlack` /
|
|
55
|
+
`master.inWhite` just inside where the data starts (`p1` / `p99`), never past it. Then a
|
|
56
|
+
gentle S-curve `curves` layer (quarter-tones moved 10..25 levels, endpoints fixed). To lift
|
|
57
|
+
dark areas without touching bright ones, raise `master.gamma` (1.2..1.8) instead of exposure.
|
|
58
|
+
**Check:** `clipping` stays under 0.5 % per channel; shadows and highlights still have detail.
|
|
59
|
+
5. **Colour.** `vibrance` before `saturation` (it spares skin and colours already strong),
|
|
60
|
+
`hueSaturation` per range for one colour, `colorBalance` for a cast left in the shadows or
|
|
61
|
+
highlights. Small moves: real corrections are ±10..30.
|
|
62
|
+
**Check:** neutrals still neutral; skin has red above green above blue and no orange or magenta.
|
|
63
|
+
6. **Local fixes** through masks: a sky darkened, a face lifted. Select (see
|
|
64
|
+
`pixerate-image-selection-masking`), then `add_adjustment_layer` while the selection is
|
|
65
|
+
active, or `apply_selection_as_mask` with a `kind`.
|
|
66
|
+
**Check:** `measure_region` inside and just outside the mask: the change stops where it should.
|
|
67
|
+
7. **Look last**, if asked: `photoFilter`, `blackWhite`, a split tone with `colorBalance`, at
|
|
68
|
+
reduced `opacity`. Correct before you stylise.
|
|
69
|
+
8. **Verify the whole.** `measure_region` on the frame and the reference again; `render_preview`
|
|
70
|
+
and `get_histogram` where a canvas is available to look as well as measure. `undo_image` any
|
|
71
|
+
step that made it worse, and report the settings you used.
|
|
72
|
+
|
|
73
|
+
## Decision table
|
|
74
|
+
|
|
75
|
+
| Problem | Fix |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| Whole image too dark or bright, nothing clipping | `exposure` (base), ±10..30 |
|
|
78
|
+
| Dark shadows, bright sky or window | `levels` `master.gamma` 1.2..1.8, or a curves lift in the lower quarter; never exposure, which clips the sky |
|
|
79
|
+
| Bright areas blown, shadows fine | Lower the top of a curve, or `levels` `master.outWhite` a little; clipped detail cannot come back |
|
|
80
|
+
| Flat, grey, low contrast | `levels` points at `p1` / `p99`, then a gentle S-curve |
|
|
81
|
+
| Colour cast, with a neutral reference | Per-channel `levels` `inWhite` (precise), or `temperature` / `tint` (quick) |
|
|
82
|
+
| Colour cast, no reference | `temperature` / `tint` until whites and clouds look clean; ask whether warmth is wanted |
|
|
83
|
+
| Cast only in the shadows or highlights | `colorBalance` shadows or highlights |
|
|
84
|
+
| Dull colour | `vibrance` +10..25; `saturation` only for non-portrait scenes |
|
|
85
|
+
| One colour wrong (green grass too yellow, sky too cyan) | `hueSaturation` on that range |
|
|
86
|
+
| Contrast change shifts colours | Put the curves or levels layer in `luminosity` blend (`set_layer_properties`) |
|
|
87
|
+
| Tilted horizon | `set_rotation`, then `set_crop` inside the corners |
|
|
88
|
+
|
|
89
|
+
## Recipes
|
|
90
|
+
|
|
91
|
+
**Neutral from a grey card.** `measure_region` on the card gives mean levels per channel (from
|
|
92
|
+
`stats`). Pick the target (the green level, or the mean), and set each channel's `inWhite` to
|
|
93
|
+
`255 × channel / target` in one `add_adjustment_layer` `levels` call (`params: { red: { inWhite },
|
|
94
|
+
green: { inWhite }, blue: { inWhite } }`). Measure the card again, and the whole frame for clipping.
|
|
95
|
+
|
|
96
|
+
**Lift shadows, keep the sky.** `add_adjustment_layer` `levels` with `params: { master: { gamma:
|
|
97
|
+
1.6 } }`; measure the sky's `clipping` and the foreground's luminance mean; raise or lower gamma.
|
|
98
|
+
For a lift limited to the foreground, add it while a selection of the foreground is active.
|
|
99
|
+
|
|
100
|
+
**Crop to a ratio, as large as possible.** The largest crop with ratio `w:h` inside an image of
|
|
101
|
+
`W × H` is `W` wide when `W / H` < `w / h`, else `H` tall; position it to keep the subject in
|
|
102
|
+
frame and give it room on the side it faces.
|
|
103
|
+
|
|
104
|
+
## Pitfalls
|
|
105
|
+
|
|
106
|
+
- **Correcting in the wrong order.** Colour judged before exposure or white balance gets redone;
|
|
107
|
+
a crop after grading changes what the histogram meant.
|
|
108
|
+
- **Exposure for shadows.** Raising exposure to open shadows clips the sky; use gamma or curves.
|
|
109
|
+
- **Points past the data.** `inBlack` above `p1` or `inWhite` below `p99` crushes or blows
|
|
110
|
+
everything beyond; `clipping` shows it.
|
|
111
|
+
- **Saturation on skin.** It turns skin orange fast; use vibrance, and measure a skin patch.
|
|
112
|
+
- **Trusting the screen alone.** Measure neutrals and clipping; a preview at 512 px hides small
|
|
113
|
+
clipped areas.
|
|
114
|
+
- **Stacking corrections in base sliders.** They cannot be masked or faded; use layers for
|
|
115
|
+
anything beyond global exposure and white balance.
|
|
116
|
+
- **`get_histogram` before a render.** It reads the last `render_preview` and fails without one;
|
|
117
|
+
`measure_region` needs no render.
|
|
118
|
+
|
|
119
|
+
## Tools used
|
|
120
|
+
|
|
121
|
+
- `get_image_info`, `get_adjustments`, `list_controls`, `list_layers`: read the frame, settings, control ids and stack
|
|
122
|
+
- `measure_region`: mean colour, cast, percentiles and clipping of any rectangle, without a render
|
|
123
|
+
- `render_preview`, `get_histogram`, `capture_view`: look at the result and read its histogram
|
|
124
|
+
- `set_rotation`, `set_crop`, `reset_crop`: straighten and frame
|
|
125
|
+
- `set_adjustments`: exposure, white balance and other base controls, one undo step per call
|
|
126
|
+
- `add_adjustment_layer`, `update_adjustment_layer`, `remove_adjustment_layer`: levels, curves, vibrance, hue and colour balance as layers
|
|
127
|
+
- `set_layer_properties`: blend mode (luminosity), opacity and name of a correction layer
|
|
128
|
+
- `apply_selection_as_mask`: a masked adjustment layer for a local fix
|
|
129
|
+
- `undo_image`: take back a step
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Crop this photo to a 4:5 portrait for Instagram, as large as possible, keeping the whole person (the tall figure at x 50-70, y 15-70) in frame.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [110, 140, 90],
|
|
8
|
+
"shapes": [{ "shape": "rect", "x": 50, "y": 15, "width": 20, "height": 55, "color": [180, 120, 100] }]
|
|
9
|
+
},
|
|
10
|
+
"checks": [
|
|
11
|
+
{ "why": "a 4:5 portrait crop", "tool": "get_image_info", "expect": { "crop": { "$expr": "v && Math.abs(v.width / v.height - 0.8) <= 0.02" } } },
|
|
12
|
+
{ "why": "as large as the frame allows (full height)", "tool": "get_image_info", "expect": { "crop": { "height": { "$min": 78 } } } },
|
|
13
|
+
{ "why": "the whole person is inside", "tool": "get_image_info", "expect": { "crop": { "$expr": "v && v.x <= 50 && v.x + v.width >= 70 && v.y <= 15 && v.y + v.height >= 70" } } }
|
|
14
|
+
],
|
|
15
|
+
"solution": [
|
|
16
|
+
{ "tool": "get_image_info" },
|
|
17
|
+
{ "tool": "set_crop", "args": { "x": 28, "y": 0, "width": 64, "height": 80 } }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "The foreground of this landscape is far too dark, but the sky is already bright. Brighten the foreground without blowing out the sky.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [40, 45, 40],
|
|
8
|
+
"shapes": [{ "shape": "rect", "x": 0, "y": 0, "width": 120, "height": 30, "color": [225, 232, 245] }]
|
|
9
|
+
},
|
|
10
|
+
"checks": [
|
|
11
|
+
{ "why": "the foreground is lifted into the midtones", "tool": "measure_region", "args": { "x": 0, "y": 40, "width": 120, "height": 40 }, "expect": { "stats": { "luminance": { "mean": { "$min": 70, "$max": 160 } } } } },
|
|
12
|
+
{ "why": "the sky keeps its detail: nothing clips to white", "tool": "measure_region", "args": { "x": 0, "y": 0, "width": 120, "height": 30 }, "expect": { "clipping": { "highlightsPercent": { "red": { "$max": 0.5 }, "green": { "$max": 0.5 }, "blue": { "$max": 0.5 } } } } },
|
|
13
|
+
{ "why": "the sky stays bright", "tool": "measure_region", "args": { "x": 0, "y": 0, "width": 120, "height": 30 }, "expect": { "stats": { "luminance": { "mean": { "$min": 200 } } } } }
|
|
14
|
+
],
|
|
15
|
+
"solution": [
|
|
16
|
+
{ "tool": "measure_region" },
|
|
17
|
+
{ "tool": "add_adjustment_layer", "args": { "kind": "levels", "name": "Lift shadows", "params": { "master": { "gamma": 1.8 } } } },
|
|
18
|
+
{ "tool": "measure_region", "args": { "x": 0, "y": 0, "width": 120, "height": 30 } }
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "This photo has a cold blue cast. The grey card in the lower left (x 10-30, y 50-70) should be neutral grey. Fix the colour without clipping anything, and keep it adjustable.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [50, 60, 85],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "rect", "x": 0, "y": 0, "width": 120, "height": 20, "color": [120, 135, 170] },
|
|
10
|
+
{ "shape": "rect", "x": 10, "y": 50, "width": 20, "height": 20, "color": [70, 80, 105] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "the grey card is neutral (cast within ±4 levels)", "tool": "measure_region", "args": { "x": 12, "y": 52, "width": 16, "height": 16 }, "expect": { "cast": { "redBlue": { "$min": -4, "$max": 4 }, "greenMagenta": { "$min": -4, "$max": 4 } } } },
|
|
15
|
+
{ "why": "the card is a mid grey, not crushed or blown", "tool": "measure_region", "args": { "x": 12, "y": 52, "width": 16, "height": 16 }, "expect": { "stats": { "luminance": { "mean": { "$min": 90, "$max": 170 } } } } },
|
|
16
|
+
{ "why": "no channel clips anywhere", "tool": "measure_region", "expect": { "clipping": { "highlightsPercent": { "red": { "$max": 0.5 }, "green": { "$max": 0.5 }, "blue": { "$max": 0.5 } }, "shadowsPercent": { "red": { "$max": 0.5 }, "green": { "$max": 0.5 }, "blue": { "$max": 0.5 } } } } },
|
|
17
|
+
{ "why": "the correction is a layer, not baked into the photo", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "adjustment" } } } }
|
|
18
|
+
],
|
|
19
|
+
"solution": [
|
|
20
|
+
{ "tool": "measure_region", "args": { "x": 12, "y": 52, "width": 16, "height": 16 } },
|
|
21
|
+
{ "tool": "add_adjustment_layer", "args": { "kind": "levels", "name": "Neutralise cast", "params": { "red": { "inWhite": 140 }, "green": { "inWhite": 159 }, "blue": { "inWhite": 209 } } } },
|
|
22
|
+
{ "tool": "measure_region", "args": { "x": 12, "y": 52, "width": 16, "height": 16 } },
|
|
23
|
+
{ "tool": "measure_region" }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-image-portrait-retouch
|
|
3
|
+
description: The portrait retouching workflow in the Pixerate image editor - removing blemishes, spots and stray hairs, healing larger flaws, evening skin while keeping its texture, dodging and burning, brightening eyes and teeth, and keeping the result natural and reversible on separate layers. Use when asked to retouch, clean up, airbrush or touch up a portrait, headshot or face, remove spots, pimples, acne, wrinkles, stray hairs or dust, smooth or even skin, brighten eyes or whiten teeth, or when spot_heal, heal_brush, clone_stamp, dodge or burn are available. Load with pixerate-image-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-image-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Retouching a portrait
|
|
10
|
+
|
|
11
|
+
Good retouching removes what is temporary (a spot, a stray hair, a shine) and keeps what makes
|
|
12
|
+
the person look like themselves (moles they keep, freckles, pores, laughter lines). Work from
|
|
13
|
+
the global to the small, on layers above the photo, so every step can be faded or taken back.
|
|
14
|
+
The healing tools paint onto an empty layer when they sample `current-below`; tonal tools
|
|
15
|
+
(dodge, burn, sponge, smudge) change the pixels they touch, so they work on a duplicate.
|
|
16
|
+
|
|
17
|
+
This skill is the method. `pixerate-image-editor` has the tool reference, and its
|
|
18
|
+
`references/retouching.md` the detail of each retouching tool.
|
|
19
|
+
|
|
20
|
+
## Goal and quality bar
|
|
21
|
+
|
|
22
|
+
- Temporary flaws are gone: a blemish area measures like the skin around it (`measure_region`
|
|
23
|
+
on a small box around it: luminance `p1` close to the surrounding skin).
|
|
24
|
+
- Skin keeps its texture: no flat, plastic patches. Heal and clone copy real texture; blur only
|
|
25
|
+
locally and lightly.
|
|
26
|
+
- Tone and colour are natural: skin has red above green above blue, eyes and teeth brightened
|
|
27
|
+
only a little (they stay off-white).
|
|
28
|
+
- The photo layer is unchanged: retouching lives on named layers ("Retouch", "Dodge & burn",
|
|
29
|
+
"Eyes") the user can fade or switch off.
|
|
30
|
+
|
|
31
|
+
## Inputs to confirm
|
|
32
|
+
|
|
33
|
+
- **How much:** a light clean-up (temporary flaws only, the default), a polished commercial
|
|
34
|
+
retouch, or something specific.
|
|
35
|
+
- **What to keep:** moles, freckles, scars and features that are part of the person stay unless
|
|
36
|
+
the user asks.
|
|
37
|
+
- **Where:** the flaws to fix when the user has named them (positions or a description).
|
|
38
|
+
|
|
39
|
+
## Working method
|
|
40
|
+
|
|
41
|
+
1. **Global correction first.** Exposure and white balance as in `pixerate-image-photo-correction`,
|
|
42
|
+
so retouching is judged on the final tones.
|
|
43
|
+
**Check:** skin `measure_region` has a plausible colour (red > green > blue, no strong cast).
|
|
44
|
+
2. **Find the flaws.** `capture_view` the face at 2-4× scale in parts; list spots, hairs and
|
|
45
|
+
flaws with positions. `measure_region` on a small box around a suspect spot compared with a
|
|
46
|
+
box of clean skin beside it tells a spot from a shadow.
|
|
47
|
+
**Check:** a list of flaws with coordinates and sizes.
|
|
48
|
+
3. **Make a retouch layer.** `add_pixel_layer` named "Retouch" above the photo; all healing goes
|
|
49
|
+
on it with `sample: "current-below"`.
|
|
50
|
+
**Check:** `list_layers` shows it active above the photo.
|
|
51
|
+
4. **Heal small to large.** `spot_heal` for spots (one dab, `brush.size` a little larger than the
|
|
52
|
+
spot, `hardness` 0.2-0.5); a short stroke of dabs along a stray hair. `heal_brush` from a clean
|
|
53
|
+
source of the same texture for larger flaws; `clone_stamp` near edges (lips, eyes, hairline)
|
|
54
|
+
where healing would pull colour across.
|
|
55
|
+
**Check:** `measure_region` around each flaw: its `p1` is near the clean skin's; `capture_view`
|
|
56
|
+
shows texture, not a smear.
|
|
57
|
+
5. **Even tone (dodge and burn)**, only if asked for more than a clean-up. Duplicate the photo
|
|
58
|
+
(`duplicate_layer`) and `dodge` / `burn` at `exposure` 0.1-0.2, `range: "midtones"`, along
|
|
59
|
+
shadows under the eyes or uneven patches; or paint on a layer in `soft-light` blend.
|
|
60
|
+
**Check:** the area's luminance moved by a few levels, not tens.
|
|
61
|
+
6. **Eyes and teeth.** Select them (`select_ellipse`, `select_polygon`), feather 1-2 px, and add a
|
|
62
|
+
`levels` or `curves` layer while the selection is active (`master.gamma` 1.2-1.6). For yellow
|
|
63
|
+
teeth, a `hueSaturation` layer lowering the yellows' saturation, masked the same way.
|
|
64
|
+
**Check:** `measure_region` inside rises; just outside (skin, lips) does not change.
|
|
65
|
+
7. **Review the whole face** at normal scale (`render_preview` or `capture_view` at 1×). Lower a
|
|
66
|
+
layer's `opacity` where it went too far (`set_layer_properties`). Report what you removed.
|
|
67
|
+
|
|
68
|
+
## Decision table
|
|
69
|
+
|
|
70
|
+
| Flaw | Tool |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| Spot, pimple, dust (smaller than about 15 px) | `spot_heal`, `proximity-match` or `content-aware` |
|
|
73
|
+
| Stray hair across skin | `spot_heal` along it, short strokes; `clone_stamp` where it crosses an edge |
|
|
74
|
+
| Larger blemish, scar the user wants gone | `heal_brush` from clean skin of the same texture and light |
|
|
75
|
+
| Flaw next to an edge (lip line, eyelid, hairline) | `clone_stamp`, with a source along the same edge |
|
|
76
|
+
| Shine on forehead or nose | `heal_brush` at reduced brush opacity from matte skin nearby |
|
|
77
|
+
| Dark circles, uneven patches | `dodge` (midtones, exposure 0.1-0.2) on a duplicate, or a masked curves layer |
|
|
78
|
+
| Dull eyes | Masked `levels` gamma 1.2-1.6; `sharpen_stroke` lightly on the iris |
|
|
79
|
+
| Yellow teeth | Masked `hueSaturation` (yellows, saturation -20..-40), then a little brightness |
|
|
80
|
+
| Redness | Masked `hueSaturation` (reds, saturation down a little) |
|
|
81
|
+
|
|
82
|
+
## Recipes
|
|
83
|
+
|
|
84
|
+
**Clean-up pass.** `add_pixel_layer` "Retouch" → one `spot_heal` per spot (`points: [{x, y}]`,
|
|
85
|
+
`sample: "current-below"`, `brush: { size, hardness: 0.3 }`) → `measure_region` around each.
|
|
86
|
+
|
|
87
|
+
**Stray hair.** `spot_heal` with `points` every 8-10 px along the hair and a brush 1.5-2× its
|
|
88
|
+
width; check the whole length with one `measure_region` box over it.
|
|
89
|
+
|
|
90
|
+
**Brighter eyes.** `select_ellipse` each eye (`mode: "add"` for the second) → `modify_selection`
|
|
91
|
+
`feather` 1 → `add_adjustment_layer` `levels` with `params: { master: { gamma: 1.4 } }` →
|
|
92
|
+
`measure_region` on an eye and on the skin beside it.
|
|
93
|
+
|
|
94
|
+
## Pitfalls
|
|
95
|
+
|
|
96
|
+
- **Healing on the photo layer.** Mistakes become permanent; heal onto an empty layer with
|
|
97
|
+
`current-below`.
|
|
98
|
+
- **An empty layer with `sample: "current"`** has nothing to copy, so the stroke does nothing.
|
|
99
|
+
- **Over-smoothing.** Blurring skin removes pores and looks fake; heal flaws instead.
|
|
100
|
+
- **Big brushes.** A heal brush much larger than the flaw pulls in colour from around it; keep it
|
|
101
|
+
just over the flaw.
|
|
102
|
+
- **Healing across an edge** smears lips or lashes into skin; clone along edges instead.
|
|
103
|
+
- **Removing features.** Moles, freckles and scars are part of a face; ask before removing them.
|
|
104
|
+
- **Bright white eyes and teeth** look unnatural; small gamma moves, and measure.
|
|
105
|
+
|
|
106
|
+
## Tools used
|
|
107
|
+
|
|
108
|
+
- `list_layers`, `capture_view`, `render_preview`, `measure_region`: find flaws, check each fix, review
|
|
109
|
+
- `add_pixel_layer`, `duplicate_layer`, `set_layer_properties`: retouch and dodge-and-burn layers; fade them
|
|
110
|
+
- `spot_heal`, `heal_brush`, `clone_stamp`, `set_clone_source`: remove spots, hairs and flaws
|
|
111
|
+
- `dodge`, `burn`, `sponge`: even tone and colour on a duplicate
|
|
112
|
+
- `sharpen_stroke`, `blur_stroke`: crisp eyes, soften a harsh edge, locally
|
|
113
|
+
- `select_ellipse`, `select_polygon`, `modify_selection`: select eyes and teeth
|
|
114
|
+
- `add_adjustment_layer`: masked brightening or colour change of eyes and teeth
|
|
115
|
+
- `undo_image`: take back a step
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Brighten the eyes a little (the two dark ovals at x 35-45 and x 55-65, y 15-21) without changing the skin.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 100,
|
|
6
|
+
"height": 60,
|
|
7
|
+
"background": [200, 160, 140],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "ellipse", "x": 35, "y": 15, "width": 10, "height": 6, "color": [70, 55, 50] },
|
|
10
|
+
{ "shape": "ellipse", "x": 55, "y": 15, "width": 10, "height": 6, "color": [70, 55, 50] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "the eyes are brighter", "tool": "measure_region", "args": { "x": 38, "y": 17, "width": 4, "height": 2 }, "expect": { "stats": { "luminance": { "mean": { "$min": 75, "$max": 140 } } } } },
|
|
15
|
+
{ "why": "the other eye too", "tool": "measure_region", "args": { "x": 58, "y": 17, "width": 4, "height": 2 }, "expect": { "stats": { "luminance": { "mean": { "$min": 75, "$max": 140 } } } } },
|
|
16
|
+
{ "why": "the skin is unchanged", "tool": "measure_region", "args": { "x": 10, "y": 35, "width": 80, "height": 20 }, "expect": { "meanColor": "#c8a08c" } },
|
|
17
|
+
{ "why": "it is an adjustable, masked layer", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "adjustment", "mask": { "kind": "raster" } } } } }
|
|
18
|
+
],
|
|
19
|
+
"solution": [
|
|
20
|
+
{ "tool": "select_ellipse", "args": { "x": 35, "y": 15, "width": 10, "height": 6 } },
|
|
21
|
+
{ "tool": "select_ellipse", "args": { "x": 55, "y": 15, "width": 10, "height": 6, "mode": "add" } },
|
|
22
|
+
{ "tool": "modify_selection", "args": { "op": "feather", "radius": 1 } },
|
|
23
|
+
{ "tool": "add_adjustment_layer", "args": { "kind": "levels", "name": "Eyes", "params": { "master": { "gamma": 1.6 } } } }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Remove the two dark blemishes on this skin (around x 30, y 30 and x 70, y 22). Don't change the original photo layer.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 100,
|
|
6
|
+
"height": 60,
|
|
7
|
+
"background": [200, 160, 140],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "rect", "x": 29, "y": 29, "width": 3, "height": 3, "color": [120, 80, 70] },
|
|
10
|
+
{ "shape": "rect", "x": 69, "y": 21, "width": 3, "height": 3, "color": [120, 80, 70] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "the first blemish is gone", "tool": "measure_region", "args": { "x": 26, "y": 26, "width": 9, "height": 9 }, "expect": { "stats": { "luminance": { "p1": { "$min": 155 } } } } },
|
|
15
|
+
{ "why": "the second blemish is gone", "tool": "measure_region", "args": { "x": 66, "y": 18, "width": 9, "height": 9 }, "expect": { "stats": { "luminance": { "p1": { "$min": 155 } } } } },
|
|
16
|
+
{ "why": "the retouching is on its own layer above the photo", "tool": "list_layers", "expect": { "layers": { "$length": { "$min": 2 }, "$some": { "id": "background", "mask": null } } } }
|
|
17
|
+
],
|
|
18
|
+
"solution": [
|
|
19
|
+
{ "tool": "add_pixel_layer", "args": { "name": "Retouch" } },
|
|
20
|
+
{ "tool": "spot_heal", "args": { "points": [{ "x": 30, "y": 30 }], "sample": "current-below", "brush": { "size": 10, "hardness": 0.3 } } },
|
|
21
|
+
{ "tool": "spot_heal", "args": { "points": [{ "x": 70, "y": 22 }], "sample": "current-below", "brush": { "size": 10, "hardness": 0.3 } } }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "There's a stray dark hair across the cheek (a thin line at y 45, from x 20 to 60). Remove it without touching the original layer.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 100,
|
|
6
|
+
"height": 60,
|
|
7
|
+
"background": [200, 160, 140],
|
|
8
|
+
"shapes": [{ "shape": "rect", "x": 20, "y": 45, "width": 41, "height": 1, "color": [90, 70, 60] }]
|
|
9
|
+
},
|
|
10
|
+
"checks": [
|
|
11
|
+
{ "why": "the hair is gone along its whole length", "tool": "measure_region", "args": { "x": 18, "y": 43, "width": 45, "height": 5 }, "expect": { "stats": { "luminance": { "p1": { "$min": 155 } } } } },
|
|
12
|
+
{ "why": "on its own layer", "tool": "list_layers", "expect": { "layers": { "$length": { "$min": 2 } } } }
|
|
13
|
+
],
|
|
14
|
+
"solution": [
|
|
15
|
+
{ "tool": "add_pixel_layer", "args": { "name": "Retouch" } },
|
|
16
|
+
{ "tool": "spot_heal", "args": { "points": [{ "x": 20, "y": 45.5 }, { "x": 30, "y": 45.5 }, { "x": 40, "y": 45.5 }, { "x": 50, "y": 45.5 }, { "x": 60, "y": 45.5 }], "sample": "current-below", "brush": { "size": 8, "hardness": 0.3 } } }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pixerate-image-product-photo
|
|
3
|
+
description: The product photo workflow in the Pixerate image editor - cutting a product out of its backdrop, putting it on pure white or a brand colour, grounding it with a soft shadow, keeping its colours accurate, cropping and centring it to marketplace ratios with consistent margins, and checking the result before export. Use when asked to prepare product shots, packshots or e-commerce, marketplace, Amazon, Etsy or catalogue images, put a product on white, remove or replace a product background, add a product shadow, or crop and centre products consistently, or when select_subject, add_layer_mask, add_layer_effect or set_crop are available. Load with pixerate-image-editor, which lists every tool.
|
|
4
|
+
metadata:
|
|
5
|
+
kind: workflow
|
|
6
|
+
reference: pixerate-image-editor
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Preparing a product photo
|
|
10
|
+
|
|
11
|
+
A product shot is a cutout on a controlled background: the product's edge and colour must be
|
|
12
|
+
exact, the background uniform, and every image in a set framed the same way. Build it as layers
|
|
13
|
+
so each part stays adjustable: the photo with a mask hiding its backdrop, a background fill
|
|
14
|
+
layer below it, a shadow (an effect, or its own layer) between, and the crop on top.
|
|
15
|
+
|
|
16
|
+
This skill is the method. `pixerate-image-editor` has the full tool reference, and
|
|
17
|
+
`pixerate-image-selection-masking` the detail of selecting and refining the edge.
|
|
18
|
+
|
|
19
|
+
## Goal and quality bar
|
|
20
|
+
|
|
21
|
+
- The background is uniform: pure white is 255 in every channel (`measure_region` `stats`
|
|
22
|
+
luminance `p1` ≥ 250 and a cast within ±2), or exactly the brand colour asked for.
|
|
23
|
+
- The product's colour is unchanged by the cutout (measure it before and after) and its edge
|
|
24
|
+
has no halo of the old backdrop (`inspect_mask_edge` `haloScore` under 0.35).
|
|
25
|
+
- The product sits on something: a soft shadow below it, unless the user wants it floating.
|
|
26
|
+
- The frame matches the destination: ratio, product centred (or as the set requires), and the
|
|
27
|
+
product filling a consistent share of the frame (about 75-85 % of the longer side for most
|
|
28
|
+
marketplaces).
|
|
29
|
+
- Everything stays editable: masked, not erased; the shadow an effect or a layer.
|
|
30
|
+
|
|
31
|
+
## Inputs to confirm
|
|
32
|
+
|
|
33
|
+
- **Background:** pure white (the default for marketplaces), a colour (hex), or transparent.
|
|
34
|
+
- **Destination ratio and margin:** 1:1 (most marketplaces), 4:5, 3:2, or a size in pixels; how
|
|
35
|
+
much of the frame the product should fill. For a set, the same for every image.
|
|
36
|
+
- **Shadow:** a soft contact or drop shadow (the default), a reflection, or none.
|
|
37
|
+
- **Colour reference:** a swatch or hex the product must match, when colour accuracy matters.
|
|
38
|
+
|
|
39
|
+
## Working method
|
|
40
|
+
|
|
41
|
+
1. **Read and measure the product.** `list_layers`, `get_image_info`, then `measure_region` on a
|
|
42
|
+
flat part of the product: its mean colour is the reference you will protect.
|
|
43
|
+
**Check:** you have the product's bounds and colour.
|
|
44
|
+
2. **Correct the photo first** if it needs it (white balance from the backdrop, exposure): the
|
|
45
|
+
cutout and the measure should be on corrected colours (`pixerate-image-photo-correction`).
|
|
46
|
+
**Check:** the backdrop is neutral before you remove it.
|
|
47
|
+
3. **Select the product.** On a plain backdrop, `magic_wand` on the product (`contiguous: true`)
|
|
48
|
+
or on the backdrop and `invert_selection`; on a busy one, `select_subject` or `select_object`
|
|
49
|
+
with a box, then `refine_selection`. Hard-edged products want a crisp edge (refine radius
|
|
50
|
+
1-3, no feather).
|
|
51
|
+
**Check:** `get_selection` bounds hug the product; `inspect_mask_edge` `issues` is empty.
|
|
52
|
+
4. **Hide the backdrop, do not delete it.** `add_layer_mask` `from: "selection"` on the photo
|
|
53
|
+
layer (or `add_vector_mask` for a geometric product that must stay razor sharp). Then
|
|
54
|
+
`add_pixel_layer` with the background `color` placed `belowId` the photo layer.
|
|
55
|
+
**Check:** `measure_region` on a corner: `p1` ≥ 250 (white) or the brand colour; on the
|
|
56
|
+
product: the colour from step 1.
|
|
57
|
+
5. **Fix the edge colour** if the old backdrop tints it: `set_edge_decontamination`, or
|
|
58
|
+
`modify_selection` `contract` 1 px before masking.
|
|
59
|
+
**Check:** `inspect_mask_edge` with `target: "layer-mask"` shows no halo.
|
|
60
|
+
6. **Ground it.** With the product on its own layer (`layer_via_copy`, or the masked photo),
|
|
61
|
+
`add_layer_effect` `drop-shadow` with `angle` 90 (light from above), a small `distance`
|
|
62
|
+
(3-10 px at web sizes), a `size` larger than the distance and `opacity` 0.25-0.45.
|
|
63
|
+
**Check:** `list_layers` shows the effect; the shadow reads as contact, not as a cutout.
|
|
64
|
+
7. **Frame it.** `set_crop` at the ratio, centred on the product's bounds, with the margin asked.
|
|
65
|
+
**Check:** `get_image_info` `crop`: ratio right, centre within a pixel or two of the
|
|
66
|
+
product's centre, product share of the side as intended.
|
|
67
|
+
8. **Final check.** `measure_region` on the background corners and the product; `render_preview`
|
|
68
|
+
where available. Report the crop, the background colour and the shadow settings so the rest
|
|
69
|
+
of the set can match.
|
|
70
|
+
|
|
71
|
+
## Decision table
|
|
72
|
+
|
|
73
|
+
| Backdrop | Selection |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| Plain, contrasting | `magic_wand` on the product or backdrop (tolerance 10-30) |
|
|
76
|
+
| Plain but close to the product colour | `select_color_range` on the backdrop with low fuzziness, then refine |
|
|
77
|
+
| Busy or textured | `select_subject`, or `select_object` with the product's box |
|
|
78
|
+
| Glass, transparent or reflective product | Select the outline, then lower the mask's `density` where it should show through; ask how it should look |
|
|
79
|
+
| Product with straight edges (a box, a screen) | `add_vector_mask` from the selection, or a path |
|
|
80
|
+
|
|
81
|
+
| Destination | Frame |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| Marketplace main image | 1:1, white, product about 85 % of the frame |
|
|
84
|
+
| Catalogue or site grid | The grid's ratio, the same margin on every image |
|
|
85
|
+
| Social post | 4:5 or 1:1, room for text if the user asks |
|
|
86
|
+
|
|
87
|
+
## Recipes
|
|
88
|
+
|
|
89
|
+
**On white.** `magic_wand` on the product → `inspect_mask_edge` → `add_layer_mask` (`layerId`
|
|
90
|
+
of the photo, `from: "selection"`) → `add_pixel_layer` (`color: "#ffffff"`, `belowId` the photo)
|
|
91
|
+
→ `measure_region` on a corner and on the product.
|
|
92
|
+
|
|
93
|
+
**Centred square crop.** Product bounds `x, y, w, h`; side `S = max(w, h) / share` (share 0.7-0.85,
|
|
94
|
+
no larger than the image allows); `set_crop` with `x + w/2 - S/2`, `y + h/2 - S/2`, `S`, `S`.
|
|
95
|
+
|
|
96
|
+
**Contact shadow.** `layer_via_copy` the selected product (or use the masked layer), then
|
|
97
|
+
`add_layer_effect` `kind: "drop-shadow"` with `settings: { angle: 90, distance: 6, size: 10,
|
|
98
|
+
opacity: 0.4 }`; adjust with `update_layer_effect`.
|
|
99
|
+
|
|
100
|
+
## Pitfalls
|
|
101
|
+
|
|
102
|
+
- **Off-white that looks white.** 245 grey reads as white on screen and fails a marketplace
|
|
103
|
+
check; measure `p1`, not the mean.
|
|
104
|
+
- **Erasing the backdrop.** Deleted pixels cannot be fixed later; mask instead.
|
|
105
|
+
- **Colour shift from the cutout.** A decontamination or a colour adjustment above the product
|
|
106
|
+
changes it; measure the product before and after, and keep corrections below the product.
|
|
107
|
+
- **A halo of the old backdrop** shows on white more than anywhere; inspect the edge and
|
|
108
|
+
decontaminate or contract by a pixel.
|
|
109
|
+
- **A shadow that floats.** A large distance or a sideways angle looks pasted on; keep it short
|
|
110
|
+
and below.
|
|
111
|
+
- **Inconsistent sets.** Each image cropped by eye drifts; compute the crop from the product's
|
|
112
|
+
bounds with the same share for every image.
|
|
113
|
+
|
|
114
|
+
## Tools used
|
|
115
|
+
|
|
116
|
+
- `list_layers`, `get_image_info`, `measure_region`: read the stack, the frame and the colours
|
|
117
|
+
- `magic_wand`, `select_color_range`, `select_subject`, `select_object`, `invert_selection`: select the product
|
|
118
|
+
- `refine_selection`, `modify_selection`, `get_selection`, `inspect_mask_edge`: refine and check the edge
|
|
119
|
+
- `add_layer_mask`, `add_vector_mask`, `set_layer_mask_properties`: hide the backdrop, editably
|
|
120
|
+
- `set_edge_decontamination`: remove the backdrop's colour from the edge
|
|
121
|
+
- `add_pixel_layer`: the background fill below the product
|
|
122
|
+
- `layer_via_copy`: the product on its own layer
|
|
123
|
+
- `add_layer_effect`, `update_layer_effect`: the shadow
|
|
124
|
+
- `set_crop`, `reset_crop`: the frame
|
|
125
|
+
- `render_preview`: look at the result
|
|
126
|
+
- `undo_image`: take back a step
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "The product looks like it's floating. Put it on its own layer and add a soft shadow under it that I can still adjust.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 90,
|
|
7
|
+
"background": [200, 198, 195],
|
|
8
|
+
"shapes": [{ "shape": "rect", "x": 40, "y": 25, "width": 40, "height": 40, "color": [30, 90, 160] }]
|
|
9
|
+
},
|
|
10
|
+
"checks": [
|
|
11
|
+
{ "why": "the product is on its own layer, cut to its shape", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "pixel", "bounds": { "x": { "$min": 39, "$max": 41 }, "y": { "$min": 24, "$max": 26 }, "width": { "$min": 39, "$max": 42 }, "height": { "$min": 39, "$max": 42 } } } } } },
|
|
12
|
+
{ "why": "a live drop shadow on that layer", "tool": "list_layers", "expect": { "layers": { "$some": { "effects": { "$expr": "v && JSON.stringify(v).includes('drop-shadow')" } } } } }
|
|
13
|
+
],
|
|
14
|
+
"solution": [
|
|
15
|
+
{ "tool": "magic_wand", "args": { "x": 60, "y": 45, "tolerance": 20, "contiguous": true } },
|
|
16
|
+
{ "tool": "layer_via_copy", "args": {} },
|
|
17
|
+
{ "tool": "add_layer_effect", "args": { "layerId": { "$read": "list_layers", "path": "activeLayerId" }, "kind": "drop-shadow", "settings": { "distance": 6, "size": 10, "opacity": 0.4, "angle": 90 } } }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Crop to a square for the marketplace listing with the product centred, leaving some breathing room around it (the product should fill roughly two thirds of the frame).",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 90,
|
|
7
|
+
"background": [200, 198, 195],
|
|
8
|
+
"shapes": [{ "shape": "rect", "x": 40, "y": 25, "width": 40, "height": 40, "color": [30, 90, 160] }]
|
|
9
|
+
},
|
|
10
|
+
"checks": [
|
|
11
|
+
{ "why": "a square crop", "tool": "get_image_info", "expect": { "crop": { "$expr": "v && Math.abs(v.width - v.height) <= 1" } } },
|
|
12
|
+
{ "why": "the product is centred (within 2 px)", "tool": "get_image_info", "expect": { "crop": { "$expr": "v && Math.abs(v.x + v.width / 2 - 60) <= 2 && Math.abs(v.y + v.height / 2 - 45) <= 2" } } },
|
|
13
|
+
{ "why": "the product fills 60-85 % of the side", "tool": "get_image_info", "expect": { "crop": { "$expr": "v && 40 / v.width >= 0.6 && 40 / v.width <= 0.85" } } }
|
|
14
|
+
],
|
|
15
|
+
"solution": [
|
|
16
|
+
{ "tool": "get_image_info" },
|
|
17
|
+
{ "tool": "set_crop", "args": { "x": 32, "y": 17, "width": 56, "height": 56 } }
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Put this product on a pure white background for the shop, without changing the product's colour. Keep it editable.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 90,
|
|
7
|
+
"background": [200, 198, 195],
|
|
8
|
+
"shapes": [{ "shape": "rect", "x": 40, "y": 25, "width": 40, "height": 40, "color": [30, 90, 160] }]
|
|
9
|
+
},
|
|
10
|
+
"checks": [
|
|
11
|
+
{ "why": "the background is pure, neutral white", "tool": "measure_region", "args": { "x": 0, "y": 0, "width": 30, "height": 20 }, "expect": { "stats": { "luminance": { "p1": { "$min": 250 } } }, "cast": { "redBlue": { "$min": -2, "$max": 2 } } } },
|
|
12
|
+
{ "why": "the area under the product is white too", "tool": "measure_region", "args": { "x": 0, "y": 72, "width": 120, "height": 18 }, "expect": { "stats": { "luminance": { "p1": { "$min": 250 } } } } },
|
|
13
|
+
{ "why": "the product keeps its colour", "tool": "measure_region", "args": { "x": 45, "y": 30, "width": 30, "height": 30 }, "expect": { "stats": { "red": { "mean": { "$min": 26, "$max": 34 } }, "green": { "mean": { "$min": 86, "$max": 94 } }, "blue": { "mean": { "$min": 156, "$max": 164 } } } } },
|
|
14
|
+
{ "why": "the backdrop is hidden by a mask, not deleted", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "pixel", "mask": { "kind": "raster" } } } } }
|
|
15
|
+
],
|
|
16
|
+
"solution": [
|
|
17
|
+
{ "tool": "magic_wand", "args": { "x": 60, "y": 45, "tolerance": 20, "contiguous": true } },
|
|
18
|
+
{ "tool": "inspect_mask_edge" },
|
|
19
|
+
{ "tool": "add_layer_mask", "args": { "layerId": "background", "from": "selection" } },
|
|
20
|
+
{ "tool": "add_pixel_layer", "args": { "name": "White", "color": "#ffffff", "belowId": "background" } },
|
|
21
|
+
{ "tool": "measure_region", "args": { "x": 0, "y": 0, "width": 30, "height": 20 } }
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"prompt": "Make just the red ball black and white, and keep it adjustable so I can tweak it later. The sign in the corner should stay red.",
|
|
3
|
+
"fixture": {
|
|
4
|
+
"editor": "image",
|
|
5
|
+
"width": 120,
|
|
6
|
+
"height": 80,
|
|
7
|
+
"background": [40, 90, 200],
|
|
8
|
+
"shapes": [
|
|
9
|
+
{ "shape": "ellipse", "x": 40, "y": 20, "width": 40, "height": 40, "color": [210, 40, 40] },
|
|
10
|
+
{ "shape": "rect", "x": 100, "y": 6, "width": 12, "height": 8, "color": [210, 40, 40] }
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"checks": [
|
|
14
|
+
{ "why": "a Black & White adjustment layer, masked to the ball and not the sign", "tool": "list_layers", "expect": { "layers": { "$some": { "type": "adjustment", "kind": "blackWhite", "mask": { "kind": "raster", "defaultValue": 0, "paintedBounds": { "x": { "$min": 38, "$max": 41 }, "y": { "$min": 18, "$max": 21 }, "width": { "$min": 39, "$max": 44 }, "height": { "$min": 39, "$max": 44 } } } } } } },
|
|
15
|
+
{ "why": "the photo's own pixels are not changed", "tool": "list_layers", "expect": { "layers": { "$some": { "id": "background", "type": "pixel", "mask": null } } } }
|
|
16
|
+
],
|
|
17
|
+
"solution": [
|
|
18
|
+
{ "tool": "magic_wand", "args": { "x": 60, "y": 40, "tolerance": 20, "contiguous": true } },
|
|
19
|
+
{ "tool": "get_selection" },
|
|
20
|
+
{ "tool": "add_adjustment_layer", "args": { "kind": "blackWhite" } }
|
|
21
|
+
]
|
|
22
|
+
}
|