Наши партнеры








Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net


Содержание

Книги

Статьи
Программирование в X Window средствами Free Pascal
А. П. Полищук, С. А. Семериков


1.2 Текст и графика
В данном разделе описываются возможности, которые имеет программист для вывода текста и произвольных графических изображений. Особенностью X является то, что рисовать можно не только в окне, но и в специально подготовленной области памяти. Данная область называется картой пикселей и идентифицируется целым числом, имеющим тип TPixmap. Карта толщиной в один бит имеет специальное название - битовая.


Прежде чем начать работу с графикой, программа должна выделить себе специальную структуру данных и получить указатель на нее. Эта структура называется графическим контекстом (Graphic Context (GC)). Указатель на GC используется в качестве одного из параметров при вызове "рисующих" функций X. Графический контекст содержит ряд атрибутов, влияющих на отображение объектов: текста, линий, фигур и др. Выделенный GC должен быть освобожден до завершения работы программы.

Графический контекст создается процедурой XCreateGC(), имеющей следующий прототип:
function XCreateGC(prDisplay : PDisplay; nDrawable : TDrawable;
          nValueMask : cardinal; prValues : PXGCValues) : TGC; cdecl; external;

Первый аргумент - это указатель на структуру типа TDisplay, который программа получает после вызова XOpenDisplay(); второй - идентификатор окна (или карты пикселей), в котором программа будет рисовать; третий - битовая маска, определяющая, какие атрибуты GC задаются; последний аргумент - структура типа TXGCValues, определяемая следующим образом:
TXGCValues = record
     Xfunction : longint; { Renamed function to Xfunction }
     plane_mask : cardinal;
     foreground : cardinal;
     background : cardinal;
     line_width : longint;
     line_style : longint;
     cap_style : longint;
     join_style : longint;
     fill_style : longint;
     fill_rule : longint;
     arc_mode : longint;
     tile : TPixmap;
     stipple : TPixmap;
     ts_x_origin : longint;
     ts_y_origin : longint;
     font : TFont;
     subwindow_mode : longint;
     graphics_exposures : TBool;
     clip_x_origin : longint;
     clip_y_origin : longint;
     clip_mask : TPixmap;
     dash_offset : longint;
     dashes : char;
  end;
PXGCValues = ^TXGCValues;
Значения полей данной структуры будут объяснены ниже. Каждому из них соответствует бит в маске, которая передается в качестве третьего параметра при вызове процедуры XCreateGC(). Эти биты обозначаются символическими константами, определенными в модуле X. Если бит установлен, то значение соответствующего атрибута должно быть взято из переданной XCreateGC() структуры TXGCValues. Если бит сброшен, то атрибут приникает значение по умолчанию.

Следующий пример показывает процесс создания графического контекста, в котором устанавливаются два атрибута: цвет фона и цвет переднего плана.
. . . . . . .
var
  prGC : TGC;
  rValues : TXGCValues;
  prDisplay : PDisplay;
  nScreenNum : integer;
. . . . . . . .
  rValues.foreground := XBlackPixel (prDisplay, nScreenNum);
  rValues.background := XWhitePixel (prDisplay, nScreenNum);
. . . . . . . .
  prGC := XCreateGC (prDisplay, XRootWindow (prDisplay, nScreenNum),
  (GCForeground OR GCBackground), @rValues);
Вызов XCreateGC() - не единственный способ создания графического контекста. Так, например, новый контекст может быть получен из уже существующего GC с помощью XCopyGC().

Когда контекст порожден, его атрибуты могут изменяться процедурой XChangeGC(). Например:
rValues.line_width := 10;
XChangeGC (prDisplay, prGC, GCLineWidth, @rValues);
Приведенный фрагмент кода меняет ширину линий, рисуемых с помощью графического контекста.

Для того, чтобы получить значение полей GC, используется процедура XGetGCValues().



В предыдущем разделе мы говорили, что GC имеет ряд атрибутов, воздействующих на вывод изображений. Для текста это цвет и шрифт, для линий - цвет и толщина и т.д. Как уже упоминалось выше, атрибуты контекста задаются в момент его создания. Потом они могут меняться с помощью функции XChangeGC(). Кроме того, X поддерживает специальные функции для изменения параметров GC.

Ниже перечисляются основные характеристики графического контекста и процедуры, меняющие их.

Режим рисования (поле Xfunction в структуре TXGCValues) указывает, каким образом комбинируются при рисовании цвет графики и цвет изображения, на которое накладывается графика. Данное поле задает некоторую логическую функцию. Возможные значения:
      GXclear         0x0  0
      GXand           0x1  src AND dst
      GXandReverse    0x2  src AND NOT dst
      GXcopy          0x3  src
      GXandInverted   0x4  (NOT src) AND dst
      GXnoop          0x5  dst
      GXxor           0x6  src XOR dst
      GXor            0x7  src OR dst
      GXnor           0x8  (NOT src) AND (NOT dst)
      GXequiv         0x9  (NOT src) XOR dst
      GXinvert        0xa  NOT dst
      GXorReverse     0xb  src OR (NOT dst)
      GXcopyInverted  0xc  NOT src
      GXorInverted    0xd  (NOT src) OR dst
      GXnand          0xe  (NOT src) OR (NOT dst)
      GXset           0xf  1
По умолчанию Xfunction равно GXcopy. Устанавливается режим рисования с помощью процедуры XSetFunction().

Изменяемые цветовые плоскости. Каждый пиксель задается с помощью N бит. Биты с одним номером во всех пикселях образуют как бы плоскости, идущие параллельно экрану. Получить число плоскостей для конкретного дисплея можно с помощью функции XDisplayPlanes(). Поле plane_mask структуры графического контекста определяет, в каких плоскостях идет рисование при вызове функций X. Если бит поля установлен, то при рисовании соответствующая плоскость изменяется, в противном случае она не затрагивается.

Цвет переднего плана и фона (поля foreground и background) задают цвета, используемые при рисовании линий текста и других графических элементов. Устанавливаются значения указанных полей функциями XSetForeground() и XSetBackground() соответственно.

Атрибуты, влияющие на рисование линий. Шесть параметров определяют вид прямых, дуг и многоугольников, изображаемых с помощью X Window.

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

2. Поле line_style определяет тип линии. Возможные значения следующие:
LineSolid - сплошная линия;
LineOnOffDash - пунктирная линия; промежутки между штрихами не закрашиваются;
LineDoubleDash - пунктирная линия; промежутки между штрихами закрашиваются цветом фона.

3. Параметр cap_style определяет вид линии в крайних точках, если ее ширина больше 1 пикселя. На рис. 1.4 приведены значения параметра и соответствующий вид конца линии.


Рис. 1.4. Значения параметра cap_style графического контекста

4. Поле join_style определяет, как соединяются линии друг с другом. На рис. 1.5 показаны соответствующие возможности. Параметр имеет смысл при толщине линии большей 1.


Рис. 1.5. Значения параметра join_style графического контекста

5. Если линия пунктирная, то поле dashes дает длину пунктира и промежутков в пикселях.

6. Параметр dash_offset указывает, с какого места начинать рисование первой черточки пунктирной линии.

Для установки параметров линии используется процедура XSetLineAttributes().

Шрифт. Поле font определяет шрифт, используемый для вывода текста. Задать этот параметр можно с помощью процедуры XSetFont().

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

Способ закраски определяется полем fill_style. Он устанавливается процедурой XSetFillStyle() и воздействует на все функции, рисующие линии, текст и фигуры. Исключение составляет случай, когда выводится линия, для которой значение line_width равно 0. Возможные значения параметра fill_style перечислены ниже.

FillSolid - для закраски используются цвета переднего плана и фона.

FillTiled - для закраски используется карта пикселей, определяемая параметром tile графического контекста; при этом карта как бы располагается в окне так, что ее левый верхний угол имеет координаты ts_x_origin и ts_y_origin; затем определяется ее пересечение с рисуемой графикой, и пиксели, попавшие в пересечение, закрашиваются; значения полей ts_x_origin, ts_y_origin устанавливаются процедурой XSetTSOrigin(); карта tile должна иметь ту же толщину (число бит-на-пиксел), что и окно, в котором производится рисование.

FillStippled - для закраски используется карта пикселей, задаваемая полем stipple; данная карта должна иметь толщину в 1 бит; способ закраски такой же, как и в случае FillTiled с той лишь разницей, что рисуются лишь те пиксели графики, которым соответствует установленный бит в карте stipple; цвет пикселя задается полем foreground.

FillOpaqueStippled - аналогично значению FillStippled, только пиксели, для которых не установлен бит в карте stipple, закрашиваются цветом фона.

Для задания полей tile и stipple можно использовать карты любого размера. На некоторых устройствах при определенных размерах рисование идет намного быстрее. Для получения таких размеров можно использовать процедуры XQueryBestSize(), XQueryBestStipple(), XQueryBestTile().

Режим заполнения многоугольников указывает, как заполнять цветом многоугольники, стороны которых пересекаются. Возможные значения следующие:
EvenOddRule - заполняются точки фигуры, определяемые по следующему правилу: пусть для некоторой линии растра n1, n2, ..., nk - стороны многоугольника, которые ее пересекают; тогда закрашиваются точки между n1 и n2, n3 и n4, и т.д.
WindingRule - заполняется вся внутренность фигуры.

Режим заполнения дуг (поле arc_mode). Параметр задается процедурой XSetArcMode() и влияет на вид фигур, рисуемых процедурами XFillArc() и XFillArcs().

Влияние подокон на рисование графических примитивов определяется полем subwindow_mode. Оно устанавливается процедурой XSetSubwindowMode() и имеет следующие значения:
ClipByChildren - часть графики, перекрываемая подокнами, не видна;
IncludeInferiors - графика рисуется поверх всех подокон.

Генерация запроса на перерисовку при копировании частей окон (поле graphics_exposures). Когда часть окна копируется куда-либо, то вполне вероятна ситуация, что исходное изображение перекрыто, возможно не полностью, другими окнами или недоступна по другим причинам. В этом случае может быть необходимо сообщить клиенту, в окно которого происходит копирование, что часть нового изображения не может быть получена простым переносом пикселей, а должна быть перерисована. Если поле graphics_exposures равно True, то X посылает при копировании следующее:
- одно или несколько событий GraphicsExpose, если перерисовка необходима;
- событие NoExpose, если исходное окно полностью доступно и дополнительного рисования не требуется.
Если поле равно False, то событие не посылается. Устанавливается параметр процедурой XSetGraphicsExposures().

Область отсечения (задается полями clip_mask, clip_x_origin, clip_y_origin). Это битовая карта, говорящая о том, какие пиксели выводятся, а какие нет при всех операциях рисования. Если бит карты установлен, то соответствующий пиксель появится в окне, а если бит сброшен, то пиксель будет пропущен. Положение в окне верхнего левого угла области отсечения определяется параметрами clip_x_origin и clip_y_origin.
Эти параметры устанавливаются процедурой XSetClipOrigin(). Сама область отсечения задается с помощью процедур XSetClipMask(), XSetClipRectangles() или XSetClipRegion().



Текст был и, видимо, будет важным средством информационного обмена между программами и пользователем. X Window позволяет выводить строки в любой части окна, используя большое количество шрифтов.

Для вывода текста используются процедуры XDrawString(), XDrawImageString() и XDrawText(). Каждая из них имеет две версии. Первая используется для шрифтов, имеющих не более 256 символов. Если же символов больше ("большие" шрифты), то применяется вторая версия. Функции, работающие с "большими" шрифтами, имеют имена XDrawString16(), XDrawImageString16() и XDrawText16(). Параметры процедур, выводящих текст, задают дисплей, окно, графический контекст, строку, ее положение и т.д. Рисование идет в соответствии с областью отсечения контекста. Буквы или их части, находящиеся за пределами области отсечения, не изображаются. Наиболее часто употребляется процедура XDrawString() (XDrawString16()). Ее параметры дают строку, ее длину и положение в окне. Текст рисуется цветом переднего плана, выбранного в GC.

Функция XDrawImageString() (XDrawImageString16()) похожа на предыдущую процедуру с той лишь разницей, что фон символов при рисовании закрашивается цветом фона, установленного в GC. XDrawString() и XDrawImageString() выводят символы, используя шрифт, установленный в GC.

XDrawText() (XDrawText16()) позволяет рисовать несколько строк сразу, используя при этом разные шрифты. Каждая рисуемая единица задается структурой TXTextItem.
Процедура XDrawText16() использует структуру TXDrawText16.

Поле font в приведенных структурах (TXTextItem и TXDrawText16) задает шрифт, используемый для рисования. Если значение поля font - None, то применяется шрифт, выбранный в GC.

Как мы уже говорили ранее, текст, как правило, рисуется шрифтом, выбранным в графическом контексте. X версии 11.4 и ниже поддерживает только растровые шрифты, а начиная с версии 11.5 и выше X Window имеет также и векторные шрифты.

В растровых шрифтах каждому символу соответствует некоторый битовый шаблон, определяющий порядок закраски пикселей при рисовании. Если бит шаблона равен 1, то соответствующий элемент изображения закрашивается цветом переднего плана GC, если же он равен 0, то он закрашивается либо цветом фона, либо вообще не рисуется.

В векторных шрифтах каждый символ описывается последовательностью линий, которые, будучи составлены вместе, и дают его изображение. Размеры символов варьируются от шрифта к шрифту. Для их описания используется структура TXCharStruct. Сам шрифт описывается структурой TXFontStruct.

Перед тем, как выводить текст, используя тот или иной шрифт, последний должен быть загружен в X Window и выбран в графическом контексте.

Загрузка шрифта осуществляется процедурой XLoadFont(). Она берет в качестве аргумента имя шрифта, находит его и возвращает программе соответствующий идентификатор. Этот идентификатор передается затем процедуре XSetFont(), чтобы выбрать шрифт в GC. Заметим, что реально шрифт с данным именем загружается сервером лишь один раз. После этого при обращениях к XLoadFont() с тем же именем шрифта, функция возвращает ссылку на шрифт, уже находящийся в памяти компьютера.

По умолчанию X ищет файл со шрифтом в директории /usr/lib/X11/fonts. Программист может задать дополнительные директории для поиска с помощью процедуры XSetFontPath().

Имя шрифта в X начинается с "-" и состоит из двух частей. Между ними стоит "--". В свою очередь, каждая из частей состоит из полей-слов, разделенных "-".
В первой части указывается следующее:
1)изготовитель шрифтам (foundry), например adobe;
2)семейство шрифта (font family), например courier, helvetica;
3)жирность шрифта (weight), например bold;
4)наклон шрифта (slant);
5)ширина букв шрифта (width).
Во второй части указывается следующее:
1)размер шрифта в пикселах (pixels);
2)размер шрифта в десятых долях "точки" ("точка" равна 1/72 дюйма);
3)горизонтальное разрешение устройства, для которого разработан шрифт (horizontal resolution in dpi); величина измеряется в числе точек на дюйм;
4)вертикальное разрешение устройства, для которого разработан шрифт (vertical resolution in dpi); величина измеряется в числе точек на дюйм;
5)тип шрифта (spacing); возможные значения параметра следующие: m - шрифт с фиксированной шириной символов; p - пропорциональный шрифт с переменной шириной символов;
6)средняя ширина символов шрифта, измеренная в десятых долях пикселя (average width);
7)множество символов шрифта в кодировке ISO (International Standards Organisation) или других (character set).

Ниже приведен пример названия шрифта.
-adobe-courier-bold-o-normal--10-100-75-75-m-60-iso8859-1

Части имени могут заменяться символом "*" или "?". В этом случае X подбирает шрифт, сличая имена имеющихся шрифтов с предоставленным шаблоном, так, как это делается при поиске файлов в UNIX. Например, шаблону
*charter-medium-i-*-240-*
соответствуют имена
-hit-charter-medium-i-normal-25-240-75-75-p-136-iso8859-1
-hit-charter-medium-i-normal-33-240-100-75-p-136-iso8859-1

Названия шрифтов, доступных в системе, хранятся в соответствующей базе данных. Получить список имен шрифтов можно с помощью процедуры XListFonts() или XListFontsWithInfo(). Список шрифтов, возвращаемый этими функциями, должен быть освобожден вызовом XFreeFontNames().

Некоторые шрифты, такие как "fixed" или "9x15", доступны всегда.

Получить информацию о загруженном шрифте можно с помощью функции XQueryFont(), которая возвращает заполненную структуру типа XFontInfo. Одновременно загрузить шрифт и получить информацию о нем можно с помощью процедуры XLoadQueryFont().

Когда информация о шрифте больше не нужна, ее следует освободить с помощью XFreeFontInfo(). Когда становится не нужен и сам шрифт, последний надо "сбросить", обратившись к процедуре XUnloadFont(). Функция XFreeFont() объединяет в себе XFreeFontInfo() и XUnloadFont().

Следующий фрагмент кода загружает шрифт "courier", создает GC и выводит с его помощью строку "Hello, world!".
var
  prDisplay : PDisplay;
  prGC : TGC;
  nWnd : TWindow;
  prFontInfo : PXFontStruct;
. . . . . . .
(* Загружаем шрифт *)
prFontInfo := XLoadQueryFont(prDisplay, '*-courier-*' );
if ( prFontInfo = NIL) then
begin
  writeln('Font not found!');
  halt(1);
end;
. . . . . . .
(* Создаем GC и рисуем строку *)
prGC := XCreateGC(prDisplay, nWnd, 0, NIL);
XSetForeground (prDisplay, prGC, XBlackPixel(prDisplay, 0));
XSetFont (prDisplay, prGC, prFontInfo^.fid);
XDrawString (prDisplay, nWnd, prGC, 10, 50, 'Hello, world!',
      strlen ('Hello, world!') );
XFreeGC (prDisplay, prGC);
. . . . . . .
(* "Сбрасываем" шрифт *)
XFreeFont (prDisplay, prFontInfo);
. . . . . . .
Для отображения символов кириллицы необходимо использовать один из локализованных шрифтов в той кодировке, которая поддерживается вашей системой (как правило, это koi8-r (koi8-u)). По умолчанию загружается первый из шрифтов, соответствующий шаблону, поэтому для корректного отображения текста с кириллицей необходимо в шаблоне указывать кодировку.



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

В связи с этим появилось понятие "цветовой карты" - палитры. Палитра является таблицей того же размера, что и количество одновременно отображаемых данным экранным контроллером цветов. Каждый элемент палитры содержит RGB (Красные, Зеленые и Синие) величины различных цветов (все цвета могут быть нарисованы, используя некоторую комбинацию красного, зеленого и синего).

Для того, чтобы сделать использование цветов близким к тому, которое предполагал программист, были введены функции выделения палитры. Вы можете попросить, выделить вас элемент палитры для цвета, заданного набором RGB-значений. Если он уже существовал, вы получите индекс в таблице. Если цвет не существовал, и таблица не заполнена, должна быть выделена новая ячейка палитры, содержащая данные значения RGB, и возвращен ее индекс. Если таблица была заполнена, процедура должна была закончиться неудачей. Вы могли затем запросить получение элемента палитры с цветом, ближайшим к требуемому. Это означает, что фактически рисование на экране будет произведено с использованием цвета, близкого к желаемому, но не того же самого.

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

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

Для доступа к стандартной экранной палитре, определена функция XDefaultColormap(), возвращающая дескриптор палитры, используемой по умолчанию на первом экране (напоминаем, что сервер X может поддерживать несколько различных экранов, каждый из которых может иметь свои собственные ресурсы).
var
  screen_colormap : TColormap;

  screen_colormap := XDefaultColormap(display, XDefaultScreen(display));
Другой макрос, связанный с распределением новой палитры, работает так:
var
  default_visual : PVisual;
  my_colormap : TColormap;

  default_visual := XDefaultVisual(display, XDefaultScreen(display));
  (* Создаем новую палитру, количество цветов в которой       *)
  (* определяется количеством цветов, поддерживаемых данным экраном. *)
  my_colormap := XCreateColormap(display,
                    win,
                    default_visual,
                    AllocNone);
Имейте в виду, что дескриптор окна используется только для того, чтобы позволить серверу X создать палитру для данного экрана. Мы можем затем использовать эту палитру для любого окна, нарисованного на том же экране.

Как только мы получили доступ к некоторой палитре, мы можем начать распределять цвета. Это делается с помощью функций XAllocNamedColor() и XAllocColor(). Первая из них - XAllocNamedColor() - принимает имя цвета (например, "red", "blue", "brown" и т.д.) и распределяет ближайший цвет, который может в действительности рисоваться на экране. XAllocColor() принимает цвет RGB, и распределяет ближайший цвет, который может отображаться на экране. Обе функции используют структуру TXColor, содержащую следующие поля:
pixel : cardinal - индекс палитры, используемый для рисования данным цветом.
red : word - красная составляющая RGB-значения цвета.
green : word - зеленая составляющая RGB-значения цвета.
blue : word - синяя составляющая RGB-значения цвета.

Пример использования этих функций:
var
  (* Эта структура будет содержать выделенные цветовые данные *)
  system_color_1, system_color_2 : TXColor;
  (* Эта структура будет содержать точные RGB-значения именованных *)
  (* цветов, которые могут отличаться от выделенных        *)
  exact_color : TXColor;
  rc : TStatus;

  (* Выделяем "красный" элемент палитры *)
  rc := XAllocNamedColor(display,
               screen_colormap,
               'red',
               @system_color_1,
               @exact_color);
  (* проверяем успешность выделения *)
  if (rc = 0) then begin
    writeln('XAllocNamedColor - выделить "красный" цвет не удалось.');
  end
  else begin
    writeln('Элемент палитры "красный" выделен как (',
            system_color_1.red, ', ', system_color_1.green, ', ',
            system_color_1.blue, ') в RGB-значениях.');
  end;

  (* выделяем цвет со значениями (30000, 10000, 0) в RGB. *)
  system_color_2.red := 30000;
  system_color_2.green := 10000;
  system_color_2.blue := 0;
  rc := XAllocColor(display,
            screen_colormap,
            @system_color_2);
  (* проверяем успешность выделения *)
  if (rc = 0) then begin
     writeln('XAllocColor - цвет (30000,10000,0) выделить не удалось.');
  end
  else begin
    (* что-то делаем с выделенным цветом... *)
  .
  .
  end;
После того, как мы распределили желаемые цвета, мы можем использовать их, рисуя текст или графику. Для этого нам нужно установить эти цвета как передний план и цвет фона для некоторого GC (графического контекста), и затем используйте этот GC для рисования. Это делается с помощью функций XSetForeground() и XSetBackground():
XSetForeground(display, my_gc, screen_color_1.pixel);
XSetForeground(display, my_gc, screen_color_2.pixel);

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



Xlib не имеет никаких средств для работы с популярными графическими форматами, такими как gif, jpeg или tiff. На программиста (или высокоуровневые графические библиотеки) оставлен перевод эти форматы изображений в форматы, с которыми знаком X сервер - битовыми и пиксельными картами.

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

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

В действительности, пиксельная карта X может трактоваться как окно, которое не появляется на экране. Многие графические операции, которые работают в окнах, точно также будут работать в пиксельных картах - достаточно подставить дескриптор пиксельной карты вместо дескриптора окна. В страницах справочного руководства видно, что все эти функции принимают TDrawable, не TWindow, поскольку как окна так и пиксельные карты - рисуемые элементы, и они оба могут использоваться, чтобы рисовать в них такими функциями, как, например, XDrawArc(), XDrawText(), и т.п.

Один из способов загрузки битового изображение из файла в память - включение файла побитового изображения в программу директивой #include препроцессора языка С.

Покажем, как можно получить доступ к файлу непосредственно:
var
  (* эта переменная будет содержать дескриптор новой пиксельной карты *)
  bitmap : TPixmap;

  (* эти переменные будут содержать размер загружаемой битовой карты *)
  bitmap_width, bitmap_height : word;

  (* эти переменные будут содержать положение горячей точки *)
  (* загружаемой битовой карты               *)
  hotspot_x, hotspot_y : integer;

  (* эта переменная будет содержать дескриптор корневого окна экрана, *)
  (* для которого мы хотим создать пиксельную карту          *)
  root_win : TWindow;
  rc : longint;

  root_win := XDefaultRootWindow(display);

  (* загружаем битовую карту из файла "icon.bmp", создаем *)
  (* пиксельную карту, содержащую свои данные в сервере, *)
  (* и сохраняем ее дескриптор в переменной bitmap    *)
  rc := XReadBitmapFile(display, root_win,
             'icon.bmp',
             @bitmap_width, @bitmap_height,
             @bitmap,
             @hotspot_x, @hotspot_y);
  (* проверяем, удалось ли создать пиксельную карту *)
  case (rc) of
    BitmapOpenFailed:
      writeln('XReadBitmapFile - не могу открыть файл "icon.bmp"');
    BitmapFileInvalid:
      writeln('XReadBitmapFile - файл "icon.bmp" не содержит корректного битового изображения.');
    BitmapNoMemory:
      writeln('XReadBitmapFile - не хватает памяти.');
    BitmapSuccess:
      (* битовая карта успешно загружена - что-то делаем с ней... *)
    .
    .
  end;
Имейте в виду, что параметр root_win не имеет ничего общего с данным битовым изображением - битовая карта не связывается с этим окном. Этот дескриптор окна использован только для определения экрана, для которого мы хотим создать пиксельную карту. Это существенно, так как для того, чтобы быть полезной, пиксельная карта должна поддерживать то же количество цветов, что и экран делает.

Как только мы получили дескриптор пиксельной карты, сгенерированный из битового изображения, мы можем нарисовать ее в некотором окне, используя функцию XCopyPlane(). Эта функция позволяет указать, в какой рисуемой области (окне, или даже другой пиксельной карте) и в какой позиции будет отображена данная пиксельная карта.
(* Рисовать ранее загруженную битовую карту в заданном окне, в *)
(* позиции x=100, y=50. Мы хотим скопировать всю битовую карту, *)
(* поэтому указываем координаты x=0, y=0 для копирования с   *)
(* начала битового изображения и его полный размер       *)
XCopyPlane(display, bitmap, win, gc,
     0, 0,
     bitmap_width, bitmap_height,
     100, 50,
     1);
Мы могли также скопировать заданный прямоугольный фрагмент пиксельной карты вместо полного ее копирования. Последний параметр в функции XCopyPlane() определяет, какой слой (цветовую плоскость) исходного изображения мы хотим скопировать в целевое окно. Для битовых изображений всегда копируется плоскость номер 1.

Часто бывает необходимо создать неинициализированную пиксельную карту, чтобы в дальнейшем в ней можно было рисовать. Это полезно для графических редакторов (создание нового пустого "холста" вызовет создание новой пиксельной карты, в которой будет храниться изображение). Это полезно при чтении различных форматов изображений - мы загружаем графические данные в память, создаем на сервере пиксельную карту, а затем рисуем расшифрованные графические данные на этой пиксельной карте.
var
  (* эта переменная будет содержать дескриптор новой пиксельной карты *)
  pixmap : TPixmap;

  (* эта переменная будет содержать дескриптор корневого окна экрана, *)
  (* для которого мы хотим создать пиксельную карту          *)
  root_win : TWindow;

(* эта переменная будет содержать глубину цвета создаваемой *)
(* пиксельной карты - количество бит, используемых для   *)
(* представления индекса цвета в палитре (количество цветов *)
(* равно степени двойки глубины)              *)
  depth : longint;

  root_win := XDefaultRootWindow(display);
  depth := XDefaultDepth(display, XDefaultScreen(display));

 (* создаем новую пиксельную карту шириной 30 и высотой в 40 пикселей *)
  pixmap := XCreatePixmap(display, root_win, 30, 40, depth);

  (* для полноты ощущений нарисуем точку в центре пиксельной карты *)
  XDrawPoint(display, pixmap, gc, 15, 20);
После получения дескриптора пиксельной карты мы можем отобразить ее в некотором окне, используя функцию XCopyArea(). Эта функция позволяет указать устройство рисования (окно или даже другую пиксельную карту) и в какую позицию этого устройства пиксельная карта будет отображена.
(* Рисовать ранее загруженную битовую карту в заданном окне, в *)
(* позиции x=100, y=50. Мы хотим скопировать всю битовую карту, *)
(* поэтому указываем координаты x=0, y=0 для копирования с   *)
(* начала битового изображения и его полный размер       *)
XCopyArea(display, bitmap, win, gc,
     0, 0,
     bitmap_width, bitmap_height,
     100, 50);
Мы могли также скопировать заданный прямоугольный фрагмент пиксельной карты вместо полного ее копирования.

Отметим, что на одном и том же экране возможно создавать пиксельные карты различных глубин. Когда мы выполняем операции копирования (пиксельной карты в окно и т.п.), мы должны убедиться, что источник и приемник имеют одну и ту же глубину. Если их глубина различается, операция не удастся. Единственное исключение - копирование указанной битовой плоскости пиксельной карты с помощью показанной ранее функции XCopyPlane(). В этом случае мы можем скопировать указанную плоскость в окно-приемник - в действительности устанавливается указанный бит в цвете каждого копируемого пикселя. Это может быть использовано для создания забавных графических эффектов.

Наконец, когда все операции над данной пиксельной картой выполнены, ее необходимо освободить, чтобы освободить ресурсы Х сервера. Это делается с помощью функции XFreePixmap():
(* освобождение пиксельной карты с заданным дескриптором *)
XFreePixmap(display, pixmap);


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

Есть два основных метода создания курсоров. Первый из них - использование набора предопределенных курсоров, поставляемых с Xlib. Второй - использование битовых изображений, определенных пользователем.

В первом методе используется специальный шрифт "cursor" и функция XCreateFontCursor(). Эта функция принимает идентификатор формы, и возвращает дескриптор на созданный курсор. Список возможных шрифтовых идентификаторов находится в файле /usr/include/X11/cursorfont.h. Всего их более 70; вот некоторые из таких курсоров:
XC_arrow - обычный курсор в форме стрелки, отображаемый сервером.
XC_pencil - курсор в форме карандаша.
XC_watch - песочные часы.

Создать курсор с использованием этих идентификаторов несложно. Из файла /usr/include/X11/cursorfont.h узнаем номера необходимых идентификаторов и опеределяем их:
const
  XC_watch=150;

var
  (* эта переменная содержит дескриптор создаваемого курсора *)
  watch_cursor : TCursor;

  (* создаем курсор "песочные часы" *)
  watch_cursor := XCreateFontCursor(display, XC_watch);
Другой метод создания курсора - использование пары пиксельных карт глубиной 1. Одна пиксельная карта определяет форму курсора, а другая работает как маска, определяющая, какие пиксели курсора действительно будут нарисованы. Остальная часть пикселей будет прозрачной. Создание такого курсора осуществляется с помощью функции XCreatePixmapCursor(). В качестве примера создадим курсор, используя битовое изображение "icon.bmp". Будем предполагать, что оно уже загружено в память и преобразовано в пиксельную карту, дескриптор которой сохранен в переменной bitmap. Мы хотим, что оно было полностью прозрачным. Это означает, что только черные фрагменты нарисуются, а белые будут прозрачными. Чтобы достигнуть такого эффекта, будем использовать иконку и как пиксельную карту курсора, и как маску пиксельной карты.
var
  (* эта переменная содержит дескриптор создаваемого курсора *)
  icon_cursor : TCursor;

  (* вначале необходимо определить основной и фоновый цвета курсора *)
  cursor_fg, cursor_bg : TXColor;

  screen_colormap : TColormap;
  rc : TStatus;

  (* получаем доступ к палитре экрана по умолчанию *)
  screen_colormap := XDefaultColormap(display, XDefaultScreen(display));

  (* выделяем черный и белый цвета *)
  rc := XAllocNamedColor(display,
               screen_colormap,
               'black',
               @cursor_fg,
               @cursor_fg);
  if (rc = 0) then begin
    writeln('XAllocNamedColor - невозможно распределить цвет "black"');
    halt(1);
  end;
  rc := XAllocNamedColor(display,
               screen_colormap,
               'white',
               @cursor_bg,
               @cursor_bg);
  if (rc = 0) then begin
    writeln('XAllocNamedColor - невозможно распределить цвет "white"');
    halt(1);
  end;

  (* Наконец, создаем курсор. Горячую точку устанавливаем ближе к *)
  (* верхнему левому углу курсора - позиции (x=5, y=4). *)
  icon_cursor := XCreatePixmapCursor(display, bitmap, bitmap,
                 @cursor_fg, @cursor_bg,
                 5, 4);
Когда мы определяем курсор, необходимо определить, какой пиксель курсора является указателем, доставляемым пользователю в различные события от мыши. Обычно, мы выберем позицию курсора, которая визуально выглядит похожей на "горячую точку". Например, на курсоре в виде стрелки конец стрелки будет определен как горячая точка.

Когда курсор больше не нужен, его необходимо освободить, используя функцию XFreeCursor():
XFreeCursor(display, icon_cursor);

После того, как курсор создан, необходимо сообщить X серверу об окне, к которому он должен быть подключен. Это делается с помощью XDefineCursor(), и заставляет сервер X менять указатель мыши на форму этого курсора всякий раз, когда указатель мыши перемещается внутри этого окно. Мы можем отключить этот курсор от нашего окна с помощью функции XUndefineCursor(), которая заставит отображаться встроенный курсор.
(* прикрепить курсор к окну *)
XDefineCursor(display, win, icon_cursor);

(* отключить курсор от окна *)
XUndefineCursor(display, win);


1. Напишите программу, выводящую текстовое сообщение в произвольную позицию (в пределах окна) произвольным цветом. Цвет и координаты должны меняться при изменении размеров окна.


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


3. Нарисуйте в окне график функции sin(x) на отрезке [-π; π]. Оси подпишите курсивом, метки по осям - обычным шрифтом, начало координат (0) выделите жирным шрифтом.


4. Нарисуйте в окне 100 окружностей. Цвет, координаты центра и радиус выбирать случайным образом.


5. Используя StructureNotifyMask и русский шрифт, модифицируйте программу из первого задания лабораторной работы #1 таким образом, чтобы сообщение всегда отображалось в центре окна.


6. Составьте программу, выводящую в окно все символы стандартного курсорного шрифта.