Рассмотрим классический пример: программу, которая позволяет открыть файл, содержащий растровое изображение, и просмотреть его на экране. Изображение при этом можно масштабировать, а если его размер в текущем масштабе превышает размер экрана — прокручивать.
В классических операционных системах такая задача зачастую решается приложением с закрытой архитектурой, то есть таким, которое либо само содержит весь требуемый код, либо использует сторонние компоненты, но сугубо по собственной инициативе. Ключевой момент заключается в том, что использовать часть приложения нельзя — можно либо запустить его как есть, либо воспользоваться другим решением.
Для того, чтобы понять, как эта задача решается в рамках ОС «Сивелькирия», придётся сначала заглянуть «под капот» данной программы и понять, что именно она делает и какими понятиями оперирует. Ниже перечислены сущности, возникающие в процессе работы программы.
- Расположение изображения. В классическом случае оно описывается путём к файлу на диске. При чуть более широком рассмотрении в эту категорию также попадают адреса в локальной сети или в Интернете. Это, однако, не исчерпывает всех возможностей: изображение может находиться в оперативной памяти, на выходе некоторой программы (например, обработчика фотографий или отрисовщика графиков), на web-странице, в сообщении чата или электронной почты, внутри архива или офисного документа. Несмотря на то, что технически все эти варианты отличаются друг от друга и требуют различной обработки, с точки зрения пользователя все они служат одной цели: указывают, где находится изображение, которое он хочет просмотреть. Создание интерфейса, позволяющего выбирать среди стольких вариантов, представляет собой нетривиальную задачу, однако концептуально никаких препятствий к такому подходу нет. Более того: не существует причины, по которой пользователю не следует давать возможности просмотреть изображение, находящееся в каком-либо из перечисленных выше мест.
- Последовательность байтов, представляющих хранимое изображение. Способ обращения к этой последовательности будет определяться способом хранения, однако с точки зрения алгоритмов, читающих эти байты с целью вывода изображения на экран, разница вряд ли существенна.
- Формат файла изображения. Популярными форматами являются, например, jpeg и gif. Окончательный формат определяется по содержимому файла: если файл jpeg был ошибочно сохранён с расширением .gif, попытка интерпретировать его как gif или полный отказ от обработки будут недальновидными. В то же время, подсказки о предполагаемом формате могут быть получены, например, при анализе расширения файла или заголовков, переданных web-сервером.
- Кодек, содержащий необходимые алгоритмы для извлечения данных (карты пикселей и служебной информации) из сырого байтового представления.
- Представление содержимого графического файла в виде одной или более страниц, а страницы — в виде одного или более кадров. Некоторые форматы, такие как tiff, позволяют упаковывать в один файл более одной страницы. Другие — например, gif — поддерживают покадровую анимацию.
- Размер полного изображения.
- Параметры цвета изображения: цветовая модель, глубина цвета, палитра, поддержка прозрачности.
- Карта пикселей изображения или его части.
- Геометрия (размер и смещение) области изображения, подлежащей выводу на экран.
- Окно (или другая форма экранного интерфейса), осуществляющее вывод изображения на экран устройства.
В рамках ОС «Сивелькирия» каждая из перечисленных выше сущностей описывается некоторым интерфейсом данных. Такое представление позволяет использовать их во многих контекстах за пределами первоначальной модели (о чём будет сказано далее).
Каждый интерфейс реализуется некоторым объектом, который создаётся каким-либо модулем. Код методов объекта выполняется в рамках породившего его модуля. При этом говорить о запуске модулей в качестве отдельных приложений некорректно, так как у них нет ни собственных потоков, ни памяти за пределами созданных ими объектов. Если требуется хранение состояния, выходящего за рамки работы с одним изображением, оно хранится в отдельном объекте, доступ к которому осуществляется по общим правилам — через объектный интерфейс ОС.
Сама структура модулей, участвующих в работе данной программы, может выглядеть следующим образом:
- Первый модуль определяет поведение объекта, реализующего интерфейс «Расположение объекта» (изображение является частным случаем объекта). В частности, он определяет способ получения доступа к байтам из данного расположения. При этом он может использовать модули, предоставляющие прямой доступ к диску или поддержку сети, в зависимости от физического расположения объекта.
- Второй модуль осуществляет непосредственный доступ к байтам изображения. Кроме того, он предоставляет подсказки о возможном типе содержимого, основываясь на способе хранения (расширение файла, заголовки web-сервера и т. п.).
- Третий модуль, используя подсказки о типе содержимого и доступ к его байтовому представлению, определяет фактический тип содержимого (формат графического файла).
- Четвёртый модуль реализует кодек, который позволяет извлекать служебную (разбивка по страницам и кадрам, размер, цвет) и графическую (карты пикселов) информацию из байтового представления изображения. При этом может извлекаться как полная, так и частичная информация: например, вызывающему контексту может требоваться конкретный кадр или конкретная страница. Другой пример извлечения частичной информации — это предварительный просмотр миниатюры изображения в низком разрешении: полное чтение файла jpeg для этого может не требоваться.
- Пятый модуль принимает данные о геометрии полного изображения, карту пикселей, содержащихся в изображении, и данные о том, какой фрагмент изображения и в каком масштабе следует отобразить. На основе этих данных он — уже с учётом масштабирования — строит карту пикселов, непосредственно предназначенную для вывода на экран, и передаёт её вызвавшему контексту.
- Наконец, модуль верхнего уровня реализует пользовательский интерфейс. Он отвечает за отрисовку окна, вывод полученной карты пикселей и реакцию на действия пользователя. Некоторые из этих действий могут требовать обращения к иным модулям — например, если пользователь инициирует открытие или сохранение файла, будет запущен модуль, отвечающий за просмотр расположения файлов на диске, который, в свою очередь, вернёт интерфейс, описывающий положение выбранного пользователем файла.
- Разумеется, кроме описанных модулей, в работе программы могут принимать участие модули, обеспечивающие дополнительные возможности: логирование, отрисовку компонентов окна, звуковую обратную связь и так далее.
Легко видеть, что такая структура делает повторное использование функциональности максимально простым. Так, установка в системе нового кодека означает, что данный тип изображений будет автоматически поддержан во всех контекстах и во всех программах. Различные модули могут быть написаны разными программистами и на разных языках, однако в рамках данной модели взаимодействия эти различия несущественны. Один и тот же кодек может быть использован как для загрузки изображений на экран в данном интерфейсе, так и для их отрисовки в программе обмена сообщениями, браузере, просмотрщике содержимого директории и так далее.
Поддержка новых сценариев использования осуществляется элементарно. Например, добавление нескольких дополнительных интерфейсов позволяет поддержать такие действия, как переход к следующему и предыдущему изображениям (безотносительно их расположения и способа доступа к ним) или применение фильтров. Введение тонкого клиента также не представляет проблем: поскольку прохождение данных и вызовов через границы модуля контролируется операционной системой, возможно вынести затратные операции (например, декодирование содержимого файла и масштабирование изображения) на другую машину.
Поскольку прототипы всех модулей, описанных выше, известны операционной системе, она знает об их потребностях с системной точки зрения и может действовать соответственно. Например, операция масштабирования изображения может выполняться в отдельном потоке, чтобы работа с крупными изображениями не блокировала интерфейс на слабых компьютерах. Более того, поскольку модули не делают предположений о том, в каких потоках они выполняются, возможны дополнительные оптимизации: например, поток пользовательского интерфейса может отделяться от потока, отвечающего за вычисления (в данном случае — за масштабирование), лишь в том случае, если последний не успел закончить работу в течение заранее определённого промежутка времени, а при быстром повторном изменении масштаба отрисовка изображения в новом масштабе может быть начата в дополнительном потоке ещё до того, как поток, занимающийся отрисовкой, которую уже не требуется заканчивать, успеет обработать сигнал к завершению. Поскольку операционная система располагает информацией обо всех выполняющихся потоках, о загрузке и фактических возможностях процессоров компьютера, данные оптимизации могут быть эффективнее, чем те, которые может сделать автор отдельного приложения на основе предположений о среде его выполнения.
Вопрос о том, каким образом модули, совместно обеспечивающие решение прикладной задачи, компонуются между собой, может быть решён несколькими способами. Например, очевидно, что выбор модуля, осуществляющего чтение байтов файла с диска, определяется файловой системой данного раздела, причём во всех случаях доступа к одному и тому же разделу будет использоваться один и тот же модуль. Модуль, отвечающий за определение формата изображения, скорее всего, будет устанавливаться на уровне системы и использоваться во всех контекстах. Исключение могут составлять случаи, когда в модуле возникает сбой: в этом случае операционная система может выполнить поиск другого установленного модуля, соответствующего данному прототипу, и при его наличии — попробовать использовать его с теми же входными данными. Таким образом, ошибка может быть обработана без вмешательства пользователя, а данные о ней — собраны и, если это допускается политиками безопасности данной машины, переданы разработчикам проблемного модуля вместе с необходимой сопутствующей информацией. В случае, если в каком-либо модуле часто возникают проблемы, операционная система может принять решение об исключении его из цепочки поиска или понижении его приоритета в ней.
В других случаях выбор модуля может определяться пользователем и храниться в конфигурации. Так, разные модули масштабирования изображений могут обеспечивать различный стиль отрисовки (параметры антиалиасинга при уменьшении масштаба или размытия границ пикселей при его увеличении). В зависимости от контекста, пользователю может требоваться различный подход (резкие границы пикселей для точного позиционирования или размытые — для визуального комфорта) .
Способы запуска модулей также могут различаться. Например, модуль, ответственный за отрисовку окна программы просмотра изображений, может быть вызван модулем, отвечающим за отрисовку рабочего стола (при щелчке на графическом файле на рабочем столе), или модулем, отображающим меню запуска программ. После запуска модуль окна подгружает те модули, которые необходимы ему для выполнения текущей задачи.
Данное описание является лишь демонстрацией принципиальной возможности реализации подобной схемы взаимодействия и не может рассматриваться в качестве законченной инструкции по написанию программы просмотра изображений, операционной системы и/или модулей для неё.