программирование

Надпись в «Гитхабе» ★

С тех пор как в первый раз увидел панель активности «Гитхаба», хочу на ней что-нибудь написать. Я даже всерьёз как-то порывался составить себе карту на год, чтобы по ней коммитить, пока не получится рисунок, но поленился. А позже сообразил, что можно сдвигать дату коммита в прошлое.

График вкладов за год: 587 правок, слово “NUMBER” из зелёных квадратов по месяцам, шкала активности от «Less» до «More».
Моя панель активности за год на «Гитхабе»

И вот, годы спустя, я наконец сподобился сделать скрипт, чтобы автоматизировать процесс и написать себе там слово «ПРИВЕТ».

Скрипт переводит подготовленную картинку размером 52×7 пискелей в нужную интенсивность коммитов и распределяет их по датам. Для надписи я использовал шрифт silkscreen, русифицированный когда-то Димой Смирновым и чуть-чуть его размыл в графическом редакторе.

Если захотите себе такую же, вам понадобится так же bash, git и утилиты из набора ImageMagick. Создаёте пустой репозиторий на «Гитхабе», привязываете его к локальному, запускаете мою утилиту с картинкой и любуетесь изображением на панели активности.

4 комментария

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

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

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

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

Эмулятор ZX Spectrum в браузере Opera: меню «ZX-РЕВЮ 11-12 1997» с пунктами «Описание оболочки», «Путеводитель», «Авторская разработка» и др.; внизу — виртуальная клавиатура.
Окно браузера, в котором запущен эмулятор; наша задача — понять где тут экран эмулируемого компьютера

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

$im = imagecreatefrompng($screenshot_file);

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

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

Эмулятор ZX Spectrum в браузере Opera: меню «ZX-РЕВЮ 11-12 1997» с пунктами «Описание оболочки», «Путеводитель» и др., внизу — виртуальная клавиатура.
Окно браузера после применения фильтров: переведено в ч/б, выделены края

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

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);

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

Оставить комментарий

CVE 2023-0567

В ПХП нашли достаточно серьёзный, на мой взгляд, баг — на некоторых повреждённых хешах функция password_verify возвращает true на любой пароль. Получается, хитрый хакер может оставить себе тайный ход — поменять в базе хеш на битый, и пользователь ничего не заметит — пароль-то по-прежнему подходит.

Вот как это выглядит:

var_dump(password_verify("foo", '$2y$04$00000000$')); // true
var_dump(password_verify("bar", '$2y$04$00000000$')); // true

Удивляет, что один из разработчиков попытался защитить текущее поведение интерпретатора:

The documentation for password_verify() clearly states that the $hash has to be valid:

> hash
> A hash created by password_hash().

I fail to see how this is a bug if you feed it garbage; also the documentation to crypt states:

> Using characters outside of this range in the salt will cause crypt() to return a zero-length string. The two digit cost parameter is the base-2 logarithm of the iteration count for the underlying Blowfish-based hashing algorithm and must be in range 04-31, values outside this range will cause crypt() to fail.

PHP clearly behaves as described.

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

При анализе оказалось, что баг занесён ещё в 2008-м году (!), причём исследовавший эту историю разработчик хоть и осторожен в выражениях, всё же похоже считает, что уязвимость появилась неслучайно:

PHP’s implementation of crypt_blowfish differs from the upstream Openwall version by adding a «PHP Hack», which allows one to cut short the BCrypt salt by including a $ character within the characters that represent the salt.

Hashes that are affected by the «PHP Hack» may erroneously validate any password as valid when used with password_verify and when comparing the return value of crypt() against the input.

The PHP Hack exists since the first version of PHP’s own crypt_blowfish implementation that was added in 1e820ec.

No clear reason is given for the PHP Hack’s existence. This commit removes it, because BCrypt hashes containing a $ character in their salt are not valid BCrypt hashes.

Оставить комментарий

Бессонный хакатон: нажатие на кнопку

Продолжаю писать про свой бессонный хакатон.

В решении, которое я накидал за ту ночь, планировалось нажимать на кнопки эмулятора «ЭплСкриптом» — на «Маке» это логичнее всего, так как этот язык заточен как раз под такие задачи — автоматизацию работы со сторонними графическими приложениями.

Удивительно, но у меня это сделать не получилось — несмотря на то, что я слал кнопки как описано в документации, в эмуляторе ничего не происходило.

Так как время уходило, — сроку я себе выделил до утра, пришлось использовать другое решение — через утилиту cliclick, которая умеет эмулировать нажатие мышкой:

cliclick m:800,630 w:200 c:. w:200 m:+100,+100

Я просто закодировал координаты нужной кнопки в том месте, где у меня она была на экране. Одна проблема — кнопка после нажатия «залипала», но я решил тем, что через 200 миллисекунд отводил курсор в сторону — w:200 m:+100,+100 на птичьем языке утилиты.

Уже позже, во время рефакторинга, я обратил внимание, что когда я нажимаю кнопку на своём ноутбуке, то в эмуляторе иногда нажимаются не одна кнопка, а две. Если жать стрелки вниз или вверх, то в эмуляторе происходит нажатие на CS+6 и на CS+7 соответственно, причём оболочка электронного журнала правильно реагирует и на эту комбинацию, и на одиночные «6» и «7».

Для эксперимента попробовал слать вместе с основной клавишей ещё и клавишу-модификатор и меня ждал успех, правда пришлось перебрать их все. Получилась вот такая функция:

function press_key {
    local key
    if [[ "$1" =~ ^[0-9]{2,}$ ]]; then
        key="key code $1"
    else
        key="keystroke \"$1\""
    fi

    osascript <<PRESS
tell application "System Events" to $key using command down
delay .03
tell application "System Events" to $key
PRESS
}

Она определяет что ей передали — код символа или сам символ и «нажимает» запрошенную клавишу. Второй раз я «нажимаю» ту же клавишу уже без модификатора, иначе она почему-то опять же «залипает» на виртуальной клавиатуре.

Оставить комментарий

Скриншоты из «ZX-Ревю»

Продолжаю писать про свой «бессонный хакатон».

В комментариях к первой заметке читатель Евгений Суреев посоветовал использовать внутренние средства оболочки электронного журнала, чтобы получать скриншоты. Этот способ существует, но мне он показался чересчур сложным и медленным. Но для очистки совести я сего всё же попробовал.

Скриншот меню программы для ZX Spectrum: пункты «Полоса прокрутки», «Изменить цвет текста», подсказки F6–F12, заголовок «21 ПРИ ПРОСМОТРЕ ВНЕШНЕГО РАЗДЕЛА...
Получившийся, в результате проделанных ниже шагов, скриншот

Получились следующие шаги:

  1. в эмулятор, кроме виртуального «диска» с журналом подключаем второй пустой диск;
  2. листаем на нужный экран внутри эмулятора;
  3. нажимаем в эмуляторе кнопки SS+S;
  4. переводим в появившемся окне курсор в поле выбора дисковода, пишем там B;
  5. в этом же окне переводим курсор в поле «Имя файла», пишем туда номер текущей страницы;
  6. повторяем шаги 2—5 до конца раздела;
Скриншот интерфейса с диалоговым окном «Запись экрана на диск», где указан дисковод B и имя файла Screen02; на фоне — меню с настройками и инструкциями по клавишам.
Меню сохранения скриншота в файл внутри эмулятора, нужно выбрать дисковод и имя файла
  1. скидываем куда-то на диск компьютера образ со скриншотами;
  2. при помощи утилиты mctrd достаём из образа скриншоты;
  3. при помощи утилиты zx-image преобразовываем скриншоты в PNG;
  4. преобразовываем полученное в JPG, склеиваем с большой файл, закидываем в оболочку;

Весь этот интерактив с браузером (вызвать меню, ввести диск, ввести имя) — довольно долгий, всё-таки эмулятор работает примерно со скоростью «Спектрума», а он был не особо шустрым. Перевод курсора в координаты это пока тоже проблема — мышки нет, надо выяснить где курсор сейчас и поставить его в нужное поле. Есть и проблема с сохранением дампа — надо как-то из браузера найти нужную папку.

В общем, кажется слишком много проблем, которые надо решать, причём я ещё не знаю удастся ли их решить хорошо.

6 комментариев

А теперь неспеша всё отрефакторим

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

Способ я придумал, реализовал и выложил на «Гитхаб». Но мозгу задача понравилось, не могу остановиться и перестать думать о том как всё переделать. Уже начал накидывать новый прототип. Выкладывать целиком пока не буду, там не готово, но кое-какие куски покажу в нескольких постах — там много интересных проблем.

Первое, что мне хотелось бы сделать — избавиться от привязки к браузеру. В прошлой реализации я просто активизировал «Сафари», после чего считал, что в активном окне открыт нужный мне эмулятор. Я подумал, что хорошо бы посмотреть какие у нас запущены браузеры и проверить открыто ли в каком-то из них то, что мне нужно.

Так как я делаю всё под «Мак», программировать решил на «ЭплСкрипте» — так проще всего.

on is_running(appName)
    tell application "System Events" to (name of processes) contains appName
end is_running

set browsers to {"Safari", "Chrome", "Opera", "Firefox", "Edge", "Yandex"}

repeat with browser in browsers
    if is_running(browser) then
        tell application browser
            if exists window "ZX Spectrum online emulator" then
                log browser
                activate
                exit repeat
            end if
        end tell
    end if
end repeat

Что тут происходит? Я перебираю все известные мне браузеры по имени, ищу какие из них запущены, в них смотрю — называется ли активное окно «ZX Spectrum online emulator», если да, остаюсь в нём, сообщая наружу какой из браузеров найден.

Можно было бы ещё перебрать окна каждого из браузеров, но есть опасность остановиться не в том эмуляторе — мало ли, вдруг у человека несколько запущено. Сделать несложно, но кажется это может добавить проблем.

Так как основная оболочка у меня на «Баше», осталось как-то из «ЭплСкрипта» забрать имя найденного браузера. Я делаю это так:

BROWSER=$(osascript <<BROWSERS 2>&1
# … тут программа на «ЭплСкрипте»
BROWSERS
)

В итоге, в переменной $BROWSER будет лежать имя браузера или пусто, если подходящий не найден.

Перенаправление потока 2>&1 необходимо, так как команда log «ЭплСкрипта» выведет имя браузера в поток для ошибок, а конструкция $( … ) ждёт его в стандартном выводе.

Оставить комментарий

ZX-Ревю 11 и 12 за 1997 год

В результате своего хакатона я сконвертировал в формат HTML совмещённый номер 11 и 12 за 1997 год электронного журнала «ZX-Ревю». Результат захостил на «Гитхабе», можно посмотреть что получилось.

Вся навигация — курсором мыши, на пункты меню можно щёлкать, текст прокручивается (полосы прокрутки не видно, так как в оригинале её тоже нет).

Совершенно без сил отправляюсь почивать.

Оставить комментарий

Бессонный хакатон

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

А тут дочка после ветрянки подхватила какое-то ОРЗ с высокой температурой и у меня выдались две бессонные ночи — жена на конференции, мы с дочкой вдвоём. Из-за температуры она плохо спала, а я просыпался, потому что она меня часто будила.

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

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

В момент этого решения я как раз долистал в какой-то из лент до ностальгических воспоминаний о «Спектруме» и вспомнил как набрёл когда-то на архив журналов «ZX-Ревю» и «ZX-Форум». Когда-то в детстве, когда у меня у самого был «Спектрум», я их выписывал.

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

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

Обложка журнала ZX-РЕВЮ №11-12 за 1997 год с перечнем статей: описание оболочки, путеводитель, новые программы, форум, советы экспертов и каталог.
Обложка электронного журнала «ZX-Ревю» №№ 11, 12 за 1997-й год

Для начала я поискал чем можно расковырять формат, в котором распространяется журнал. Утилита нашлась быстро и я даже поправил в ней небольшой баг — она не собиралась под «МакОС».

Изучение расковырянного содержимого принесло одно расстройство — внутри оказалось бинарное представление программы на Бейсике и несколько файлов неизвестного формата. Судя по тому как отображается журнал в эмуляторе, формат не текстовый. Вряд ли я бы справился с его обратным инжинирингом за ночь.

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

В итоге получился набор инструментов из двух файлов на смеси bash и PHP.

Первый, make_screenshots скриншотит эмулятор, вырезает оттуда экран «Спектрума», жмёт на кнопку «вниз», заботится о том, чтобы экраны не дублировались и понимает когда больше не удаётся пролистнуть. Многие вещи там захардкожены — например, задержки, положение экрана эмулятора и кнопки на виртуальной клавиатуре.

Второй, convert_to_jpegs превращает полученный набор скриншотов в файлы JPEG. Во множественном числе, потому что оказалось, что максимальная высота файла JPEG — 65535 пикселей. В такую высоту у меня всё не влезало, поэтому пришлось разбивать получающуюся простыню два файла. Вообще, можно было бы использовать PNG, там такого ограничения нет, но он в таком размере очень сильно нагружает браузер.

Потом я эти скриншоты уже руками обверстал в HTML и планирую результат тоже положить на «Гитхаб», как только у меня закончится процесс оптимизации JPEG. Он очень долгий, но овчинка выделки стоит — около 20—25% экономии, а файлы там большие — по несколько мегабайт.

Как будет готово, напишу про это отдельный небольшой пост.

6 комментариев

Заказ щедро оплачен

На днях баловался со стилями магазина «Озон» при помощи браузерного плагина Stylus — избавлялся от навязчивой рекламы внутри списка товаров. Это те бесячие предложения, которые игнорируют выбранную сортировку.

Скрипт приводить не буду, всё равно его приходится править время от времени — у «Озона» используется фреймворк «vue» и из-за этого всё время меняются имена классов.

Заказ №123456789-1234 от 3 февраля: оплачен 534 ₽ Озон Картой, доставка в пункт Ozon в Казани, получатель Евгений Степанищев, бесплатно.

Заодно, в качестве баловства, заменил себе надпись «Заказ оплачен» на «Заказ щедро оплачен». Эту часть могу привести, может кто-нибудь себе захочет так же сделать:

.tsBodyM[data-widget="orderStatus"]::before {
    content: 'Заказ щедро оплачен';
    color: var(--ozTextPrimaryNegative);
    font-size: var(--bodyMFont);
    line-height: 1.29;
}

2 комментария

Angie

Спросили как я отношусь к появлению Angie — отечественного форка известнейшего веб-сервера nginx.

Как-как? Сугубо положительно, разумеется.

Когда-то в веб-разработке была известная каждому аббревиатура LAMP, которая расшифровывалась как Linux, Apache, MySQL и Perl.

Это минимальный набор инструментов, чтобы сделать любой сайт. Времена менялись, Перл сменился ПХП, тот уступил Пайтону, МайЭсКуЭль заменила МарияДБ, но аббревиатура держалась.

Всё сломалось, когда пришёл nginx и вытеснил Апач.

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

А если серьёзно, никаких заметных телодвижений ребята пока не сделали, поэтому не вижу повода о них разговаривать. В стране постоянно появляются форки известных западных продуктов (в реестре отечественного ПО они достаточно заметны) и с тем же постоянством исчезают.

2 комментария

Анекдот на Пайтоне

У меня есть друг, которому я регулярно шлю шутки про «Вархаммер». Сам в этой теме не разбираюсь, но хочется думать, что его они веселят. А я — тот самый друг, которому шлют то же, но на тему программирования. Пару раз даже присылали мои собственные шутеечки.

Вчера прислали известный неприличный анекдот про Василия Ивановича и Петьку, но написанный на «Пайтоне». И хотя у меня довольно пуританский блог, анекдот так вырвиглазно написан, что пришлось его переписать.

Вот что мне прислали:

class Chapaev:
    nyuans = True
    def fuck(Class):
        Class.nyuans = True
        Class.ass.append('Dick')

class Petyka:
    nyuans = False
    ass = []

if Chapaev.nyuans == True and Petyka.nyuans == False:
    Chapaev.fuck(Petyka)

Как по мне, плохо написано примерно всё, мне очень не понравилось, так что держите, вот вам моя версия, которая, кстати, как мне кажется, более точно отражает суть анекдота:

Chapaev = []
Petka   = []
dick    = [20, Chapaev]

Petka.insert(*dick)

isNuance = Chapaev in Petka and Petka not in Chapaev
print(isNuance) // True

Ну и пока у меня ещё был запал, переписал на ПХП известнейший мем из фильма «ДМБ»:

class Gopher implements Countable
{
    public function count(): int
    {
        return 0;
    }
}

$gopher = new Gopher;
var_dump(count($gopher)); // 0
var_dump(empty($gopher)); // false

2 комментария

Определение ширины и высоты файла JPEG

В «Эгее» есть какой-то плавающий баг, из-за которого иногда не заливаются файлы JPEG. Илья, автор «Эгеи», пытался с ним разобраться, но до конца понять проблему не смог.

Пока рабочая теория такая, что функция ПХП getimagesize (она возвращает тип изображения и его высоту с шириной) иногда выдаёт неправильные значения.

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

Вот Илья и попросил написать меня такой код на чистом ПХП. Надеюсь это позволит продвинуться в изучении проблемы — код вошёл в одиннадцатую версию «Эгеи».

Выложу его у себя тоже, чтобы не потерялся, ну и может кому-то ещё пригодится:

function readJPEGWxH(string $filename): array
{
    $file = new SplFileObject($filename);

    if (!$file->flock(LOCK_SH)) {
        throw new RuntimeException('Cannot lock the file');
    }

    if ($file->fread(2) !== "\xFF\xD8") {
        throw new RuntimeException('Unknown format');
    }

    for (;;) {
        $decoded = @unpack('H4segment/nlen', $file->fread(4));

        if ($decoded === false) {
            throw new RuntimeException('Unknown format');
        }

        ['segment' => $segment, 'len' => $len] = $decoded;

        // SOFn (Start Of Frame)?
        if (preg_match('/^ffc[0-3]/', $segment)) {
            $segdata = @unpack('Cbits/nheight/nwidth', $file->fread(5));

            if ($segdata === false) {
                throw new RuntimeException('Unknown format');
            }

            return [$segdata['width'], $segdata['height']];
        }

        // len = full lenght of segment except segment magic code
        if ($file->fseek($len - 2, SEEK_CUR) !== 0) {
            throw new RuntimeException('Cannot find start of frame');
        }
    }
}

5 комментариев

AVIF в «Эгее»!

Как говорится, если нельзя, но очень хочется, то можно. «Эгея», как я писал не понимает формат AVIF, но мне его удалось всё-таки в неё вставить, причём даже малой кровью.

Изображение ниже я взял из своей заметки про Красную площадь в коронавирусный период. В оригинале оно весит 1,1 мегабайт в формате JPEG, в этой заметке используется сконвертированное изображение размером в 179 килобайт. Почувствуйте разницу.

Добавлено позднее: в небе, кстати, я отчётливо вижу артефакты сжатия, надо играть с параметром качества, прежде чем использовать. Но в остальном картинка как будто бы не попортилась.

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

Красно-белая церковь с разноцветными луковичными куполами и золотыми крестами на фоне голубого неба с белыми облаками, вид снизу через каменную стену и металлический забор.
Моя фотография храма Василия Блаженного (1555—1561 гг.) из заметки «Красная площадь без людей»

Итак, «Эгея» не поддерживает AVIF, но я её в «Эгею» вставил. Как же мне это удалось?

Дело в том, что «Эгея» поддерживает формат SVG, а SVG поддерживает внутри себя растровую графику, в том числе вставленную через протокол «data». Протокол «data» — это картинка, закодированная в текстовую строку, вида data:mime/type;encoding,encoded_data.

Вообще, SVG поддерживает и внешние ресурсы (то есть в теории способен подгрузить картинку снаружи), но при вставке через тег img (как в «Эгее») в нём это запрещено по соображениям безопасности.

Поэтому мысль такая — переводим графику в AVIF, кодируем в base64 («data» так умеет, а иначе бинарные данные не вставить), из-за кодирования они увеличатся на 33%, но даже при этом овчинка выделки стоит, а потом получившееся запихиваем в SVG.

Для конвертации я написал вот такой небольшой bash-скрипт (для работы требуется пакет ImageMagick):

#!/bin/bash

if [ -z "$1" ]; then
    echo "Usage: $0 <picturename>"
    exit 1
fi

NAME="$1"
NEWNAME="${NAME%.*}.svg"
WH=( $(identify -ping -format '%w %h' "$NAME") )

if [[ "$NAME" == *@2x* ]]; then
    WH=( $(( ${WH[0]} / 2 )) $(( ${WH[1]} / 2 )) )
fi

cat<<XML > "$NEWNAME"
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${WH[*]}" width="${WH[0]}" height="${WH[1]}">
<image width="${WH[0]}" height="${WH[1]}"
href="data:image/avif;base64,$(convert "$NAME" "avif:-" | base64)"/>
</svg>
XML

echo "$NAME" to "$NEWNAME" was converted.

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

Дополнение: уже сообщают, что в 15-й и ниже Айос ничего не видно.

13 комментариев

Формат AVIF и «Эгея»

Есть такой современный формат графики — AVIF, который родился из видеоформата AV1. Интересен он тем, что даёт иногда существенную выгоду от использования — в моём непоказательном эксперименте на первой попавшейся картинке он уделал формат JPEG на порядок).

Вместе с тем он неплохо поддерживается современными браузерами — 79% по данным сайта CanIUse. Это ещё без учёта полифилов.

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

Таблица поддержки браузеров: Chrome, Edge, Safari, Firefox, Opera, IE и мобильные версии показывают версии с поддержкой (зелёные), частичной (жёлтые) и отсутствием (красные).
Уровень поддержки браузерами формата графики AVIF

У меня возникла хорошая, на первый взгляд, идея чуть упростить себе жизнь через модуль mod_rewrite веб-сервера «Апач» («Эгея» его требует, у меня на хостинге он тоже используется) при помощи примерно такого правила:

<IfModule mod_rewrite.c>
AddType image/avif .avif

RewriteEngine On
RewriteCond %{HTTP_ACCEPT} image/avif
RewriteCond %{REQUEST_FILENAME} \.jpg$
RewriteCond "%{REQUEST_FILENAME}.avif" -s
RewriteRule ^.*$ $0.avif [END,T=image/avif]
</IfModule>

Что тут написано? Если браузер поддерживает формат AVIF (смотрим в заголовке), и запрашивает JPEG (определяется по расширению), и существует файл ненулевой длины с расширением .avif (то есть image.jpg.avif, например), то отдаём ему AVIF.

Хорошо работает, пока в папке с картинками лежит только image.jpg.avif, как только кладёшь ещё и image.jpg — перестаёт, отдаётся только она. Вчера час наверное думал что не так, пока наконец не понял — поверх «Апача» на хостинге стоит nginx, а ему мои правила по барабану.

Если картинки по урлу нет, то nginx отдаёт запрос дальше в «Апач», срабатывает правило, отдаётся AVIF, как только кладёшь JPEG, nginx его видит, отдаёт по урлу, не спрашивая «Апач» ни о чём.

Что с этим делать пока — не знаю, вероятно ничего, nginx никто отключить не даст (у меня обычный дешёвый хостинг без затей), буду думать над другими способами.

2 комментария

«⠀» (Braille Pattern Blank)

Недавно один из символов Юникода немного потрепал мне нервы — в одном из структурированных файлов, содержащих пользовательский ввод встретилось что-то, что не смогло нормально сконвертироваться из UTF-8 в CP1251. Зачем там нужна именно эта кодировка оставим за скобками — ну вот такие требования.

Случаи, когда что-то не может сконвертироваться мы специально отлавливаем, так как иногда оказывается, что всё-таки можно. Особенно показателен случай с «й», когда вместо одной буквы пришла «и» скомбинированная с отдельной краткой — в Юникоде так можно. Нормализация почему-то эту пару не объединила и конвертация падала на этой странной букве.

Скриншот терминала: предупреждение о Ruby в macOS и команды irb, проверяющие символ \xE2\xA0\x80 как «BRAILLE PATTERN BLANK» через unicode/name.
Выводим символ — пусто; проверяем — пробельный ли? нет; смотрим его название

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

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

Библиотека libiconv конвертировать его отказалась, хотя, как по мне, он вполне может быть сконвертирован в обычный пробел, что мы теперь и делаем.

2 комментария

Почему ПЗУ «прошивают»?

Во времена, когда я чаще паял какие-нибудь схемы, чем программировал, на глаза то и дело попадался термин «прошить ПЗУ». «ПЗУ» — это постоянное запоминающее устройство, когда-то обязательная часть каждого компьютера. Обычно в нём хранилась некая программа, которая стартовала при включении компьютера и содержала в себе некий первичный интерфейс для взаимодействия с пользователем.

Если эту программу надо было обновить, то ПЗУ «перепрошивали» — при помощи специального устройства — программатора, стирали предыдущую версию и «шили» новую.

Я как-то не задумывался почему это так называется. Ну шьют и шьют, такой термин. Оказывается дело в том, что память раньше состояла из очень маленьких ферритовых колец, которые в буквальном смысле прошивали при помощи иголки про́волочкой. Технология изменилась, а термин остался.

На выставке представлены электронные компоненты ЭВМ М-220: усилитель мощности УМП, матрица МОЗУ ёмкостью 4096 бит и свидетельство о приоритете изобретения от 11 апреля 1989 г.
В блоке магнитного оперативного запоминающего устройства (МОЗУ) едва видны «колечки» — ферритовые кольца очень маленького размера (фото сделано в музее компании АйСиЭл в 2018 году)

Из самиздатской книги «Фокал снаружи и изнутри» (автор — Uirh):

ПЗУ тех времён (постоянное запоминающее устройство с навечно еще на заводе прошитой информацией) делалось тоже на колечках, но размером куда как побольше и из «магнитомягкого» материала. Колечек брали столько, скольки разрядное слово в этом ПЗУ; устанавливали их в один ряд; выходная катушка на каждое из них наматывалась многовитковая, а входных было столько, сколько слов информации надо было записать. Причем информация в буквальном смысле прошивалась: брали иголку, вдевали в неё проволочку для очередного слова и протягивали через весь ряд.

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

С появлением микросхем постоянных и полупостоянных запоминающих устройств больше так не делают. Но термин «прошить» по отношении к ПЗУ — так с тех времён и остался.

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

2 комментария

Десятичная запятая

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

На этом месте я вдруг задумался — видел ли я хоть раз язык, где используется десятичная запятая?

Так и не вспомнил, зато на память пришёл забавный баг, который был когда-то в четвёртом ПХП: если установить русскую локаль, появлялась возможность складывать числа в строках, используя десятичную запятую:

<?php
var_dump('0,4' + '0,7');
setlocale(LC_ALL, 'ru_RU.koi8r');
var_dump('0,4' + '0,7');

Как можно убедиться онлайн, результат будет следующим:

int(0)
float(1,1)

Работает в версиях 4.0—4.2, в 4.3 уже не повторяется. Я не помню каким образом на него наткнулся, но чувство недоумения от этого эффекта до сих пор живо в моей памяти.

2 комментария

Неблокирующий режим файлов в PHP

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

Смотрел недавно исходники языка ПХП, хотел изучить подробнее как реализованы там стримы. Пока читал, случайно натолкнулся на недокументированную возможность — у функции fopen, как оказалось, есть режим открытия в неблокирующем режиме (буква n).

#if defined(O_NONBLOCK)
        if (strchr(mode, 'n')) {
                flags |= O_NONBLOCK;
        }
#endif

Доступно не на всех системах, только там, где есть константа O_NONBLOCK в fcntl.h, то есть в Линуксах, например.

Для меня это довольно полезное знание, так как я давно ищу возможность проверить из ПХП не зависла ли подмонтированная через NFS система хранения. Из-за таких зависаний виснет драйвер в ядре и все системные вызовы, которые к нему обращаются. В конечном счёте виснет процесс, который пытается работать с такой файловой системой, да так, что его не получается убить через SIGKILL.

В общем, надо будет попробовать при следующем таком инциденте — не спасает ли открытие в неблокирующем режиме. Код, кажется, должен выглядеть примерно так:

function tryToOpen(string $path, float $timeout): ?bool
{
	$dio_fd = dio_open($path, O_RDONLY | O_NONBLOCK);
	if ($dio_fd === false) {
		return null;
	}

	foreach (glob('/proc/self/fd/*') as $file) {
	    if (is_link($file) && readlink($file) === $path) {
		$fd = basename($file);
		$read = [fopen("php://fd/$fd", 'rn')];
		$write = $except = null;

		$timeout_us = (int) ($timeout * 1e6);
		return stream_select($read, $write, $except, 0, $timeout_us) === 1;
             }
	}
	return null;
}

Тут используется модуль ПХП Direct IO, чтобы обратиться к системной функции open мимо ПХП. Приходится так делать, потому что функция fopen, которая есть в языке, сначала попытается проверить существование файла и в моей задаче сразу зависнет.

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

Переоткрытие файла нужно, так как в вызов stream_select, который я использую для ожидания данных из файла с таймаутом, можно передать результат fopen, но не dio_open.

Надеюсь случай испытать этот код представится нескоро.

2 комментария

Алгол и Баш

Удивительно, я и не догадывался, что иногда употребляю ключевые слова родом из «Алгола-68»!

Оказывается автор командной оболочки bsh Стивен Борн притащил ключевые слова IF/FI и CASE/ESAC прямиком из «Алгола». Позже оттуда они попали в более известный сейчас bash, на котором я нередко пишу.

Более того, Стивен Борн по всей видимости насколько крепко фанател по этому языку, что специальными макросами превратил Си, на котором написал bsh, в некое подобие «Алгола» (исходники есть в интернете).

Если скачать себе пару заголовочных файлов (и выкинуть из них всё то, что мешает компиляции), то можно и сейчас попробовать этот макроязык в деле (это я пузырьковую сортировку написал):

#pragma GCC diagnostic ignored "-Wimplicit-int"
#pragma GCC diagnostic ignored "-Wreturn-type"

#include "mac.h"
#include "mode.h"
#include <stdio.h>

bubbleSort(arr, n)
    INT arr[], n;
BEGIN
    INT i, j;

    FOR i = 0; i < n - 1; i++
    DO  FOR j = 0; j < n - i - 1; j++
        DO  IF arr[j] > arr[j + 1]
            THEN    arr[j] = arr[j] - arr[j+1];
                    arr[j+1] = arr[j] + arr[j+1];
                    arr[j] = arr[j+1] - arr[j];
            FI
        OD
    OD
END
 
printArray(arr, n)
    INT arr[], n;
BEGIN
    INT i;
    FOR i = 0; i < n; i++
    DO
        printf("%d ", arr[i]);
    OD

    printf("\n");
END

main()
BEGIN
    INT arr[] = { 0xFE, 0xED, 0xBE, 0xEF };
    INT n = 1[&arr] - arr;

    printArray(arr, n);
    bubbleSort(arr, n);
    printArray(arr, n);
END

Примерно так выглядят исходники bsh. Единственное моё добавление — BEGIN и END, которые Борн для описания тела функций не использует, но зато в «Алголе» принято делать именно так.

Кстати (век живи — век учись), такое неординарное объявление функций, как у меня в листинге (а я подсмотрел его в исходниках bsh), — как оказалось, устаревший синтаксис Си, причём он до сих пор поддерживается компиляторами.

6 комментариев

Ушла эпоха

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

Вообще, Перл я любил, но была одна вещь, от которой меня некисло бомбило — это синтаксис передачи параметров в функции. Полюбуйтесь:

print sub {
    my ($a, $b) = @_;
    return $a + $b;
}->(1, 2);

Язык помещает параметры в специальную переменную @_ (с символа «собачки» в Перле начинаются массивы), откуда их можно достать любыми способами, которые подходят для массива. Всратейший синтаксис с кучей граблей. Самые очевидные — в случае копирования единственного параметра забыть скобки вокруг переменной и получить вместо параметра длину массива аргументов. Так устроен Перл — при присваивании массива скаляру мы получаем его длину.

Самые аккуратные ребята могли бы задавать так называемые «прототипы», где в специальных значках выражалось что функции хотелось бы получить на вход, например вот так: $$$;$. Но такое в чужом коде я видел только в учебниках, так как этот птичий язык мало кто помнил.

И вот недавно я читаю, что в версии 5.36 зарелизили синтаксис сигнатур, позволяющий явно определить параметры функции, полюбуйтесь какая красота:

use v5.36;

sub range($from, $to, $step = 1) {
    my $current;
    return sub {
    	($from, $current) = ($from + $step, $from);
        return undef if $current > $to;
        return $current;
    }
}

my $iter = range(0, 256);
while (defined (my $v = $iter->())) {
    print $v, "\n"
}

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

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

10 комментариев

← 1 15 16 17 18 19 67 →