fix: preserve dark artwork mood in ambient background
Two issues caused visible dark rectangles on simple/dark artwork: 1. Texture normalization always mapped to [30-240] regardless of source brightness. For dark artwork (Metallica), this amplified noise 5x and turned a dark album into a light gray background. Now uses brightness-aware blending: dark artwork gets gentle lift (preserves mood), colorful artwork gets full normalization. 2. Shader layer offsets were too large (up to 20% of texture), causing visible layer boundaries where each sampling layer showed different parts of the amplified texture. Reduced offsets by ~60% and tightened scale ranges for smoother parallax.
This commit is contained in:
2 files changed
+48
-26
No files matched your search
@@ -66,32 +66,32 @@ void main() {
|
||||
// All parameters are decorrelated for organic feel
|
||||
|
||||
// FAR LAYER: Background, slowest movement
|
||||
float farScaleBase = 0.7;
|
||||
float farScaleBreath = farScaleBase + sin(time * 0.1) * 0.05 * intensity;
|
||||
float farRotation = sin(time * 0.05) * 0.03 * intensity; // ~1.7° max
|
||||
float farScaleBase = 0.8;
|
||||
float farScaleBreath = farScaleBase + sin(time * 0.1) * 0.03 * intensity;
|
||||
float farRotation = sin(time * 0.05) * 0.02 * intensity;
|
||||
vec2 farOffset = vec2(
|
||||
sin(time * 0.08) * 0.20,
|
||||
cos(time * 0.06) * 0.15
|
||||
sin(time * 0.08) * 0.08,
|
||||
cos(time * 0.06) * 0.06
|
||||
) * intensity;
|
||||
vec3 farLayer = sampleLayer(uv, farOffset, farScaleBreath, farRotation);
|
||||
|
||||
// MID LAYER: Middle ground, medium movement
|
||||
float midScaleBase = 0.9;
|
||||
float midScaleBreath = midScaleBase + sin(time * 0.15 + 1.0) * 0.04 * intensity;
|
||||
float midRotation = sin(time * 0.08 + 2.0) * 0.025 * intensity; // ~1.4° max
|
||||
float midScaleBreath = midScaleBase + sin(time * 0.15 + 1.0) * 0.03 * intensity;
|
||||
float midRotation = sin(time * 0.08 + 2.0) * 0.015 * intensity;
|
||||
vec2 midOffset = vec2(
|
||||
cos(time * 0.12 + 2.0) * 0.14,
|
||||
sin(time * 0.10 + 1.0) * 0.12
|
||||
cos(time * 0.12 + 2.0) * 0.06,
|
||||
sin(time * 0.10 + 1.0) * 0.05
|
||||
) * intensity;
|
||||
vec3 midLayer = sampleLayer(uv, midOffset, midScaleBreath, midRotation);
|
||||
|
||||
// NEAR LAYER: Foreground, fastest movement
|
||||
float nearScaleBase = 1.3;
|
||||
float nearScaleBreath = nearScaleBase + sin(time * 0.2 + 2.5) * 0.06 * intensity;
|
||||
float nearRotation = sin(time * 0.12 + 4.0) * 0.02 * intensity; // ~1.1° max
|
||||
float nearScaleBase = 1.15;
|
||||
float nearScaleBreath = nearScaleBase + sin(time * 0.2 + 2.5) * 0.04 * intensity;
|
||||
float nearRotation = sin(time * 0.12 + 4.0) * 0.015 * intensity;
|
||||
vec2 nearOffset = vec2(
|
||||
sin(time * 0.18 + 3.5) * 0.08,
|
||||
cos(time * 0.15 + 2.5) * 0.07
|
||||
sin(time * 0.18 + 3.5) * 0.04,
|
||||
cos(time * 0.15 + 2.5) * 0.03
|
||||
) * intensity;
|
||||
vec3 nearLayer = sampleLayer(uv, nearOffset, nearScaleBreath, nearRotation);
|
||||
|
||||
|
||||
@@ -162,18 +162,20 @@ function applyColorAdjustments(
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return sourceCanvas;
|
||||
|
||||
// First pass: strong saturation boost, moderate brightness
|
||||
ctx.filter = 'saturate(2.5) brightness(0.85) contrast(1.1)';
|
||||
// First pass: saturation boost, moderate brightness
|
||||
ctx.filter = 'saturate(2.0) brightness(0.85) contrast(1.1)';
|
||||
ctx.drawImage(sourceCanvas, 0, 0);
|
||||
|
||||
// Second pass: normalize color range and lift shadows
|
||||
// Second pass: brightness-aware normalization
|
||||
// Dark artwork stays dark (preserves mood), colorful artwork gets full expansion
|
||||
const imageData = ctx.getImageData(0, 0, size, size);
|
||||
const data = imageData.data;
|
||||
|
||||
// Find actual min/max for each channel to normalize
|
||||
// Measure source: per-channel range + average brightness
|
||||
let minR = 255, maxR = 0;
|
||||
let minG = 255, maxG = 0;
|
||||
let minB = 255, maxB = 0;
|
||||
let totalBrightness = 0;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
minR = Math.min(minR, data[i]);
|
||||
@@ -182,26 +184,46 @@ function applyColorAdjustments(
|
||||
maxG = Math.max(maxG, data[i + 1]);
|
||||
minB = Math.min(minB, data[i + 2]);
|
||||
maxB = Math.max(maxB, data[i + 2]);
|
||||
totalBrightness += (data[i] + data[i + 1] + data[i + 2]) / 3;
|
||||
}
|
||||
|
||||
// Target range: 30-240 (preserve some headroom, lift shadows)
|
||||
const pixelCount = size * size;
|
||||
const avgBrightness = totalBrightness / pixelCount;
|
||||
|
||||
// Normalization strength scales with source brightness:
|
||||
// Very dark (avg < 20): ~0.15 strength (mostly gentle lift, preserve darkness)
|
||||
// Medium (avg ~80): ~1.0 strength (full normalization)
|
||||
// Already bright: 1.0 (full normalization)
|
||||
const normStrength = Math.min(1.0, avgBrightness / 80);
|
||||
|
||||
const targetMin = 30;
|
||||
const targetMax = 240;
|
||||
const targetRange = targetMax - targetMin;
|
||||
|
||||
// Calculate normalization factors per channel
|
||||
const rangeR = maxR - minR || 1;
|
||||
const rangeG = maxG - minG || 1;
|
||||
const rangeB = maxB - minB || 1;
|
||||
|
||||
// Gentle lift factor for dark artwork (1.5x brightness)
|
||||
const liftFactor = 1.5;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
// Normalize each channel to target range
|
||||
data[i] = targetMin + ((data[i] - minR) / rangeR) * targetRange;
|
||||
data[i + 1] = targetMin + ((data[i + 1] - minG) / rangeG) * targetRange;
|
||||
data[i + 2] = targetMin + ((data[i + 2] - minB) / rangeB) * targetRange;
|
||||
// Full normalization target
|
||||
const normR = targetMin + ((data[i] - minR) / rangeR) * targetRange;
|
||||
const normG = targetMin + ((data[i + 1] - minG) / rangeG) * targetRange;
|
||||
const normB = targetMin + ((data[i + 2] - minB) / rangeB) * targetRange;
|
||||
|
||||
// Gentle lift target (preserves relative color, just brighter)
|
||||
const liftR = Math.min(255, data[i] * liftFactor);
|
||||
const liftG = Math.min(255, data[i + 1] * liftFactor);
|
||||
const liftB = Math.min(255, data[i + 2] * liftFactor);
|
||||
|
||||
// Blend: dark artwork → mostly lift, bright artwork → full normalization
|
||||
data[i] = liftR + (normR - liftR) * normStrength;
|
||||
data[i + 1] = liftG + (normG - liftG) * normStrength;
|
||||
data[i + 2] = liftB + (normB - liftB) * normStrength;
|
||||
|
||||
// Boost reds slightly for warmth
|
||||
data[i] = Math.min(255, data[i] * 1.12);
|
||||
data[i] = Math.min(255, data[i] * 1.08);
|
||||
}
|
||||
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
Reference in new issue
Block a user