81 lines
3.8 KiB
Python
81 lines
3.8 KiB
Python
# Замер кадра: доли поверхностей, границы панелей, высоты полос, базовые линии текста.
|
||
# Инструмент существует ради нормы «работать замером, а не на глаз» (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)
|