Add visible-spectrum RGB estimate to blackbody chart

This commit is contained in:
2026-06-21 10:14:37 +02:00
parent 8810139e9d
commit 320d257fbb
+113 -2
View File
@@ -135,6 +135,17 @@ require __DIR__ . '/header.php';
font-weight: 600;
}
.bb-color-chip {
display: inline-block;
width: 1.1rem;
height: 1.1rem;
border-radius: 999px;
border: 1px solid rgba(255,255,255,0.2);
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.22);
background: rgba(255,255,255,0.08);
flex-shrink: 0;
}
.bb-stat-sub {
color: var(--text-dim);
font-size: 0.8rem;
@@ -256,8 +267,9 @@ require __DIR__ . '/header.php';
</div>
<div class="bb-stat">
<span class="bb-stat-label">Relative Farbwirkung</span>
<span class="bb-color-chip" id="bbColorChip" aria-hidden="true"></span>
<span class="bb-stat-value" id="bbColorLabel">–</span>
<span class="bb-stat-sub">grob aus dem Peak-Bereich</span>
<span class="bb-stat-sub" id="bbColorMeta">aus dem sichtbaren Spektrum</span>
</div>
</div>
@@ -302,6 +314,8 @@ require __DIR__ . '/header.php';
const peakRegionOutput = document.getElementById('bbPeakRegion');
const fluxOutput = document.getElementById('bbFlux');
const colorLabelOutput = document.getElementById('bbColorLabel');
const colorChipOutput = document.getElementById('bbColorChip');
const colorMetaOutput = document.getElementById('bbColorMeta');
const canvas = document.getElementById('bb-canvas');
const ctx = canvas.getContext('2d');
@@ -399,6 +413,100 @@ require __DIR__ . '/header.php';
return 'rot bis infrarotdominiert';
}
function wavelengthToLinearRgb(wavelengthNm) {
let red = 0;
let green = 0;
let blue = 0;
if (wavelengthNm >= 380 && wavelengthNm < 440) {
red = -(wavelengthNm - 440) / 60;
blue = 1;
} else if (wavelengthNm < 490) {
green = (wavelengthNm - 440) / 50;
blue = 1;
} else if (wavelengthNm < 510) {
green = 1;
blue = -(wavelengthNm - 510) / 20;
} else if (wavelengthNm < 580) {
red = (wavelengthNm - 510) / 70;
green = 1;
} else if (wavelengthNm < 645) {
red = 1;
green = -(wavelengthNm - 645) / 65;
} else if (wavelengthNm <= 780) {
red = 1;
}
let factor = 0;
if (wavelengthNm >= 380 && wavelengthNm < 420) {
factor = 0.3 + 0.7 * ((wavelengthNm - 380) / 40);
} else if (wavelengthNm <= 700) {
factor = 1;
} else if (wavelengthNm <= 780) {
factor = 0.3 + 0.7 * ((780 - wavelengthNm) / 80);
}
return {
r: red * factor,
g: green * factor,
b: blue * factor
};
}
function toSrgbChannel(value) {
const clamped = clamp(value, 0, 1);
if (clamped <= 0.0031308) {
return 12.92 * clamped;
}
return 1.055 * Math.pow(clamped, 1 / 2.4) - 0.055;
}
function estimateVisibleRgb(temperature) {
let red = 0;
let green = 0;
let blue = 0;
for (let wavelengthNm = 380; wavelengthNm <= 780; wavelengthNm += 5) {
const radiance = planckSpectralRadiance(wavelengthNm * 1e-9, temperature);
const rgb = wavelengthToLinearRgb(wavelengthNm);
red += radiance * rgb.r;
green += radiance * rgb.g;
blue += radiance * rgb.b;
}
const maxChannel = Math.max(red, green, blue, 1e-12);
return {
r: Math.round(toSrgbChannel(red / maxChannel) * 255),
g: Math.round(toSrgbChannel(green / maxChannel) * 255),
b: Math.round(toSrgbChannel(blue / maxChannel) * 255)
};
}
function rgbToHex(rgb) {
return '#' + [rgb.r, rgb.g, rgb.b].map(function (channel) {
return channel.toString(16).padStart(2, '0').toUpperCase();
}).join('');
}
function describeVisibleRgb(rgb) {
if (rgb.r > 245 && rgb.g > 235 && rgb.b > 235) {
return 'neutralweiß';
}
if (rgb.r > 245 && rgb.g > 220 && rgb.b < 205) {
return 'warmweiß';
}
if (rgb.b > rgb.r + 25 && rgb.b > rgb.g + 10) {
return 'kühlbläulich';
}
if (rgb.r > rgb.b + 25 && rgb.g > rgb.b) {
return 'warmgelblich';
}
if (rgb.r > 235 && rgb.b > 235) {
return 'weiß mit Magenta-Stich';
}
return 'leicht getöntes Weiß';
}
function drawVisibleBand(plotLeft, plotTop, plotWidth, plotHeight, xMin, xMax) {
const visibleStart = Math.max(380, xMin);
const visibleEnd = Math.min(750, xMax);
@@ -431,11 +539,14 @@ require __DIR__ . '/header.php';
const peakNm = WIEN_DISPLACEMENT_NM_K / temperature;
const totalFlux = STEFAN_BOLTZMANN * Math.pow(temperature, 4);
const visibleRgb = estimateVisibleRgb(temperature);
peakNmOutput.textContent = formatGermanNumber(peakNm, peakNm < 100 ? 1 : 0) + ' nm';
peakRegionOutput.textContent = describePeakRegion(peakNm);
fluxOutput.textContent = formatScientific(totalFlux) + ' W/m²';
colorLabelOutput.textContent = describeColor(peakNm);
colorLabelOutput.textContent = describeVisibleRgb(visibleRgb);
colorChipOutput.style.backgroundColor = 'rgb(' + visibleRgb.r + ', ' + visibleRgb.g + ', ' + visibleRgb.b + ')';
colorMetaOutput.textContent = rgbToHex(visibleRgb) + ' | Peak: ' + describeColor(peakNm);
const ratio = Math.min(window.devicePixelRatio || 1, 2);
const width = canvas.clientWidth;