Это мой персональный дневник. Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Бессонный хакатон: где экран?

Продолжаю писать о рефакторинге кода, написанного в свой бессонный хакатон. На этот раз хочется избавиться от подобранных вручную координат экрана эмулируемого компьютера — это неаккуратно и не универсально, — в другом браузере эти координаты скорее всего будут другими.

У меня какое-то время не было хороших идей как бы это сделать, всё что мне приходило в голову — это перебрать координаты и найти несколько примыкающих к друг другу палок определённого размера и цвета, которые окаймляют искомый экран.

Такое решение наводило на меня тоску, но потом вспомнилось, что в графической библиотеке ПХП есть фильтр, который выделяет края и я решил глянуть не будет ли от него пользы.

Окно браузера, в котором запущен эмулятор; наша задача — понять где тут экран эмулируемого компьютера

Код для наложения фильтров выглядит следующим образом:

$im = imagecreatefrompng($screenshot_file);

imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);

Мне казалось, что на получившемся изображении можно искать четыре точки экрана на известном расстоянии, но оказалось, что у такого метода есть ложные срабатывания. Поэтому я стал перебирать на экране все горизонтальные белые линии, пока не встречаю две параллельные нужной мне длины.

Окно браузера после применения фильтров: переведено в ч/б, выделены края

Таким образом очень надёжно находится граница экрана эмулятора, от которой на фиксированном расстоянии находится искомый экран. В итоге получается вот такой код:

const FRAME_W = 1039;
const FRAME_H = 808;
const FRAME_COLOR = 0xFFFFFF;
const FRAME_BORDER = 30;

$im = imagecreatefrompng($_SERVER["argv"][1]);

[$w, $h] = [imagesx($im), imagesy($im)];
imagefilter($im, IMG_FILTER_GRAYSCALE);
imagefilter($im, IMG_FILTER_EDGEDETECT);

for ($y = 0; $y <= $h - FRAME_H; $y++) {
    for ($x = 0; $x <= $w - FRAME_W; $x++) {
        $is_frame = imagecolorat($im, $x, $y) === FRAME_COLOR;

        for ($i = 1; $i<FRAME_W; $i+=2) {
            $is_frame = 
                $is_frame &&
                imagecolorat($im, $x + $i, $y) === FRAME_COLOR &&
                imagecolorat($im, $x + $i, $y + FRAME_H - 1) === FRAME_COLOR;

            if (!$is_frame) {
                break;
            }
        }

        if ($is_frame) {
            printf(
                "%d,%d\n",
                intdiv($x, 2) + FRAME_BORDER,
                intdiv($y, 2) + FRAME_BORDER
            );
            exit(0);
        }
    }
}
exit(1);

У меня тут всё в удвоенных координатах, которые я потом делю на два, чтобы получить реальные, потому что у меня экран «Ретина». Надо бы найти где-нибудь обычный экран и посмотреть что придётся переделать. Думаю, все мои константные координаты и размеры надо будет поделить на два.