Skip to content

feat(raster): auto tone suggestions - #600

Merged
github-actions[bot] merged 1 commit into
devfrom
lead/tone-pack-tp-6
Oct 19, 2026
Merged

github-actions[bot] merged 1 commit into
devfrom
lead/tone-pack-tp-6

Conversation

@prakashUXtech

@prakashUXtech prakashUXtech commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What

AutoTone.suggest(for:) replaces the TP-0 stub in Sources/CoreRaster/Tone/AutoTone.swift. It returns a white balance (temperature, tint) and linear levels (inBlack, inWhite, gamma). The suggestion is applied as whiteBalance and then levels with output 0...1.

  • White balance: computed from the mean straight linear rgb of the brightest 5% of pixels by luma, counting only pixels with no channel at 0 or 1. The mean goes back through whiteBalance's own gain model (gains 2^(T/200), 2^(−M/200), 2^(−T/200)), which gives T = 100·log2(b/r) and M = 100·log2(g²/(r·b)), each clamped to −100...100. Applying it makes that mean grey.
  • Levels: read from the Rec. 709 luma Histogram (4096 bins, linear) of the white-balanced buffer. Black and white points sit at 0.1% and 99.9%. Gamma = ln t / ln 0.5 with t = (median − black) / span, which puts the median at 0.5, and is clamped to 0.1...10.
  • AutoToneTests is turned on (the three .disabled("waits on TP-6") traits are removed and no assertion is changed). The autoToneStubThrowsNotBuilt row is removed from ToneStubTests.swift.
  • Tests/mutations/CoreRaster.AutoTone.json holds the packet's two mutations.

Why

TP-6 of the tone-pack packet (slice TP-map): auto tone suggests levels and a white balance that neutralise a cast.

Packet ambiguities resolved

  • Order: the white balance is computed first, from the input. The levels are then read from the white-balanced buffer (by reusing whiteBalance itself) because the tests apply them in that order.
  • "Non-clipped": a pixel with any channel at or below 0, or at or above 1, is left out of the bright set. This keeps blown highlights and pure black out of the cast estimate.
  • Neutral suggestions (documented in the doc comment; suggest never throws or returns NaN):
    • Temperature and tint are 0 when the bright set is empty (a black, blown-out or fully transparent buffer).
    • Levels are 0, 1, 1 when nothing is counted or when the black and white points are less than 1/256 apart (a flat image).
    • Gamma is 1 when the median falls outside the open span.
  • Quantile precision: each quantile is the mean of the rank read from the bottom and from the top of the histogram, interpolated within its bin. With one-sided bin reads, the ramp's median lands 2/4096 low and gamma misses the 1e-3 bound. The symmetric read puts it at exactly 0.5.
  • Helpers: none of the ColorMix/ColorAdjust helpers (encoded/decoded/checkFinite/mappingStraight) are needed. Auto tone reads linear values and maps no pixels itself. It reuses Histogram, Histogram.luminanceWeights and PixelBuffer.whiteBalance.
  • ToneStubTests.swift: autoToneHoldsItsValues is kept. It tests the struct's memberwise init, which TP-6 does not outgrow. Only the stub's "throws not built" row is removed.

Proof

  • AutoToneTests (castNeutralised, rampIsIdentity, blackSuggestsIdentity) and autoToneHoldsItsValues pass.
  • scripts/mutate --validate: OK, 664 anchors resolve exactly once.
  • scripts/mutate --run --plan CoreRaster.AutoTone: 2/2 caught.
    • Clip 1% instead of 0.1% is caught by rampIsIdentity.
    • Dividing by an empty bright set (guard count > 0 changed to >= 0) is caught by blackSuggestsIdentity.
  • scripts/ci: CI: PASS on this head after rebasing on origin/dev. Earlier attempts failed only on wall-clock budget tests under shared-container load (ThumbnailStoreRecoveryTests.fuzzedIndexThrowsOrRecovers), and the load-guarded retry passed.

Docs MCP: checked Sequence.reversed() (returns an Array) and the SIMD3 min()/max() reductions with apple-docs. The math functions (log, log2) come from Glibc/Darwin, as in ColorAdjust.swift.

Part of #432

Belt-Lead: act-1a150bfbcf7-sfwy
Feature: tone-pack
Slice: TP-map
Mutate: 2/2 CoreRaster.AutoTone
Fast-Check: pass
Head: 1e2457c


@prakashUXtech prakashUXtech added the fast-lane Small green PR, auto-merge when eligible label Oct 19, 2026 — with Claude
prakashUXtech added a commit that referenced this pull request Oct 19, 2026
@github-actions
github-actions Bot merged commit 75ce7b8 into dev Oct 19, 2026
1 check passed
@github-actions github-actions Bot added the fast-lane-merged Merged by the fast lane; review after the fact label Oct 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Fast lane: merged into dev (139 lines, 4 files, Fast-Check marker).

@github-actions github-actions Bot mentioned this pull request Oct 19, 2026
prakashUXtech added a commit to qbtrix/creative-apps that referenced this pull request Oct 19, 2026
…rallel (#3126)

Moves the creative-core pin from bf5426e to 0de5687, the head of core
`main` (green, promoted from dev). The range has the resident-rendering
speedups from qbtrix/creative-core#620: the batched composite commits
without waiting, the resident dirty check is storage identity instead of
a pixel compare, and the Float16 tile upload vectorizes. It also has
qbtrix/creative-core#626, which repoints the mutation anchor #620 moved.

Core range (58 commits):
qbtrix/creative-core@bf5426e...0de5687

Core PRs in the range: qbtrix/creative-core#567 qbtrix/creative-core#568
qbtrix/creative-core#569 qbtrix/creative-core#570
qbtrix/creative-core#571 qbtrix/creative-core#573
qbtrix/creative-core#574 qbtrix/creative-core#575
qbtrix/creative-core#576 qbtrix/creative-core#577
qbtrix/creative-core#578 qbtrix/creative-core#579
qbtrix/creative-core#580 qbtrix/creative-core#581
qbtrix/creative-core#582 qbtrix/creative-core#583
qbtrix/creative-core#584 qbtrix/creative-core#585
qbtrix/creative-core#586 qbtrix/creative-core#587
qbtrix/creative-core#588 qbtrix/creative-core#589
qbtrix/creative-core#590 qbtrix/creative-core#591
qbtrix/creative-core#592 qbtrix/creative-core#593
qbtrix/creative-core#594 qbtrix/creative-core#595
qbtrix/creative-core#596 qbtrix/creative-core#597
qbtrix/creative-core#598 qbtrix/creative-core#599
qbtrix/creative-core#600 qbtrix/creative-core#601
qbtrix/creative-core#602 qbtrix/creative-core#603
qbtrix/creative-core#605 qbtrix/creative-core#606
qbtrix/creative-core#607 qbtrix/creative-core#608
qbtrix/creative-core#609 qbtrix/creative-core#612
qbtrix/creative-core#613 qbtrix/creative-core#614
qbtrix/creative-core#615 qbtrix/creative-core#616
qbtrix/creative-core#617 qbtrix/creative-core#618
qbtrix/creative-core#619 qbtrix/creative-core#620
qbtrix/creative-core#621 qbtrix/creative-core#622
qbtrix/creative-core#623 qbtrix/creative-core#625
qbtrix/creative-core#626

Besides the pin:
- `project.yml` gets the same revision, as in #3023.
- `PhotoWorkspace.residentComposited` syncs a region's layer tiles
across the cores (through `PhotoLayerComposite.inParallel`), as the CPU
route renders its tiles. Steps keep their order. On a brush frame that
crosses two tiles the sync went from about 0.73 ms to 0.39 ms.
- `Tests/render-budgets.md` has the new 8K table and says where a
resident brush frame's time goes.

8K brush frame, release on the M2 Pro (median of three runs; the Mac was
loaded, with another Photo running): CPU 1.0 ms, GPU-resident 1.4 ms
(was 1.7). The GPU route is still behind the CPU. Most of what's left is
the display into the presenter's texture, which waits about 0.55 ms for
the GPU because the presenter draws from its own queue. Full redraw on
the resident route is 31 ms (was 35) and the first picture 166 ms (was
192).

Checked locally on macOS: `scripts/bump-core`'s steps at 0de5687. `swift
build --build-tests` passed. Every `*ModelTests` target passed except
four Library wall-clock budget tests
(`oneEditOnSixtyPhotosStaysInBudget`, `cullOneWithinBudget`,
`beginImportWithinBudget`, `orderRebuildWithinBudget`), which went over
in the parallel run on the loaded Mac and pass when run alone. The brush
perf suites (PhotoBrushDisplayTests, PhotoBrushLatencyTests,
PhotoBrushInkLatencyTests) passed. With the parallel sync on top,
`PhotoModelTests` and `PhotoRenderBackendTests` passed (647 tests), and
`PhotoRenderBudgetTests` ran in release with
`Tests/SuiteUITests/SegmentedTabsTests.swift` set aside, since it
doesn't link in release on SDK 27.
prakashUXtech added a commit that referenced this pull request Oct 19, 2026
@prakashUXtech prakashUXtech mentioned this pull request Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fast-lane Small green PR, auto-merge when eligible fast-lane-merged Merged by the fast lane; review after the fact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant