Last active
November 28, 2025 14:34
-
-
Save kbob/d2a7e0ffcdbb0dca089032491df1084f to your computer and use it in GitHub Desktop.
Apply Knuth/Dockrey glitch function to BadApple
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| from itertools import chain, repeat | |
| import numpy as np | |
| from PIL import Image, ImageSequence | |
| GIF_PATH = 'Bad.Apple!!.full.2116173.gif' | |
| FRAME_COUNT = 3110 | |
| # BadApple is 20 frames/second. We can output a 20 FPS image sequence | |
| # or we can repeat the BadApple frames and run the glitch at 60 FPS. | |
| EMIT_60FPS = True | |
| # If you only glitch 25% of the pixels, the video is easier to make out. | |
| GLITCH_25 = True | |
| # Matthew Dockrey's version of this | |
| # https://clacks.link/@attoparsec/115590622107796156 | |
| # has X and Y reversed, decrements X offset | |
| MIMIC_DOCKREY = True | |
| SCROLL_VERTICALLY = MIMIC_DOCKREY | |
| DECREMENT_OFFSET = MIMIC_DOCKREY | |
| # very small t values are less interesting | |
| MIN_T = 350 | |
| # At 20 FPS, animate the glitch faster | |
| T_STEP_20FPS = 3 | |
| T_STEP_60FPS = 1 | |
| # Knuth's bitwise fractal (AoCP vol 4A) | |
| # (((x ^ ~y) & ((x - 350) >> 3))**2 >> 12) & 1 | |
| def glitch(w, h, t): | |
| x = np.arange(w) | |
| y = np.arange(h) | |
| if SCROLL_VERTICALLY: | |
| l, r = y, ~x | |
| o = y[:, np.newaxis] # reshape as column so broadcast will work | |
| else: | |
| l, r = ~y, x | |
| o = x | |
| m = 3 if GLITCH_25 else 1 | |
| bit = ((((np.bitwise_xor.outer(l, r) & (o - t) >> 3))**2 >> 12) & m) == 1 | |
| return bit | |
| def print_array(name, a): | |
| print(f'{name}: ' | |
| f'shape={a.shape} ' | |
| f'dtype={a.dtype} ' | |
| f'element={a[0, 0]}') | |
| src_seq = ImageSequence.Iterator(Image.open(GIF_PATH)) | |
| t_seq = range(MIN_T, MIN_T + FRAME_COUNT, T_STEP_20FPS) | |
| if EMIT_60FPS: | |
| src_seq = chain.from_iterable(repeat(f, 3) for f in src_seq) | |
| t_seq = range(MIN_T, MIN_T + 3 * FRAME_COUNT, T_STEP_60FPS) | |
| if DECREMENT_OFFSET: | |
| t_seq = reversed(t_seq) | |
| dst_seq = [] | |
| for (t, src_im) in zip(t_seq, src_seq): | |
| src = np.array(src_im) | |
| bit = glitch(*src_im.size, t) | |
| if src.shape[2:] == (): | |
| # one color channel | |
| mask = bit | |
| elif src.shape[2:] == (4, ): | |
| # four color channels | |
| alpha = np.ones(bit.shape, dtype=bit.dtype) | |
| mask = np.stack(3 * (bit, ) + (alpha, ), axis=-1) | |
| else: | |
| raise NotImplementedError(f'unexpected pixel shape {i=} {src.shape=}') | |
| dst = src * ~mask + (255 - src) * mask | |
| dst_im = Image.fromarray(dst) | |
| dst_seq.append(dst_im) | |
| dst_seq[0].save( | |
| 'test.png', | |
| save_all=True, | |
| append_images=dst_seq[1:], | |
| duration=(1000 / 60 if EMIT_60FPS else 1000 / 20), | |
| loop=0 | |
| ) | |
| # Follow this with | |
| # ffmpeg -i test.png -pix_fmt yuv420p output.mp4 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment