experiment(render): revert blue_mul; shift BLUE palette target to (8,32,220)
CI / test (push) Has been cancelled

Experiment #4 (blue×0.95) made shadow regions appear MORE blue, not
less — reducing source blue still leaves positive blue error after a
BLACK mapping, and the dither spends that error on neighbours,
creating blue dither dots in dark regions.

Reverting blue_mul to 1.0. Experiment #5 takes a different attack on
the same problem: shift the BLUE palette mapping target from the
muted (24, 64, 192) to a more saturated (8, 32, 220). Doesn't change
what the panel displays (the blue ink is fixed); it just makes
Euclidean distance from skin tones to "BLUE" larger in the algorithm's
view, so the dither prefers RED/WHITE/YELLOW for borderline pixels.

Render-compare's BASELINE struct now carries its own frozen palette,
so half-A keeps the original (24,64,192) BLUE target while half-B
pulls the shifted palette from the live pipeline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 14:03:25 -04:00
parent 488fc3d0f4
commit 324f1b2641
2 changed files with 31 additions and 18 deletions
+12 -9
View File
@@ -39,7 +39,8 @@ use Symfony\Component\DependencyInjection\Attribute\Autowire;
)]
final class RenderCompareCommand extends Command
{
private const PALETTE = [
/** Frozen baseline palette — render-baseline-2026-05-14. NEVER touch. */
private const BASELINE_PALETTE = [
0x0 => [26, 26, 26 ],
0x1 => [245, 245, 240],
0x2 => [240, 208, 0 ],
@@ -52,13 +53,14 @@ final class RenderCompareCommand extends Command
private const HALF_H = 800;
private const FULL_H = 1600;
/** Baseline tunables — must NOT track the live pipeline. Frozen reference. */
/** Baseline tunables — frozen reference, never tracks the live pipeline. */
private const BASELINE = [
'saturation' => 130,
'gamma' => 1.0,
'sharpen' => 0.8,
'blur' => 0.0,
'blue_mul' => 1.0,
'palette' => self::BASELINE_PALETTE,
'dither' => \Imagick::DITHERMETHOD_FLOYDSTEINBERG,
];
@@ -96,6 +98,7 @@ final class RenderCompareCommand extends Command
'sharpen' => RenderImageMessageHandler::SHARPEN_SIGMA,
'blur' => RenderImageMessageHandler::BLUR_SIGMA,
'blue_mul' => RenderImageMessageHandler::BLUE_CHANNEL_MUL,
'palette' => RenderImageMessageHandler::PALETTE,
'dither' => RenderImageMessageHandler::DITHER_METHOD,
];
@@ -186,7 +189,7 @@ final class RenderCompareCommand extends Command
$im->cropImage(self::W, self::HALF_H, 0, 0);
$im->setImagePage(self::W, self::HALF_H, 0, 0);
$palStrip = $this->buildPaletteStrip();
$palStrip = $this->buildPaletteStrip($params['palette']);
$im->remapImage($palStrip, $params['dither']);
$palStrip->destroy();
@@ -200,17 +203,17 @@ final class RenderCompareCommand extends Command
for ($i = 0; $i < $total; $i += 2) {
$base0 = $i * 3;
$base1 = $base0 + 3;
$n0 = $this->nearestPalette(ord($blob[$base0]), ord($blob[$base0 + 1]), ord($blob[$base0 + 2]));
$n1 = $this->nearestPalette(ord($blob[$base1]), ord($blob[$base1 + 1]), ord($blob[$base1 + 2]));
$n0 = $this->nearestPalette($params['palette'], ord($blob[$base0]), ord($blob[$base0 + 1]), ord($blob[$base0 + 2]));
$n1 = $this->nearestPalette($params['palette'], ord($blob[$base1]), ord($blob[$base1 + 1]), ord($blob[$base1 + 2]));
$output .= chr(($n0 << 4) | $n1);
}
return $output;
}
private function buildPaletteStrip(): \Imagick
private function buildPaletteStrip(array $palette): \Imagick
{
$palImagick = new \Imagick();
foreach (self::PALETTE as $rgb) {
foreach ($palette as $rgb) {
$hex = sprintf('#%02x%02x%02x', $rgb[0], $rgb[1], $rgb[2]);
$tmp = new \Imagick();
$tmp->newImage(1, 1, new \ImagickPixel($hex));
@@ -224,11 +227,11 @@ final class RenderCompareCommand extends Command
return $strip;
}
private function nearestPalette(int $r, int $g, int $b): int
private function nearestPalette(array $palette, int $r, int $g, int $b): int
{
$best = 0x1;
$bestDist = PHP_INT_MAX;
foreach (self::PALETTE as $index => $rgb) {
foreach ($palette as $index => $rgb) {
$dist = ($r - $rgb[0]) ** 2 + ($g - $rgb[1]) ** 2 + ($b - $rgb[2]) ** 2;
if ($dist < $bestDist) {
$bestDist = $dist;