Перейти к содержимому
ProgramCrafter

Отрисовщик картинок из интернета

Рекомендуемые сообщения

Программа умеет получать картинки по ссылке и отрисовывать их в OpenComputers. Поддерживается примитивный даунскейлинг.

wget -fq https://raw.githubusercontent.com/ProgramCrafter/lua-utils/main/images-drawer/draw-random-img.lua

Работа всё ещё в процессе. На данный момент:

1. Проверяю на работу только GIF.

2. Некоторые GIF некорректно парсятся по вине библиотеки.

3. К библиотеке GIF нужен патч, чтобы хоть какие-то гифки показывались.

wget -fq https://gist.githubusercontent.com/ProgramCrafter/d1b279aec9e473794df115d1301dcb27/raw/8166f23ee3daba8ca8ec305589b3d9a258f6674f/gif.lua /usr/lib/gif.lua

4. Даунскейлинг примитивный: если картинку надо уменьшить, то из каждого квадрата 2x2 пикселя выбирается левый верхний.

5. Требования: тир3 GPU и монитор, интернет-карта, 6 планок тир3,5 памяти.

 

Используемые библиотеки:

Скрытый текст

 

 

Зато результат неплохой:

screen-SWMEyvi.png

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
20 минут назад, ProgramCrafter сказал:

5. Требования: тир3 GPU и монитор, интернет-карта, 6 планок тир3,5 памяти.

А как шесть планок-то впихнуть в компьютер? Насколько я помню, четыре планки являются максимумом.

 

Upd: И ещё вопрос. Чересстрочная гребёнка была и на оригинальном изображении? Либо появилась в результате преобразований?

 

Изменено пользователем eu_tomat
upd

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Только что, eu_tomat сказал:

А как шесть планок-то впихнуть в компьютер?

Секрет на самом деле простой :D: отредактировать настройки майнкрафта или оцелота.

Я утроил размер одной планки памяти и у меня работает всё стабильно.

 

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

 

Строки 238-245 конфига:

      ramSizes: [
        576
        768
        1152
        1536
        2304
        3072
      ]

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Насчёт потребления памяти: вот расчёт, на что как минимум нужна память, в случае GIF.

  1. 270 КБ ест сама OpenOS с загрузки;
  2. + 350 КБ жрут библиотеки graffiti и gif при загрузке через require;
  3. + 1500 КБ занимает файл картинки;
  4. + 150 КБ уходит на буфер для одного кадра (строка длиной 500*300 в случае GIF);
  5. + (сколько-то, >=250, скорее всего) КБ нужно в качестве накладных расходов на декодер.

В сумме получается 2520 КБ, что существенно больше 2048 КБ - двух планок тир3,5 памяти.

 

Возможные пути обхода:

  1. Избавиться от зависимости от OpenOS.
    - программе нужны библиотеки serialization и event (обе для отладки),
    - отрисовщик загружает библиотеки (graffiti, gif) из интернета с помощью wget,
    - wget тянет зависимостями библиотеки filesystem, internet, shell и text,
    - отрисовщик сохраняет файл в tmpfs и использует для этого io.open...
  2. Не грузить библиотеки заранее.
    - тогда в "решающий момент" (когда попадётся картинка с другим форматом) пользователь будет ждать, когда загрузится кодек
  3. Не хранить в памяти файл картинки.
    - невозможно из-за того, что gif.lua использует seek, и нет возможности перемотать (особенно назад) поток из интернета
  4. Уменьшить накладные расходы на декодер.
    - надо разбираться в коде каждой библиотеки
  5. Складывать картинку на диск.
    - в tmpfs не влезает, поэтому придётся использовать обычный диск,
    - а если диск забит? с обычными настройками алмазный диск всего 4 МБ, и этого хватит всего на три картинки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Для начала я бы предпочёл применить самые доступные способы.

4 часа назад, ProgramCrafter сказал:

В сумме получается 2520 КБ, что существенно больше 2048 КБ - двух планок тир3,5 памяти.

  • Получается 2.48 планки памяти. То есть, можно справиться даже тремя планками из четырёх возможных, если использовать серверный корпус вместо обычного.
  • Заказ уборки мусора после каждой попытки отображения очередной картинки позволит поддерживать максимальный запас свободной памяти.
  • Быстрый отказ от получения слишком больших картинок поможет программе не допускать нехватки памяти, если сервер решил отдать неожиданного крупный файл. По возможности следует ориентироваться по данным HTTP-заголовка. Но в крайнем случае можно и просто контролировать потребление памяти при получении очередного фрагмента данных.
  • Оборачивание критичных вызовов в pcall позволяет обработать в том числе и ошибки, связанные с нехваткой памяти. Главное в этом случае — как можно скорее заказать уборку мусора. Но до этого желательно вообще не доводить.
  • Для лучшей оценки потребности в памяти имеет смысл записывать в журнал её потребление в конце каждого цикла вывода картинки непосредственно перед уборкой мусора. Учитывая, что картинки обновляются не часто, журналирование не будет сильно нагружать систему. Я бы записывал в журнал объём использованной памяти после каждого успешного вывода картинки, а также все опасные ситуации, которые удалось обработать: неожиданно крупные файлы, отдаваемые сервером картинок; ошибки, перехваченные с помощью pcall. Возможно, имеет смысл журналировать и случаи, когда объём свободной памяти опустился ниже установленного лимита, который программист определил как резервный.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
13 часа назад, ProgramCrafter сказал:

Даунскейлинг примитивный: если картинку надо уменьшить, то из каждого квадрата 2x2 пикселя выбирается левый верхний.

Было ли билинейную интерполяцию сделать затруднительным? Значение каждого входного пикселя делить на четыре, прибавлять к выходному значению и после декодирования округлить. Элементарный алгоритм, причём в данном случае можно ещё существенно упростить: просуммировать значения каналов пикселей в блоке 2×2, поделить на 4 и округлить. А лучше обобщить алгоритм для уменьшения пикчи с произвольным масштабом.

 

Алсо:

  • в коде смешан snake_case и camelCase (причём в луа обычно юзают второе, не вижу смысла отходить от этого)
  • busy-loop в ожидании finishConnect: читать пункт 3 и не насиловать процессор
  • неудовлетворительная обработка ошибок (см. выше): на той же строке 75 следует хотя бы в assert обернуть stream.read
  • 127 строка пугает: можно было определить нужную степень двойки и затем иметь готовый и быстрый результат `((x - 1) << scale) + 1`
  • каждый хендлер рисует пикчу, как вздумается: с точки зрения чистоты лучше бы все либы заставить каждый распаршенный блок пикселей отдавать в коллбэк, который бы был единым для всех форматов, выполнял интерполяцию и отдавал на буфер
  • (костыли с жпг непередаваемы)
  • спать один тик на каждый read звучит не очень здорово
  • насколько я могу судить, никакая из либ не требует бэктрекинга, поэтому кэширование выглядит излишне

Дальше мне смотреть было лень.

Изменено пользователем Fingercomp
алсо

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
4 часа назад, eu_tomat сказал:

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

Попробую, хотя тестировать в майнкрафте пока затруднительно, а в оцелоте серверных стоек нет (буду ждать, когда появятся :D).

 

4 часа назад, eu_tomat сказал:

Заказ уборки мусора после каждой попытки отображения очередной картинки позволит поддерживать максимальный запас свободной памяти.

Это правда, но вся память нужна для каждого кадра и одновременно.

 

4 часа назад, eu_tomat сказал:

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

К сожалению, большинство картинок слишком большие.

 

4 часа назад, eu_tomat сказал:

По возможности следует ориентироваться по данным HTTP-заголовка.

Да, я так и делаю. К счастью, сервер отдаёт Content-Length.

 

3 часа назад, Fingercomp сказал:

Значение каждого входного пикселя делить на четыре

Для этого надо пиксель дробить на R,G,B составляющие. Поэтому пока затруднительно.

 

3 часа назад, Fingercomp сказал:

в коде смешан snake_case и camelCase (причём в луа обычно юзают второе, не вижу смысла отходить от этого)

Я пытаюсь писать на всех языках в одном стиле.

Кстати, где там camelCase?

 

3 часа назад, Fingercomp сказал:

busy-loop в ожидании finishConnect: читать пункт 3 и не насиловать процессор

to be fixed

 

3 часа назад, Fingercomp сказал:

неудовлетворительная обработка ошибок (см. выше): на той же строке 75 следует хотя бы в assert обернуть stream.read

Функцию exact_readable вообще стоит вынести в отдельную библиотеку. Не делаю так потому, что каждый раз надо сбрасывать package.loaded.

 

3 часа назад, Fingercomp сказал:

127 строка пугает

?

 

3 часа назад, Fingercomp сказал:

каждый хендлер рисует пикчу, как вздумается

to be fixed

 

3 часа назад, Fingercomp сказал:

(костыли с жпг непередаваемы)

16 часов назад, ProgramCrafter сказал:

Проверяю на работу только GIF

Вид самой библиотеки программы для отрисовки JPG тоже непередаваем.

 

3 часа назад, Fingercomp сказал:

спать один тик на каждый read звучит не очень здорово

Вроде read отдаёт максимум данных, сколько может (+ в заданном размере буфера)?

 

3 часа назад, Fingercomp сказал:

насколько я могу судить, никакая из либ не требует бэктрекинга

Надо смотреть для каждой библиотеки использование метода seek - а желательно убирать перемотку вообще.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
11 минуту назад, ProgramCrafter сказал:

Это правда, но вся память нужна для каждого кадра и одновременно.

Так я и не предлагаю заказывать уборку во время формирования кадра. Смысл именно в том, чтобы освободить максимум памяти, занятой предыдущими кадрами. Это позволит начать формировать очередной кадр, имея максимум памяти. То есть, каждый кадр будет формироваться в одинаковых по доступной памяти условиях.

 

15 минут назад, ProgramCrafter сказал:

К сожалению, большинство картинок слишком большие.

Я имел в виду немного другое. После того как ты уже установил память в свой компьютер и оставил его работать, а не просто тестироваться, то можешь заранее вычислить, что файл определённого размера и формата не может быть обработан с имеющимся объёмом памяти. В этом случае нет смысла дожидаться его загрузки, надо запрашивать другой файл. Тесты тестами, но вдруг используемый тобой сервис преподнесёт тебе сюрприз и выдаст ссылку на файл ещё большего размера, чем ты ожидал? Но предварительный анализ позволит твоей программе даже не скачивать картинки, которые она не способна переварить.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
1 час назад, ProgramCrafter сказал:

Я пытаюсь писать на всех языках в одном стиле.

Это уже несколько оффтоп, конечно, но я считаю это дурным начинанием: на каждом языке есть свои устоявшиеся стилевые конвенции. В Java везде писатьКакВерблюд, в C# методы, как в жаве, но с заглавной буквы, чтобы выпендриться, а на си и питоне snake_case. Для удобства других программистов их следует соблюдать. Ты уже читал код jpg-либы: ощущаемый диссонанс или даже дисфория — прямое следствие написания не как надо.

 

Особенно это я нахожу важным при написании библиотек (не наш случай, то есть): когда код пестрит то_такими, тоТакими, ТоСякими, товообщевоттакими вызовами и переменными (был проект на плюсах такой, со всеми вариантами, кроме разве тотального капса...), и читать неприятно, и писать тоже затруднительно, потому что нужно постоянно вспоминать и чинить регистр каждой конкретной библиотеки.

 

1 час назад, ProgramCrafter сказал:

Вроде read отдаёт максимум данных, сколько может (+ в заданном размере буфера)?

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

 

1 час назад, ProgramCrafter сказал:

Для этого надо пиксель дробить на R,G,B составляющие. Поэтому пока затруднительно.

local r, g, b = (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff

Вроде не очень сложно.

 

Если в буферах хранить уже уменьшенные версии картинок (а полноразмерные всё равно не нужны, так что их даже не собирать и на лету ресайзить надо), то конечный буфер можно будет уменьшить до 16 килобайт (160×100). Чтобы не конкатить строки при сборке буфера, можно хранить таблицу из 2000 чисел, где каждое число хранит 8 пикселей по 8 бит: скорость доступа на изменение будет выше, чтение тоже не особо замедлится, и памяти буфер будет стабильно потреблять 32808 байт (которые OC зачтёт как 18227) всё время работы. (Без упаковки будет 256808 байт — для OC 142671. Если использовать вложенные таблицы, результат можно улучшить.)

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Добавлена экспериментальная поддержка PNG!

 

Разумеется, обычная библиотека graffiti не подходит - нужно использовать патч, улучшающий производительность.

wget -fq https://gist.githubusercontent.com/ProgramCrafter/d1b279aec9e473794df115d1301dcb27/raw/9048fa36feaaedccecf54c7ae626174af17abb42/graffiti.lua /usr/lib/graffiti.lua

 

screen-HCZRGQn.png

 

Картинка откопана на форуме)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Даунскейлинг готов и интегрирован в отрисовку GIF и JPG.

Последствия: JPG открывается плохо, с OOM.

Скрытый текст

screen-180-jpg.png

 

Рекомендую использовать для запуска программы контейнер (https://computercraft.ru/topic/6134-polu-docker/), в котором лежат графические библиотеки со всеми нужными патчами:

wget -fq https://raw.githubusercontent.com/ProgramCrafter/lua-utils/main/images-drawer/archive.bin

 

 

До масштабирования JPG работал! :)

Скрытый текст

unknown.png

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Отрисовка четырёх основных форматов картинок (PNG, JPG, BMP, GIF) работает!

В частности, BMP и GIF отрисовываются без использования временных файлов и поэтому работают внутри контейнера. У всех форматов, кроме PNG, даунскейл происходит по одному алгоритму.

 

JPEG:

Скрытый текст

screen-180.png

 

GIF:

Скрытый текст

screen-159db32.png

 

BMP:

Скрытый текст

screen-sdl2-hello.png?raw=true

 

PNG:

Скрытый текст

screen-HCZRGQn.png?raw=true

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяйтесь к обсуждению

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

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отобразить как ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.


×
×
  • Создать...