Программируем смартфоны Symbian Series 60. Графика

Начиная с этой статьи мы с вами займемся центральной панелью. Центральной панелью приложения должен быть объект, порожденный от класса CCoeControl. Это может быть или ваш объект, или какой-либо из стандартных — например, список, редактор текста и т.п. Сегодня мы рассмотрим, как создать свою главную панель. Для этого вы должны породить свой класс от CCoeControl. Класс CCoeControl имеет ряд виртуальных функций, которые вызываются при возникновении различных событий. Среди них нет чисто виртуальных функций, поэтому вы можете сами выбирать, какие из них вам надо переопределять, а какие — нет.

Рисование в панели


Чаще всего при создании своей панели вы будете переопределять функцию, рисующую содержимое панели. Она имеет следующее описание:
virtual void Draw(const TRect& aRect) const;

Данная функция вызывается каждый раз, когда необходимо перерисовать объект, и обладает одним аргументом, описывающим размеры и положение прямоугольной области, которую необходимо нарисовать. Рисование выполняется с помощью системного контекста рисования. Это объект типа CWindowGc, получаемый с помощью функции SystemGc() класса CCoeControl. Пример:
void CExampleFontContainer::Draw(const TRect& aRect) const {
CWindowGc& gc = SystemGc();
gc.DrawLine(TPoint(10, 10), TPoint(50, 40));
}

Для перерисовки окна используются функции DrawNow() и DrawDefferd(). Эти функции помечают панель как требующую перерисовки, а DrawNow() еще и вызывает функцию Draw(). Если же вы хотите вывести изображение при вызове функции, отличной от Draw, то также должны сами активировать и деактивировать контекст рисования и уведомить сервер окна о том, что вы в нем что-то нарисовали. Пример:

CWindowGc& gc = SystemGc();
// Активация контекста рисования
gc.Activate(Window());
// Уведомляем сервер окна о начале рисования
Window().BeginRedraw();
gc.DrawLine(TPoint(10, 10), TPoint(50, 40));
// Уведомляем сервер окна об окончании рисования
Window().EndRedraw();
// Деактивация контекста рисования
gc.Deactivate();

Необходимо отметить, что при рисовании в Symbian используется двойная буферизация, т.е., когда вы что-то рисуете в контексте рисования, то рисование выполняется не на экране, а во внеэкранном буфере. Это позволяет избежать эффекта мерцания при выводе изображения (например, когда вы перед выводом изображения очищаете экран). По окончании рисования содержимое внеэкранного буфера выводится на экран. Функция Window().EndRedraw() сообщает серверу окна, что содержимое внеэкранного буфера можно копировать на экран, однако это не означает, что копирование будет выполнено немедленно. Если вы хотите сразу вывести внеэкранный буфер на экран, то вызовите функцию
CEikonEnv::Static()->Flush();

Давайте рассмотрим теперь, что можно нарисовать, используя контекст рисования. Класс CWindowGc предоставляет полный набор функций для рисования графических примитивов: линий, прямоугольников, многоугольников, ломаных линий, эллипсов, дуг, хорд и секторов (надо отметить, что на других платформах (Windows Mobile, Palm) стандартных функций для рисования дуг, хорд и секторов нет). Для графических примитивов доступны функции для установки цвета и стиля линий, а также различные варианты закраски замкнутых фигур. Все эти функции достаточно стандартны, и рассматривать их подробно нет смысла.

Вывод текста

Класс CWindowGc позволяет выводить текст. Внимание: по умолчанию в контексте рисования не выбран ни один шрифт, поэтому при попытке вывести текст ваше приложение будет аварийно закрываться. Перед выводом текста необходимо с помощью функции UseFont(const CFont *aFont) выбрать шрифт. По окончании работы с текстом необходимо с помощью функции DiscardFont() освободить контекст рисования от шрифта. В качестве аргумента функции UseFont передается указатель на объект CFont, описывающий шрифт. С помощью класса CFont можно узнать различные параметры шрифта: высоту, ширину символа или строки и др. Получить объект CFont можно несколькими способами:

1. Выбрать один из системных шрифтов с помощью класса CEikonEnv. Класс CEikonEnv содержит следующие функции для получения системных шрифтов:
TitleFont() — крупный шрифт, используемый для заголовков.
AnnotationFont() — обычный шрифт, используемый при выдаче различных сообщений.
LegendFont() — второй вариант обычного шрифта — в зависимости от версии смартфона близок по начертанию или полностью совпадает с
AnnotationFont().
DenseFont() — тонкий шрифт.
SymbolFont() — шрифт, содержащий различные служебные символы (достался по наследству от OC EPOC32).
Пример отображения на экране начертания шрифтов (см. рис.):
void CExampleFontContainer::Draw(const TRect& aRect) const {
CWindowGc& gc = SystemGc();
gc.Clear();
gc.UseFont(CEikonEnv::Static()->TitleFont());
gc.DrawText(_L("Title Font"), TPoint(10, 20));
gc.UseFont(CEikonEnv::Static()->AnnotationFont());
gc.DrawText(_L("Annotation Font"), TPoint(10, 40));
gc.UseFont(CEikonEnv::Static()->LegendFont());
gc.DrawText(_L("Legend Font"), TPoint(10, 60));
gc.UseFont(CEikonEnv::Static()->DenseFont());
gc.DrawText(_L("Dense Font"), TPoint(10, 80));
gc.UseFont(CEikonEnv::Static()->SymbolFont());
gc.DrawText(_L("Symbol Font"), TPoint(10, 100));
gc.DiscardFont();
}

2. Выбрать один из системных шрифтов с помощью статической функции FontFromId() класса AknLayoutUtils (эта возможность появилась начиная с SDK2fp2). Функция FontFromId() имеет один аргумент — константу, обозначающую тип шрифта. Доступны три варианта:
EAknLogicalFontTitleFont — крупный шрифт, используемый для заголовков — обычно это тот же шрифт, что и возвращаемый с помощью
CEikonEnv::Static()->TitleFont().
EAknLogicalFontPrimaryFont — основной шрифт, используемый операционной системой для вывода различных сообщений.
EAknLogicalFontSecondaryFont — мелкий шрифт — обычно это тот же шрифт, что и CEikonEnv::Static()->DenseFont().
Как выглядят эти шрифты на смартфонах с разрешением 352х412, показано на рисунке.

3. Получить пользовательский шрифт с помощью функции Font() класса CEikonEnv (требуется подключение к проекту библиотек egul.lib и gdi.lib). У функции Font() один аргумент — ссылка на структуру TLogicalFont, описывающую параметры шрифта. У создаваемого шрифта задаются следующие параметры:
. категория шрифта: EView (нормальный системный шрифт), EButton (шрифт для кнопок панели управления), EAnnotation (малый системный шрифт) и ETitle (большой системный шрифт);
. стиль: ENormal, EBold, EItalic, EBoldItalic, ESuperscript, ESubscript, ELight и ECustom;
. увеличивающий коэффициент.
Пример:
void CExampleFontContainer::Draw(const TRect& aRect) const {
CWindowGc& gc = SystemGc();
gc.Clear();
TZoomFactor zoom;
TLogicalFont lf(TLogicalFont::EView, TLogicalFont::ENormal, zoom);
gc.UseFont(CEikonEnv::Static()->Font(lf));
gc.DrawText(_L("User Font"), TPoint(10, 30));
gc.DiscardFont();
}
Запрашиваемый вами шрифт может отсутствовать в системе, тогда функция Font() возвращает указатель на максимально близкий по начертанию шрифт из присутствующих. Фактически какой бы шрифт вы ни запросили, выбор осуществляется среди нескольких перечисленных выше системных шрифтов. Получить шрифт, отличный от системного, с помощью функции Font() можно только по его Uid.

4. Получить шрифт по его имени с помощью статической функции FontFromName() класса AknLayoutUtils. Для этого надо знать имена шрифтов, установленных в смартфоне.
С выводом текста разобрались. Теперь о выводе изображений.

Загрузка и вывод изображений

В Symbian "родным" графическим форматом является mbm. Mbm-файл содержит одно или несколько изображений, при этом включаемые в него
изображения могут иметь разные размеры и глубину цвета. Изображения, включаемые в mbm-файл, сжимаются с помощью библиотеки zlib. Исходные изображения должны быть в bmp-формате. Создать mbm-файл можно двумя способами:

1) включить в файл проекта описание mbm-файла;
2) воспользоваться утилитой bmconv.exe, находящейся в папке epoc32\tools.
Рассмотрим первый способ. Описание mbm-файла в файле проекта имеет следующий вид:

START BITMAP <имя mbm-файла>
HEADER
TARGETPATH <папка расположения mbm-файла>
SOURCEPATH <папка с исходными изображениями>
SOURCE <формат> <имя bmp-файла>
SOURCE <формат> <имя bmp-файла>

END

TARGETPATH задает папку, в которой будет лежать mbm-файл в устройстве. Для S60.1 и S60.2 это должна быть папка, в которой лежит ваш app- файл (обычно \system\apps\<имя программы>), или вложенная в нее. Для S60.3 расположение другое. Если имя mbm-файла совпадает с именем exe-файла, то он может располагаться либо в папке \resource\apps, либо в папке \private\<UID вашей программы>. Если имя mbm-файла отличается от имени exe- файла или mbm-файлов несколько, то они должны располагаться в папке \private\<UID вашей программы> или вложенной в нее.

SOURCEPATH задает папку, в которой лежат исходные изображения. Одна или несколько строк SOURCE задают изображения, включаемые в mbm-файл. У строки SOURCE два параметра: формат включаемого изображения и имя исходного bmp-файла. Формат изображения определяет количество цветов во включаемом изображении. Если заданное количество цветов отлично от количества цветов исходного изображения, происходит автоматическое преобразование к нужному формату. Однако иногда преобразование не поддерживается — например, исходное изображение цветное, а при включении оно описано как черно-белое. Поэтому рекомендуется делать так, чтобы формат исходного изображения совпадал с форматом включаемого (или был максимально близок). Возможны следующие форматы включаемого изображения:

1 — черно-белые изображения;
2 — четыре градации серого (2 бита на точку);
4 — 16 градаций серого (4 бита на точку);
8 — 256 градаций серого (8 бит на точку);
C4 — цветное изображение, 16 цветов (4 бита на точку);
C8 — цветное изображение, 256 цветов (8 бит на точку);
C16 — цветное изображение, 65.536 цветов (16 бит на точку);
C24 — цветное изображение, 16.777.216 цветов (24 бита на точку).

Необходимо отметить, что, хотя во многих современных смартфонах используются экраны с 262 тысячами цветов, графическая подсистема S60 работает с 16-битным цветом (только программы для работы с фотокамерой, используя прямой доступ к видеопамяти, могут работать со всеми 262 тысячами цветов). Поэтому включать в mbm-файл изображения C24 нет смысла. С16 предпочтительнее, так как он занимает меньше места, а кроме того система с ним работает быстрее всего, так как не нужны дополнительные преобразования цвета. Пример описания mbm-файла:

START BITMAP HelloWorld.mbm
HEADER
TARGETPATH \system\apps\helloworld
SOURCEPATH .
SOURCE c16 bitmap1.bmp
SOURCE 1 bitmap1_mask.bmp
END

После компиляции проекта, содержащего данный фрагмент, в папке epoc32\data\z\system\apps\helloworld появится файл HelloWorld.mbm. Если вы компилируете проект для эмулятора, то файл HelloWorld.mbm появится также в папке epoc32\release\winscw\udeb\z\system\apps\helloworld. Кроме mbm- файла, в папке epoc32\include будет создан заголовочный файл HelloWorld.mbg, содержащий следующее:

enum TMbmHelloworld {
EMbmHelloworldBitmap1,
EMbmHelloworldBitmap1_mask
};

Эти константы нам нужны будут для доступа к изображениям, включенным в mbm-файл. Теперь перейдем к выводу изображений из mbm-файла. Первое, что мы должны сделать, — загрузка изображения. Пример:

TBuf<256> filename;
TParse path;
path.Set(Application()->AppFullName(), NULL, NULL);
filename = path.Drive();
filename.Append( _L("\\system\\apps\\helloworld\\HelloWorld.mbm"));
CFbsBitmap *Bitmap = new (ELeave) CFbsBitmap();
User::LeaveIfError(Bitmap->Load(filename, EMbmHelloworldBitmap1));

В этом примере мы сначала формируем имя mbm-файла. Для этого получаем полное имя файла программы и вырезаем из него имя диска. Затем к имени диска дописываем имя mbm-файла с путем (сформировать имя mbm-файла лучше всего в конструкторе класса CAknAppUi и затем просто использовать его в программе). После того как имя mbm-файла сформировано, мы создаем изображение CFbsBitmap и с помощью функции Load() загружаем в него изображение с идентификатором EMbmHelloworldBitmap1. Не забудьте для этого примера подключить заголовочный файл:
#include <HelloWorld.mbg>

Теперь загруженное изображение можно вывести на экран. Для этого используются функции BitBlt() и BitBltMasked() класса CWindowGc (функция BitBltMasked() выводит изображение с использованием черно-белой маски). Начиная со второй редакции в S60 появилась поддержка и других форматов графических файлов (bmp, jpeg, gif, png). Вы можете загружать и сохранять изображения в различных форматах. Мы рассмотрим только загрузку. За загрузку изображений в формате, отличном от mbm, отвечает класс CImageDecoder. Для его использования к проекту необходимо подключить библиотеку ImageConversion.lib. Рассмотрим пример загрузки jpeg-файла. Первое, что мы делаем, — формируем имя файла.

TBuf<256> filename;
TParse path;
path.Set(Application()->AppFullName(), NULL, NULL);
filename = path.Drive();
filename.Append( _L("\\system\\apps\\helloworld\\HelloWorld.jpg"));
Затем создаем объект CImageDecoder. При этом происходит загрузка заголовка изображения (но не самого изображения).
CImageDecoder *aDecoder = CImageDecoder::FileNewL( CEikonEnv::Static()->FsSession(), filename, CImageDecoder::EOptionAlwaysThread); CleanupStack::PushL(aDecoder);
Следующим этапом создаем изображение CFbsBitmap, используя информацию из заголовка jpeg-файла.
CFbsBitmap *Bitmap = new (ELeave) CFbsBitmap();
User::LeaveIfError( Bitmap->Create(
aDecoder->FrameInfo().iOverallSizeInPixels,
aDecoder->FrameInfo().iFrameDisplayMode));
И последним этапом загружаем изображение
TRequestStatus status;
aDecoder->Convert(&status, *Bitmap);

Загрузка изображения происходит асинхронно. Поэтому, когда функция Convert() вернет управление, изображение не будет загружено — будет лишь создан отдельный поток, в котором и будет выполняться загрузка. Переменная status во время загрузки будет иметь значение KErrUnderflow, а по окончании — KErrNone, если загрузка прошла успешно, или же содержать код ошибки. Если вам необходимо в вашем основном потоке дождаться загрузки изображения, добавьте в программу еще одну строку:
User::WaitForRequest(status);
и не забудьте по окончании загрузки удалить объект aDecoder:
CleanupStack::PopAndDestroy();

Работа с внеэкранным буфером

Хотя графическая подсистема Symbian поддерживает двойную буферизацию, часто бывает удобнее иметь свой собственный внеэкранный буфер (особенно в играх). Создается внеэкранный буфер как изображение CFbsBitmap. Пример:
iBitmap = new (ELeave) CFbsBitmap();
User::LeaveIfError( iBitmap->Create( TSize(width, height), CEikonEnv::Static()->ScreenDevice()->DisplayMode()));

Последним параметром функции Create является тип изображения. В нашем примере он задается таким же, как и тип экрана (в современных устройствах это EColor64K — 16 бит на точку). Этот вариант изображения выводится быстрее всего. Но вы можете задать и другой формат. Доступны следующие (цифра обозначает количество цветов): черно-белые — EGray2, EGray4, EGray16, EGray256 — и цветные — EColor16, EColor256, EColor4K (12 бит на точку — "родной" формат для S60.1), EColor64K и EColor16M. Для рисования во внеэкранном буфере необходимо получить его контекст рисования CFbsBitGc. Делается это следующим образом:

CFbsBitGc *imageGc;
CFbsBitmapDevice* bitmapDevice = CFbsBitmapDevice::NewL(iBitmap);
CleanupStack::PushL(bitmapDevice);
User::LeaveIfError( bitmapDevice->CreateContext(imageGc));
CleanupStack::PushL(imageGc);
// Рисуем
CleanupStack::PopAndDestroy(2);

Контекст рисования CFbsBitGc предоставляет те же средства рисования, что и контекст рисования окна CWindowGc. Выводить внеэкранный буфер на экран можно несколькими способами:
1. Используя контекст рисования окна. Это наиболее простой и безопасный способ, однако при этом у нас будет уже не двойная, а тройная буферизация. В особо ресурсоемких играх это может быть причиной снижения производительности.
2. Используя контекст рисования экрана CFbsScreenDevice.
3. Используя прямой доступ к видеопамяти. Это наиболее быстрый, но и наиболее сложный вариант. Дело в том, что у разных устройств
организация видеопамяти может различаться.

При использовании контекста рисования экрана CFbsScreenDevice или прямого доступа к видеопамяти рисование происходит непосредственно в видеопамять. Это может быть источником проблем. Дело в том, что, если у вас на передний план выйдет другая программа, то рисование через CFbsScreenDevice затрет содержимое окна активной программы. В результате у вас будет работать одна программа, а на экране будет изображение от другой. Это, конечно, не приведет к краху системы, но все же неприятная ситуация. Избежать этого позволяет механизм управления прямым доступом к видеопамяти CDirectScreenAccess. Этот класс сигнализирует приложению через интерфейс MDirectScreenAccess, когда доступ к видеопамяти разрешен, а когда запрещен.

На сегодня все.

Алексей Аношенко


Компьютерная газета. Статья была опубликована в номере 02 за 2007 год в рубрике mobile

©1997-2024 Компьютерная газета