Add visible-spectrum RGB estimate to blackbody chart
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user