Everything is going well but ihe particles never form completely on the black area of two.jpeg, this image is of words those words are in black letters the particles should form them letters in the image you should never see the second image but you should see the particles form it does that make any sense? import numpy as np
import os
import time
from PIL import Image
from moviepy.editor import VideoClip
from moviepy.video.fx.all import fadein, fadeout
from tqdm import tqdm

# Paths
image_one_path = '/home/daddy/morph/one.jpeg'
image_two_path = '/home/daddy/morph/two.jpeg'  # Changed to two.jpeg
output_path = '/home/daddy/morph/output'
output_filename = f'morph{int(time.time())}.mp4'

# Function to create each frame of the video
def make_frame(t):
    if t < duration1:
        # Display the first image
        frame_array = img1_array.copy()
    elif t < duration1 + duration2:
        # Morphing effect with particle explosion and implosion
        progress = (t - duration1) / duration2  # Normalize progress to [0, 1]

        # Create an empty frame with transparency
        frame_array = np.zeros_like(img1_array)

        for idx, (x, y) in enumerate(particle_positions):
            # Extract the patch from the first image
            patch = img1_array[y:y+particle_size, x:x+particle_size].copy()

            # Explosion offsets
            offset_x, offset_y = explosion_offsets[idx]

            if progress < 0.5:
                # Explosion phase
                explosion_progress = progress * 2  # Scale progress to [0, 1]
                current_x = x + explosion_progress * offset_x
                current_y = y + explosion_progress * offset_y
            else:
                # Implosion phase
                implosion_progress = (progress - 0.5) * 2  # Scale progress to [0, 1]

                # Target positions in the second image
                target_x, target_y = particle_to_target_position.get((x, y), (x, y))

                # Current position moves from exploded position to target position
                current_x = (x + offset_x) + implosion_progress * (target_x - (x + offset_x))
                current_y = (y + offset_y) + implosion_progress * (target_y - (y + offset_y))

            # Convert positions to integers and ensure they stay within frame boundaries
            current_x = int(np.clip(current_x, 0, width - particle_size))
            current_y = int(np.clip(current_y, 0, height - particle_size))

            # Place the current patch onto the frame
            frame_array[current_y:current_y+particle_size, current_x:current_x+particle_size] = patch

    else:
        # Display the second image
        frame_array = img2_array.copy()

    # Convert the NumPy array back to an image
    frame_image = Image.fromarray(frame_array, 'RGBA')
    # Return frame as an RGB image (MoviePy expects RGB format)
    return np.array(frame_image.convert('RGB'))

# Main program
if __name__ == "__main__":
    # Input parameters with default values
    duration1 = int(input("Enter duration for first image display (seconds, default 5): ") or 5)
    duration2 = int(input("Enter duration for morphing effect (seconds, default 5): ") or 5)
    duration3 = int(input("Enter duration for second image display (seconds, default 5): ") or 5)
    particle_size = int(input("Enter particle size (pixels, default 4): ") or 4)

    # Load images and ensure they are in RGBA format
    img1 = Image.open(image_one_path).convert('RGBA').resize((1024, 1024))
    img2 = Image.open(image_two_path).convert('RGBA').resize((1024, 1024))

    # Convert images to NumPy arrays
    img1_array = np.array(img1)
    img2_array = np.array(img2)

    # Get image dimensions
    width, height = img1.size

    # Calculate the number of particles in the x and y directions
    particles_in_row = width // particle_size
    particles_in_col = height // particle_size

    # Prepare lists to store particle positions and explosion offsets
    particle_positions = []
    explosion_offsets = []

    # Generate particle positions and explosion offsets
    for i in range(particles_in_col):
        for j in range(particles_in_row):
            x = j * particle_size
            y = i * particle_size
            particle_positions.append((x, y))

            # Randomize explosion offsets for more dynamic effect
            offset_x = np.random.uniform(-width, width)
            offset_y = np.random.uniform(-height, height)
            explosion_offsets.append((offset_x, offset_y))

    # Detect target pixels in the second image (e.g., black pixels)
    # We'll use pixels where the sum of RGB values is less than a threshold (i.e., dark pixels)
    target_threshold = int(input("Enter threshold for target pixel detection (0-765, default 100): ") or 100)

    # Sum the RGB channels
    img2_rgb_sum = img2_array[:, :, :3].sum(axis=2)

    # Find positions where the pixel is darker than the threshold
    target_pixels = np.where(img2_rgb_sum < target_threshold)

    # Zip the coordinates
    target_pixel_positions = list(zip(target_pixels[1], target_pixels[0]))  # (x, y)

    # Handle cases where there are more particles than target pixels or vice versa
    num_particles = len(particle_positions)
    num_targets = len(target_pixel_positions)

    # Map particles to target pixels
    particle_to_target_position = {}

    if num_particles <= num_targets:
        # Assign each particle to a target pixel
        selected_targets = np.random.choice(num_targets, num_particles, replace=False)
        for idx, particle_pos in enumerate(particle_positions):
            target_idx = selected_targets[idx]
            target_pos = target_pixel_positions[target_idx]
            particle_to_target_position[particle_pos] = target_pos
    else:
        # There are more particles than target pixels
        # Some particles will fade out; map particles to targets randomly
        selected_targets = np.random.choice(num_targets, num_particles)
        for idx, particle_pos in enumerate(particle_positions):
            target_idx = selected_targets[idx]
            target_pos = target_pixel_positions[target_idx]
            particle_to_target_position[particle_pos] = target_pos

    total_duration = duration1 + duration2 + duration3

    # Create the video clip
    animation = VideoClip(make_frame, duration=total_duration)

    # Apply fade-in and fade-out effects
    animation = fadein(animation, 1.5).fx(fadeout, 1.5)

    # Add progress bar with TQDM
    print("Rendering Video:")
    with tqdm(total=int(total_duration * 24)) as pbar:
        animation = animation.set_fps(24)  # Set FPS to 24
        animation = animation.fl(lambda gf, t: (pbar.update(), gf(t))[1], apply_to="mask")

        # Write the video file
        animation.write_videofile(os.path.join(output_path, output_filename))