Учебная деятельность    

GGI - General Graphics Interface

http://www.ibiblio.org/ggicore/

LibGGI - быстрая простая небольшая библиотека вывода графики для прикладных программ. Она скрывает от пользователя конкретную систему вывода графики при помощи унифицированного интерфейса. LibGGI поддерживает различные системы вывода графики (targets): X11, SVGAlib, Linux framebuffer и проч.
  При компиляции программ на Си, использующих GGI, компилятору необходимо передавать дополнительную опцию -lggi:
gcc ggiex.c -lggi

Перед использованием функций библиотеки LibGGI она должна быть инициализирована при помощи функции ggiInit. При возникновении ошибки ggiInit возвращает ненулевой результат. После завершения работы с библиотекой следует освободить ресурсы при помощи функции ggiExit.
uses ggi;

function ggiInit:integer;
function ggiExit:integer;
#include <ggi/ggi.h>

int ggiInit(void);
int ggiExit(void);

Для начала работы нужно создать при помощи функции ggiOpen новый контекст визуализации (visual). Чтобы освободить все ресурсы, связанные с тем или иным контекстом визуализации, его надо закрыть при помощи ggiClose.
function ggiOpen(target:PChar,[]):TGGIVisual;
function ggiClose(vis:TGGIVisual):integer;
ggi_visual_t ggiOpen(const char *target, ...);
int ggiClose(ggi_visual_t vis);

В качестве параметра функции ggiOpen можно указать название предпочитаемой системы вывода графики. В таком случае второй параметр должен быть NULL (nil).
visual:=ggiOpen('aa',[nil]); visual=ggiOpen('aa',NULL);

Если же использовать систему вывода по умолчанию (определяется по переменным среды окружения), то в функции ggiOpen указывается единственный параметр - NULL.
visual:=ggiOpen(nil,[]); visual=ggiOpen(NULL);

Примеры:
program gr1;
  uses ggi;
  var vis:TGGIVisual;
begin
  if ggiInit<>0 then begin
    writeln('ggiInit failed!');
    halt(1);
  end;
  vis:=ggiOpen(nil,[]);
  if vis=nil then begin
    writeln('ggiOpen failed!');
    halt(1);
  end;
  (* ... *)
  ggiClose(vis);
  ggiExit;
end.
#include <stdio.h>
#include <ggi/ggi.h>
ggi_visual_t vis;
int main() {
  if (ggiInit()) {
    fprintf(stderr,"ggiInit failed!\n");
    return 1;
  }
  vis=ggiOpen(NULL);
  if (vis==NULL) {
    fprintf(stderr,"ggiOpen failed!\n");
    return 1;
  }
  /* ... */
  ggiClose(vis);
  ggiExit();
}

Перед началом использования контекста визуализации необходимо задать для него режим при помощи функции ggiSetMode или одного из ее вариантов. Если режим успешно изменен, функции возвращают 0.
function ggiSetMode(vis:TGGIVisual; tm:TGGIMode):integer;
function ggiSetTextMode(vis:TGGIVisual; cols,rows:integer; vcols,vrows:integer; fontx,fonty:integer; type:TGGIGraphtype):integer;
function ggiSetGraphMode(vis:TGGIVisual; x,y:integer; xv,yv:integer; type:TGGIGraphtype):integer;
function ggiSetSimpleMode(vis:TGGIVisual; xsize,ysize:integer; frames:integer; type:TGGIGraphtype):integer;
int ggiSetMode(ggi_visual_t vis, ggi_mode *tm);
int ggiSetTextMode(ggi_visual_t vis, int cols, int rows, int vcols, int vrows, int fontx, int fonty, ggi_graphtype type);
int ggiSetGraphMode(ggi_visual_t vis, int x, int y, int xv, int yv, ggi_graphtype type);
int ggiSetSimpleMode(ggi_visual_t vis, int xsize, int ysize, int frames, ggi_graphtype type);

Библиотека LibGGI хранит информацию о режиме контекста визуализации в структуре типа ggi_mode (TGGIMode), включающую поля:

В качестве значений поля graphtype можно использовать предопределенные константы: GT_TEXT16, GT_TEXT32, GT_1BIT, GT_2BIT, GT_4BIT, GT_8BIT, GT_15BIT, GT_16BIT, GT_24BIT, GT_32BIT, GT_AUTO.

Более удобным может оказаться задание режима в виде текстовой строки. В этом случае текстовая строка, описывающая режим, передается функции ggiParseMode, которая заполняет структуру ggi_mode соответствующими значениями. Если функция получает пустую строку, то включается механизм автовыбора подходящего режима. Функция возвращает нулевое значение, если строка успешно прошла синтаксический разбор. При наличии ошибок, фрагмент строки, вызвавший ошибку, будет выдан на stderr. Однако и в этом случае структура ggi_mode будет заполнена значениями, которые были успешно интерпретированы, а остальные поля - значениями по умолчанию.
function ggiParseMode(mode_str:PChar; var tm:TGGIMode):integer; int ggiParseMode(const char *mode_str, ggi_mode *tm);

Строка режима задается в виде:
S width x height x depth V width x height F frames D width x height [ graphtype ]
(frames - количество кадров, graphtype - одна из перечисленных выше констант). Может быть опущен любой токен этой строки, за исключением символов, обозначающих, что за значение следует за ними. Количество пробелов (а также их полное отсутствие) не играет никакой роли. Токены после S определяют видимый размер контекста визуализации в пикселах. Токены после V определяют виртуальный размер контекста визуализации в пикселах. Виртуальный размер должен быть не меньше видимого размера. Если виртуальный размер больше видимого, то в каждый момент времени отображается только фрагмент контекста визуализации. При помощи функций LibGGI можно изменить положение отображаемого фрагмента по отношению к полному (виртуальному) размеру контекста визуализации. После D задается количество точек на элемент изображения. Для графических режимов это всегда 1х1. Для текстовых режимов эти значения определяют размер ячейки одного символа в точках.

Примеры:

Получить текущий режим контекста визуализации можно при помощи функции ggiGetMode, а текстовое представление режима - при помощи функции ggiSPrintMode. Не забудьте отвести под параметр str функции ggiSPrintMode достаточное количество памяти.
function ggiGetMode(vis:TGGIVisual; var tm:TGGIMode):integer; int ggiGetMode(ggi_visual_t visual, ggi_mode *tm);
function ggiSPrintMode(str:PChar; tm:TGGIMode):integer; int ggiSPrintMode(char *str, ggi_mode *tm);

В случае системы вывода графики X11 каждый контекст визуализации соответствует отдельному окну. Создавая несколько контекстов визуализации, приложение открывает несколько окон. В приведенном ниже примере для аварийного завершения программы использована процедура ggiPanic, которая перед, собственно, завершением программы выводит на stderr указанное сообщение, закрывает все созданные контексты визуализации и освобождает ресурсы LibGGI.
program gr1;
  uses ggi;
  var win1,win2:TGGIVisual;
    mode:TGGIMode;
    str:array [0..255] of char;
begin
  if ggiInit<>0 then begin
    writeln('ggiInit failed!');
    halt(1);
  end;
  win1:=ggiOpen('x',[nil]);
  win2:=ggiOpen('x',[nil]);
  if (win1=nil) or (win2=nil) then 
    ggiPanic('ggiOpen failed!',[]);
  ggiParseMode('',mode);
  ggiSetMode(win1,mode);
  ggiSPrintMode(str,mode);
  writeln(str);
  ggiParseMode('320x200',mode);
  ggiSetMode(win2,mode);
  (* ... *)
  ggiClose(win1);
  ggiClose(win2);
  ggiExit;
end.
#include <stdio.h>
#include <ggi/ggi.h>
ggi_visual_t win1,win2;
ggi_mode mode;
char str[255];
int main() {
  if (ggiInit()) {
    fprintf(stderr,"ggiInit failed!\n");
    return 1;
  }
  win1=ggiOpen("x",NULL);
  win2=ggiOpen("x",NULL);
  if (!win1 || !win2)
    ggiPanic("ggiOpen failed!\n");
  ggiParseMode("",&mode);
  ggiSetMode(win1,&mode);
  ggiSPrintMode(str,&mode);
  printf("%s\n",str);
  ggiParseMode("320x200",&mode);
  ggiSetMode(win2,&mode);
  /* ... */
  ggiClose(win1);
  ggiClose(win2);
  ggiExit();
}

В новом контексте визуализации, как правило, основным и фоновым цветом устанавливается черный. Изменить фоновый или основной цвет, а также запросить текущее значение фонового и основного цвета можно при помощи следующих функций:
function ggiSetGCForeground(vis:TGGIVisual; color:TGGIPixel):integer;
function ggiGetGCForeground(vis:TGGIVisual; var color:TGGIPixel):integer;
function ggiSetGCBackground(vis:TGGIVisual; color:TGGIPixel):integer;
function ggiGetGCBackground(vis:TGGIVisual; var color:TGGIPixel):integer;
int ggiSetGCForeground(ggi_visual_t vis, ggi_pixel color);
int ggiGetGCForeground(ggi_visual_t vis, ggi_pixel *color);
int ggiSetGCBackground(ggi_visual_t vis, ggi_pixel color);
int ggiGetGCBackground(ggi_visual_t vis, ggi_pixel *color);

Тип ggi_pixel (TGGIPixel) - это 32-битное число, которое содержит информацию о цвете. Битовый формат этого числа зависит от режима контекста визуализации. Чтобы закодировать цвет в таком формате, удобно использовать структуру ggi_color (TGGIColor) с полями r, g, b, хранящими информацию о красной, зеленой и синей компонентах цвета (0..65535). Эта структура используется в следующих функциях конвертирования:
function ggiMapColor(vis:TGGIVisual; col:TGGIColor):TGGIPixel
function ggiUnmapPixel(vis:TGGIVisual; pixel:TGGIPixel; var col:TGGIColor):integer;
ggi_pixel ggiMapColor(ggi_visual_t vis, const ggi_color *col);
int ggiUnmapPixel(ggi_visual_t vis, ggi_pixel pixel, ggi_color *col);

Пример:
  uses ggi;
  var vis:TGGIVisual;
  const clr:TGGIColor=(r:$FFFF; g:$8000; b:0; a:0);
begin
  (* ... *)
  ggiSetGCForeground(vis,ggiMapColor(vis,clr));
  (* ... *)
end.
#include <ggi/ggi.h>
ggi_visual_t vis;
ggi_color clr={0xFFFF,0x8000,0,0};
int main() {
  /* ... */
  ggiSetGCForeground(vis,ggiMapColor(vis,&clr));
  /* ... */
}

Для построения изображений можно использовать следующие функции:
function ggiDrawPixel(vis:TGGIVisual; x,y:integer):integer;
function ggiPutPixel(vis:TGGIVisual; x,y:integer; pixel:TGGIPixel):integer;
function ggiGetPixel(vis:TGGIVisual; x,y:integer; var pixel:TGGIPixel):integer;
function ggiDrawLine(vis:TGGIVisual; x,y,xe,ye:integer):integer;
function ggiDrawBox(vis:TGGIVisual; x,y,w,h:integer):integer;
int ggiDrawPixel(ggi_visual_t vis, int x, int y);
int ggiPutPixel(ggi_visual_t vis, int x, int y, ggi_pixel pixel);
int ggiGetPixel(ggi_visual_t vis, int x, int y, ggi_pixel *pixel);
int ggiDrawLine(ggi_visual_t vis, int x, int y, int xe, int ye);
int ggiDrawBox(ggi_visual_t vis, int x, int y, int w, int h);

Для отображения в контексте визуализации символов и текстовых строк используются функции ggiPutc и ggiPuts. Поддерживается только моноширинный шрифт и символы с кодами 32-127 (без локализации). При помощи функции ggiGetCharSize можно выяснить размер ячейки одного символа.
function ggiPutc(vis:TGGIVisual; x,y:integer; c:char):integer;
function ggiPuts(vis:TGGIVisual; x,y:integer; str:PChar):integer;
function ggiGetCharSize(vis:TGGIVisual; var width,height:integer):integer;
int ggiPutc(ggi_visual_t vis, int x, int y, char c);
int ggiPuts(ggi_visual_t vis, int x, int y, const char *str);
int ggiGetCharSize(ggi_visual_t vis, int *width, int *height);

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

Для системы вывода X11 характерно, что она является изначально асинхронной. LibGGI, чтобы обеспечить синхронный режим, периодически вызывает ggiFlush в фоновом режиме. Эти действия могут занимать до 50% выделенного программе процессорного времени. В приложениях, где эффективность работы с графикой критична, для системы вывода X11 следует использовать асинхронный режим.

Способ вывода контекста визуализации меняется при помощи функции ggiSetFlags. Параметр ggi_flags (TGGIFlags) - битовое поле. Для включения асинхронного режима определена константа-маска: GGIFLAG_ASYNC. Менять способ вывода следует сразу после создания контекста (ggiOpen), до определения его режима (ggiSetMode).
function ggiSetFlags(vis:TGGIVisual; flags:TGGIFlags):integer;
function ggiGetFlags(vis:TGGIVisual):TGGIFlags;
function ggiFlush(vis:TGGIVisual):integer;
int ggiSetFlags(ggi_visual_t vis, ggi_flags flags);
ggi_flags ggiGetFlags(ggi_visual_t vis);
int ggiFlush(ggi_visual_t vis);

Пример:
  ggiSetFlags(vis,ggiGetFlags(vis) or GGIFLAG_ASYNC);
  (* ... *)
  ggiFlush(vis);
  ggiSetFlags(vis,ggiGetFlags(vis) | GGIFLAG_ASYNC);
  /* ... */
  ggiFlush(vis);

Для быстрого переноса старых приложений DOS в LibGGI есть функции простейшей работы с клавиатурой: ggiGetc - получить код символа из буфера клавиатуры (если буфер пуст, программа приостанавливается до появления там символа); ggiKbhit - проверить состояние буфера клавиатуры (если буфер пуст, возвращается 0).
function ggiGetc(vis:TGGIVisual):integer;
function ggiKbhit(vis:TGGIVisual):integer;
int ggiGetc(ggi_visual_t vis);
int ggiKbhit(ggi_visual_t vis);

Приведенная ниже программа выводит белым цветом в верхней части экрана строку, соответствующую режиму работы ее контекста визуализации, а затем начинает рисовать в случайном месте отведенной ей графической области случайным цветом точку. Это продолжается до тех пор, пока пользователь не нажмет какую-нибудь клавишу.
program gr1;
  uses ggi;
  var
    vis:TGGIVisual;
    mode:TGGIMode;
    s:array [0..255] of char;
  const
    clr:TGGIColor=(r:$FFFF; g:$FFFF; b:$FFFF; a:0);
begin
  if ggiInit<>0 then begin
    writeln('ggiInit failed');
    halt(1);
  end;
  vis:=ggiOpen(nil,[]);
  if vis=nil then ggiPanic('ggiOpen failed',[]);
  ggiSetFlags(vis,GGIFLAG_ASYNC);
  ggiParseMode('',mode);
  ggiSetMode(vis,mode);
  ggiSPrintMode(s,mode);
  ggiSetGCForeground(vis,ggiMapColor(vis,clr));
  ggiPuts(vis,10,10,s);
  while ggiKbhit(vis)=0 do begin
    clr.r:=random(65535);
    clr.g:=random(65535);
    clr.b:=random(65536);
    ggiPutPixel(vis, random(mode.virt.x),
      random(mode.virt.y), ggiMapColor(vis,clr));
    ggiFlush(vis);
  end;
  ggiClose(vis);
  ggiExit;
end.
#include <stdio.h>
#include <stdlib.h>
#include <ggi/ggi.h>
ggi_visual_t vis;
ggi_mode mode;
char s[255];
ggi_color clr={0xFFFF,0xFFFF,0xFFFF,0};

int main() {
  if (ggiInit()) {
    fprintf(stderr,"ggiInit failed\n");
    exit(1);
  }
  vis=ggiOpen(NULL);
  if (!vis) ggiPanic("ggiOpen failed");
  ggiSetFlags(vis,GGIFLAG_ASYNC);
  ggiParseMode("",&mode);
  ggiSetMode(vis,&mode);
  ggiSPrintMode(s,&mode);
  ggiSetGCForeground(vis,ggiMapColor(vis,&clr));
  ggiPuts(vis,10,10,s);
  while (!ggiKbhit(vis)) {
    clr.r=rand();
    clr.g=rand();
    clr.b=rand();
    ggiPutPixel(vis, rand()%mode.virt.x,
      rand()%mode.virt.y, ggiMapColor(vis,&clr));
    ggiFlush(vis);
  }
  ggiClose(vis);
  ggiExit();
}

Особенности удалённого запуска GGI-приложений.

GGI-приложения возможно запустить на удалённой машине так, чтобы они выводили своё графическое окно на локальном X-сервере. Для этого в параметрах системы вывода графики "x" следует отключить использование разделяемой памяти (-noshm).

Примерный сценарий работы в этом случае будет таким:

  1. Со своей домашней машины с линуксом (убунтой, дебианом, гентой и т.п.) вы подключаетесь к кафедральному SSH-серверу, на котором установлена библиотека LibGGI:
    ssh -X pupkin@saturn.phys.petrsu.ru
    Ключ -X (икс большой) необходим, чтобы обеспечить удалённым графическим приложениям доступ к вашему X-серверу (X11 Forwarding).
  2. После авторизации вы получаете доступ к удалённой машине. Там вы создаёте свои файлы, компилируете их:
    gcc -Wall ggiex.c -lggi
  3. Готовую программу следует запускать, установив в переменной окружения GGI_DISPLAY предпочтительную систему вывода графики (x) и её параметры (-noshm):
    GGI_DISPLAY=x:-noshm ./a.out
    Более подробную информацию о переменных окружения для библиотеки LibGGI можно найти в её справке (man libggi).