diff --git a/public/blackbody_radiation.php b/public/blackbody_radiation.php index de8e774..52e8855 100644 --- a/public/blackbody_radiation.php +++ b/public/blackbody_radiation.php @@ -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';
Relative Farbwirkung + – - grob aus dem Peak-Bereich + aus dem sichtbaren Spektrum
@@ -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;