# Замер кадра: доли поверхностей, границы панелей, высоты полос, базовые линии текста. # Инструмент существует ради нормы «работать замером, а не на глаз» (FRONTEND_PLAN.md §5): # каждая сессия, трогающая масштаб и зазоры, обязана привести числа, а не впечатление. # # python3 scripts/measure.py references/fleet.png .shots/showcase.png # # Оба кадра приводятся к CSS-пикселям по своему масштабу (аргумент --scale, по умолчанию 2 — # и референс, и наш снимок сняты при deviceScaleFactor 2). import sys from collections import Counter from PIL import Image def surfaces(image, top=8): counts = Counter(image.getdata()) total = sum(counts.values()) return [ ('#%02x%02x%02x' % color, round(100 * n / total, 2)) for color, n in counts.most_common(top) ] def runs(values, minimum=2): """Отрезки одинакового значения подряд: (значение, начало, длина).""" out = [] start = 0 for index in range(1, len(values) + 1): if index == len(values) or values[index] != values[start]: if index - start >= minimum: out.append((values[start], start, index - start)) start = index return out def column_profile(image, y): """Цвета вдоль горизонтального среза — так видны границы панелей и промежутки.""" return [image.getpixel((x, y)) for x in range(image.width)] def row_profile(image, x): return [image.getpixel((x, y)) for y in range(image.height)] def report(path, scale): image = Image.open(path).convert('RGB') print(f'\n=== {path} — {image.width}x{image.height} физ, {image.width // scale}x{image.height // scale} CSS ===') print('поверхности (доля кадра):') for color, share in surfaces(image): print(f' {color} {share:5.2f}%') middle = image.height // 2 # Проба берётся по центру верхней полосы, а не в углу: у референса углы окна macOS скруглены, # и в углу лежит чёрный фон рабочего стола, а не фон оболочки. shell = image.getpixel((image.width // 2, 2 * scale)) print('\nфон оболочки (верхняя полоса): #%02x%02x%02x' % shell) # Горизонтальный срез по середине: отрезки фона оболочки = промежутки между панелями. strip = column_profile(image, middle) gaps = [(start, length) for color, start, length in runs(strip) if color == shell] print('промежутки фона по горизонтали (CSS: начало, ширина):') for start, length in gaps: print(f' x={start / scale:7.1f} w={length / scale:5.1f}') # Вертикальный срез по левому краю панели: верхняя полоса и статус-полоса. x = gaps[0][0] + gaps[0][1] + 4 if gaps else 20 column = row_profile(image, x) bands = [(start, length) for color, start, length in runs(column) if color == shell] print(f'полосы фона по вертикали при x={x / scale:.1f} (CSS: начало, высота):') for start, length in bands: print(f' y={start / scale:7.1f} h={length / scale:5.1f}') if __name__ == '__main__': args = [a for a in sys.argv[1:] if not a.startswith('--')] scale = 2 for a in sys.argv[1:]: if a.startswith('--scale='): scale = int(a.split('=')[1]) for path in args: report(path, scale)