main
  1#!/usr/bin/env python3
  2"""
  3Kaleidoscope Effect - Create mirror/rotation effects.
  4
  5Apply kaleidoscope effects to frames or objects for psychedelic visuals.
  6"""
  7
  8import sys
  9from pathlib import Path
 10import math
 11
 12sys.path.append(str(Path(__file__).parent.parent))
 13
 14from PIL import Image, ImageOps, ImageDraw
 15import numpy as np
 16
 17
 18def apply_kaleidoscope(frame: Image.Image, segments: int = 8,
 19                       center: tuple[int, int] | None = None) -> Image.Image:
 20    """
 21    Apply kaleidoscope effect by mirroring/rotating frame sections.
 22
 23    Args:
 24        frame: Input frame
 25        segments: Number of mirror segments (4, 6, 8, 12 work well)
 26        center: Center point for effect (None = frame center)
 27
 28    Returns:
 29        Frame with kaleidoscope effect
 30    """
 31    width, height = frame.size
 32
 33    if center is None:
 34        center = (width // 2, height // 2)
 35
 36    # Create output frame
 37    output = Image.new('RGB', (width, height))
 38
 39    # Calculate angle per segment
 40    angle_per_segment = 360 / segments
 41
 42    # For simplicity, we'll create a radial mirror effect
 43    # A full implementation would rotate and mirror properly
 44    # This is a simplified version that creates interesting patterns
 45
 46    # Convert to numpy for easier manipulation
 47    frame_array = np.array(frame)
 48    output_array = np.zeros_like(frame_array)
 49
 50    center_x, center_y = center
 51
 52    # Create wedge mask and mirror it
 53    for y in range(height):
 54        for x in range(width):
 55            # Calculate angle from center
 56            dx = x - center_x
 57            dy = y - center_y
 58
 59            angle = (math.degrees(math.atan2(dy, dx)) + 180) % 360
 60            distance = math.sqrt(dx * dx + dy * dy)
 61
 62            # Which segment does this pixel belong to?
 63            segment = int(angle / angle_per_segment)
 64
 65            # Mirror angle within segment
 66            segment_angle = angle % angle_per_segment
 67            if segment % 2 == 1:  # Mirror every other segment
 68                segment_angle = angle_per_segment - segment_angle
 69
 70            # Calculate source position
 71            source_angle = segment_angle + (segment // 2) * angle_per_segment * 2
 72            source_angle_rad = math.radians(source_angle - 180)
 73
 74            source_x = int(center_x + distance * math.cos(source_angle_rad))
 75            source_y = int(center_y + distance * math.sin(source_angle_rad))
 76
 77            # Bounds check
 78            if 0 <= source_x < width and 0 <= source_y < height:
 79                output_array[y, x] = frame_array[source_y, source_x]
 80            else:
 81                output_array[y, x] = frame_array[y, x]
 82
 83    return Image.fromarray(output_array)
 84
 85
 86def apply_simple_mirror(frame: Image.Image, mode: str = 'quad') -> Image.Image:
 87    """
 88    Apply simple mirror effect (faster than full kaleidoscope).
 89
 90    Args:
 91        frame: Input frame
 92        mode: 'horizontal', 'vertical', 'quad' (4-way), 'radial'
 93
 94    Returns:
 95        Mirrored frame
 96    """
 97    width, height = frame.size
 98    center_x, center_y = width // 2, height // 2
 99
100    if mode == 'horizontal':
101        # Mirror left half to right
102        left_half = frame.crop((0, 0, center_x, height))
103        left_flipped = ImageOps.mirror(left_half)
104        result = frame.copy()
105        result.paste(left_flipped, (center_x, 0))
106        return result
107
108    elif mode == 'vertical':
109        # Mirror top half to bottom
110        top_half = frame.crop((0, 0, width, center_y))
111        top_flipped = ImageOps.flip(top_half)
112        result = frame.copy()
113        result.paste(top_flipped, (0, center_y))
114        return result
115
116    elif mode == 'quad':
117        # 4-way mirror (top-left quadrant mirrored to all)
118        quad = frame.crop((0, 0, center_x, center_y))
119
120        result = Image.new('RGB', (width, height))
121
122        # Top-left (original)
123        result.paste(quad, (0, 0))
124
125        # Top-right (horizontal mirror)
126        result.paste(ImageOps.mirror(quad), (center_x, 0))
127
128        # Bottom-left (vertical mirror)
129        result.paste(ImageOps.flip(quad), (0, center_y))
130
131        # Bottom-right (both mirrors)
132        result.paste(ImageOps.flip(ImageOps.mirror(quad)), (center_x, center_y))
133
134        return result
135
136    else:
137        return frame
138
139
140def create_kaleidoscope_animation(
141    base_frame: Image.Image | None = None,
142    num_frames: int = 30,
143    segments: int = 8,
144    rotation_speed: float = 1.0,
145    width: int = 480,
146    height: int = 480
147) -> list[Image.Image]:
148    """
149    Create animated kaleidoscope effect.
150
151    Args:
152        base_frame: Frame to apply effect to (or None for demo pattern)
153        num_frames: Number of frames
154        segments: Kaleidoscope segments
155        rotation_speed: How fast pattern rotates (0.5-2.0)
156        width: Frame width if generating demo
157        height: Frame height if generating demo
158
159    Returns:
160        List of frames with kaleidoscope effect
161    """
162    frames = []
163
164    # Create demo pattern if no base frame
165    if base_frame is None:
166        base_frame = Image.new('RGB', (width, height), (255, 255, 255))
167        draw = ImageDraw.Draw(base_frame)
168
169        # Draw some colored shapes
170        from core.color_palettes import get_palette
171        palette = get_palette('vibrant')
172
173        colors = [palette['primary'], palette['secondary'], palette['accent']]
174
175        for i, color in enumerate(colors):
176            x = width // 2 + int(100 * math.cos(i * 2 * math.pi / 3))
177            y = height // 2 + int(100 * math.sin(i * 2 * math.pi / 3))
178            draw.ellipse([x - 40, y - 40, x + 40, y + 40], fill=color)
179
180    # Rotate base frame and apply kaleidoscope
181    for i in range(num_frames):
182        angle = (i / num_frames) * 360 * rotation_speed
183
184        # Rotate base frame
185        rotated = base_frame.rotate(angle, resample=Image.BICUBIC)
186
187        # Apply kaleidoscope
188        kaleido_frame = apply_kaleidoscope(rotated, segments=segments)
189
190        frames.append(kaleido_frame)
191
192    return frames
193
194
195# Example usage
196if __name__ == '__main__':
197    from core.gif_builder import GIFBuilder
198
199    print("Creating kaleidoscope GIF...")
200
201    builder = GIFBuilder(width=480, height=480, fps=20)
202
203    # Create kaleidoscope animation
204    frames = create_kaleidoscope_animation(
205        num_frames=40,
206        segments=8,
207        rotation_speed=0.5
208    )
209
210    builder.add_frames(frames)
211    builder.save('kaleidoscope_test.gif', num_colors=128)