Thumbnail.

Rule 30

Description

Inspired by A New Kind of Science by Stephen Wolfram, et al., I’ve been meaning to put at least one of its famous cellular automata rules in allrgb. I never got around to it, so I asked Chatgpt, which seemed happy to do it for me. It even picked a very nice rule!

Python code that can be adjusted and run:

import numpy as np
from PIL import Image

# --- CONFIGURATION ---
IMAGE_SIZE = 4096
TOTAL_COLORS = IMAGE_SIZE * IMAGE_SIZE
RULE = 101  # Wolfram's CA Rule
SEED_ROW_CENTER = 1

# --- STEP 1: Generate All 24-bit RGB Colors ---
def generate_rgb_colors():
    colors = np.arange(TOTAL_COLORS, dtype=np.uint32)
    r = (colors >> 16) & 0xFF
    g = (colors >> 8) & 0xFF
    b = colors & 0xFF
    return np.stack([r, g, b], axis=1).astype(np.uint8)

# --- STEP 2: Generate CA Binary Mask (0 or 1 for each pixel) ---
def generate_ca_binary_mask(rule, size):
    rule_bin = np.array([int(b) for b in np.binary_repr(rule, width=8)], dtype=np.uint8)
    ca = np.zeros((size, size), dtype=np.uint8)
    ca[0, size // 2] = SEED_ROW_CENTER

    for y in range(1, size):
        left = np.roll(ca[y - 1], 1)
        center = ca[y - 1]
        right = np.roll(ca[y - 1], -1)
        pattern = (left << 2) | (center << 1) | right
        ca[y] = rule_bin[7 - pattern]

    return ca

# --- STEP 3: Measure Number of 0s and 1s in CA ---
def count_dark_light_pixels(ca_mask):
    num_light = np.count_nonzero(ca_mask)  # CA=1
    num_dark = TOTAL_COLORS - num_light    # CA=0
    return num_dark, num_light

# --- STEP 4: Split Colors by Luminance, Then Shuffle ---
def split_and_shuffle_colors(colors, num_dark, num_light):
    def luminance(c):
        r, g, b = c / 255.0
        return 0.2126 * r + 0.7152 * g + 0.0722 * b

    lums = np.array([luminance(c) for c in colors])
    sorted_indices = np.argsort(lums)

    # Take the lowest luminance for dark, highest for light
    dark_indices = sorted_indices[:num_dark]
    light_indices = sorted_indices[num_dark:]

    dark_colors = colors[dark_indices]
    light_colors = colors[light_indices]

    rng = np.random.default_rng(42)
    rng.shuffle(dark_colors)
    rng.shuffle(light_colors)

    return dark_colors, light_colors

# --- STEP 5: Assign Colors to Pixels Based on CA Mask ---
def assign_colors_by_ca(ca_mask, dark_colors, light_colors):
    output = np.zeros((IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.uint8)
    dark_idx = 0
    light_idx = 0
    for y in range(IMAGE_SIZE):
        for x in range(IMAGE_SIZE):
            if ca_mask[y, x] == 0:
                output[y, x] = dark_colors[dark_idx]
                dark_idx += 1
            else:
                output[y, x] = light_colors[light_idx]
                light_idx += 1
    return output

# --- MAIN EXECUTION ---
if __name__ == "__main__":
    print("Generating RGB colors...")
    all_colors = generate_rgb_colors()

    print("Generating cellular automaton (Rule {})...".format(RULE))
    ca_mask = generate_ca_binary_mask(RULE, IMAGE_SIZE)

    print("Counting dark/light CA pixels...")
    num_dark, num_light = count_dark_light_pixels(ca_mask)

    print("Splitting and shuffling colors...")
    dark_colors, light_colors = split_and_shuffle_colors(all_colors, num_dark, num_light)

    print("Assigning colors to image...")
    image = assign_colors_by_ca(ca_mask, dark_colors, light_colors)

    print("Saving image...")
    Image.fromarray(image, mode='RGB').save("rgb_ca_shuffled_by_luminance_split.png")
    print("Done! Saved as 'rgb_ca_shuffled_by_luminance_split.png'"

Author

ACJ
53 entries

Stats

Date
Colors16,777,216
Pixels16,777,216
Dimensions4,096 × 4,096
Bytes50,361,688