|
| 1 | +## Stars.py ########## |
| 2 | +## Daren Neyland ## |
| 3 | +## C-FROM: gaw1ik ## |
| 4 | +## SITE: (GitHub) ## |
| 5 | +## DATE: 1/15/2026 ## |
| 6 | +###################### |
| 7 | + |
| 8 | +# -*- coding: utf-8 -*- |
| 9 | +##""" |
| 10 | +##Created on Tue Jul 7 00:08:46 2020 |
| 11 | +## |
| 12 | +##This script generates a GIF file which contains an animation |
| 13 | +##resembling things ranging from stars to planets to biological |
| 14 | +##cells. |
| 15 | +## |
| 16 | +##The variables in the Inputs section can |
| 17 | +##be adjusted to produce a wide range of visual results. The Inputs |
| 18 | +##section functions as a basic user interface, and only |
| 19 | +##the Inputs should be needed to operate the script. |
| 20 | +## |
| 21 | +##The GIF |
| 22 | +##will automatically be saved to the working directory, so |
| 23 | +##please be mindful of where that is. |
| 24 | +## |
| 25 | +##@author: Brian |
| 26 | +##""" |
| 27 | + |
| 28 | +#%% Setup Environment |
| 29 | + |
| 30 | +from IPython import get_ipython |
| 31 | +get_ipython().magic('reset -sf') |
| 32 | + |
| 33 | +import numpy as np |
| 34 | + |
| 35 | +import math |
| 36 | + |
| 37 | +import cairo |
| 38 | + |
| 39 | +from PIL import Image |
| 40 | + |
| 41 | +from random import seed, choice, random |
| 42 | + |
| 43 | +#%% Inputs |
| 44 | + |
| 45 | +filename = 'stars_1.gif' |
| 46 | + |
| 47 | +# GIF Options |
| 48 | +s = 1 # width and height of frame |
| 49 | +nframes = 200 # number of frames |
| 50 | +frame_duration = 1000/24*3 |
| 51 | +pixel_scale = 400 |
| 52 | + |
| 53 | +# Size/Arrangement Options |
| 54 | +seed_number = 8 |
| 55 | +n_stars = 50 |
| 56 | +r_seq = np.arange(0.0010,0.0040,0.0001)*2.5# choice sequence for initial radii |
| 57 | +# r_seq = [0.010]*50 + [0.030]*20 + [0.140]*5 |
| 58 | + |
| 59 | +# Twinlke Options |
| 60 | +# bright_seq = np.arange(0.6, 0.8, 0.05) |
| 61 | +bright_seq = [0.8]*5 + [1] |
| 62 | +# bright_seq = [1] |
| 63 | + |
| 64 | +# Jitter Options |
| 65 | +jitter_mode = 'jitter' |
| 66 | +jit_size = 0.0005 |
| 67 | +jit_seq = [-jit_size] + [0] + [jit_size] |
| 68 | + |
| 69 | +# Color Options |
| 70 | +color_mode = 'constant' |
| 71 | +fill_mode = 'fill' |
| 72 | +line_thickness = 0.01 |
| 73 | +color_const = [1,1,0.8] |
| 74 | +c_seq = np.arange(0.5,1.05,0.05) |
| 75 | + |
| 76 | +#%% Define Functions |
| 77 | + |
| 78 | +""" This function chooses the initial color for the bubbles |
| 79 | +in one of two modes: constant or random""" |
| 80 | + |
| 81 | +def choose_color(color_mode,c_seq): |
| 82 | + if color_mode=='constant': |
| 83 | + b = choice([0.3,0.6,1.0]) |
| 84 | + color = [color_const[0]*b,color_const[1]*b,color_const[2]*b] # some constant color |
| 85 | + elif color_mode=='random': |
| 86 | + # c_seq = np.arange(0.5,1.05,0.05) |
| 87 | + color = [choice(c_seq),choice(c_seq),choice(c_seq)] |
| 88 | + return color |
| 89 | + |
| 90 | +#%% Make star Class |
| 91 | + |
| 92 | +class star: |
| 93 | + |
| 94 | + # Initializer / Instance Attributes |
| 95 | + def __init__(self, base_center, radius, base_color): |
| 96 | + |
| 97 | + self.base_center = base_center |
| 98 | + self.inst_center = base_center |
| 99 | + self.radius = radius |
| 100 | + self.base_color = base_color |
| 101 | + self.inst_color = base_color |
| 102 | + |
| 103 | + def draw(self,ctx,fill_mode): |
| 104 | + |
| 105 | + ctx.set_source_rgb(self.inst_color[0], self.inst_color[1], self.inst_color[2]) |
| 106 | + ctx.arc(self.inst_center[0], self.inst_center[1], self.radius, 0, 2*math.pi) |
| 107 | + if (fill_mode=='fill'): |
| 108 | + ctx.fill() |
| 109 | + elif (fill_mode=='outline'): |
| 110 | + ctx.set_line_width(line_thickness) |
| 111 | + ctx.stroke() |
| 112 | + |
| 113 | + def twinkle(self,bright_seq): |
| 114 | + |
| 115 | + b = choice(bright_seq) |
| 116 | + self.inst_color = [self.base_color[0]*b, self.base_color[1]*b, self.base_color[2]*b] |
| 117 | + |
| 118 | + def jitter(self,jit_size,jit_seq,jitter_mode): |
| 119 | + if (jitter_mode=='jitter'): |
| 120 | + cx, cy = self.base_center[0], self.base_center[1] |
| 121 | + elif (jitter_mode=='walk'): |
| 122 | + cx, cy = self.inst_center[0], self.inst_center[1] |
| 123 | + jit_x, jit_y = choice(jit_seq), choice(jit_seq) |
| 124 | + self.inst_center = [cx+jit_x, cy+jit_y] |
| 125 | + |
| 126 | +#%% Function for converting cairo surface to PIL image |
| 127 | + |
| 128 | +""" This function converts a cairo surface to a |
| 129 | +PIL image. The drawings below are done using cairo, |
| 130 | +and thus are contained within so-called cairo surfaces. |
| 131 | +They must be converted back to PIL Images so that the PIL |
| 132 | +save function can be used to save the animation |
| 133 | +as a GIF.""" |
| 134 | + |
| 135 | +def pilImageFromCairoSurface( surface ): |
| 136 | + cairoFormat = surface.get_format() |
| 137 | + if cairoFormat == cairo.FORMAT_ARGB32: |
| 138 | + pilMode = 'RGB' |
| 139 | + # Cairo has ARGB. Convert this to RGB for PIL which supports only RGB or |
| 140 | + # RGBA. |
| 141 | + argbArray = np.frombuffer( bytes(surface.get_data()), 'c' ).reshape( -1, 4 ) |
| 142 | + rgbArray = argbArray[ :, 2::-1 ] |
| 143 | + pilData = rgbArray.reshape( -1 ).tostring() |
| 144 | + else: |
| 145 | + raise ValueError( 'Unsupported cairo format: %d' % cairoFormat ) |
| 146 | + pilImage = Image.frombuffer( pilMode, |
| 147 | + ( surface.get_width(), surface.get_height() ), pilData, "raw", |
| 148 | + pilMode, 0, 1 ) |
| 149 | + pilImage = pilImage.convert( 'RGB' ) |
| 150 | + return pilImage |
| 151 | + |
| 152 | +#%% Determine random starting positions for stars |
| 153 | + |
| 154 | +r_seq = list(r_seq) |
| 155 | +bright_seq = list(bright_seq) |
| 156 | +stars = [] |
| 157 | +seed(seed_number) |
| 158 | + |
| 159 | +for _ in range(n_stars): |
| 160 | + cx = random()*s |
| 161 | + cy = random()*s |
| 162 | + r0 = choice(r_seq) |
| 163 | + b = choice(bright_seq) |
| 164 | + c0 = choose_color(color_mode,c_seq) |
| 165 | + # c0 = (np.uint8(choice(c_seq)*255),np.uint8(choice(c_seq)*255),np.uint8(choice(c_seq)*255)) |
| 166 | + new_starro = star( [cx, cy], r0, c0 ) |
| 167 | + stars.append(new_starro) |
| 168 | + |
| 169 | +#%% Make the images for the GIF |
| 170 | +images = [] |
| 171 | +seed(1) |
| 172 | + |
| 173 | +# Setup "frames" list (frame numbers) |
| 174 | +frames = [] |
| 175 | +for f in range(nframes): |
| 176 | + frames.append(f) |
| 177 | + |
| 178 | +# Make each frame |
| 179 | +for f in frames: |
| 180 | + |
| 181 | + # initialize the blank canvas each frame |
| 182 | + surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, s*pixel_scale, s*pixel_scale) |
| 183 | + ctx = cairo.Context(surface) |
| 184 | + ctx.scale(pixel_scale, pixel_scale) # Normalizing the canvas |
| 185 | + |
| 186 | + # Draw each star and update their attributes |
| 187 | + for i, starro in enumerate(stars, 1): |
| 188 | + |
| 189 | + # Draw the star |
| 190 | + starro.draw(ctx,fill_mode) |
| 191 | + |
| 192 | + # Twinkle the star |
| 193 | + starro.twinkle(bright_seq) |
| 194 | + |
| 195 | + # Jitter the star for the next frame |
| 196 | + starro.jitter(jit_size,jit_seq,jitter_mode) |
| 197 | + |
| 198 | + # Move the star for the next frame |
| 199 | + # starro.walk(walk_size) |
| 200 | + |
| 201 | + im = pilImageFromCairoSurface(surface) |
| 202 | + |
| 203 | + images.append(im) |
| 204 | + |
| 205 | +#%% Save as GIF |
| 206 | + |
| 207 | +images[0].save(filename, |
| 208 | + save_all=True, |
| 209 | + append_images=images[1:], |
| 210 | + optimize=False, |
| 211 | + duration=frame_duration, |
| 212 | + loop=0) |
| 213 | + |
| 214 | +print('DONE') |
0 commit comments