Compare commits

...

2 Commits

Author SHA1 Message Date
96af50ee6b new color palette 2025-07-05 22:07:46 +00:00
afd0fd84f6 backtrack 2025-07-05 21:55:50 +00:00
3 changed files with 133 additions and 153 deletions

View File

@ -795,6 +795,12 @@
<option value="rgb">RGB Split</option> <option value="rgb">RGB Split</option>
<option value="hsv">HSV</option> <option value="hsv">HSV</option>
<option value="rainbow">Rainbow</option> <option value="rainbow">Rainbow</option>
<option value="thermal">Thermal</option>
<option value="neon">Neon</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="vaporwave">Vaporwave</option>
<option value="dithered">Dithered</option>
<option value="palette">Palette</option>
</select> </select>
</label> </label>
<label style="color: #ccc; font-size: 12px; margin-right: 10px;"> <label style="color: #ccc; font-size: 12px; margin-right: 10px;">
@ -853,6 +859,12 @@
<option value="rgb">RGB Split</option> <option value="rgb">RGB Split</option>
<option value="hsv">HSV</option> <option value="hsv">HSV</option>
<option value="rainbow">Rainbow</option> <option value="rainbow">Rainbow</option>
<option value="thermal">Thermal</option>
<option value="neon">Neon</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="vaporwave">Vaporwave</option>
<option value="dithered">Dithered</option>
<option value="palette">Palette</option>
</select> </select>
</div> </div>
<div class="mobile-menu-item"> <div class="mobile-menu-item">

View File

@ -57,16 +57,6 @@ export class FakeShader {
private offscreenCtx: OffscreenCanvasRenderingContext2D | null = null; private offscreenCtx: OffscreenCanvasRenderingContext2D | null = null;
private useOffscreen: boolean = false; private useOffscreen: boolean = false;
// Adaptive resolution scaling
private adaptiveCanvas: HTMLCanvasElement;
private adaptiveCtx: CanvasRenderingContext2D;
private currentScale: number = 1.0;
private targetRenderTime: number = 16; // Target 60 FPS
private performanceHistory: number[] = [];
private lastScaleAdjustment: number = 0;
private minScale: number = 0.25;
private maxScale: number = 1.0;
private renderStartTime: number = 0;
// Multi-worker state // Multi-worker state
private tileResults: Map<number, ImageData> = new Map(); private tileResults: Map<number, ImageData> = new Map();
@ -108,8 +98,6 @@ export class FakeShader {
this.ctx = canvas.getContext('2d')!; this.ctx = canvas.getContext('2d')!;
this.code = code; this.code = code;
// Initialize adaptive resolution canvas
this.initializeAdaptiveCanvas();
// Initialize offscreen canvas if supported // Initialize offscreen canvas if supported
this.initializeOffscreenCanvas(); this.initializeOffscreenCanvas();
@ -130,21 +118,6 @@ export class FakeShader {
this.compile(); this.compile();
} }
private initializeAdaptiveCanvas(): void {
this.adaptiveCanvas = document.createElement('canvas');
this.adaptiveCtx = this.adaptiveCanvas.getContext('2d')!;
this.updateAdaptiveCanvasSize();
}
private updateAdaptiveCanvasSize(): void {
const scaledWidth = Math.floor(this.canvas.width * this.currentScale);
const scaledHeight = Math.floor(this.canvas.height * this.currentScale);
if (this.adaptiveCanvas.width !== scaledWidth || this.adaptiveCanvas.height !== scaledHeight) {
this.adaptiveCanvas.width = scaledWidth;
this.adaptiveCanvas.height = scaledHeight;
}
}
private initializeOffscreenCanvas(): void { private initializeOffscreenCanvas(): void {
if (typeof OffscreenCanvas !== 'undefined') { if (typeof OffscreenCanvas !== 'undefined') {
@ -186,14 +159,8 @@ export class FakeShader {
// Single worker mode // Single worker mode
this.isRendering = false; this.isRendering = false;
if (response.success && response.imageData) { if (response.success && response.imageData) {
// Put ImageData on adaptive resolution canvas // Put ImageData directly on main canvas
this.adaptiveCtx.putImageData(response.imageData, 0, 0); this.ctx.putImageData(response.imageData, 0, 0);
// Upscale to main canvas with proper interpolation
this.upscaleToMainCanvas();
// Monitor performance and adjust scale
this.updatePerformanceMetrics();
} else { } else {
console.error('Render failed:', response.error); console.error('Render failed:', response.error);
this.fillBlack(); this.fillBlack();
@ -244,11 +211,6 @@ export class FakeShader {
return; return;
} }
// Update adaptive canvas size based on current scale
this.updateAdaptiveCanvasSize();
// Start performance timing
this.renderStartTime = performance.now();
this.isRendering = true; this.isRendering = true;
this.currentRenderID = id; this.currentRenderID = id;
@ -263,27 +225,24 @@ export class FakeShader {
} }
private renderWithSingleWorker(id: string, currentTime: number): void { private renderWithSingleWorker(id: string, currentTime: number): void {
const scaledMouseX = this.mouseX * this.currentScale;
const scaledMouseY = this.mouseY * this.currentScale;
this.worker.postMessage({ this.worker.postMessage({
id, id,
type: 'render', type: 'render',
width: this.adaptiveCanvas.width, width: this.canvas.width,
height: this.adaptiveCanvas.height, height: this.canvas.height,
time: currentTime, time: currentTime,
renderMode: this.renderMode, renderMode: this.renderMode,
mouseX: scaledMouseX, mouseX: this.mouseX,
mouseY: scaledMouseY, mouseY: this.mouseY,
mousePressed: this.mousePressed, mousePressed: this.mousePressed,
mouseVX: this.mouseVX * this.currentScale, mouseVX: this.mouseVX,
mouseVY: this.mouseVY * this.currentScale, mouseVY: this.mouseVY,
mouseClickTime: this.mouseClickTime, mouseClickTime: this.mouseClickTime,
touchCount: this.touchCount, touchCount: this.touchCount,
touch0X: this.touch0X * this.currentScale, touch0X: this.touch0X,
touch0Y: this.touch0Y * this.currentScale, touch0Y: this.touch0Y,
touch1X: this.touch1X * this.currentScale, touch1X: this.touch1X,
touch1Y: this.touch1Y * this.currentScale, touch1Y: this.touch1Y,
pinchScale: this.pinchScale, pinchScale: this.pinchScale,
pinchRotation: this.pinchRotation, pinchRotation: this.pinchRotation,
accelX: this.accelX, accelX: this.accelX,
@ -305,8 +264,8 @@ export class FakeShader {
this.tilesCompleted = 0; this.tilesCompleted = 0;
this.totalTiles = this.workerCount; this.totalTiles = this.workerCount;
const width = this.adaptiveCanvas.width; const width = this.canvas.width;
const height = this.adaptiveCanvas.height; const height = this.canvas.height;
const tileHeight = Math.ceil(height / this.workerCount); const tileHeight = Math.ceil(height / this.workerCount);
// Distribute tiles to workers // Distribute tiles to workers
@ -316,9 +275,6 @@ export class FakeShader {
if (startY >= height) return; // Skip if tile is outside canvas if (startY >= height) return; // Skip if tile is outside canvas
const scaledMouseX = this.mouseX * this.currentScale;
const scaledMouseY = this.mouseY * this.currentScale;
worker.postMessage({ worker.postMessage({
id: `${id}_tile_${index}`, id: `${id}_tile_${index}`,
type: 'render', type: 'render',
@ -328,17 +284,17 @@ export class FakeShader {
startY: startY, startY: startY,
time: currentTime, time: currentTime,
renderMode: this.renderMode, renderMode: this.renderMode,
mouseX: scaledMouseX, mouseX: this.mouseX,
mouseY: scaledMouseY, mouseY: this.mouseY,
mousePressed: this.mousePressed, mousePressed: this.mousePressed,
mouseVX: this.mouseVX * this.currentScale, mouseVX: this.mouseVX,
mouseVY: this.mouseVY * this.currentScale, mouseVY: this.mouseVY,
mouseClickTime: this.mouseClickTime, mouseClickTime: this.mouseClickTime,
touchCount: this.touchCount, touchCount: this.touchCount,
touch0X: this.touch0X * this.currentScale, touch0X: this.touch0X,
touch0Y: this.touch0Y * this.currentScale, touch0Y: this.touch0Y,
touch1X: this.touch1X * this.currentScale, touch1X: this.touch1X,
touch1Y: this.touch1Y * this.currentScale, touch1Y: this.touch1Y,
pinchScale: this.pinchScale, pinchScale: this.pinchScale,
pinchRotation: this.pinchRotation, pinchRotation: this.pinchRotation,
accelX: this.accelX, accelX: this.accelX,
@ -480,19 +436,19 @@ export class FakeShader {
} }
private compositeTiles(): void { private compositeTiles(): void {
const width = this.adaptiveCanvas.width; const width = this.canvas.width;
const height = this.adaptiveCanvas.height; const height = this.canvas.height;
const tileHeight = Math.ceil(height / this.workerCount); const tileHeight = Math.ceil(height / this.workerCount);
// Clear adaptive canvas // Clear main canvas
this.adaptiveCtx.clearRect(0, 0, width, height); this.ctx.clearRect(0, 0, width, height);
// Composite all tiles // Composite all tiles directly on main canvas
for (let i = 0; i < this.workerCount; i++) { for (let i = 0; i < this.workerCount; i++) {
const tileData = this.tileResults.get(i); const tileData = this.tileResults.get(i);
if (tileData) { if (tileData) {
const startY = i * tileHeight; const startY = i * tileHeight;
this.adaptiveCtx.putImageData(tileData, 0, startY); this.ctx.putImageData(tileData, 0, startY);
} }
} }
@ -502,12 +458,6 @@ export class FakeShader {
// Mark rendering as complete // Mark rendering as complete
this.isRendering = false; this.isRendering = false;
// Upscale to main canvas
this.upscaleToMainCanvas();
// Monitor performance
this.updatePerformanceMetrics();
// Process pending renders // Process pending renders
if (this.pendingRenders.length > 0) { if (this.pendingRenders.length > 0) {
this.pendingRenders.shift(); this.pendingRenders.shift();
@ -519,81 +469,6 @@ export class FakeShader {
} }
} }
private upscaleToMainCanvas(): void {
// Clear main canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Set interpolation based on scale
if (this.currentScale < 0.5) {
// Use smooth interpolation for heavily downscaled content
this.ctx.imageSmoothingEnabled = true;
this.ctx.imageSmoothingQuality = 'high';
} else {
// Use pixel-perfect scaling for minimal downscaling
this.ctx.imageSmoothingEnabled = false;
}
// Draw scaled content to main canvas
this.ctx.drawImage(
this.adaptiveCanvas,
0, 0, this.adaptiveCanvas.width, this.adaptiveCanvas.height,
0, 0, this.canvas.width, this.canvas.height
);
}
private updatePerformanceMetrics(): void {
const renderTime = performance.now() - this.renderStartTime;
// Add to performance history
this.performanceHistory.push(renderTime);
if (this.performanceHistory.length > 10) {
this.performanceHistory.shift(); // Keep only last 10 measurements
}
// Adjust scale if we have enough data and enough time has passed
const now = performance.now();
if (this.performanceHistory.length >= 3 && now - this.lastScaleAdjustment > 500) {
this.adjustRenderScale();
this.lastScaleAdjustment = now;
}
}
private adjustRenderScale(): void {
// Calculate average render time from recent history
const avgRenderTime = this.performanceHistory.reduce((a, b) => a + b, 0) / this.performanceHistory.length;
const tolerance = 2; // 2ms tolerance
if (avgRenderTime > this.targetRenderTime + tolerance) {
// Too slow - scale down
const newScale = Math.max(this.minScale, this.currentScale * 0.85);
if (newScale !== this.currentScale) {
this.currentScale = newScale;
console.log(`Scaling down to ${(this.currentScale * 100).toFixed(0)}% (${avgRenderTime.toFixed(1)}ms avg)`);
}
} else if (avgRenderTime < this.targetRenderTime - tolerance && this.currentScale < this.maxScale) {
// Fast enough - try scaling up
const newScale = Math.min(this.maxScale, this.currentScale * 1.1);
if (newScale !== this.currentScale) {
this.currentScale = newScale;
console.log(`Scaling up to ${(this.currentScale * 100).toFixed(0)}% (${avgRenderTime.toFixed(1)}ms avg)`);
}
}
}
setAdaptiveQuality(enabled: boolean, targetFPS: number = 60): void {
if (enabled) {
this.targetRenderTime = 1000 / targetFPS;
this.currentScale = 1.0;
this.performanceHistory = [];
} else {
this.currentScale = 1.0;
}
}
getCurrentScale(): number {
return this.currentScale;
}
// Simplified method - kept for backward compatibility but always uses all cores // Simplified method - kept for backward compatibility but always uses all cores
setMultiWorkerMode(enabled: boolean, workerCount?: number): void { setMultiWorkerMode(enabled: boolean, workerCount?: number): void {

View File

@ -494,6 +494,24 @@ class ShaderWorker {
case 'rainbow': case 'rainbow':
return this.rainbowColor(absValue); return this.rainbowColor(absValue);
case 'thermal':
return this.thermalColor(absValue);
case 'neon':
return this.neonColor(absValue);
case 'cyberpunk':
return this.cyberpunkColor(absValue);
case 'vaporwave':
return this.vaporwaveColor(absValue);
case 'dithered':
return this.ditheredColor(absValue);
case 'palette':
return this.paletteColor(absValue);
default: default:
return [absValue, absValue, absValue]; return [absValue, absValue, absValue];
} }
@ -545,6 +563,81 @@ class ShaderWorker {
} }
} }
private thermalColor(value: number): [number, number, number] {
const t = value / 255.0;
if (t < 0.25) {
return [0, 0, Math.round(t * 4 * 255)];
} else if (t < 0.5) {
return [0, Math.round((t - 0.25) * 4 * 255), 255];
} else if (t < 0.75) {
return [Math.round((t - 0.5) * 4 * 255), 255, Math.round((0.75 - t) * 4 * 255)];
} else {
return [255, 255, Math.round((t - 0.75) * 4 * 255)];
}
}
private neonColor(value: number): [number, number, number] {
const t = value / 255.0;
const intensity = Math.pow(Math.sin(t * Math.PI), 2);
const glow = Math.pow(intensity, 0.5);
return [
Math.round(glow * 255),
Math.round(intensity * 255),
Math.round(Math.pow(intensity, 2) * 255)
];
}
private cyberpunkColor(value: number): [number, number, number] {
const t = value / 255.0;
const phase = (t * 3) % 1;
if (phase < 0.33) {
return [Math.round(255 * (1 - phase * 3)), 0, Math.round(255 * phase * 3)];
} else if (phase < 0.67) {
const p = (phase - 0.33) * 3;
return [0, Math.round(255 * p), Math.round(255 * (1 - p))];
} else {
const p = (phase - 0.67) * 3;
return [Math.round(255 * p), Math.round(255 * (1 - p)), 255];
}
}
private vaporwaveColor(value: number): [number, number, number] {
const t = value / 255.0;
const pink = Math.sin(t * Math.PI);
const purple = Math.sin(t * Math.PI * 0.7 + Math.PI / 3);
const blue = Math.sin(t * Math.PI * 0.5 + Math.PI / 2);
return [
Math.round(255 * (0.8 + 0.2 * pink)),
Math.round(255 * (0.3 + 0.7 * purple)),
Math.round(255 * (0.6 + 0.4 * blue))
];
}
private ditheredColor(value: number): [number, number, number] {
const levels = 4;
const step = 255 / (levels - 1);
const quantized = Math.round(value / step) * step;
const error = value - quantized;
const dither = (Math.random() - 0.5) * 32;
const final = Math.max(0, Math.min(255, quantized + error + dither));
return [final, final, final];
}
private paletteColor(value: number): [number, number, number] {
const palette = [
[0, 0, 0],
[87, 29, 149],
[191, 82, 177],
[249, 162, 162],
[255, 241, 165],
[134, 227, 206],
[29, 161, 242],
[255, 255, 255]
];
const index = Math.floor((value / 255.0) * (palette.length - 1));
return palette[index] as [number, number, number];
}
private sanitizeCode(code: string): string { private sanitizeCode(code: string): string {
// Strict whitelist approach - extended to include new interaction variables // Strict whitelist approach - extended to include new interaction variables
// Variables: x, y, t, i, mouseX, mouseY, mousePressed, mouseVX, mouseVY, mouseClickTime, touchCount, touch0X, touch0Y, touch1X, touch1Y, pinchScale, pinchRotation, accelX, accelY, accelZ, gyroX, gyroY, gyroZ // Variables: x, y, t, i, mouseX, mouseY, mousePressed, mouseVX, mouseVY, mouseClickTime, touchCount, touch0X, touch0Y, touch1X, touch1Y, pinchScale, pinchRotation, accelX, accelY, accelZ, gyroX, gyroY, gyroZ