#!/usr/bin/env python3 """Cuenta colores únicos de un PNG sin PIL (no hay PIL ni en el hub ni en el worker). python3 png-colores.py captura.png [antes.png ...] La medida que no se puede falsear (scripts/wlr/sway-headless.sh): 1 color = sólo el fondo del swaybg ⇒ NINGÚN cliente dibujó, aunque el árbol del compositor liste la ventana. """ import sys, zlib, struct from collections import Counter def leer_png(ruta): d = open(ruta, "rb").read() assert d[:8] == b"\x89PNG\r\n\x1a\n", "no es un PNG" i, idat, ihdr = 8, b"", None while i < len(d): ln, tipo = struct.unpack(">I4s", d[i:i + 8]) cuerpo = d[i + 8:i + 8 + ln] if tipo == b"IHDR": ihdr = struct.unpack(">IIBBBBB", cuerpo) elif tipo == b"IDAT": idat += cuerpo elif tipo == b"IEND": break i += 12 + ln w, h, prof, color, _, _, entre = ihdr assert prof == 8 and entre == 0, f"sólo 8 bits sin entrelazado (prof={prof} entrelazado={entre})" canales = {0: 1, 2: 3, 4: 2, 6: 4}[color] cruda, bpp, salida, prev = zlib.decompress(idat), canales, [], bytearray(w * canales) pos = 0 for _ in range(h): filtro = cruda[pos]; pos += 1 linea = bytearray(cruda[pos:pos + w * canales]); pos += w * canales for x in range(len(linea)): a = linea[x - bpp] if x >= bpp else 0 b = prev[x] c = prev[x - bpp] if x >= bpp else 0 if filtro == 1: linea[x] = (linea[x] + a) & 255 elif filtro == 2: linea[x] = (linea[x] + b) & 255 elif filtro == 3: linea[x] = (linea[x] + (a + b) // 2) & 255 elif filtro == 4: p = a + b - c pa, pb, pc = abs(p - a), abs(p - b), abs(p - c) linea[x] = (linea[x] + (a if pa <= pb and pa <= pc else b if pb <= pc else c)) & 255 salida.append(bytes(linea)); prev = linea return w, h, canales, salida for ruta in sys.argv[1:]: w, h, canales, filas = leer_png(ruta) c = Counter() for f in filas: for x in range(0, len(f), canales): c[bytes(f[x:x + 3])] += 1 print(f"{ruta}: {w}x{h} colores únicos = {len(c)}") for px, n in c.most_common(6): print(f" #{px.hex()} {n * 100.0 / (w * h):5.1f} %")