Grade Images
Create reproducible photo color grades while preserving spatial structure, identity, and texture. Let visual reasoning choose intent and parameters; let the bundled deterministic renderer change pixels.
Non-negotiable defaults
- Never overwrite an input image.
- Default to
preservation.mode: strict
.
- Keep preservation strict at every aesthetic intensity. Do not confuse structural safety with a weak color grade.
- Never use a generative image editor for a color-only request.
- Never synthesize or composite a light source, flare, beam, starburst, halo shape, reflection, or painted highlight.
- Treat aesthetic intensity and effect permission as independent. or is not consent for an effect.
- Keep controlled effects absent unless the user explicitly approves source-derived highlight diffusion after being told that it can soften local contrast around existing highlights.
- Never claim to recover detail that is clipped in an encoded JPEG or PNG.
- Render a low-resolution preview before a full batch when the requested look is subjective.
- Save the exact recipe beside every final result.
- Keep texture unchanged by default. Never infer sharpening from a style, reference, intensity, or prior task.
- Allow only schema 1.3 bounded output sharpening after an explicit user request; fail closed on every other texture-changing operation.
- Never add denoise, deblur, restoration, super-resolution, clarity, dehaze, grain, blur, local contrast, synthetic detail, or defect repair.
Read
preservation.md before rendering. Read
recipe-schema.md before authoring or modifying a recipe. Read
intensity-strategies.md before interpreting a subjective look or strength request. Read
wabi-sabi-deep-gray.md when the prompt asks for
,
,
,
, a dark-gray minimalist look, warm accents against desaturated cool surroundings, or an equivalent low-key style. Read
controlled-light-effects.md whenever the prompt or reference may require glow, diffusion, haze, dreaminess, sacred light, or another optical-light quality. Read
quality-gates.md before accepting final outputs. Read
testing.md before creating multi-variant previews or regression cases. For color decisions and parameter interpretation, read
color-science.md.
Read texture-refinement.md whenever the user explicitly requests sharpening, output sharpening, or texture refinement. Do not read a reference image as permission for texture processing.
Read
semantic-routing.md before enabling skin protection in a warm scene, or whenever people presence is uncertain. Run
with visually established people evidence and keep effect cues separate from permission.
Read bounded-correction.md when a preview fails mapped quality or intent gates and a deterministic correction attempt is appropriate. Keep its budget at two rounds and stop for review on unmapped, structural, or unresolved warnings.
Read batch-consistency.md before rendering a reviewed look across a set. Use bounded workers, require deterministic order and one shared look hash, and review correction and output-distribution outliers separately.
Read v4-architecture.md when using the v0.4.0 intent IR, evidence graph, compiled render graph, or closed-loop controller. Keep the versioned graph artifacts beside the recipe and report.
Read
film-color-routing.md whenever the prompt asks for
,
, a negative-film, disposable-camera, film-print, analog, or equivalent film-color look. Run
before selecting or authoring that look.
Read
classic-documentary.md whenever the prompt asks for
,
,
, archival documentary, vivid documentary, or an equivalent documentary-color look. Run
scripts/route_documentary.py
before selecting or authoring that look.
For camera RAW or DNG input, also read raw-processing.md before opening the file.
Choose a mode
- : inspect files and report technical characteristics without changing them.
- : correct exposure, white balance, clipping risk, and tonal balance without adding an aesthetic look.
- : apply a described creative color treatment after technical correction.
- : derive a safe color treatment from a reference image without neural style transfer.
- : normalize each source independently, then apply one shared creative look.
- : render two or more independently authored alternatives and a labeled comparison sheet.
Do not treat identical parameters as perceptual consistency. For a batch, keep per-image correction separate from the shared look.
Preflight
- Locate the skill directory and use its bundled scripts by absolute path.
- Require Python 3.10+, Pillow, and NumPy. For camera RAW or DNG, additionally require the optional dependency. If unavailable, stop and report the missing dependency; do not silently switch to a generative editor or an embedded JPEG preview.
- Accept single-frame, 8-bit JPEG and PNG plus LibRaw-supported camera RAW files. Reject animation and unsupported encoded high-bit-depth inputs instead of silently losing frames or precision. Preserve dimensions and alpha structure. Convert a valid embedded ICC profile or developed RAW image to the sRGB working/output space; preserve supported EXIF data when requested. Record a warning when an embedded profile is invalid, an untagged CMYK JPEG requires an uncertain default conversion, maker-specific RAW metadata cannot be copied into the derivative, or RAW white balance falls back from valid camera metadata.
- Determine whether the user supplied a reference image, a named look, or only a correction request. Separately determine aesthetic intensity: , , , or .
- For a subjective look with no clear intensity cue, ask the user to choose conservative, standard, bold, or transformative. If no answer is available and continuing is appropriate, use standard and state that choice. Never silently default a subjective look to conservative.
- Separately decide whether color and tone can satisfy the request. If controlled diffusion may be needed, ask for effect permission; never infer it from intensity or prior tasks. Treat texture permission separately as well: only the user's current explicit sharpening request may enable schema 1.3 refinement.
- Before a subjective render, state a concise treatment contract: magnitude, tonal and color axes, direction, scope, must-be-obvious outcome, protected exceptions, and effect status. Use this to expose misunderstandings; do not request confirmation unless a choice or effect permission is genuinely needed.
Interpret intent
-
Treat preservation and aesthetic intensity as independent controls. Keep geometry, identity, facial features, objects, and texture protected even for a bold or transformative grade.
-
Treat
as a style, not a synonym for low saturation or low intensity. Preserve vivid signature colors unless the preview shows clipping, hue breakage, or fluorescence.
-
Treat
as tonal density and controlled color separation, not automatic fading or desaturation.
-
Treat generic
,
,
, or
as an ambiguous color-and-tone family. Route it to classic-negative, daylight-disposable, and cinematic-print color-only alternatives instead of silently choosing one averaged look. Do not infer grain, vignette, blur, light leak, distortion, sharpening, crop, or another texture or geometry effect from film language.
-
Treat
,
, and equivalent dark-overlay language as a perceptual target for low exposure and deep-gray tonal placement. Do not composite a literal overlay or flatten recoverable texture.
-
Treat an explicit sharpening or output-sharpening request as eligible for schema 1.3
only after source inspection confirms that the image is already focused and does not need repair. Reject clarity, dehaze, microcontrast, generic texture enhancement, denoise, deblur, and restoration. Never claim recovered detail.
-
Map
,
,
,
,
, or
to conservative. Map
,
,
,
,
,
,
, or
to bold.
-
Map
,
,
,
,
,
,
, or
to transformative. Explicitly choosing transformative is sufficient magnitude instruction; do not weaken it back to bold.
-
Treat
,
,
,
,
,
,
,
,
,
, and equivalent phrases as possible effect cues, not effect consent. A mood word alone does not require an effect when color and tone can satisfy the treatment contract.
-
Translate object-language color requests into honest source-color ranges after visual inspection. A request such as
may use schema 1.2 hue, saturation, and luminance gates, but never claim semantic flower segmentation. Disclose when visually similar source colors may move together.
-
When the user changes direction, rebuild the creative look from the new intent. Do not retain incompatible assumptions from the previous recipe.
-
Treat generic
,
,
, or
as a two-family color-and-tone request. Route it to vivid-documentary and archival-documentary alternatives unless a source-appropriate scene cue selects one. Route explicit grassland, pastoral, earth, or ochre documentary language to the source-gated earth-documentary subprofile. Do not infer grain, dust, softness, vignette, clarity, dehaze, or sharpening from documentary or archival language. Use
documentary-vivid-refine.json
only when sharpening is also explicit.
Workflow
1. Analyze
Run:
text
python scripts/analyze.py INPUT [INPUT ...] --output analysis.json
Use the measurements as evidence, not as an automatic aesthetic verdict. Inspect the images visually as well. Identify the subject, important skin tones, lighting conditions, likely neutral areas, and requested mood.
When skin protection may be useful, build an auditable route record with
. Pass
only after visual inspection establishes that people are present; otherwise pass
or
. Treat its decision as recipe-routing evidence, not semantic segmentation.
For RAW, treat demosaicing, white-balance source routing, output color conversion, and automatic brightness as part of the recorded source-development contract. Keep automatic white balance and brightness disabled, review any metadata fallback warning, and inspect the neutral baseline before adding a creative look.
For film-language prompts, run:
text
python scripts/route_film.py "PROMPT" [--output route.json]
Use
as a source-analysis-dependent baseline. For
, render the returned recipes together so the user can choose a direction. Report every
item as unsupported under strict preservation; never enable another effect as a substitute.
For documentary-language prompts, run:
text
python scripts/route_documentary.py "PROMPT" [--output route.json]
Use
only after confirming that the source supports the returned tonal direction. For
, compare the vivid and archival recipes together. Treat their sample-derived measurements as routing evidence, not targets that override clipping, skin, or gamut safety. Report unsupported effect requests separately.
Before the first render from a new camera family or LibRaw version, run
python scripts/raw_check.py INPUT --output raw-check.json --full-decode
. Require deterministic preview pixels, unchanged source hash, recorded development provenance, and a successful full decode. Add
only when fallback from as-shot metadata is unacceptable.
2. Author a recipe
Start from
assets/recipes/neutral-correction.json
,
,
,
, the shadow-safe
, schema 1.2
for a low-key asphalt-gray base with restrained cool colors and warmer focal accents,
classic-negative-standard.json
,
daylight-disposable-standard.json
,
cinematic-print-standard.json
, or schema 1.2
transformative-cool-violet.json
when a major warm-to-violet change is explicitly requested. After explicit effect consent only,
soft-dream-source-glow.json
is available as a source-derived diffusion starting point. After an explicit sharpening request only, schema 1.3
output-refine-standard.json
is the neutral texture baseline. Keep technical correction, creative look, effects, and texture permission separate. Record the chosen style, intensity, selection method, effect permission, and texture permission under
.
For classic-documentary work, start with
documentary-vivid-standard.json
,
documentary-archive-standard.json
, or the source-gated
documentary-earth-standard.json
after routing; keep source-specific correction separate from the shared look. Use schema 1.3
documentary-vivid-refine.json
only when the user explicitly requested sharpening and the source passes texture preflight.
Use a preset fast path before authoring from scratch. When the user explicitly asks for a transformative warm/cream blossom or foliage scene to become pale violet, with yellow/orange suppressed, a cold restrained night mood, and no effects, first verify that the source actually contains the named warm/cream family and is not already a cool-violet result. Then copy
transformative-cool-violet.json
unchanged as the first color baseline. Do not reapply it to an already transformed source merely because the prompt repeats the target style. Do not replace it with a newly invented recipe, enable skin protection, or raise exposure merely to increase a global difference score. Render it once, inspect the result, and adjust only a visibly mismatched conceptual axis. If the prompt conflicts with the preset or the source family is absent, author a new recipe normally.
Do not delete or neutralize a template's documented
. A nonzero near-black allowance is valid only for schema 1.2 or 1.3 transformative work with an explicit darker tonal hierarchy and a written reason. It changes one warning baseline only; all structural, clipping, edge, texture, effect, and content gates remain strict.
Use only one global chroma control:
,
, or schema 1.1/1.2
. Prefer vibrance when a strong look must increase muted colors without driving already-saturated colors into clipping. Stacking global chroma controls fails validation. Schema 1.2
may additionally remap named source color families because each range records its own bounded selection and purpose.
Validate before rendering:
text
python scripts/grade.py validate RECIPE.json
Unknown fields and forbidden operations must fail validation.
For a reference image, derive a recipe at the selected strength:
text
python scripts/match.py SOURCE REFERENCE --template assets/recipes/neutral-correction.json --strength 0.65 --output match.json [--disable-skin-protection]
Use approximately
,
, or
for conservative, standard, or bold. Transformative reference work may search
, then add contract-specific hue ranges when global distribution matching cannot express the requested selective change. Inspect the generated diagnostics and preview the recipe. A derived match is a starting point, not an aesthetic ground truth.
Reference matching automatically chooses vibrance instead of global saturation when muted and already-saturated regions require materially different chroma ratios. Continue iterating until
passes; do not stop merely because structural preservation passes.
For bidirectional references, inspect the generated shadow-span, highlight-span, and saturation-distribution ratios. Rich-to-soft and soft-to-rich must not collapse into similar recipes.
3. Render a preview
For v0.4.0 graph execution, compile the interpreted request, scene evidence, and reviewed recipe into auditable artifacts, then execute the compiled graph:
text
python scripts/v4.py INPUT "PROMPT" --recipe RECIPE.json \
--people-evidence present|absent|unknown --output-dir previews --max-size 1200
Accept an automatically corrected output only when the v4 manifest and nested controller manifest both report
. The compatibility backend must retain the legacy recipe's pixel math after explicit routing decisions are applied.
For one subjective treatment, prefer the single-pass preview command. It decodes and renders once, creates the labeled original/result sheet, writes the recipe copy and quality report, and records stage timings:
text
python scripts/preview.py INPUT --recipe RECIPE.json --output-dir previews --max-size 1200 [--reference REFERENCE]
Use the lower-level renderer only when an individual image without the preview bundle is specifically needed:
text
python scripts/grade.py render INPUT --recipe RECIPE.json --output PREVIEW.png --max-size 1600
Label the preview with the selected style and intensity, then show it beside the original. For subjective or batch work, obtain confirmation before full-resolution rendering. A standard preview should be clearly different at fit-to-screen size; a bold preview should be unmistakably different; a transformative preview should clearly satisfy its must-be-obvious contract even when viewed alone. A decisive localized hue-family change may satisfy transformative even when the global mean delta is modest. Never alter exposure, contrast, or unrelated colors solely to clear a global-difference threshold. If the requested axis is still weak, strengthen that axis or explain the clipping/gamut limit. Adjust one conceptual dimension at a time.
Keep preview iteration efficient. Analyze once, select or author one baseline recipe, and run
once. Do not write an ad hoc contact-sheet script. Do not repeat render/compare cycles when the report passes and visual inspection satisfies the treatment contract. When revision is necessary, change one evidenced axis and rerun once; after two unsuccessful revisions, present alternatives or explain the limit instead of meter-chasing.
For a mapped combination such as shadow crush plus an underpowered selected intensity,
may evaluate its fixed bounded bracket in the second round. Supply the scene route when it changed skin protection. Accept the result only when the manifest status is
and the final report contains no warnings.
When intensity or interpretation is unresolved, render meaningful alternatives together:
text
python scripts/variants.py INPUT --variant conservative=A.json --variant standard=B.json --variant bold=C.json --variant transformative=D.json --output-dir previews --max-size 1200
Show the labeled comparison sheet, retain every individual preview and recipe, and ask the user to choose a direction. When bold is already explicit, compare distinct bold treatments instead of repeating the three intensity levels.
When one reviewed base recipe defines the intended style, derive its four creative strengths automatically:
text
python scripts/variants.py INPUT --base-recipe BASE.json --output-dir previews --max-size 1200
Confirm that automatic derivation left correction, protection, and effect permission unchanged.
When source glow is enabled, also label the preview
source-derived glow: approved
, inspect it at 100%, and confirm that every bright region traces back to an existing source highlight. Remove the effect if it produces a detached bright shape or obscures important text, facial features, or texture.
When output sharpening is enabled, label the preview
source-derived output sharpen: explicitly requested
, inspect it at 100%, and review skin, text, foliage, wires, rooflines, JPEG blocks, and dark noise. Reduce only the sharpen amount when a texture warning occurs. Remove texture processing if the source is defocused, motion-blurred, heavily compressed, or would require repair.
4. Render final images
text
python scripts/grade.py render INPUT --recipe RECIPE.json --output OUTPUT.png
Prefer PNG for a lossless graded master. When the user needs JPEG, encode once from the original decode at quality 95 or higher. Never chain intermediate JPEG files.
For RAW input, describe PNG as a lossless derivative of the recorded development, not as a lossless copy of the sensor mosaic. Preserve the untouched RAW file and keep the development settings in the manifest.
Review
raw_development.white_balance_source
before accepting RAW output.
is the normal as-shot route;
means camera coefficients were missing, invalid, or suspiciously identity-like;
means neither metadata source was usable and requires neutral-color review. Use
raw_check.py --require-camera-wb
when a workflow must reject either fallback. Review
before attributing a slow run to RAW decoding: full-resolution PNG encoding can dominate the total on large files.
For a batch, create one recipe per image containing its correction section and the same shared
section. Keep the recipe files with the outputs.
Generate those per-image recipes with:
text
python scripts/batch.py INPUT [INPUT ...] --look LOOK.json --strength 0.8 --output-dir recipes [--disable-skin-protection]
Review
for correction outliers before rendering. Render each image from its original file with its corresponding recipe.
For a reviewed batch,
can derive and render those recipes with bounded parallelism. Require
and
output_order_deterministic
, then inspect every outlier reason before accepting the set.
Disable skin protection after visual inspection when the scene contains no people or the heuristic mask is overbroad. Keep it enabled only when the preview confirms useful coverage.
5. Verify
Run:
text
python scripts/compare.py INPUT OUTPUT --recipe RECIPE.json --output report.json [--reference REFERENCE]
Treat a failed hard gate as a failed deliverable. Report warnings about new clipping, JPEG loss, aggressive skin shifts, or uncertain masks. Do not describe a warning as a pass.
For schema 1.3, also review
texture.mean_gradient_ratio
,
texture.p90_gradient_ratio
, and
texture.highpass_std_ratio
. A numeric pass does not excuse visible halos, doubled contours, amplified artifacts, or roughened skin.
Review the
section. For standard, bold, or transformative strategies, do not accept a low-visual-delta warning merely because structural gates passed. For transformative hue-range work, a high P95 localized delta can satisfy the contract even when the global mean is modest; verify the named color family and inspect boundaries at 100%. Revise the aesthetic recipe only when the requested axis remains visibly underpowered or a documented quality problem exists.
When a reference is supplied, review
by tone, chroma, global color, and tonal-zone color. A preservation pass is not an aesthetic match. Review
, adjust one conceptual dimension at a time, and rerun comparison. Do not reduce intentional clipping or saturation merely because it is strong when the reference contains the same characteristic within tolerance.
Match a reference safely
Analyze source and reference separately. Match these components independently:
- shadow, midtone, and highlight luminance distribution;
- overall white-balance direction;
- muted, median, and already-saturated chroma intensity;
- shadow, midtone, and highlight color tendencies;
- protected skin response.
Convert the comparison into an ordinary recipe and render it through the same strict engine. Do not copy pixels, synthesize content, or use neural style transfer. Expose the same conservative, standard, bold, or transformative strength choice used for other subjective looks.
For local release regression, run
python scripts/regress.py MANIFEST.json --output-dir RESULTS
with private images kept outside the repository. Require both directions to pass independently.
After the user chooses an intensity, search bounded reference strengths inside that choice:
text
python scripts/search_match.py SOURCE REFERENCE --template TEMPLATE.json --intensity bold --output-dir match-search
Show all candidates and the selected result. Accept the automatic selection only when it passes structural, clipping, near-black, and saturation gates. Do not cross intensity boundaries to improve the match score.
Output contract
For each completed task, provide:
- graded image files;
- the exact versioned JSON recipe;
- a machine-readable quality report;
- a labeled comparison sheet when alternatives were requested;
- a concise human summary of corrections, creative choices, and warnings.
Keep the original files untouched. State clearly when an output is a lossy JPEG derivative rather than the lossless master.