Thumbnail.

Country Flags v1

Description

Like a cliché nerd (think Sheldon Cooper), I’ve always been fascinated by country flags. So much so, that my company de idee even has a free to use online platform dedicated to it called de vlag (the flag in Dutch).

I figured it would be an interesting (read: extra nerdy) exercise to put all the official country flags in an image that contains all 24-bit rgb colors exactly once, while keeping the flags recognizable. This is my first attempt, created with Chatgpt 5, Python 3, and a Google Colab notebook. Please feel free to come up with a better version!

# ============================================
# 4096×4096 "All-RGB" World Flags
#  - HSVY + palette-aware mapping (bijective: uses every 24-bit color exactly once)
#  - Uniform gray background (constant)
#  - Background: lowest-chroma Y≈gray colors first
#  - Flags: HSVY rank + palette-hue tiebreaker
#  - NEW: shuffle only within each flag's individual color fields
#         (per-flag quantization labels), NOT whole-flag regions
#  - Verifies via checksum + XOR
# ============================================

!pip -q install pillow requests tqdm

import os, math, hashlib
from datetime import datetime
import requests
import numpy as np
from PIL import Image
from tqdm import tqdm

# -----------------------------
# Config
# -----------------------------
CANVAS_SIZE = 4096
INCLUDE_UN_OBSERVERS = True
USE_GOOGLE_DRIVE = False
SAVE_DIR = "/content"
CACHE_DIR_NAME = "flags_cache"

UNIFORM_GRAY = 224          # 0..255 uniform background gray
MARGIN_PX = 4               # inner cell padding
PALETTE_COLORS = 6          # per-flag quantization & palette tiebreak

# -----------------------------
# Optional: mount Drive
# -----------------------------
if USE_GOOGLE_DRIVE:
    from google.colab import drive
    drive.mount('/content/drive', force_remount=True)
    SAVE_DIR = "/content/drive/MyDrive"

ASSETS_DIR = os.path.join(SAVE_DIR, "allRGB", "assets")
os.makedirs(ASSETS_DIR, exist_ok=True)
CACHE_DIR = os.path.join(ASSETS_DIR, CACHE_DIR_NAME)
os.makedirs(CACHE_DIR, exist_ok=True)

# -----------------------------
# REST Countries fetch
# -----------------------------
def fetch_country_list(include_observers=True, timeout=60):
    url = "https://restcountries.com/v3.1/all?fields=name,flags,unMember,cca2"
    resp = requests.get(url, timeout=timeout)
    resp.raise_for_status()
    data = resp.json()

    wanted = []
    observers_set = {"Holy See", "Palestine", "State of Palestine"}
    for c in data:
        name = (c.get("name") or {}).get("common")
        un_member = bool(c.get("unMember"))
        flags = c.get("flags") or {}
        png = flags.get("png")
        cca2 = c.get("cca2")
        if not (name and png and cca2):
            continue
        if un_member:
            wanted.append(dict(name=name, png=png, cca2=cca2.lower(), un_member=True))
        elif include_observers and name in observers_set:
            wanted.append(dict(name=name, png=png, cca2=cca2.lower(), un_member=False))
    wanted.sort(key=lambda d: d["name"])
    return wanted

def safe_filename(s):
    return "".join(ch if ch.isalnum() or ch in "-_." else "_" for ch in s)

def download_flag_png(item, cache_dir=CACHE_DIR, timeout=60):
    url = item["png"]
    code = item["cca2"]
    base = f"{safe_filename(code)}.png"
    path = os.path.join(cache_dir, base)
    if os.path.exists(path) and os.path.getsize(path) > 0:
        return path
    try:
        r = requests.get(url, timeout=timeout)
        r.raise_for_status()
        with open(path, "wb") as f:
            f.write(r.content)
        return path
    except Exception:
        try:
            if "/w320/" in url:
                url2 = url.replace("/w320/", "/w640/")
                r2 = requests.get(url2, timeout=timeout)
                r2.raise_for_status()
                with open(path, "wb") as f:
                    f.write(r2.content)
                return path
        except Exception:
            pass
        return None

# -----------------------------
# Grid & mosaic with per-flag label maps
# -----------------------------
def best_grid(n, width=CANVAS_SIZE, height=CANVAS_SIZE):
    cols = int(math.ceil(math.sqrt(n)))
    rows = int(math.ceil(n / cols))
    return rows, cols

def place_flags_mosaic_with_labels(flags, gray=UNIFORM_GRAY, canvas_size=CANVAS_SIZE,
                                   margin=MARGIN_PX, palette_colors=PALETTE_COLORS):
    """
    Returns:
      mosaic_rgb (RGB)
      flag_id_map (H,W) int32: -1 for background, else flag index
      per_flag (list of dicts):
         - name, cca2
         - hues: palette hues (uint8 0..255) for tiebreaker
         - paste_x, paste_y
         - label_map: (h_resized, w_resized) int16, -1 for transparent,
                      else palette index 0..(palette_colors-1)
    """
    N = len(flags)
    rows, cols = best_grid(N, canvas_size, canvas_size)
    cell_w = canvas_size // cols
    cell_h = canvas_size // rows

    pad = (int(gray), int(gray), int(gray))
    mosaic = Image.new("RGBA", (canvas_size, canvas_size), pad + (255,))
    flag_id_map = np.full((canvas_size, canvas_size), -1, dtype=np.int32)
    per_flag = []

    i = 0
    for r in range(rows):
        for c in range(cols):
            if i >= N:
                break
            info = flags[i]
            path = info["local"]
            try:
                flag = Image.open(path).convert("RGBA")
            except Exception:
                per_flag.append(dict(name=info["name"], cca2=info["cca2"], hues=np.array([], dtype=np.uint8),
                                     paste_x=0, paste_y=0, label_map=np.zeros((0,0), dtype=np.int16)))
                i += 1
                continue

            max_w = max(1, cell_w - 2 * margin)
            max_h = max(1, cell_h - 2 * margin)
            fw, fh = flag.size
            scale = min(max_w / fw, max_h / fh)
            new_w = max(1, int(round(fw * scale)))
            new_h = max(1, int(round(fh * scale)))
            flag_resized = flag.resize((new_w, new_h), Image.LANCZOS)

            paste_x = c * cell_w + (cell_w - new_w) // 2
            paste_y = r * cell_h + (cell_h - new_h) // 2

            # Composite onto mosaic
            mosaic.alpha_composite(flag_resized, (paste_x, paste_y))

            # Flag-id map (alpha>0)
            alpha = np.array(flag_resized.split()[-1], dtype=np.uint8)
            mask = alpha > 0
            if mask.any():
                yy, xx = np.nonzero(mask)
                fy = paste_y + yy
                fx = paste_x + xx
                ok = (fy >= 0) & (fy < canvas_size) & (fx >= 0) & (fx < canvas_size)
                flag_id_map[fy[ok], fx[ok]] = i

            # Quantize to get per-pixel palette indices
            qimg = flag_resized.convert("RGB").quantize(colors=palette_colors, method=Image.MEDIANCUT)
            label_map = np.array(qimg, dtype=np.int16)  # 0..K-1 indices over full rect
            # Set -1 where flag is transparent
            label_map[~mask] = -1

            # Palette hues for tiebreaker
            palette = qimg.getpalette()[:palette_colors*3]
            pal = np.array(palette, dtype=np.uint8).reshape(-1, 3)
            pr, pg, pb = pal[:,0].astype(np.float32)/255.0, pal[:,1].astype(np.float32)/255.0, pal[:,2].astype(np.float32)/255.0
            pmax = np.maximum.reduce([pr, pg, pb])
            pmin = np.minimum.reduce([pr, pg, pb])
            pc = pmax - pmin
            ph = np.zeros_like(pmax, dtype=np.float32)
            maskc = pc > 1e-12
            rc = np.zeros_like(pmax); gc = np.zeros_like(pmax); bc = np.zeros_like(pmax)
            rc[maskc] = (pmax[maskc] - pr[maskc]) / pc[maskc]
            gc[maskc] = (pmax[maskc] - pg[maskc]) / pc[maskc]
            bc[maskc] = (pmax[maskc] - pb[maskc]) / pc[maskc]
            mr = maskc & (pr >= pg) & (pr >= pb)
            mg = maskc & (pg > pr) & (pg >= pb)
            mb = maskc & (pb > pr) & (pb > pg)
            ph[mr] = (bc[mr] - gc[mr]) / 6.0
            ph[mg] = (2.0 + (rc[mg] - bc[mg])) / 6.0
            ph[mb] = (4.0 + (gc[mb] - rc[mb])) / 6.0
            ph = ph % 1.0
            pal_h8 = np.floor(ph * 255.0 + 0.5).astype(np.uint8)

            per_flag.append(dict(name=info["name"], cca2=info["cca2"], hues=pal_h8,
                                 paste_x=paste_x, paste_y=paste_y, label_map=label_map))
            i += 1

    return mosaic.convert("RGB"), flag_id_map, per_flag

# -----------------------------
# HSVY helpers
# -----------------------------
def rgb_to_yhc_u8(r, g, b):
    r16 = r.astype(np.uint16); g16 = g.astype(np.uint16); b16 = b.astype(np.uint16)
    cmax = np.maximum(np.maximum(r16, g16), b16)
    cmin = np.minimum(np.minimum(r16, g16), b16)
    delta = (cmax - cmin).astype(np.uint8)
    Y = ((54 * r16 + 183 * g16 + 19 * b16 + 128) >> 8).astype(np.uint8)  # Rec709-ish

    rf = r16.astype(np.float32); gf = g16.astype(np.float32); bf = b16.astype(np.float32)
    cmaxf = cmax.astype(np.float32); deltaf = (cmax - cmin).astype(np.float32)
    hue = np.zeros_like(cmaxf, dtype=np.float32)
    nz = deltaf > 0
    mask_r = nz & (cmax == r16)
    hue[mask_r] = (60.0 * ((gf[mask_r] - bf[mask_r]) / deltaf[mask_r])) % 360.0
    mask_g = nz & (cmax == g16)
    hue[mask_g] = 60.0 * (2.0 + (bf[mask_g] - rf[mask_g]) / deltaf[mask_g])
    mask_b = nz & (cmax == b16)
    hue[mask_b] = 60.0 * (4.0 + (rf[mask_b] - gf[mask_b]) / deltaf[mask_b])
    hue = np.where(hue < 0, hue + 360.0, hue)
    H = np.floor(hue * (255.0 / 360.0) + 0.5).astype(np.uint8)
    return Y, H, delta

def make_hsvy_key(Y, H, C):
    return (Y.astype(np.uint32) << 24) | (H.astype(np.uint32) << 16) | (C.astype(np.uint32) << 8)

def hue_circ_dist8(a8, b8):
    d = np.abs(a8.astype(np.int16) - b8.astype(np.int16)).astype(np.int16)
    return np.minimum(d, 256 - d).astype(np.uint8)

def rng_for_label(label: str):
    h = hashlib.blake2b(label.encode("utf-8"), digest_size=8).digest()
    return np.random.default_rng(int.from_bytes(h, "little"))

# -----------------------------
# Build with background + flags + per-field shuffle
# -----------------------------
def build_allrgb_palette_aware(target_rgb, flag_id_map, per_flag, out_png_path, bg_gray=UNIFORM_GRAY):
    W = H = CANVAS_SIZE
    assert target_rgb.size == (W, H)

    arr = np.array(target_rgb, dtype=np.uint8)
    flat = arr.reshape(-1, 3)
    tr, tg, tb = flat[:,0], flat[:,1], flat[:,2]

    # Background mask
    bgR = np.uint8(bg_gray); bgG = np.uint8(bg_gray); bgB = np.uint8(bg_gray)
    bg_mask = (tr == bgR) & (tg == bgG) & (tb == bgB)
    K = int(bg_mask.sum())
    print(f"Background pixels: {K:,d} / {flat.shape[0]:,d}")

    # All 24-bit colors
    N = 1 << 24
    codes = np.arange(N, dtype=np.uint32)
    r_all = (codes >> 16).astype(np.uint8)
    g_all = ((codes >> 8) & 255).astype(np.uint8)
    b_all = (codes & 255).astype(np.uint8)

    print("Precomputing Y,H,C for all colors…")
    Y_all, H_all, C_all = rgb_to_yhc_u8(r_all, g_all, b_all)

    # -------- Phase A: background = lowest-C, Y≈bgGray ----------
    print("Selecting lowest-chroma colors for background…")
    if K > 0:
        idx_lowC = np.argpartition(C_all, K - 1)[:K]
        bgY = np.uint8(bg_gray)
        Ydiff = np.abs(Y_all[idx_lowC].astype(np.int16) - int(bgY)).astype(np.uint8)
        key_bg = (C_all[idx_lowC].astype(np.uint32) << 24) | (Ydiff.astype(np.uint32) << 16) | (H_all[idx_lowC].astype(np.uint32) << 8) | (codes[idx_lowC] & 0xFF)
        order_bg = np.argsort(key_bg, kind="stable")
        color_idx_bg = idx_lowC[order_bg]
    else:
        color_idx_bg = np.array([], dtype=np.int64)

    px_idx_all = np.arange(flat.shape[0], dtype=np.uint32)
    px_idx_bg = px_idx_all[bg_mask]
    assert px_idx_bg.size == color_idx_bg.size, "Background selection mismatch."

    out_r = np.empty(N, dtype=np.uint8)
    out_g = np.empty(N, dtype=np.uint8)
    out_b = np.empty(N, dtype=np.uint8)

    # Place background colors (we'll shuffle them later within background)
    out_r[px_idx_bg] = r_all[color_idx_bg]
    out_g[px_idx_bg] = g_all[color_idx_bg]
    out_b[px_idx_bg] = b_all[color_idx_bg]

    # -------- Phase B: foreground via HSVY + palette-hue tiebreak ----------
    remain_mask_pixels = ~bg_mask
    px_idx_fg = px_idx_all[remain_mask_pixels]

    taken = np.zeros(N, dtype=bool)
    taken[color_idx_bg] = True
    color_idx_fg = np.flatnonzero(~taken)
    del taken

    Yt, Ht, Ct = rgb_to_yhc_u8(tr[remain_mask_pixels], tg[remain_mask_pixels], tb[remain_mask_pixels])
    tkey = make_hsvy_key(Yt, Ht, Ct)

    print("Computing palette-aware tiebreaker…")
    flag_ids_flat = flag_id_map.reshape(-1)[remain_mask_pixels]
    pal_delta = np.full(flag_ids_flat.shape, 128, dtype=np.uint8)

    unique_flags = np.unique(flag_ids_flat)
    for fid in unique_flags:
        if fid < 0:
            continue
        hues = per_flag[fid]["hues"]
        idx = np.nonzero(flag_ids_flat == fid)[0]
        if idx.size == 0 or hues.size == 0:
            continue
        h_local = Ht[idx]
        mind = np.full(idx.size, 255, dtype=np.uint8)
        for hp in hues:
            d = hue_circ_dist8(h_local, np.uint8(hp))
            mind = np.minimum(mind, d)
        pal_delta[idx] = mind

    tidx = np.arange(px_idx_fg.size, dtype=np.uint32)
    target_order = np.lexsort((tidx, pal_delta.astype(np.uint32), tkey))

    Yc = Y_all[color_idx_fg]; Hc = H_all[color_idx_fg]; Cc = C_all[color_idx_fg]
    ckey = make_hsvy_key(Yc, Hc, Cc)
    color_order = np.lexsort((color_idx_fg, ckey))

    sorted_px = px_idx_fg[target_order]
    sorted_colors = color_idx_fg[color_order]

    out_r[sorted_px] = r_all[sorted_colors]
    out_g[sorted_px] = g_all[sorted_colors]
    out_b[sorted_px] = b_all[sorted_colors]

    # -------- NEW: shuffle within color fields (not whole flags) ----------
    print("Shuffling within individual color fields of each flag…")

    # Background region shuffle (all background as one region)
    def shuffle_region(px_idx, label):
        if px_idx.size <= 1:
            return
        rng = rng_for_label(label)
        order = np.arange(px_idx.size)
        rng.shuffle(order)
        rr = out_r[px_idx].copy(); gg = out_g[px_idx].copy(); bb = out_b[px_idx].copy()
        out_r[px_idx] = rr[order]; out_g[px_idx] = gg[order]; out_b[px_idx] = bb[order]

    shuffle_region(px_idx_bg, f"background_gray_{bg_gray}")

    # For each flag: shuffle inside each palette label cluster separately
    Hcvs = H  # just to avoid confusion — using globals W,H

    for fid in unique_flags:
        if fid < 0:
            continue
        meta = per_flag[fid]
        name = meta["name"]; px = meta["paste_x"]; py = meta["paste_y"]
        lbl = meta["label_map"]  # (h_res, w_res) int16, -1 transparent
        if lbl.size == 0:
            continue

        h_res, w_res = lbl.shape
        # Map label pixels to canvas linear indices
        yy, xx = np.nonzero(lbl >= 0)
        if yy.size == 0:
            continue
        canvas_y = py + yy
        canvas_x = px + xx
        ok = (canvas_y >= 0) & (canvas_y < H) & (canvas_x >= 0) & (canvas_x < W)
        canvas_y = canvas_y[ok]; canvas_x = canvas_x[ok]
        labels_here = lbl[yy[ok], xx[ok]]

        # For each palette label value present, shuffle within that set
        for k in np.unique(labels_here):
            idx_k = (labels_here == k)
            if not np.any(idx_k):
                continue
            cy = canvas_y[idx_k]; cx = canvas_x[idx_k]
            px_idx = (cy.astype(np.uint32) * W) + cx.astype(np.uint32)
            shuffle_region(px_idx, f"flag_{fid}_{name}_field_{int(k)}")

    # -------- Save + exactness checks ----------
    out = np.stack([out_r, out_g, out_b], axis=1).reshape(H, W, 3)
    del out_r, out_g, out_b, r_all, g_all, b_all, Y_all, H_all, C_all

    print("Verifying perfect 24-bit coverage (checksum + XOR)…")
    packed = (out[...,0].astype(np.uint32) << 16) | (out[...,1].astype(np.uint32) << 8) | out[...,2].astype(np.uint32)

    N24 = 1 << 24
    sum1 = int(packed.sum(dtype=np.uint64))
    expected_sum = (N24 * (N24 - 1)) // 2

    def xor_reduce(u32arr, block=8_000_000):
        xr = np.uint32(0)
        flat = u32arr.ravel()
        for s in range(0, flat.size, block):
            e = min(flat.size, s + block)
            xr ^= np.bitwise_xor.reduce(flat[s:e])
        return int(xr)
    def xor_0_to(m): return [m, 1, m+1, 0][m & 3]

    xor1 = xor_reduce(packed)
    expected_xor = xor_0_to(N24 - 1)
    assert sum1 == expected_sum and xor1 == expected_xor, "Coverage mismatch: not all 24-bit colors used exactly once."

    Image.fromarray(out, mode="RGB").save(out_png_path, format="PNG", compress_level=0)
    print(f"Saved: {out_png_path}")

# -----------------------------
# Main
# -----------------------------
def main():
    print("Fetching countries & flag URLs…")
    countries = fetch_country_list(INCLUDE_UN_OBSERVERS)
    print(f"Fetched {len(countries)} candidates.")

    print("Downloading flag PNGs (cached)…")
    kept = []
    for item in tqdm(countries):
        p = download_flag_png(item)
        if p:
            item["local"] = p
            kept.append(item)
    print(f"Using {len(kept)} flags.")
    if len(kept) == 0:
        raise RuntimeError("No flags downloaded. Check network access or the API.")

    print("Building mosaic + per-flag label maps (uniform gray background)…")
    mosaic, flag_id_map, per_flag = place_flags_mosaic_with_labels(
        kept, gray=UNIFORM_GRAY, canvas_size=CANVAS_SIZE, margin=MARGIN_PX, palette_colors=PALETTE_COLORS
    )

    ts = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
    preview_path = os.path.join(ASSETS_DIR, f"flags_target_uniformgray_{CANVAS_SIZE}_{ts}.png")
    mosaic.save(preview_path, format="PNG")
    print(f"Saved target preview: {preview_path}")

    out_path = os.path.join(ASSETS_DIR, f"allrgb_flags_hsvy_paletteaware_fields_shuffled_{CANVAS_SIZE}_{ts}.png")
    build_allrgb_palette_aware(mosaic, flag_id_map, per_flag, out_path, bg_gray=UNIFORM_GRAY)
    print("Done.")

if __name__ == "__main__":
    np.set_printoptions(suppress=True)
    main()

Author

ACJ
68 entries

Stats

Date
Colors16,777,216
Pixels16,777,216
Dimensions4,096 × 4,096
Bytes50,351,853