|
| 1 | +# https://github.com/Gouvernathor/renpy-ChromaGlitch |
| 2 | +init python: |
| 3 | + class glitch(renpy.Displayable): |
| 4 | + """ |
| 5 | + `randomkey` |
| 6 | + Follows the rules of the random modume's seed function. |
| 7 | + If not set, a random seed is generated when the transform is applied, |
| 8 | + and stays the same afterwards. |
| 9 | + If you want the effect to be random for each render operation, set to None. |
| 10 | +
|
| 11 | + `chroma` |
| 12 | + Boolean, whether to apply the chromatic aberration effect. |
| 13 | +
|
| 14 | + `minbandheight` |
| 15 | + Minimum height of each slice. |
| 16 | +
|
| 17 | + `offset` |
| 18 | + The offset of each slice will be between -offset and offset pixels. |
| 19 | +
|
| 20 | + `nslices` |
| 21 | + Number of slicings to do (the number of slices will be nslices + 1). |
| 22 | + Setting this to 0 is not supported. |
| 23 | + None (the default) makes it random. |
| 24 | + """ |
| 25 | + |
| 26 | + NotSet = object() |
| 27 | + |
| 28 | + def __init__(self, child, *, randomkey=NotSet, chroma=True, minbandheight=1, offset=30, nslices=None, **properties): |
| 29 | + super().__init__(**properties) |
| 30 | + self.child = renpy.displayable(child) |
| 31 | + if randomkey is self.NotSet: |
| 32 | + randomkey = renpy.random.random() |
| 33 | + self.randomkey = randomkey |
| 34 | + self.chroma = chroma |
| 35 | + self.minbandheight = minbandheight |
| 36 | + self.offset = offset |
| 37 | + self.nslices = nslices |
| 38 | + |
| 39 | + def render(self, width, height, st, at): |
| 40 | + child = self.child |
| 41 | + child_render = renpy.render(child, width, height, st, at) |
| 42 | + cwidth, cheight = child_render.get_size() |
| 43 | + if not (cwidth and cheight): |
| 44 | + return child_render |
| 45 | + render = renpy.Render(cwidth, cheight) |
| 46 | + randomobj = renpy.random.Random(self.randomkey) |
| 47 | + chroma = self.chroma and renpy.display.render.models |
| 48 | + offset = self.offset |
| 49 | + minbandheight = self.minbandheight |
| 50 | + nslices = self.nslices |
| 51 | + if nslices is None: |
| 52 | + nslices = min(int(cheight/minbandheight), randomobj.randrange(10, 21)) |
| 53 | + |
| 54 | + theights = sorted(randomobj.randrange(cheight+1) for k in range(nslices)) # y coordinates demarcating all the strips |
| 55 | + offt = 0 # next strip's lateral offset |
| 56 | + fheight = 0 # sum of the size of all the strips added this far |
| 57 | + while fheight<cheight: |
| 58 | + # theight is the height of this particular strip |
| 59 | + if theights: |
| 60 | + theight = max(theights.pop(0)-fheight, minbandheight) |
| 61 | + else: |
| 62 | + theight = cheight-theight |
| 63 | + |
| 64 | + slice_render = child_render.subsurface((0, fheight, cwidth, theight)) |
| 65 | + |
| 66 | + if offt and chroma: |
| 67 | + for color_mask, chponder in (((False, False, True, True), 1.25), ((False, True, False, True), 1.), ((True, False, False, True), .75)): |
| 68 | + chroma_render = slice_render.subsurface((0, 0, cwidth, theight)) |
| 69 | + chroma_render.add_property("gl_color_mask", color_mask) |
| 70 | + render.blit(chroma_render, (round(offt*chponder), round(fheight))) |
| 71 | + |
| 72 | + else: |
| 73 | + render.blit(slice_render, (offt, round(fheight))) |
| 74 | + |
| 75 | + fheight += theight |
| 76 | + if offt: |
| 77 | + offt = 0 |
| 78 | + else: |
| 79 | + offt = randomobj.randrange(-offset, offset+1) |
| 80 | + |
| 81 | + return render |
| 82 | + |
| 83 | + def visit(self): |
| 84 | + return [self.child] |
| 85 | + |
| 86 | + class animated_glitch(glitch): |
| 87 | + """ |
| 88 | + Glitches in a way that changes over time, but consistently, unlike glitch(randomkey=None). |
| 89 | + Sets a timeout at the beginning. At the end of each timeout, sets a new one and changes the glitching. |
| 90 | +
|
| 91 | + `timeout_base` |
| 92 | + The time in seconds between two changes of the glitching. |
| 93 | + Can be either single float (or integer) value, or a tuple of two values between which the timeout |
| 94 | + will be chosen following a uniform distribution, respecting the randomkey. |
| 95 | + Defaults to .1 second. |
| 96 | +
|
| 97 | + `timeout_vanilla` |
| 98 | + The length in seconds of the periods of time over which the child will be shown without any glitch. |
| 99 | + Same values and meaning as `timeout_base`, except that if False, the child will never be shown without glitching. |
| 100 | + If `timeout_base` is passed, defaults to the same value. Otherwise, defaults to (1, 3). |
| 101 | + """ |
| 102 | + |
| 103 | + def __init__(self, *args, timeout_base=None, timeout_vanilla=None, **kwargs): |
| 104 | + super().__init__(*args, **kwargs) |
| 105 | + if timeout_vanilla is None: |
| 106 | + if timeout_base is None: |
| 107 | + timeout_vanilla = (1, 3) |
| 108 | + else: |
| 109 | + timeout_vanilla = timeout_base |
| 110 | + if timeout_base is None: |
| 111 | + timeout_base = .1 |
| 112 | + |
| 113 | + self.timeout_base = timeout_base |
| 114 | + self.timeout_vanilla = timeout_vanilla |
| 115 | + self.set_timeout(vanilla=(timeout_vanilla is not False)) |
| 116 | + |
| 117 | + def set_timeout(self, vanilla, st=0): |
| 118 | + if vanilla: |
| 119 | + timeout = self.timeout_vanilla |
| 120 | + else: |
| 121 | + timeout = self.timeout_base |
| 122 | + |
| 123 | + if not isinstance(timeout, (int, float)): |
| 124 | + timeout = renpy.random.Random(self.randomkey).uniform(*timeout) |
| 125 | + |
| 126 | + self.timeout = timeout + st |
| 127 | + self.showing_vanilla = vanilla |
| 128 | + |
| 129 | + def render(self, width, height, st, at): |
| 130 | + vanilla = self.showing_vanilla |
| 131 | + |
| 132 | + if st >= self.timeout: |
| 133 | + randomkey = self.randomkey |
| 134 | + randomobj = renpy.random.Random(randomkey) |
| 135 | + self.randomkey = randomobj.random() |
| 136 | + |
| 137 | + # determine whether to show vanilla or not |
| 138 | + if vanilla or (self.timeout_vanilla is False): |
| 139 | + # if we were showing it or if showing it is disabled |
| 140 | + vanilla = False |
| 141 | + else: |
| 142 | + vanilla = (randomobj.random() < .3) |
| 143 | + |
| 144 | + self.set_timeout(vanilla, st) |
| 145 | + |
| 146 | + renpy.redraw(self, st-self.timeout) |
| 147 | + |
| 148 | + if vanilla: |
| 149 | + return renpy.render(self.child, width, height, st, at) |
| 150 | + else: |
| 151 | + return super().render(width, height, st, at) |
| 152 | + |
| 153 | + class squares_glitch(renpy.Displayable): |
| 154 | + """ |
| 155 | + `squareside` |
| 156 | + The size, in pixels, of the side of the squares the child image will be cut to. This will |
| 157 | + be adjusted so that all the "squares" (rectangles, really) have the same width and the |
| 158 | + same height, and that none is cut at the borders of the image. Defaults to 20 pixels. |
| 159 | +
|
| 160 | + `chroma` |
| 161 | + The probability for each square to get a chromatic effect. Defaults to .25. |
| 162 | +
|
| 163 | + `permutes` |
| 164 | + The percentage of squares which will be moved to another square's place. If not passed, |
| 165 | + defaults to a random value between .1 and .4. |
| 166 | + """ |
| 167 | + |
| 168 | + NotSet = object() |
| 169 | + |
| 170 | + def __init__(self, child, *args, randomkey=NotSet, **kwargs): |
| 171 | + super().__init__() |
| 172 | + self.child = renpy.displayable(child) |
| 173 | + self.args = args |
| 174 | + if randomkey is self.NotSet: |
| 175 | + randomkey = renpy.random.random() |
| 176 | + self.randomkey = randomkey |
| 177 | + self.kwargs = kwargs |
| 178 | + |
| 179 | + def render(self, width, height, st, at): |
| 180 | + cwidth, cheight = renpy.render(self.child, width, height, st, at).get_size() |
| 181 | + return renpy.render(self.glitch(self.child, |
| 182 | + cwidth, cheight, renpy.random.Random(self.randomkey), |
| 183 | + *self.args, **self.kwargs), |
| 184 | + width, height, |
| 185 | + st, at) |
| 186 | + |
| 187 | + @staticmethod |
| 188 | + def glitch(child, cwidth, cheight, randomobj, squareside=20, chroma=.25, permutes=None): |
| 189 | + if not renpy.display.render.models: |
| 190 | + chroma = False |
| 191 | + if not (cwidth and cheight): |
| 192 | + return child |
| 193 | + |
| 194 | + ncols = round(cwidth/squareside) |
| 195 | + nrows = round(cheight/squareside) |
| 196 | + square_width = absolute(cwidth/ncols) |
| 197 | + square_height = absolute(cheight/nrows) |
| 198 | + |
| 199 | + lizt = [] |
| 200 | + for y in range(nrows): |
| 201 | + for x in range(ncols): |
| 202 | + lizt.append(Transform(child, |
| 203 | + crop=(absolute(x*square_width), absolute(y*square_height), square_width, square_height), |
| 204 | + subpixel=True, |
| 205 | + )) |
| 206 | + |
| 207 | + if permutes is None: |
| 208 | + permutes = randomobj.randrange(10, 40)/100 # between 10% and 40% |
| 209 | + permutes = round(permutes*ncols*nrows) |
| 210 | + permute_a = randomobj.sample(range(ncols*nrows), permutes) |
| 211 | + permute_b = randomobj.sample(range(ncols*nrows), permutes) |
| 212 | + |
| 213 | + for a, b in zip(permute_a, permute_b): |
| 214 | + lizt[a], lizt[b] = lizt[b], lizt[a] |
| 215 | + |
| 216 | + for k, el in enumerate(lizt): |
| 217 | + if randomobj.random() < chroma: |
| 218 | + lizt[k] = Transform(el, |
| 219 | + gl_color_mask=(randomobj.random()<.33, randomobj.random()<.33, randomobj.random()<.33, True), |
| 220 | + # matrixcolor=HueMatrix(randomobj.random()*360), |
| 221 | + ) |
| 222 | + |
| 223 | + return Grid(ncols, nrows, *lizt) |
| 224 | + |
| 225 | + def __eq__(self, other): |
| 226 | + return (type(self) == type(other)) and (self.args == other.args) and (self.kwargs == other.kwargs) |
| 227 | + |
| 228 | +label glitch_example: |
| 229 | + |
| 230 | + show expression glitch("eileen happy") |
| 231 | + |
| 232 | + e "I'm glitched and happy!" |
| 233 | + |
| 234 | + show eileen concerned at glitch |
| 235 | + |
| 236 | + e "I'm glitched and concerned..." |
| 237 | + |
| 238 | + # Use the randomkey parameter if you want to make the animation periodic |
| 239 | + image eileen glitched: |
| 240 | + glitch("eileen vhappy") |
| 241 | + pause 1.0 |
| 242 | + glitch("eileen happy", offset=60, randomkey=None) |
| 243 | + pause 0.1 |
| 244 | + repeat |
| 245 | + |
| 246 | + show eileen glitched |
| 247 | + |
| 248 | + e "I'm glitched and animated." |
| 249 | + |
| 250 | + jump start |
0 commit comments