spec/dark_transform_spec.cr @ trunk
spec
scale = 0.75 fontscale = 1.500 box wid 1.380 ht 0.340 with .nw at (0.035, -0.035) fill 0xCBD5E1 color 0xCBD5E1 box wid 1.380 ht 0.340 with .nw at (0.000, 0.000) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.022) fill 0xffffff color 0xffffff line from (0.140, 0.000) to (0.140, -0.340) color white line from (1.240, 0.000) to (1.240, -0.340) color white text "require 'spec'" color white at (0.690, -0.170)→ /pikchrshow
crystal source
require "spec"
json block
{
"kind": "require",
"name": "spec",
"doc": null,
"children": []
}
../src/gpcr/tokens
scale = 0.75 fontscale = 1.500 box wid 2.353 ht 0.340 with .nw at (0.035, -0.455) fill 0xCBD5E1 color 0xCBD5E1 box wid 2.353 ht 0.340 with .nw at (0.000, -0.420) fill 0x7C83E6 color 0x7C83E6 box wid 0.300 ht 0.065 rad 0.018 at (0.370, -0.443) fill 0xffffff color 0xffffff line from (0.140, -0.420) to (0.140, -0.760) color white line from (2.213, -0.420) to (2.213, -0.760) color white text "require '../src/gpcr/tokens'" color white at (1.177, -0.590)→ /pikchrshow
crystal source
require "../src/gpcr/tokens"
json block
{
"kind": "require",
"name": "../src/gpcr/tokens",
"doc": null,
"children": []
}
private def parse_hex(hex : String) : {Int32, Int32, Int32}
# Skip the "0x" prefix (2 chars), then parse 6 hex digits.
s = hex.starts_with?("0x") || hex.starts_with?("0X") ? hex[2..] : hex
r = s[0, 2].to_i(16)
g = s[2, 2].to_i(16)
b = s[4, 2].to_i(16)
{r, g, b}
end
# Apply the Fossil pikchr dark transform to a single RGB triple.
# Returns the transformed {r, g, b} where each channel is in [0, 127].
private def pikchr_dark_transform(r : Int32, g : Int32, b : Int32) : {Int32, Int32, Int32}
m = r > g ? (r > b ? r : b) : (g > b ? g : b)
tr = (r + 254 - m) // 2
tg = (g + 254 - m) // 2
tb = (b + 254 - m) // 2
# Clamp to [0, 127] (negative values from integer truncation clamp to 0;
# max channel always yields exactly 127).
{tr < 0 ? 0 : tr, tg < 0 ? 0 : tg, tb < 0 ? 0 : tb}
end
describe "Fossil pikchr dark transform — formula invariants" do
it "max channel always transforms to 127" do
# For any colour with max_channel = M, T(M) = (M + 254 - M) / 2 = 127.
[71, 128, 192, 207, 230, 255].each do |m|
tr, tg, tb = pikchr_dark_transform(m, 0, 0)
tr.should eq(127), "T(max=#{m}, g=0, b=0): expected r=127 got #{tr}"
tr2, tg2, tb2 = pikchr_dark_transform(0, m, 0)
tg2.should eq(127), "T(r=0, max=#{m}, b=0): expected g=127 got #{tg2}"
tr3, tg3, tb3 = pikchr_dark_transform(0, 0, m)
tb3.should eq(127), "T(r=0, g=0, max=#{m}): expected b=127 got #{tb3}"
end
end
it "min channel (0) with max=255 transforms to 0 (clamped from -0.5)" do
# (0 + 254 - 255) / 2 = -1/2 = 0 after integer truncation and clamp.
tr, tg, tb = pikchr_dark_transform(255, 0, 0)
tg.should eq(0)
tb.should eq(0)
end
it "output channels are always in [0, 127]" do
samples = [
{255, 171, 25}, {76, 151, 255}, {89, 192, 89},
{255, 68, 68}, {153, 102, 255}, {255, 253, 231},
]
samples.each do |rgb|
r, g, b = pikchr_dark_transform(rgb[0], rgb[1], rgb[2])
r.should be >= 0
r.should be <= 127
g.should be >= 0
g.should be <= 127
b.should be >= 0
b.should be <= 127
end
end
end
describe "GPCR::Tokens dark transform — mode-agnostic palette dual appearances" do
# For each token: assert T(hex) = expected_rgb exactly.
# These expected values are annotated in tokens.cr colour comments and define
# the dual-constraint specification: both the light-mode hex and the dark-mode
# T(hex) must be vivid and category-identifiable.
{
"COLOR_DEFAULT" => {GPCR::Tokens::COLOR_DEFAULT, {37, 75, 127}},
"COLOR_CONTROL" => {GPCR::Tokens::COLOR_CONTROL, {127, 85, 12}},
"COLOR_REPORTER" => {GPCR::Tokens::COLOR_REPORTER, {127, 69, 12}},
"COLOR_BOOLEAN" => {GPCR::Tokens::COLOR_BOOLEAN, {75, 127, 75}},
"COLOR_STOP" => {GPCR::Tokens::COLOR_STOP, {127, 33, 33}},
"COLOR_PROCEDURE" => {GPCR::Tokens::COLOR_PROCEDURE, {76, 50, 127}},
"COLOR_C_INNER" => {GPCR::Tokens::COLOR_C_INNER, {127, 126, 115}},
"COLOR_INPUT" => {GPCR::Tokens::COLOR_INPUT, {127, 73, 127}},
"COLOR_PINS" => {GPCR::Tokens::COLOR_PINS, {81, 121, 127}},
"COLOR_COMM" => {GPCR::Tokens::COLOR_COMM, {49, 127, 115}},
"COLOR_DATA" => {GPCR::Tokens::COLOR_DATA, {127, 50, 63}},
"COLOR_FILE" => {GPCR::Tokens::COLOR_FILE, {74, 77, 127}},
"COLOR_OPS" => {GPCR::Tokens::COLOR_OPS, {89, 127, 47}},
"COLOR_RENDERER" => {GPCR::Tokens::COLOR_RENDERER, {77, 84, 127}},
"COLOR_DRAW" => {GPCR::Tokens::COLOR_DRAW, {63, 127, 121}},
"COLOR_SPEAK" => {GPCR::Tokens::COLOR_SPEAK, {20, 103, 127}},
"COLOR_NODE" => {GPCR::Tokens::COLOR_NODE, {100, 91, 127}},
"COLOR_GALLERY" => {GPCR::Tokens::COLOR_GALLERY, {127, 41, 70}},
"COLOR_SHADOW" => {GPCR::Tokens::COLOR_SHADOW, {116, 121, 127}},
}.each do |name, pair|
hex, expected = pair
er, eg, eb = expected
it "T(#{name} = #{hex}) = rgb(#{er},#{eg},#{eb})" do
r, g, b = parse_hex(hex)
tr, tg, tb = pikchr_dark_transform(r, g, b)
tr.should eq(er), "#{name} dark R: expected #{er} got #{tr}"
tg.should eq(eg), "#{name} dark G: expected #{eg} got #{tg}"
tb.should eq(eb), "#{name} dark B: expected #{eb} got #{tb}"
end
end
it "T(COLOR_SLOT = #FFFFFF) = rgb(127, 127, 127) — gray slot in dark mode" do
r, g, b = parse_hex(GPCR::Tokens::COLOR_SLOT)
tr, tg, tb = pikchr_dark_transform(r, g, b)
tr.should eq(127)
tg.should eq(127)
tb.should eq(127)
end
it "T(COLOR_SLOT_TEXT = #263238) = rgb(118, 124, 127) — cool-gray slot text in dark mode" do
r, g, b = parse_hex(GPCR::Tokens::COLOR_SLOT_TEXT)
tr, tg, tb = pikchr_dark_transform(r, g, b)
tr.should eq(118)
tg.should eq(124)
tb.should eq(127)
end
end