Spaces:
Sleeping
Sleeping
File size: 7,796 Bytes
78475cb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | import { BLOCK_SIZE } from '../constants.js';
/**
* Renders Tetris blocks using sprite sheet with color palettes from backdrops
*/
export default class SpriteBlockRenderer {
/**
* Create a block texture from sprite sheet with palette colors
* @param {Phaser.Scene} scene - The Phaser scene
* @param {number[]} colorPalette - Array of 7 colors extracted from backdrop
* @param {number} level - Current level (1-10) determines which sprite to use
* @param {string} key - Texture key to create
* @param {number} colorIndex - Which color from palette to use (0-6)
*/
static createBlockTexture(scene, colorPalette, level, key, colorIndex) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
canvas.width = BLOCK_SIZE;
canvas.height = BLOCK_SIZE;
// Get the color to use
const color = colorPalette[colorIndex % colorPalette.length];
const r = (color >> 16) & 0xFF;
const g = (color >> 8) & 0xFF;
const b = color & 0xFF;
// Get the sprite sheet and extract pattern
const spriteSheet = scene.textures.get('blocks-spritesheet').getSourceImage();
const spriteX = (level - 1) * BLOCK_SIZE;
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCtx.imageSmoothingEnabled = false;
tempCanvas.width = spriteSheet.width;
tempCanvas.height = spriteSheet.height;
tempCtx.drawImage(spriteSheet, 0, 0);
const spriteData = tempCtx.getImageData(spriteX, 0, BLOCK_SIZE, BLOCK_SIZE);
const pixels = spriteData.data;
// Create output image data
const outputData = ctx.createImageData(BLOCK_SIZE, BLOCK_SIZE);
const output = outputData.data;
// Colorize: use grayscale brightness to modulate the base color
// Grayscale values create depth (lighter/darker variations)
for (let i = 0; i < pixels.length; i += 4) {
const alpha = pixels[i + 3];
if (alpha > 0) {
// Get grayscale brightness (0-255)
const brightness = pixels[i]; // R channel (grayscale, so R=G=B)
// Normalize brightness to a multiplier (0.5 to 1.5)
// 128 (50% gray) = 1.0x (base color)
// 0 (black) = 0.5x (darkest)
// 255 (white) = 1.5x (lightest)
const multiplier = 0.5 + (brightness / 255) * 1.0;
// Apply brightness multiplier to base color
output[i] = Math.min(255, Math.floor(r * multiplier));
output[i + 1] = Math.min(255, Math.floor(g * multiplier));
output[i + 2] = Math.min(255, Math.floor(b * multiplier));
output[i + 3] = 255;
} else {
// Transparent pixel
output[i] = 0;
output[i + 1] = 0;
output[i + 2] = 0;
output[i + 3] = 0;
}
}
ctx.putImageData(outputData, 0, 0);
// Create texture and set nearest neighbor
const texture = scene.textures.addCanvas(key, canvas);
texture.setFilter(Phaser.Textures.FilterMode.NEAREST);
}
/**
* Create a crush animation frame texture
* @param {Phaser.Scene} scene - The Phaser scene
* @param {number} color - The color to apply
* @param {number} frameIndex - Which frame (0-4)
* @param {string} key - Texture key to create
*/
static createCrushTexture(scene, color, frameIndex, key) {
// Check if texture already exists
if (scene.textures.exists(key)) {
return;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.imageSmoothingEnabled = false;
canvas.width = BLOCK_SIZE;
canvas.height = BLOCK_SIZE;
const r = (color >> 16) & 0xFF;
const g = (color >> 8) & 0xFF;
const b = color & 0xFF;
// Get the crush sprite sheet
const spriteSheet = scene.textures.get('crush-spritesheet').getSourceImage();
const spriteX = frameIndex * BLOCK_SIZE;
const tempCanvas = document.createElement('canvas');
const tempCtx = tempCanvas.getContext('2d');
tempCtx.imageSmoothingEnabled = false;
tempCanvas.width = spriteSheet.width;
tempCanvas.height = spriteSheet.height;
tempCtx.drawImage(spriteSheet, 0, 0);
const spriteData = tempCtx.getImageData(spriteX, 0, BLOCK_SIZE, BLOCK_SIZE);
const pixels = spriteData.data;
const outputData = ctx.createImageData(BLOCK_SIZE, BLOCK_SIZE);
const output = outputData.data;
// Apply grayscale brightness to color (darker = more visible, lighter/white = transparent)
for (let i = 0; i < pixels.length; i += 4) {
const brightness = pixels[i]; // Grayscale R channel
const alpha = pixels[i + 3];
// Light pixels or transparent become fully transparent
if (brightness >= 200 || alpha === 0) {
output[i] = 0;
output[i + 1] = 0;
output[i + 2] = 0;
output[i + 3] = 0;
} else {
// Darker pixels get colored - use brightness to modulate color intensity
// Darker sprite pixels = darker colored blocks
const multiplier = 0.3 + (brightness / 255) * 0.9;
output[i] = Math.min(255, Math.floor(r * multiplier));
output[i + 1] = Math.min(255, Math.floor(g * multiplier));
output[i + 2] = Math.min(255, Math.floor(b * multiplier));
output[i + 3] = 255;
}
}
ctx.putImageData(outputData, 0, 0);
const texture = scene.textures.addCanvas(key, canvas);
if (texture) {
texture.setFilter(Phaser.Textures.FilterMode.NEAREST);
}
}
/**
* Subtly enhance colors with 20% extra contrast
* @param {number[]} palette - Original palette from backdrop
* @returns {number[]} Enhanced palette with subtle contrast boost
*/
static enhancePalette(palette) {
const enhanced = [];
for (let i = 0; i < palette.length; i++) {
let color = palette[i];
let r = (color >> 16) & 0xFF;
let g = (color >> 8) & 0xFF;
let b = color & 0xFF;
// Add 20% contrast: push values away from middle gray (128)
const contrastFactor = 0.2;
r = Math.min(255, Math.max(0, Math.floor(128 + (r - 128) * (1 + contrastFactor))));
g = Math.min(255, Math.max(0, Math.floor(128 + (g - 128) * (1 + contrastFactor))));
b = Math.min(255, Math.max(0, Math.floor(128 + (b - 128) * (1 + contrastFactor))));
enhanced.push((r << 16) | (g << 8) | b);
}
return enhanced;
}
/**
* Ensure colors in palette are distinct from each other
* @param {number[]} palette - Color palette
* @returns {number[]} Palette with distinct colors
*/
static ensureDistinctColors(palette) {
const result = [palette[0]];
for (let i = 1; i < palette.length; i++) {
let color = palette[i];
let attempts = 0;
// Check if too similar to existing colors
while (attempts < 10) {
let tooSimilar = false;
for (let j = 0; j < result.length; j++) {
if (this.colorDistance(color, result[j]) < 100) {
tooSimilar = true;
break;
}
}
if (!tooSimilar) break;
// Adjust color
let r = (color >> 16) & 0xFF;
let g = (color >> 8) & 0xFF;
let b = color & 0xFF;
r = (r + 60) % 256;
g = (g + 40) % 256;
b = (b + 80) % 256;
color = (r << 16) | (g << 8) | b;
attempts++;
}
result.push(color);
}
return result;
}
/**
* Calculate color distance
*/
static colorDistance(c1, c2) {
const r1 = (c1 >> 16) & 0xFF;
const g1 = (c1 >> 8) & 0xFF;
const b1 = c1 & 0xFF;
const r2 = (c2 >> 16) & 0xFF;
const g2 = (c2 >> 8) & 0xFF;
const b2 = c2 & 0xFF;
return Math.sqrt((r1-r2)**2 + (g1-g2)**2 + (b1-b2)**2);
}
}
|