"""Brightness and colour of screenshots (U5.3), without external libraries. Prints for each PNG (8-bit RGB/RGBA, every 4th pixel in both directions): luma mean Rec. 709 luma of the sRGB values (0 black … 1 white) sat mean HSV saturation green share of pixels with a green hue (saturation > .15, hue 61°–162°) blue share of pixels with a blue hue (saturation > .15, hue 180°–259°) Usage: python3 tools/port/png_stats.py file.png [file.png …] [--crop x0 y0 x1 y1] """ import colorsys import struct import sys import zlib def read_png(path): data = open(path, 'rb').read() if data[:8] != b'\x89PNG\r\n\x1a\n': raise ValueError(path + ': not a PNG') pos, idat = 8, [] while pos < len(data): length, kind = struct.unpack('>I4s', data[pos:pos + 8]) body = data[pos + 8:pos + 8 + length] if kind == b'IHDR': width, height, depth, color, _, _, interlace = struct.unpack('>IIBBBBB', body) elif kind == b'IDAT': idat.append(body) pos += 12 + length if depth != 8 or interlace != 0 or color not in (2, 6): raise ValueError('%s: needs 8-bit RGB/RGBA without interlace' % path) bpp = 3 if color == 2 else 4 raw = zlib.decompress(b''.join(idat)) stride = width * bpp rows, prev, i = [], bytearray(stride), 0 for _ in range(height): kind, line = raw[i], bytearray(raw[i + 1:i + 1 + stride]) i += 1 + stride for x in range(stride): a = line[x - bpp] if x >= bpp else 0 b = prev[x] c = prev[x - bpp] if x >= bpp else 0 if kind == 1: line[x] = (line[x] + a) & 255 elif kind == 2: line[x] = (line[x] + b) & 255 elif kind == 3: line[x] = (line[x] + ((a + b) >> 1)) & 255 elif kind == 4: p = a + b - c pa, pb, pc = abs(p - a), abs(p - b), abs(p - c) line[x] = (line[x] + (a if pa <= pb and pa <= pc else b if pb <= pc else c)) & 255 rows.append(bytes(line)) prev = line return width, height, bpp, rows def stats(path, crop=None): width, height, bpp, rows = read_png(path) x0, y0, x1, y1 = crop if crop else (0, 0, width, height) luma = sat = green = blue = n = 0 for y in range(y0, y1, 4): row = rows[y] for x in range(x0, x1, 4): r, g, b = row[x * bpp] / 255, row[x * bpp + 1] / 255, row[x * bpp + 2] / 255 hue = colorsys.rgb_to_hls(r, g, b)[0] top = max(r, g, b) s = 0 if top == 0 else (top - min(r, g, b)) / top luma += 0.2126 * r + 0.7152 * g + 0.0722 * b sat += s n += 1 if s > .15 and .17 < hue < .45: green += 1 if s > .15 and .5 < hue < .72: blue += 1 return luma / n, sat / n, green / n, blue / n def main(argv): crop = None if '--crop' in argv: at = argv.index('--crop') crop = tuple(int(v) for v in argv[at + 1:at + 5]) argv = argv[:at] + argv[at + 5:] for path in argv: luma, sat, green, blue = stats(path, crop) print('%-48s luma %.3f sat %.3f green %.2f blue %.2f' % (path.split('/')[-1], luma, sat, green, blue)) if __name__ == '__main__': main(sys.argv[1:])