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
|
// All parameters are decorrelated for organic feel
|
||||||
|
|
||||||
// FAR LAYER: Background, slowest movement
|
// FAR LAYER: Background, slowest movement
|
||||||
float farScaleBase = 0.7;
|
float farScaleBase = 0.8;
|
||||||
float farScaleBreath = farScaleBase + sin(time * 0.1) * 0.05 * intensity;
|
float farScaleBreath = farScaleBase + sin(time * 0.1) * 0.03 * intensity;
|
||||||
float farRotation = sin(time * 0.05) * 0.03 * intensity; // ~1.7° max
|
float farRotation = sin(time * 0.05) * 0.02 * intensity;
|
||||||
vec2 farOffset = vec2(
|
vec2 farOffset = vec2(
|
||||||
sin(time * 0.08) * 0.20,
|
sin(time * 0.08) * 0.08,
|
||||||
cos(time * 0.06) * 0.15
|
cos(time * 0.06) * 0.06
|
||||||
) * intensity;
|
) * intensity;
|
||||||
vec3 farLayer = sampleLayer(uv, farOffset, farScaleBreath, farRotation);
|
vec3 farLayer = sampleLayer(uv, farOffset, farScaleBreath, farRotation);
|
||||||
|
|
||||||
// MID LAYER: Middle ground, medium movement
|
// MID LAYER: Middle ground, medium movement
|
||||||
float midScaleBase = 0.9;
|
float midScaleBase = 0.9;
|
||||||
float midScaleBreath = midScaleBase + sin(time * 0.15 + 1.0) * 0.04 * intensity;
|
float midScaleBreath = midScaleBase + sin(time * 0.15 + 1.0) * 0.03 * intensity;
|
||||||
float midRotation = sin(time * 0.08 + 2.0) * 0.025 * intensity; // ~1.4° max
|
float midRotation = sin(time * 0.08 + 2.0) * 0.015 * intensity;
|
||||||
vec2 midOffset = vec2(
|
vec2 midOffset = vec2(
|
||||||
cos(time * 0.12 + 2.0) * 0.14,
|
cos(time * 0.12 + 2.0) * 0.06,
|
||||||
sin(time * 0.10 + 1.0) * 0.12
|
sin(time * 0.10 + 1.0) * 0.05
|
||||||
) * intensity;
|
) * intensity;
|
||||||
vec3 midLayer = sampleLayer(uv, midOffset, midScaleBreath, midRotation);
|
vec3 midLayer = sampleLayer(uv, midOffset, midScaleBreath, midRotation);
|
||||||
|
|
||||||
// NEAR LAYER: Foreground, fastest movement
|
// NEAR LAYER: Foreground, fastest movement
|
||||||
float nearScaleBase = 1.3;
|
float nearScaleBase = 1.15;
|
||||||
float nearScaleBreath = nearScaleBase + sin(time * 0.2 + 2.5) * 0.06 * intensity;
|
float nearScaleBreath = nearScaleBase + sin(time * 0.2 + 2.5) * 0.04 * intensity;
|
||||||
float nearRotation = sin(time * 0.12 + 4.0) * 0.02 * intensity; // ~1.1° max
|
float nearRotation = sin(time * 0.12 + 4.0) * 0.015 * intensity;
|
||||||
vec2 nearOffset = vec2(
|
vec2 nearOffset = vec2(
|
||||||
sin(time * 0.18 + 3.5) * 0.08,
|
sin(time * 0.18 + 3.5) * 0.04,
|
||||||
cos(time * 0.15 + 2.5) * 0.07
|
cos(time * 0.15 + 2.5) * 0.03
|
||||||
) * intensity;
|
) * intensity;
|
||||||
vec3 nearLayer = sampleLayer(uv, nearOffset, nearScaleBreath, nearRotation);
|
vec3 nearLayer = sampleLayer(uv, nearOffset, nearScaleBreath, nearRotation);
|
||||||
|
|
||||||
|
|||||||
@@ -162,18 +162,20 @@ function applyColorAdjustments(
|
|||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return sourceCanvas;
|
if (!ctx) return sourceCanvas;
|
||||||
|
|
||||||
// First pass: strong saturation boost, moderate brightness
|
// First pass: saturation boost, moderate brightness
|
||||||
ctx.filter = 'saturate(2.5) brightness(0.85) contrast(1.1)';
|
ctx.filter = 'saturate(2.0) brightness(0.85) contrast(1.1)';
|
||||||
ctx.drawImage(sourceCanvas, 0, 0);
|
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 imageData = ctx.getImageData(0, 0, size, size);
|
||||||
const data = imageData.data;
|
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 minR = 255, maxR = 0;
|
||||||
let minG = 255, maxG = 0;
|
let minG = 255, maxG = 0;
|
||||||
let minB = 255, maxB = 0;
|
let minB = 255, maxB = 0;
|
||||||
|
let totalBrightness = 0;
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
minR = Math.min(minR, data[i]);
|
minR = Math.min(minR, data[i]);
|
||||||
@@ -182,26 +184,46 @@ function applyColorAdjustments(
|
|||||||
maxG = Math.max(maxG, data[i + 1]);
|
maxG = Math.max(maxG, data[i + 1]);
|
||||||
minB = Math.min(minB, data[i + 2]);
|
minB = Math.min(minB, data[i + 2]);
|
||||||
maxB = Math.max(maxB, 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 targetMin = 30;
|
||||||
const targetMax = 240;
|
const targetMax = 240;
|
||||||
const targetRange = targetMax - targetMin;
|
const targetRange = targetMax - targetMin;
|
||||||
|
|
||||||
// Calculate normalization factors per channel
|
|
||||||
const rangeR = maxR - minR || 1;
|
const rangeR = maxR - minR || 1;
|
||||||
const rangeG = maxG - minG || 1;
|
const rangeG = maxG - minG || 1;
|
||||||
const rangeB = maxB - minB || 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) {
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
// Normalize each channel to target range
|
// Full normalization target
|
||||||
data[i] = targetMin + ((data[i] - minR) / rangeR) * targetRange;
|
const normR = targetMin + ((data[i] - minR) / rangeR) * targetRange;
|
||||||
data[i + 1] = targetMin + ((data[i + 1] - minG) / rangeG) * targetRange;
|
const normG = targetMin + ((data[i + 1] - minG) / rangeG) * targetRange;
|
||||||
data[i + 2] = targetMin + ((data[i + 2] - minB) / rangeB) * 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
|
// 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);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|||||||
Reference in new issue
Block a user