Thumbnail.

Mimesia Icon

Description

The Mimesia symbol icon, behaving as a fractal. Created with Chatgpt and Google Colab.

import numpy as np
from PIL import Image

def generate_heart_with_transparent_gray_background(
    levels=3,
    output_path="fractal_heart_transparent_bg.png",
    random_seed=42
):
    """
    Generates a 16^levels × 16^levels fractal heart sprite, using every 24-bit RGB color exactly once,
    with:
      1) A dark 1-px outline (darkest colors) around each smallest sprite.
      2) A light 1-px outline (lightest colors) just outside each sprite.
      3) A smooth hue-gradient fill inside each sprite.
      4) The remaining colors shuffled into the background.
      5) The background rendered at 50% opacity (alpha=128), while the sprite stays fully opaque.
    """
    # 1. Build base 16×16 mask and outlines
    sprite_data = [
        "0000010011000000","0000100100100000","0000100100100000","0000010101000000",
        "0000000100000000","0000000100000000","0022220102222000","0221112021112200",
        "0211111211111200","0211111111111200","0011111111111000","0001111111110000",
        "0000111111100000","0000011111000000","0000001110000000","0000000100000000",
    ]
    rows, cols = 16, 16
    base_flat = np.array([int(c) for row in sprite_data for c in row], dtype=np.uint8)
    base_mask = base_flat.reshape((rows, cols))

    # identify fill, boundary, outside, interior indices
    base_i = np.arange(rows*cols) // cols
    base_j = np.arange(rows*cols) % cols
    fill_base = np.where(base_flat > 0)[0]

    boundary_base = []
    for idx in fill_base:
        i, j = base_i[idx], base_j[idx]
        for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
            ni, nj = i+di, j+dj
            if ni<0 or ni>=rows or nj<0 or nj>=cols or base_mask[ni,nj]==0:
                boundary_base.append(idx)
                break
    boundary_base = np.unique(boundary_base)

    outside_base = []
    for idx in range(rows*cols):
        if base_flat[idx] == 0:
            i, j = base_i[idx], base_j[idx]
            for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
                ni, nj = i+di, j+dj
                if 0<=ni0:
                    outside_base.append(idx)
                    break
    outside_base = np.unique(outside_base)

    interior_base = np.setdiff1d(fill_base, boundary_base, assume_unique=True)

    # 2. Build 4096×4096 fractal mask
    mask1 = (base_mask>0).astype(np.uint8)
    mask2 = np.kron(mask1, mask1)     # 256×256
    fractal = np.kron(mask2, mask1)   # 4096×4096
    H, W = fractal.shape
    total = H * W
    flat_f = fractal.ravel()

    # block positions for outlines & interior
    block_idxs = np.nonzero(mask2.ravel())[0]
    br = block_idxs // mask2.shape[1]
    bc = block_idxs % mask2.shape[1]
    n_blocks = br.size
    block_offset = br * 16 * W + bc * 16

    # 3. Precompute color metrics
    idxs = np.arange(total, dtype=np.uint32)
    R = ((idxs>>16)&0xFF).astype(np.float32)/255.0
    G = ((idxs>>8 )&0xFF).astype(np.float32)/255.0
    B = ((idxs    )&0xFF).astype(np.float32)/255.0
    lum = 0.299*(R*255) + 0.587*(G*255) + 0.114*(B*255)
    mx, mn = np.maximum.reduce([R,G,B]), np.minimum.reduce([R,G,B])
    d = mx - mn
    S = np.where(mx==0, 0, d/mx)

    # compute hue
    Hh = np.zeros_like(mx)
    nz = d != 0
    mR = (R==mx)&nz; mG = (G==mx)&nz; mB = (B==mx)&nz
    Hh[mR] = ((G[mR]-B[mR])/d[mR]) % 6
    Hh[mG] = ((B[mG]-R[mG])/d[mG]) + 2
    Hh[mB] = ((R[mB]-G[mB])/d[mB]) + 4
    Hh *= 60

    # 4. Segment the palette
    lum_order = np.argsort(lum)  # ascending lum
    # darkest for dark outline
    n_dark = n_blocks * boundary_base.size
    dark_colors = lum_order[:n_dark]
    # lightest for light outline
    n_light = n_blocks * outside_base.size
    light_colors = lum_order[-n_light:]
    # mid palette
    mid = lum_order[n_dark:-n_light]

    # interior fill = highest-sat from mid
    sat_mid = S[mid]
    sat_sorted = mid[np.argsort(-sat_mid)]
    n_int = n_blocks * interior_base.size
    int_colors = sat_sorted[:n_int]
    int_colors = int_colors[np.argsort(Hh[int_colors])]  # sort by hue
    # background = rest
    bg_colors = sat_sorted[n_int:]
    rng = np.random.default_rng(random_seed)
    rng.shuffle(bg_colors)

    # 5. Assign colors + alpha
    assign = np.empty(total, dtype=np.uint32)
    alpha = np.full(total, 128, dtype=np.uint8)  # default 50% for bg

    # dark outline positions
    bi = boundary_base//cols; bj = boundary_base%cols
    pos_dark = (block_offset[:,None] + bi[None,:]*W + bj[None,:]).ravel()
    assign[pos_dark] = dark_colors
    alpha[pos_dark] = 255

    # light outline positions
    bo_i = outside_base//cols; bo_j = outside_base%cols
    pos_light = (block_offset[:,None] + bo_i[None,:]*W + bo_j[None,:]).ravel()
    assign[pos_light] = light_colors
    alpha[pos_light] = 255

    # interior fill positions
    bi3 = interior_base//cols; bj3 = interior_base%cols
    pos_int = (block_offset[:,None] + bi3[None,:]*W + bj3[None,:]).ravel()
    assign[pos_int] = int_colors
    alpha[pos_int] = 255

    # background
    all_bg = np.nonzero(flat_f==0)[0]
    bg_pos = np.setdiff1d(all_bg, pos_light, assume_unique=True)
    assign[bg_pos] = bg_colors
    # alpha remains 128

    # 6. Build RGBA image
    Rf = ((assign>>16)&0xFF).astype(np.uint8).reshape(H,W)
    Gf = ((assign>>8 )&0xFF).astype(np.uint8).reshape(H,W)
    Bf = ((assign    )&0xFF).astype(np.uint8).reshape(H,W)
    Af = alpha.reshape(H,W)
    rgba = np.dstack((Rf, Gf, Bf, Af))

    img = Image.fromarray(rgba, mode='RGBA')
    img.save(output_path)
    print(f"Saved {H}×{W} RGBA image to '{output_path}' with 50%-transparent background.")

if __name__ == "__main__":
    generate_heart_with_transparent_gray_background()

Also see:

Author

ACJ
55 entries

Stats

Date
Colors16,777,216
Pixels16,777,216
Dimensions4,096 × 4,096
Bytes57,119,856