Быстрая функция отмены для приложения растрового редактора

Я пытаюсь создать приложение для растрового редактора для iphone, которое будет похоже на Brushes или Layers или вырезанную версию Photoshop. Я хотел бы иметь возможность поддерживать изображения с разрешением 1000x1000 с примерно 4 слоями, если это возможно.

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

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

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

Я предвижу следующие проблемы:

  • Шаблон команды: что мне делать после 500 операций редактирования, и я хочу отменить последний? Загрузка начального состояния и применение 499 может занять много времени, особенно если некоторые из них являются дорогостоящими вещами, например, применяя фильтры размытия. Мне не нравится, как отмена происходит в разное время в разных сценариях.

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

Единственные решения, о которых я могу думать, следующие:

  • Используйте шаблон команды и шаблон памяти, где каждые 10 команд или около того или после дорогостоящей операции сохраняется также все состояние (что дает вам возможность автосохранения бесплатно). Чтобы отменить, я перезагружаю ближайший моментальный снимок, а затем воспроизвожу команды. Однако я предпочел бы избежать этой сложности.

  • Используйте шаблон памяти и заставляйте пользователя ждать кэширования растровых изображений. Это не так уж плохо, если я построю это время, например. ожидая применения фильтра, но он не очень хорошо работает между мазками кисти.

Есть советы? Мне было бы интересно узнать, как это делают некоторые существующие приложения.

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

Ответ 1

Контрольная точка для каждого растрового изображения с отображением памяти Флэш-память на устройствах iOS достаточно быстра, чтобы поддерживать операции отмены/повтора на больших растровых изображениях. Используя системный вызов mmap, я легко сбрасывал 1024x768 ABGR растровые изображения, сохраняя мою программу от использования драгоценного DRAM. Я не знаю, как вы хотели бы абстрагироваться от шаблонов отмены/повтора, но способ избежать операций с большими накладными расходами состоит в том, чтобы указатели на отмену и повторение растровых изображений меняли каждый откат/повтор. Вы указали более одного уровня отмены, но я уверен, вы можете уйти с некоторой заменой указателя (сейчас я страдаю от некоторой бессонницы и пытаюсь продемонстрировать, что замена указателя оказалась слишком большой - это был довольно симпатичный мусорный psuedocode).

Кроме того, я бы не рекомендовал отмечать ваши страницы mmap'd как F_NOCACHE. Предпочтительно, чтобы кэш iOS записывал в растровое изображение в DRAM, потому что:

  • Это быстрее, если вы не флеш-флэш
  • Флэш-память предназначена для фиксированного количества записей - не очень приятно сжечь флеш пользователей (я думаю, что где-то порядка 5 миллионов записывается с качеством флэш-памяти в устройствах iOS)
  • Я считаю, что iOS достаточно агрессивно справляется с управлением памятью, что он будет отключать ваши кэшированные записи и стирать их во время кризиса памяти (хотя я никогда не заботился об этом, чтобы проверить).

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

Обратите внимание, что мне пришлось сделать файл размером фактического растрового изображения перед вызовом mmap на fd. Не переходите к гамме памяти, сопоставляя 100 растровых изображений, хотя (просто шучу - сделайте это!) Я имею в виду, сколько отменить может пользователь выполнить? Они слабы, и их пальцы устают после нескольких секунд затирания кнопок.

Ответ 2

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

Если вы принимаете u-уровни отмены, вы можете объединить уровни действий старше, чем шаги u в фоновом режиме.

У вас также может быть гибридный подход. Если действие является "маленьким", представляйте его как слой. Если он большой, как записанное действие, его нужно переиграть. Как вы говорите, вам нужна эвристика, чтобы решить между этими двумя случаями. Вы можете проверить рендеринг/экономию производительности при первом запуске приложения после установки и решить некоторые значения параметров для эвристики.

Ответ 3

Мне кажется, что момент-импульс выглядит лучше всего. Это означает, что я, вероятно, попытаюсь подумать о способах оптимального хранения этой информации. Вы упомянули, что это может быть 1000x1000 bitmap потенциально для одного действия. Не могли бы вы, например, представить растровое изображение как однобитовое растровое изображение с отдельным цветом, сохраненным в другом месте? Теперь вместо 2 МБ у вас есть 125 КБ для хранения.

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

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

Ответ 4

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

Ответ 5

То, с чем вы сталкиваетесь, похоже на ограничение ресурсов для меня, поэтому это то, что касается вашего UI/Application Design. Нет лишних трюков, чтобы получить больше памяти, более быстрый доступ к диску и т.д.

Я бы пошел на гибридный подход, используя memeton в ringbuffer и обработав каждый фильтр в следующем свободном предварительно выделенном слоте. Это даст вам ограниченный быстрый откат/повтор без повреждения кучи. Для неограниченных шагов отмены/повтора у вас есть свой командный журнал. Если вам действительно нужно 400 шагов отмены, вы также можете сохранить как каждые 10 операций моментальный снимок с помощью команд и сжать 10 этих снимков в более крупный снимок.

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

Также может быть полезно подготовить ваш быстрый буфер отмены/повтора в фоновом режиме, поэтому, когда пользователь отменяет 2 раза, вы уже подготовили 3-й откат. В большинстве случаев пользователь просматривает изображение в течение нескольких мс (около 200-400), прежде чем он выполнит следующую отмену.

Также вы можете реализовать различные стратегии отмены/повтора для обратимых операций, в которых используется минимальная память. Быстрый алгоритм сжатия без потерь (lzma и т.д.) В фоновом режиме может еще больше уменьшить потребление памяти.

Но поскольку это, по-видимому, ограничение ресурсов (IO vs. Memory vs. CPU), все ваши возможности - жить с ним и стараться максимально эффективно использовать пользователя. И это иногда становится трудной задачей.