

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<=ni
0: 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:
Date | |
---|---|
Colors | 16,777,216 |
Pixels | 16,777,216 |
Dimensions | 4,096 × 4,096 |
Bytes | 57,119,856 |