This commit is contained in:
Fillip
2024-08-07 02:37:51 -07:00
parent adfb1984a5
commit c46b0d0cfa
11 changed files with 490 additions and 101 deletions
+8
View File
@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>
+43
View File
@@ -0,0 +1,43 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="13">
<item index="0" class="java.lang.String" itemvalue="scipy" />
<item index="1" class="java.lang.String" itemvalue="transformers" />
<item index="2" class="java.lang.String" itemvalue="torch" />
<item index="3" class="java.lang.String" itemvalue="torchvision" />
<item index="4" class="java.lang.String" itemvalue="psutil" />
<item index="5" class="java.lang.String" itemvalue="torchsde" />
<item index="6" class="java.lang.String" itemvalue="pyyaml" />
<item index="7" class="java.lang.String" itemvalue="tqdm" />
<item index="8" class="java.lang.String" itemvalue="safetensors" />
<item index="9" class="java.lang.String" itemvalue="aiohttp" />
<item index="10" class="java.lang.String" itemvalue="einops" />
<item index="11" class="java.lang.String" itemvalue="Pillow" />
<item index="12" class="java.lang.String" itemvalue="kornia" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N801" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="audioowl" />
</list>
</option>
</inspection_tool>
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ComfyUI_Fill-Nodes.iml" filepath="$PROJECT_DIR$/.idea/ComfyUI_Fill-Nodes.iml" />
</modules>
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+26 -26
View File
@@ -1,41 +1,41 @@
from .nodes.fl_image_randomizer import FL_ImageRandomizer
from .nodes.FL_Image_Randomizer import FL_ImageRandomizer
from .nodes.FL_Image_Caption_Saver import FL_ImageCaptionSaver
from .nodes.fl_image_dimension_display import FL_ImageDimensionDisplay
from .nodes.FL_Image_Dimension_Display import FL_ImageDimensionDisplay
from .nodes.FL_Code_Node import FL_CodeNode
from .nodes.fl_image_pixelator import FL_ImagePixelator
from .nodes.FL_Image_Pixelator import FL_ImagePixelator
from .nodes.FL_DirectoryCrawl import FL_DirectoryCrawl
from .nodes.FL_Ascii import FL_Ascii
from .nodes.FL_Glitch import FL_Glitch
from .nodes.fl_ripple import FL_Ripple
from .nodes.fl_pixelsort import FL_PixelSort
from .nodes.FL_Ripple import FL_Ripple
from .nodes.FL_PixelSort import FL_PixelSort
from .nodes.FL_HexagonalPattern import FL_HexagonalPattern
from .nodes.fl_nftgenerator import FL_NFTGenerator
from .nodes.FL_NFTGenerator import FL_NFTGenerator
from .nodes.FL_HalfTone import FL_HalftonePattern
from .nodes.fl_randomrange import FL_RandomNumber
from .nodes.fl_promptselector import FL_PromptSelector
from .nodes.fl_shader import FL_Shadertoy
from .nodes.fl_pixelshader import FL_PixelArtShader
from .nodes.fl_infinitezoom import FL_InfiniteZoom
from .nodes.fl_paperdrawn import FL_PaperDrawn
from .nodes.fl_imagenotes import FL_ImageNotes
from .nodes.fl_imagecollage import FL_ImageCollage
from .nodes.fl_ksamplersettings import FL_KsamplerSettings
from .nodes.fl_retroeffect import FL_RetroEffect
from .nodes.fl_td_ksampler import FL_TD_KSampler
from .nodes.fl_inpaintcrop import FL_InpaintCrop
from .nodes.fl_inpaintcrop import FL_Inpaint_Stitch
from .nodes.fl_sdultimate_slices import FL_SDUltimate_Slices
from .nodes.FL_RandomRange import FL_RandomNumber
from .nodes.FL_PromptSelector import FL_PromptSelector
from .nodes.FL_Shader import FL_Shadertoy
from .nodes.FL_PixelShader import FL_PixelArtShader
from .nodes.FL_InfiniteZoom import FL_InfiniteZoom
from .nodes.FL_PaperDrawn import FL_PaperDrawn
from .nodes.FL_ImageNotes import FL_ImageNotes
from .nodes.FL_ImageCollage import FL_ImageCollage
from .nodes.FL_KsamplerSettings import FL_KsamplerSettings
from .nodes.FL_RetroEffect import FL_RetroEffect
from .nodes.FL_TD_Ksampler import FL_TD_KSampler
from .nodes.FL_InpaintCrop import FL_InpaintCrop
from .nodes.FL_InpaintCrop import FL_Inpaint_Stitch
from .nodes.FL_SD_Slices import FL_SDUltimate_Slices
from .nodes.FL_BatchAligned import FL_BatchAlign
from .nodes.fl_videocropnstitch import FL_VideoCropMask
from .nodes.fl_videocropnstitch import FL_VideoRecompose
from .nodes.fl_separatemasks import FL_SeparateMaskComponents
from .nodes.fl_pasteoncanvas import FL_PasteOnCanvas
from .nodes.FL_VideoCropNStitch import FL_VideoCropMask
from .nodes.FL_VideoCropNStitch import FL_VideoRecompose
from .nodes.FL_SeparateMasks import FL_SeparateMaskComponents
from .nodes.FL_PasteOnCanvas import FL_PasteOnCanvas
from .nodes.FL_BulletHellGame import FL_BulletHellGame
from .nodes.fl_tetrisgame import FL_TetrisGame
from .nodes.FL_TetrisGame import FL_TetrisGame
from .nodes.FL_Dither import FL_Dither
from .nodes.FL_SystemCheck import FL_SystemCheck
from .nodes.FL_ColorPicker import FL_ColorPicker
from .nodes.GradientImageGenerator import GradientImageGenerator
from .nodes.FL_GradGen import GradientImageGenerator
from .nodes.FL_MirrorAndAppendCaptions import FL_MirrorAndAppendCaptions
from .nodes.FL_ImageCaptionLayout import FL_ImageCaptionLayout
from .nodes.FL_HFHubModelUploader import FL_HFHubModelUploader
+25 -15
View File
@@ -7,6 +7,7 @@ import logging
import numpy as np
from PIL import Image
import torch.nn.functional as F
import latent_preview
class FL_KsamplerPlus:
@@ -33,7 +34,6 @@ class FL_KsamplerPlus:
},
"optional": {
"latent_image": ("LATENT",),
"vae": ("VAE",),
"image": ("IMAGE",),
}
}
@@ -145,24 +145,28 @@ class FL_KsamplerPlus:
return section, y_start, y_end, x_start, x_end, cropped_positive, cropped_negative
total_slices = x_slices * y_slices
all_slices = [(y, x) for y in range(y_slices) for x in range(x_slices)]
for i in range(0, total_slices, batch_size):
batch_slices = all_slices[i:min(i + batch_size, total_slices)]
batch_slices = [(y, x) for y in range(y_slices) for x in range(x_slices)][
i:min(i + batch_size, total_slices)]
batch_sections = [process_slice(y, x) for y, x in batch_slices]
batch_latents = torch.cat([section for section, _, _, _, _, _, _ in batch_sections], dim=0)
batch_positives = sum([pos for _, _, _, _, _, pos, _ in batch_sections], [])
batch_negatives = sum([neg for _, _, _, _, _, _, neg in batch_sections], [])
processed_batch = common_ksampler(model, seed + i, steps, cfg, sampler_name, scheduler,
batch_positives, batch_negatives,
{"samples": batch_latents}, denoise=denoise)[0]
if use_sliced_conditioning:
batch_positive = [cond for _, _, _, _, _, cond, _ in batch_sections]
batch_negative = [cond for _, _, _, _, _, _, cond in batch_sections]
else:
batch_positive = [positive] * len(batch_sections)
batch_negative = [negative] * len(batch_sections)
processed_sections = torch.split(processed_batch["samples"], b, dim=0)
# Process each slice in the batch individually
for j, (slice_latent, slice_positive, slice_negative) in enumerate(
zip(torch.split(batch_latents, 1), batch_positive, batch_negative)):
processed_slice = common_ksampler(model, seed + i + j, steps, cfg, sampler_name, scheduler,
slice_positive, slice_negative,
{"samples": slice_latent}, denoise=denoise)[0]
for (_, y_start, y_end, x_start, x_end, _, _), processed_section in zip(batch_sections,
processed_sections):
_, y_start, y_end, x_start, x_end, _, _ = batch_sections[j]
is_top = y_start == 0
is_left = x_start == 0
is_bottom = y_end == h
@@ -170,14 +174,14 @@ class FL_KsamplerPlus:
blend_mask = create_blend_mask(y_end - y_start, x_end - x_start,
overlap_height, overlap_width,
is_top, is_left, is_bottom, is_right)
blend_mask = blend_mask.unsqueeze(0).unsqueeze(0).expand_as(processed_section)
blend_mask = blend_mask.unsqueeze(0).unsqueeze(0).expand_as(processed_slice["samples"])
processed_section = processed_section.to(device=device)
processed_slice = processed_slice["samples"].to(device=device)
blend_mask = blend_mask.to(device=device)
samples[:, :, y_start:y_end, x_start:x_end] = (
samples[:, :, y_start:y_end, x_start:x_end] * (1 - blend_mask) +
processed_section * blend_mask
processed_slice * blend_mask
)
if device.type == 'cuda':
@@ -193,3 +197,9 @@ class FL_KsamplerPlus:
except Exception as e:
logging.error(f"Error in FL_UltimateUpscale: {str(e)}")
raise
@classmethod
def IS_CHANGED(s, model, positive, negative, x_slices, y_slices, overlap, batch_size, seed, steps, cfg,
sampler_name, scheduler, denoise, input_type, use_sliced_conditioning, latent_image=None, image=None,
vae=None):
return float("NaN")
+356 -60
View File
@@ -8,65 +8,359 @@ app.registerExtension({
];
if (animatedNodeClasses.includes(node.comfyClass)) {
addDenoisingTextAnimatedDisplay(node);
addModernSlotMachineAnimation(node);
}
}
});
function addDenoisingTextAnimatedDisplay(node) {
const ANIMATION_WIDTH = 120;
const ANIMATION_HEIGHT = 80;
const PIXEL_SIZE = 1;
const GRID_WIDTH = Math.floor(ANIMATION_WIDTH / PIXEL_SIZE);
const GRID_HEIGHT = Math.floor(ANIMATION_HEIGHT / PIXEL_SIZE);
function addModernSlotMachineAnimation(node) {
// Easily adjustable parameters
const PARAMS = {
// Animation dimensions
ANIMATION_WIDTH: 120,
ANIMATION_HEIGHT: 80,
X_OFFSET: 0,
Y_OFFSET: 100,
let grid = createEmptyGrid(GRID_WIDTH, GRID_HEIGHT);
let animationProgress = 0;
let xOffset = 0;
let yOffset = 90;
// Reel settings
REEL_WIDTH: 30,
REEL_HEIGHT: 30,
REEL_COUNT: 3,
SYMBOL_SIZE: 24,
SPIN_SPEED: 0.2,
function createEmptyGrid(width, height) {
return Array.from({ length: height }, () =>
Array.from({ length: width }, () => ({ value: 0, hue: 0 }))
);
}
// Game logic
WIN_PROBABILITY: 0.25,
SYMBOLS: ['💎', '🔮', '🎭', '🎨', '🎬', '🏆'],
function drawText(ctx) {
ctx.font = 'bold 20px Arial';
// Button settings
BUTTON_WIDTH: 50,
BUTTON_HEIGHT: 20,
BUTTON_Y_OFFSET: 25,
// Animation timings
SPIN_DURATION: 1000,
WIN_ANIMATION_SPEED: 0.02,
// Visual styles
BACKGROUND_COLOR: '#4B0082',
FRAME_COLOR: '#8A2BE2',
REEL_COLOR: '#2E0854',
BUTTON_COLORS: {
NORMAL: ['#8A2BE2', '#6A0DAD'],
SPINNING: ['#6A0DAD', '#4B0082']
},
TEXT_COLOR: '#FFFFFF',
// Particle system
MAX_PARTICLES: 100,
PARTICLE_DECAY_RATE: 0.02
};
let reels = Array(PARAMS.REEL_COUNT).fill().map(() => ({
symbols: [...PARAMS.SYMBOLS],
position: 0,
spinning: false,
finalPosition: 0,
offset: 0
}));
let spinning = false;
let winningSymbol = null;
let winAnimation = null;
let winAnimationProgress = 0;
let particles = [];
function drawModernSlotMachine(ctx) {
// Background
ctx.fillStyle = PARAMS.BACKGROUND_COLOR;
ctx.fillRect(0, 0, PARAMS.ANIMATION_WIDTH, PARAMS.ANIMATION_HEIGHT);
// Frame
ctx.strokeStyle = PARAMS.FRAME_COLOR;
ctx.lineWidth = 2;
ctx.strokeRect(2, 2, PARAMS.ANIMATION_WIDTH - 4, PARAMS.ANIMATION_HEIGHT - 4);
// Reels
const reelStartX = (PARAMS.ANIMATION_WIDTH - (PARAMS.REEL_COUNT * PARAMS.REEL_WIDTH + (PARAMS.REEL_COUNT - 1) * 5)) / 2;
reels.forEach((reel, i) => {
ctx.fillStyle = PARAMS.REEL_COLOR;
ctx.fillRect(reelStartX + i * (PARAMS.REEL_WIDTH + 5), 15, PARAMS.REEL_WIDTH, PARAMS.REEL_HEIGHT);
ctx.save();
ctx.beginPath();
ctx.rect(reelStartX + i * (PARAMS.REEL_WIDTH + 5), 15, PARAMS.REEL_WIDTH, PARAMS.REEL_HEIGHT);
ctx.clip();
const symbolIndex = Math.floor(reel.position) % PARAMS.SYMBOLS.length;
const y = 30 - (reel.position % 1) * PARAMS.SYMBOL_SIZE + reel.offset;
ctx.font = `${PARAMS.SYMBOL_SIZE}px Arial`;
ctx.fillStyle = PARAMS.TEXT_COLOR;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(reel.symbols[symbolIndex], reelStartX + PARAMS.REEL_WIDTH / 2 + i * (PARAMS.REEL_WIDTH + 5), y);
ctx.restore();
});
// Spin button
const buttonGradient = ctx.createLinearGradient(0, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET, 0, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT);
const buttonColors = spinning ? PARAMS.BUTTON_COLORS.SPINNING : PARAMS.BUTTON_COLORS.NORMAL;
buttonGradient.addColorStop(0, buttonColors[0]);
buttonGradient.addColorStop(1, buttonColors[1]);
ctx.fillStyle = buttonGradient;
ctx.beginPath();
ctx.roundRect(PARAMS.ANIMATION_WIDTH / 2 - PARAMS.BUTTON_WIDTH / 2, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET, PARAMS.BUTTON_WIDTH, PARAMS.BUTTON_HEIGHT, 5);
ctx.fill();
ctx.fillStyle = PARAMS.TEXT_COLOR;
ctx.font = 'bold 12px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = 'white';
ctx.fillText('CUT', ANIMATION_WIDTH / 2, ANIMATION_HEIGHT / 2 - 15);
ctx.fillText('THE NOISE', ANIMATION_WIDTH / 2, ANIMATION_HEIGHT / 2 + 15);
}
ctx.fillText('SPIN', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT / 2);
function updateGrid() {
animationProgress += 0.005; // Slightly faster animation
if (animationProgress > 1) {
animationProgress = 0;
grid = createEmptyGrid(GRID_WIDTH, GRID_HEIGHT);
// Win animation
if (winAnimation) {
winAnimation(ctx, winAnimationProgress);
}
const tempCanvas = document.createElement('canvas');
tempCanvas.width = ANIMATION_WIDTH;
tempCanvas.height = ANIMATION_HEIGHT;
const tempCtx = tempCanvas.getContext('2d');
drawText(tempCtx);
// Draw particles
particles.forEach(p => {
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
});
}
const imageData = tempCtx.getImageData(0, 0, ANIMATION_WIDTH, ANIMATION_HEIGHT);
function spin() {
if (spinning) return;
spinning = true;
winningSymbol = null;
winAnimation = null;
winAnimationProgress = 0;
particles = [];
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
const i = (y * ANIMATION_WIDTH + x) * 4;
if (imageData.data[i] > 0) {
if (grid[y][x].value === 0) {
grid[y][x].hue = Math.random() * 360; // Assign a random hue
}
if (Math.random() < animationProgress * 0.2) {
grid[y][x].value = Math.min(grid[y][x].value + 0.2, 1);
}
reels.forEach(reel => {
reel.spinning = true;
reel.spinDuration = 50 + Math.random() * 50;
reel.offset = Math.random() * PARAMS.SYMBOL_SIZE;
});
const isWin = Math.random() < PARAMS.WIN_PROBABILITY;
if (isWin) {
const winSymbolIndex = Math.floor(Math.random() * PARAMS.SYMBOLS.length);
reels.forEach(reel => reel.finalPosition = winSymbolIndex);
winningSymbol = PARAMS.SYMBOLS[winSymbolIndex];
} else {
reels.forEach(reel => reel.finalPosition = Math.floor(Math.random() * PARAMS.SYMBOLS.length));
}
setTimeout(() => {
reels.forEach(reel => {
reel.spinning = false;
reel.position = reel.finalPosition;
reel.offset = 0;
});
spinning = false;
if (isWin) triggerWin();
}, PARAMS.SPIN_DURATION);
}
function triggerWin() {
switch (winningSymbol) {
case '💎': winAnimation = diamondWinAnimation; break;
case '🔮': winAnimation = crystalBallWinAnimation; break;
case '🎭': winAnimation = masksWinAnimation; break;
case '🎨': winAnimation = paletteWinAnimation; break;
case '🎬': winAnimation = clapperboardWinAnimation; break;
case '🏆': winAnimation = trophyWinAnimation; break;
}
}
function diamondWinAnimation(ctx, progress) {
const size = 40 * Math.sin(progress * Math.PI);
ctx.font = `${size}px Arial`;
ctx.fillStyle = `rgba(255, 255, 255, ${1 - progress})`;
ctx.fillText('💎', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
// Sparkling effect
if (Math.random() < 0.3) {
particles.push({
x: Math.random() * PARAMS.ANIMATION_WIDTH,
y: Math.random() * PARAMS.ANIMATION_HEIGHT,
size: Math.random() * 3,
color: `hsl(${Math.random() * 360}, 100%, 50%)`,
life: 1
});
}
}
function crystalBallWinAnimation(ctx, progress) {
ctx.fillStyle = `rgba(128, 0, 128, ${0.5 - progress * 0.5})`;
ctx.beginPath();
ctx.arc(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2, 30 * progress, 0, Math.PI * 2);
ctx.fill();
// Swirling mist effect
for (let i = 0; i < 5; i++) {
const angle = progress * Math.PI * 2 + i * Math.PI / 2.5;
const x = PARAMS.ANIMATION_WIDTH / 2 + Math.cos(angle) * 20 * progress;
const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(angle) * 20 * progress;
ctx.font = '12px Arial';
ctx.fillStyle = `rgba(255, 255, 255, ${1 - progress})`;
ctx.fillText('✨', x, y);
}
ctx.font = '24px Arial';
ctx.fillStyle = 'white';
ctx.fillText('🔮', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
}
function masksWinAnimation(ctx, progress) {
const angle = progress * Math.PI * 4;
ctx.save();
ctx.translate(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
// Comedy mask
ctx.rotate(angle);
ctx.font = '24px Arial';
ctx.fillStyle = `rgba(255, 255, 0, ${1 - progress})`;
ctx.fillText('😂', 0, -20);
// Tragedy mask
ctx.rotate(Math.PI);
ctx.fillStyle = `rgba(0, 0, 255, ${1 - progress})`;
ctx.fillText('😭', 0, -20);
ctx.restore();
// Confetti effect
if (Math.random() < 0.2) {
particles.push({
x: Math.random() * PARAMS.ANIMATION_WIDTH,
y: -5,
size: Math.random() * 5 + 2,
color: Math.random() < 0.5 ? 'yellow' : 'blue',
life: 1,
vy: Math.random() * 2 + 1
});
}
}
function paletteWinAnimation(ctx, progress) {
const colors = ['red', 'blue', 'green', 'yellow', 'purple'];
colors.forEach((color, i) => {
const angle = (i / colors.length + progress) * Math.PI * 4;
const x = PARAMS.ANIMATION_WIDTH / 2 + Math.cos(angle) * 30 * Math.sin(progress * Math.PI);
const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(angle) * 30 * Math.sin(progress * Math.PI);
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.fill();
// Paint splatter effect
if (Math.random() < 0.1) {
particles.push({
x: x,
y: y,
size: Math.random() * 3 + 1,
color: color,
life: 1,
vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2
});
}
});
ctx.font = '24px Arial';
ctx.fillStyle = 'white';
ctx.fillText('🎨', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
}
function clapperboardWinAnimation(ctx, progress) {
ctx.save();
ctx.translate(PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2);
// Clapping effect
const clapAngle = Math.sin(progress * Math.PI * 8) * 0.3;
ctx.rotate(clapAngle);
ctx.font = '24px Arial';
ctx.fillStyle = 'white';
ctx.fillText('🎬', 0, 0);
// Starburst effect
for (let i = 0; i < 8; i++) {
const angle = i * Math.PI / 4 + progress * Math.PI * 2;
const length = 20 + Math.sin(progress * Math.PI) * 10;
ctx.strokeStyle = `rgba(255, 255, 0, ${1 - progress})`;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(Math.cos(angle) * length, Math.sin(angle) * length);
ctx.stroke();
}
ctx.restore();
}
function trophyWinAnimation(ctx, progress) {
const y = PARAMS.ANIMATION_HEIGHT / 2 + Math.sin(progress * Math.PI * 2) * 10;
ctx.font = '24px Arial';
ctx.fillStyle = `rgba(255, 215, 0, ${1 - progress})`;
ctx.fillText('🏆', PARAMS.ANIMATION_WIDTH / 2, y);
// Fireworks effect
if (Math.random() < 0.2) {
const x = Math.random() * PARAMS.ANIMATION_WIDTH;
const y = PARAMS.ANIMATION_HEIGHT;
for (let i = 0; i < 20; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 2 + 1;
particles.push({
x: x,
y: y,
size: Math.random() * 2 + 1,
color: `hsl(${Math.random() * 360}, 100%, 50%)`,
life: 1,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 2
});
}
}
ctx.font = '16px Arial';
ctx.fillStyle = `rgba(255, 255, 255, ${progress})`;
ctx.fillText('JACKPOT!', PARAMS.ANIMATION_WIDTH / 2, PARAMS.ANIMATION_HEIGHT / 2 + 30);
}
function updateAnimation() {
reels.forEach(reel => {
if (reel.spinning) {
reel.position += PARAMS.SPIN_SPEED;
if (reel.offset > 0) {
reel.offset -= 1;
}
}
});
if (winAnimation) {
winAnimationProgress += PARAMS.WIN_ANIMATION_SPEED;
if (winAnimationProgress >= 1) {
winAnimation = null;
winAnimationProgress = 0;
}
}
// Update particles
particles = particles.filter(p => {
p.x += p.vx || 0;
p.y += p.vy || 0;
p.life -= PARAMS.PARTICLE_DECAY_RATE;
return p.life > 0;
});
// Limit the number of particles
if (particles.length > PARAMS.MAX_PARTICLES) {
particles.splice(0, particles.length - PARAMS.MAX_PARTICLES);
}
}
@@ -75,32 +369,34 @@ function addDenoisingTextAnimatedDisplay(node) {
ctx.save();
const nodeWidth = this.size[0];
const baseXOffset = (nodeWidth - ANIMATION_WIDTH) / 2;
ctx.translate(baseXOffset + xOffset, -ANIMATION_HEIGHT + yOffset);
const baseXOffset = (nodeWidth - PARAMS.ANIMATION_WIDTH) / 2;
ctx.translate(baseXOffset + PARAMS.X_OFFSET, -PARAMS.ANIMATION_HEIGHT + PARAMS.Y_OFFSET);
ctx.clearRect(0, 0, ANIMATION_WIDTH, ANIMATION_HEIGHT);
// Draw appearing pixels with color and pulse effect
for (let y = 0; y < GRID_HEIGHT; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
const { value, hue } = grid[y][x];
if (value > 0) {
const pulseEffect = 0.7 + 0.3 * Math.sin(animationProgress * 10 + x * 0.1 + y * 0.1);
const alpha = value * pulseEffect;
ctx.fillStyle = `hsla(${hue}, 100%, 50%, ${alpha})`;
ctx.fillRect(x * PIXEL_SIZE, y * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE);
}
}
}
drawModernSlotMachine(ctx);
ctx.restore();
updateGrid();
updateAnimation();
this.setDirtyCanvas(true);
requestAnimationFrame(() => this.setDirtyCanvas(true));
}
};
node.onMouseDown = function(event) {
const nodeWidth = this.size[0];
const baseXOffset = (nodeWidth - PARAMS.ANIMATION_WIDTH) / 2;
const localX = event.canvasX - this.pos[0] - baseXOffset - PARAMS.X_OFFSET;
const localY = event.canvasY - this.pos[1] + PARAMS.ANIMATION_HEIGHT - PARAMS.Y_OFFSET;
if (localX >= PARAMS.ANIMATION_WIDTH / 2 - PARAMS.BUTTON_WIDTH / 2 &&
localX <= PARAMS.ANIMATION_WIDTH / 2 + PARAMS.BUTTON_WIDTH / 2 &&
localY >= PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET &&
localY <= PARAMS.ANIMATION_HEIGHT - PARAMS.BUTTON_Y_OFFSET + PARAMS.BUTTON_HEIGHT) {
spin();
return true;
}
};
node.setDirtyCanvas(true);
}