Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New filtering stage #50

Open
noobie-iv opened this issue Dec 24, 2024 · 3 comments
Open

New filtering stage #50

noobie-iv opened this issue Dec 24, 2024 · 3 comments
Labels
dispute Debate enhancement New feature or request question Further information is requested

Comments

@noobie-iv
Copy link
Member

@zvezdochiot, @plzombie

Если планируем новую ветку с промежуточными фильтрами, надо решить, как жить дальше.

Как было раньше (в старых ST, за 2008 год):

  • Исходные файлы идентифицируются по ImageId. Внутри Id - всего лишь полный путь к файлу, так что это уникальный идентификатор.
  • В ходе работы делается нарезка на страницы, которые все равно реально представлены одним изображением. Поэтому id завернут внутрь PageId, который хранит ImageId и дополнительно номер страницы.
  • Все фильтры, кроме последнего, не меняют исходное изображение, а только масштабируют, поворачивают, и подрезают. Все это умеет делать Qt на лету при рисовании QImage. Поэтому от фильтра к фильтру передается исходный QImage, а каждый фильтр только правит матрицы преобразований и контур подрезки, а Qt их рисует, когда надо - такая обработка происходит очень быстро.

После выбора новой страницы обработка идет в три стадии Обработка изображения, Обновление эскиза, Прорисовка эскиза:

  • Обработка изображения

    • Обработка происходит в Task::process()
    • Главное окно собирает список задач (у каждой задачи есть указатель на следующую, или нуль).
    • Получается цепочка LoadFileTask -> fix_orientation::Task -> page_split::Task -> ... -> null.
    • LoadFileTask загружает исходный файл, и далее по цепочке передается Id, QImage и Transformation.
    • Каждый фильтр в цепочке правит либо трансформацию (ориентация, поворот), либо подрезку (страницы, поля).
    • По завершении результаты (изображение, трансформация и подрезка) передаются в центральное изображение, где Qt и рисует исходную картинку - повернутую и подрезанную.
  • Обновление эскиза

    • Обработка происходит в CacheDrivenTask::process()
    • Первая стадия сигналит списку эскизов (ThumbnailSequence), и тот запускает вторую цепочку из CacheDrivenTask.
    • В ней нет файловых операций, она только возвращает результирующую трансформацию для Id.
    • В список эскизов помещается обновленный легковесный Item c Id внутри.
  • Прорисовка эскиза

    • Прорисовка происходит в обработчике события paint()
    • Эскиз, которому надо прорисоваться, лезет в кеш (ThumbnailPixmapCache) по PageId.
    • Кеш для PageId генерирует уникальное имя файла для записи на диск, создает и записывает туда изображение, и возвращает эскизу для прорисовки.
    • Уникальное имя получается дописыванием к исходному имени хеша, полученного из полного пути (который как раз есть в Id). Уникальный путь дает уникальных хеш и имя эскиза для записи.

Важно, что прорисовка эскизов оптимизирована. Как и с главной картинкой, на всех стадиях кроме последней, используется одно и то же изображение. Это можно посмотреть во время работы ST в каталоге out\cache\thumbs. На всех стадиях в кеше хранится одна копия эскиза, потому имя файла кеш генерирует по одному и тому же Id. А зрительно эскизы отличаются потому, что после загрузки из кеша эскиз еще кое-что дорисовывает поверх сам.

Исключение - последняя стадия, output. На этой стадии сначала Task::process() создает новое окончательное изображение, и записывает его в каталог out под именем out_file_path. А потом создает новый эскиз, у которого Id подменен на новый - PageId(ImageId(out_file_path)). И цепочка CacheDrivenTask::process() тоже возвращает новый идентификатор - PageId(ImageId(out_file_path)). После этого при рисовании эскиза в кеше появится новая запись, потому что хеш пути поменялся, и на стадии output эскиз становится другой.

Как стало в ST/STA/STU

Если попробовать поменять изображение в какой-нибудь из промежуточных стадий (как в STD) - ломается логика работы цепочек Task/CacheDrivenTask и кеша, которые в оригинале максимально легковесны. Возможно, поэтому во всех поздних версиях ST обработку изображения затолкали на вкладки последней стадии, чтобы не переделывать базовую логику.

Как сделано в STEX

После появления в середине обработки стадии dewarp понадобилось обновлять эскизы, если сделана развертка (а если только поворот - то не надо). Поэтому кеш был доработан: элементам ThumbId внутри него добавлены флаги isAffineTransform. Эти флаги отличают распрямленные изображения от обычных, и кеш генерирует для них другие имена (см. метод ThumbnailPixmapCache::Impl::getThumbFilePath). Поэтому для развернутых изображений кеш, начиная со стадии dewarp и до конца, создает дополнительные файлы эскизов, которые не смешиваются с эскизами предыдущих стадий.

Что должно быть теперь?

Теперь планируется в еще одной новой стадии менять изображение по дороге. Разумеется, нужно менять и эскиз. Какие есть варианты? Сходу могу предложить:

  • Просто генерировать в кеше для разных стадий разные имена файлов: img_1_XXX, img_2_XXX, img_3_XXX. Просто и дубово. Появятся лишние чтения - записи.
  • Ввести параметр "версия эскиза", и передавать ее по цепочкам. Какой фильтр нахулиганил - увеличивает версию, и в кеше генерируется новый файл. Это аналог решения из STEX. Видимо, средне-сложно.
  • Принудительно записывать новый файл на диск, если фильтр поменял изображение, и дальше передавать уже его Id. Придется в каталоге out завести подкаталоги для вредных фильтров. Это аналог решения в стадии output. Видимо, самое сложное.
    • В отладочной версии сейчас после распрямления видны жуткие тормоза, от стадии dewarp и до самого конца. Видимо, повторные генерации изображения тормозят. Решение с промежуточной записью должно вроде ускорить дело.
    • Теоретически, тут можно и внешнюю обработку добавить. Внешняя программа как раз через этот промежуточный сохраненный файл и обменивалась бы с ST.
@noobie-iv noobie-iv added the dispute Debate label Dec 24, 2024
@zvezdochiot
Copy link
Member

@noobie-iv

Я "вижу" логику применения комбо: до этапа "filtering" - работа с исходным изображением. После этапа "filtering" - работа с промежуточным записанным неважно куда геометрически исправленным, отмасштабированным! (причём окончательно!) и отфильтрованным изображением.

Хорошая ли система получится? Возникли сомнения. Я забыл про исходную концепцию работы с исходным изображением на всех этапах. Но эта концепция плохо вписывается в "геометрические искажения" уже сейчас. Удастся найти более лаконичное решение - хорошо. Не удастся - "на нет и суда нет". Но наработки иметь стоит, хотя бы чтобы обсуждать по делу.

Такие вот дела. 😄

@zvezdochiot zvezdochiot added enhancement New feature or request question Further information is requested labels Dec 24, 2024
@noobie-iv
Copy link
Member Author

@zvezdochiot say:

эта концепция плохо вписывается в "геометрические искажения" уже сейчас

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

Когда запускается обработка, сначала LoadFileTask загружает исходное изображение, а потом в середине стадия Deskew его на лету переделывает. В отладочной версии STEX и в более медленном STD после этой стадии начинаются тормоза из-за постоянной повторной развертки. А если добавить в таком же духе еще и стадию фильтрации с произвольно тяжелыми действиями - работать с программой можно будет только под успокоительным. А ведь вся эта многопоточная возня изначально затевалась, чтобы тормозов не было.

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

А надо, чтобы цепочка выполнялась только с минимально необходимого места. Например, стадия Filtering отработала и сохранила свою версию файла. И, если стадии до нее не менялись, то цепочку до стадии Output надо строить от Filtering, а не от LoadFileTask. А для этого надо сначала опросить все фильтры, были ли у них изменения, чтобы отловить, с какого начать. Т.е. нужна еще цепочка QueryFileChangeTask. Или надо вообще строить цепочки от конца к началу, чтобы каждый фильтр запрашивал данные от предыдущего.

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

@zvezdochiot
Copy link
Member

zvezdochiot commented Dec 30, 2024

@noobie-iv . Да я вот и усомнился в "верности выбранного пути". По логике использования надо видеть результат действия фильтров, а вот сделать это без тормозов - нереал.

Осознал я провальность данной идеи и рассматриваю данный вопрос только как эксперимент, который стоит провести, но результатом его будет не релиз, а наработки и знание их затрат ("стоимости"). Такие вот дела.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dispute Debate enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants