КаталогИндекс раздела
НазадОглавлениеВперед


9. Видеоадаптеры

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

а) Для полного "раскрытия тайн" видеоадаптеров нам пришлось бы рассматривать их на уровне портов ввода-вывода, что не соответствует выбранному нами подходу и к тому же сделало бы нас существенно зависимыми от типа видеоадаптера. Мы намеренно рассматриваем только одно (почти универсальное) средство доступа нижнего уровня: прямую запись в видеопамять, в остальном ограничиваясь средствами BIOS и DOS.

б) Даже ограничиваясь уровнем BIOS, исчерпывающее рассмотрение средств управления видеоадаптерами всех типов погребло бы нас под лавиной информации справочного характера. Мы ограничиваемся только основными средствами, ориентированными на адаптер EGA, большинство из них применимо также для MDA и CGA, все они являются подмножеством средств управления VGA.

9.1. Тип видеоадаптера

В абсолютном большинстве применений ПЭВМ комплектуются видеоадаптером одного из четырех основных типов:

В главе, посвященной анализу оборудования, мы посетовали на то, что из списка оборудования BIOS нельзя извлечь исчерпывающую информацию о типе адаптера, отличном от MDA и CGA. Как же получить эту информацию?

Видеоадаптер обслуживается прерыванием BIOS 0x10. Начиная с модели EGA обработчик этого прерывания располагается не в основном ПЗУ BIOS, а в расширении ПЗУ по адресу C000:0000. Это прерывание и в исходном своем варианте имело много функций, а для новых типов адаптеров список его функций расширяется. Эти новые функции и используются для определения типа адаптера, как показано в примере 9.1. В дальнейшем в этой главе, если речь идет просто о функции имеется в виду функция прерывания 0x10.


/*==                     ПРИМЕР 9.1                     ==*/
/*============ Определение активного адаптера ============*/
#include <dos.h>
unsigned char types1A[] = { 0,1,2,4,5,6,7,8,10,11,12,0xff };
char *stypes1A[] = { "нет дисплея","MDA,моно","CGA,цв.",
  "EGA,цв.","EGA,моно","PGA,цв.","VGA,моно,анал.",
  "VGA,цв.,анал.","MCGA,цв.,цифр.","MCGA,моно,анал.",
  "MCGA,цв.,анал.","неизвестный тип",
  "непредусмотренный код"  };
unsigned char b[64];     /* буфер данных VGA */
struct SREGS sr;
union REGS rr;
int i;
main() {
  /* Предположим, VGA */
  rr.h.ah=0x1a;
  rr.h.al=0;
  int86(0x10,&rr,&rr);
  if (rr.h.al==0x1a) {
    printf("Поддерживается ф-ция 1Ah прерывания 10h\n");
    for (i=0; i<12; i++) if (rr.h.bl==types1A[i]) break;
    printf("%s",stypes1A[i]);
    if (i>0 && i<12) {
      rr.h.ah=0x1b;
      rr.x.bx=0;
      sr.es=FP_SEG(b); rr.x.di=FP_OFF(b);
      int86(0x10,&rr,&rr);
      printf(",%dКбайт\n",((int)b[49]+1)*64);
      }
    else printf("\n");
    }
  else {
    /* Предположим, EGA */
    rr.h.ah=0x12;
    rr.h.bl=0x10;
    int86(0x10,&rr,&rr);
    if (rr.h.bl!=0x10) {
      printf("Поддерживается ф-ция 12h прерывания 10h\n");
      printf("EGA,");
      if (rr.h.bh) printf("моно"); else printf("цв.");
      printf(",%dКбайт\n",((int)rr.h.bl+1)*64);
      }
    else printf("MDA или CGA, можно уточнить по INT 11h\n");
    }
}

Функция 0x1A доступна только при наличии расширения BIOS, ориентированного на обслуживание VGA. В этом случае функция возвращает в регистре AL код 0x1A - свою "визитную карточку", а в BL - код активного видеоадаптера. Мы в нашем примере в случае, когда функция 0x1A поддерживается, обращаемся еще и к функции 0x1B - эта последняя заполняет 70-байтный блок информации о состоянии, из которого мы выбираем объем видеопамяти.
Если 0x1A не поддерживается, значит, VGA у нас нет, в этом случае можно обратиться к функции 0x12 - получение информации о EGA. При наличии расширения, ориентированного на EGA, эта функция изменяет содержимое BL (перед обращением оно должно быть 0x10) на 0 (цветной режим) или 1 (монохромный режим) а в BH возвращает объем видеопамяти.
Если же ни 0x1A, ни 0x12 не поддерживаются, то список оборудования BIOS содержит достаточную информацию о видеоадаптере.

9.2. Режимы видеоадаптера и область данных BIOS

Видеоадаптер может функционировать в одном из нескольких режимов, отличающихся возможностями текст/графика и разрешающей способностью. Полные таблицы всех возможных режимов имеются в любом справочнике, с режимами адаптера EGA читатель может ознакомиться по нашим программным примерам 9.2 и 9.9.
Режимы переключаются функцией 0, при этом номер (код) режима задается в регистре AL. При рассмотрении примера 9.2 обратите внимание на то, что весь цикл перебора режимов повторяется в ней дважды - при второй итерации внешнего цикла задаются те же коды режимов, но с единицей в старшем бите регистра AL. Эта единица определяет сохранение содержимого экрана при переключении режима, на втором этапе выполнения программы будет сохраняться заполнение экрана.


/*==                    ПРИМЕР 9.2                     ==*/
/*============= Текстовые видеоpежимы (EGA) =============*/
#include <dos.h>
#include <conio.h>
#define byte unsigned char
byte getmode();
int x_modes();
byte nmode,mode;  /* Номеp, код pежима */
union REGS rr;
byte k;
int i;
main()
{
  /* заполнение экрана */
  clrscr(); textcolor(2);
  for(i=1; i<2000; i++) cprintf("*");
  getch();
  for (k=0; k<2; k++)
    /* при k=0 - обычный режим, при k=1 - с сохранением */
    for (nmode=0; nmode<4; nmode++) {
      mode = (k) ? nmode|0x80 : nmode;
      rr.h.ah=0;     /* функция 0 */
      rr.h.al=mode;  /* код режима */
      int86(0x10,&rr,&rr);
      textcolor(nmode+3);
      cprintf("EGA текстовый режим - %02Xh (%02Xh)  %dx25",
        mode,getmode(),x_modes());
      getch();
      for (i=39; i<x_modes()*25; i++) cprintf("*");
      getch();
      }
  textattr(0x0f);
}
/*==== Номер режима - выбирается из памяти BIOS ====*/
byte getmode() {
  return(peekb(0x40,0x49));
}
/*==== Число символов в строке - из памяти BIOS ====*/
int x_modes() {
  return(peek(0x40,0x4a));
}

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

40:49(1 байт)- текущий видеорежим;
40:4A(2 байта)- число знакомест в строке;
40:4C(2 байта)- размер видеообласти;
40:4E(2 байта)- смещение в видеопамяти текущей (активной) страницы;
40:50(8x4=32 байта)- координаты курсора на видеостраницах;
40:60(2 байта)- форма курсора;
40:62(1 байт)- номер текущей видеостраницы;
40:63(4 байта)- специальная информация вода-вывода;
40:84(5 байт)- специальная информация ввода-вывода (EGA);
40:A4(4 байта)- специальная информация ввода-вывода (EGA).

9.3. Видеопамять, управление цветом

В текстовом режиме видеопамять для CGA, EGA, VGA начинается с адреса B800:0000 (для MDA - B000:0000). Видеопамять физически расположена на плате адаптера, но ее адреса входят в общее адресное пространство ПЭВМ и доступны программисту для чтения и для записи. Каждое знакоместо экрана в видеопамяти представлено двумя байтами, первый из которых содержит ASCIIZ-код символа, а второй - цветовой атрибут в формате:

  H R G B h r g b,

где h, r, g, b - соответственно яркостная, красная, зеленая, синяя составляющие цвета символа; R, G, B - красная, зеленая, синяя составляющие цвета фона; H - атрибут мигания (или яркостная составляющая цвета фона). Таким образом, комбинация hrgb определяет номер одного из 16 возможных цветов отображения символов.

Формируя коды символов и их цветовые атрибуты и записывая их в видеопамять, программист может обеспечить практически мгновенную смену изображения на экране терминала. Адрес видеопамяти, по которому расположено слово, описывающее знакоместо с координатами (x, y), вычисляется как:

B800 : 160y+2x,

если считать, что верхнее левое место экрана имеет координаты (0, 0).

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

Функция 0x10, подфункция 3 (AL=3) служит для переключения режима интерпретации старшего бита цветового атрибута как признака мерцания (BL=1) или яркостной составляющей (BL=0).

Функция 0x10, подфункция 0 позволяет выбрать палитру - задать цвет для любого из 16 возможный кодов цветового атрибута. При этом цвет выбирается из 64 возможных комбинаций (палитр), задаваемых байтом такого формата:

  0 R G B r g b,

где r, g, b - соответственно красная, зеленая, синяя составляющие цвета 1/3 интенсивности; R, G, B - красная, зеленая, синяя составляющие 2/3 интенсивности. Таким обазом, возможны, например, три градации красного цвета:
00000100- тусклый красный;
00100000- красный;
00100100- яркий красный.

При выборе палитры в BL задается код изменяемого цвета, а в BH - код устанавливаемой для него палитры. В примере 9.3 код изменяемого цвета выбирается случайным (по счетчику тиков таймера), и для него перебираются все возможные палитры. Обратите внимание на то, что при окончании работы программы для нашего цвета восстанавливается палитра по умолчанию.


/*==                    ПРИМЕР 9.3                      ==*/
/*==== Цветовые атрибуты. Прямая запись в видеопамять ====*/
#include <dos.h>
/* Сегментный адрес видеопамяти */
#define VSEG 0xb800
/* Вычисление смещения в видеопамяти */
#define VADDR(x,y) y*160+x*2-162
#define byte unsigned char
#define word unsigned int
char st[]=" x ";  /* Обpазец */
word voff;        /* Смещение в видеопамяти */
byte color;       /* Цветовой атpибут */
int x,y;          /* Экpанные кооpдинаты */
byte key;         /* Код клавиши */
byte mode;        /* Режим мерцания/код палитры */
byte pl[]=        /* Исходные палитры 16 цветов */
        { 0,1,2,3,4,5,6,56,7,8,16,24,32,40,48,56 };
union REGS rr;
char *s;
int i;
main() {
  /* очистка экрана */
  for (voff=0; voff<4000;) {
    pokeb(VSEG,voff++,' '); pokeb(VSEG,voff++,0x0f);
    }
  /* формирование картинки */
  for (color=0,y=5; y<21; y++) { /* По стpокам */
    for (x=16; x<64; x+=3,color++) { /* По столбцам */
      for (voff=VADDR(x,y),s=st; *s; s++) {
        /* Запись обpазца в видеопамять с текущими
           цветовыми атpибутами */
        pokeb(VSEG,voff++,*s); pokeb(VSEG,voff++,color);
        }
      }
    }
  /*== переключение режима мерцания ==*/
  gotoxy(1,1);
  printf("Пробел - переключение режима мерцания\n");
  printf("Enter  - продолжение работы\n");
  printf("Esc    - конец работы\n");
  mode=1;
  while ((key=getch())==' ') {
    rr.h.ah=0x10;         /* функция 10 */
    rr.h.al=0x03;         /* подфункция 3 */
    rr.h.bl=(mode=1-mode);
    int86(0x10,&rr,&rr);
    }
  /* отключить мерцание */
  rr.x.ax=0x1003; rr.h.bl=0; int86(0x10,&rr,&rr);
  if (key==27) goto EOJ;
  /*== переключение палитры ==*/
  color=peekb(0x40,0x6c)&=0x0f; /* случайное число */
  gotoxy(10,1);
  printf("переключение палитры для цвета %d  ",color);
  mode=0;
  while ((key=getch())==' '&&mode<64) {
    gotoxy(1,4); printf("Код палитры - %02Xh\n",mode);
    rr.h.ah=0x10;         /* функция 10 */
    rr.h.al=0;            /* подфункция 0 */
    rr.h.bl=color;        /* управляемый цвет */
    rr.h.bh=mode++;       /* код палитры */
    int86(0x10,&rr,&rr);
    }
  /* восстановить исходную палитру цвета */
  rr.x.ax=0x1000; rr.h.bh=pl[color]; rr.h.bl=color;
  int86(0x10,&rr,&rr);
  if (key==27) goto EOJ;
EOJ:
  /* включить мерцание */
  rr.x.ax=0x1003; rr.h.bl=1; int86(0x10,&rr,&rr);
}

Образ экрана размером 80 x 25 символов в видеопамяти занимает 4000 байт. Но видеопамять часто имеет значительно больший объем. Так, для EGA объем ее может достигать 256 Кбайт. Половина этого объема используется только в графических режимах, вторая половина составляет адресное пространство в 32 Кбайта, что позволяет разместить в ней 8 образов экрана. Это пространство разбито на 4-Кбайтные участки, называемые страницами. Таким образом, в EGA может быть 8 текстовых страниц (а при 40 символах в строке - 16 страниц по 2 Кбайта). По умолчанию построением изображения на экране управляет образ, записанный в нулевой странице, но имеется возможность переключить адаптер на отображение 1-й, 2-й и т.д. страниц. Это обеспечивает функция 5 (номер страницы задается в AL). Получить номер активной в данный момент страницы можно из области памяти BIOS. Программа примера 9.4 заполняет видеопамять текстом (разным на разных страницах), а затем обеспечивает переключение активной страницы.


/*==                     ПРИМЕР 9.4                     ==*/
/*==== Демонстpация страничной организации видеопамяти ===*/
#include <dos.h>
#include <stdio.h>
#define PgUp 0x49
#define PgDown 0x51
#define Esc 27
#define byte unsigned char
#define word unsigned int
#define VSEG 0xb800  /* Адpес начала видеопамяти */
#define NPAGE 8

main()
{
  char st[20];      /* Идентификатоp стpаницы */
  char *s;
  word voff;    /* Смещение относительно начала стp. */
  word vpage;   /* Сегментный адpес начала стpаницы */
  byte tpage;   /* Текущий номеp стpаницы */
  int flag;     /* Пpизнак окончания */
  byte attr;    /* Цвет */
  /* Разметка стpаниц */
  for (tpage=0; tpage<NPAGE; tpage++) {
     sprintf(st,"==page %d==",tpage);
     /* Каждая следующая стpаница в памяти сдвинута на
        4096 байт или на 256 паpагpафов. */
     vpage=VSEG+tpage*256;
     /* Код фонового цвета стpаницы совпадает с ее номеpом,
        код цвета символов - на 3 больше. */
     attr=(tpage<<4)|(tpage+3);
     for (s=st,voff=0; voff<4000;) {
       if (*s==0) s=st;
       pokeb(vpage,voff++,*(s++)); pokeb(vpage,voff++,attr);
       }
     }
  /* Движение по стpаницам */
  for (tpage=0,flag=0; flag==0;) {
    switch(getch()) {
      case Esc: flag++; break;
      default: putchar(7); break;
      case 0: switch(getch()) {
        case PgUp: /* Сдвиг на стpаницу назад */
          if (--tpage>0xf0) tpage=NPAGE-1;
          textpage(tpage);
          break;
        case PgDown: /* Сдвиг на стpаницу впеpед */
          if (++tpage>=NPAGE) tpage=0;
          textpage(tpage);
          break;
        default: putchar(7); break;
        }
      }
    }
}
/*==== Установка текущей стpаницы видеопамяти ====*/
textpage(byte pg) {
union REGS rr;
  rr.h.ah=5;  /* Функция 5 */
  rr.h.al=pg; /* Номеp стpаницы */
  int86(0x10,&rr,&rr);
}

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

9.4. Управление курсором, вывод на терминал

BIOS предоставляет следующие средства управления курсором: функция 3 - чтение координат курсора, функция 2 - установка курсора по заданным координатам. При обращениях к этим функциям в BH задается номер страницы, а в DL, DH получаются (или задаются) координаты - номер строки и колонки соответственно (нумерация начинается с 0). Позиционирование курсора иллюстрируется первой частью примера 9.5.

Курсор отображается на экране в виде одной или нескольких горизонтальных линий на матрице символа. Для того, чтобы задать форму курсора нужно задать номер S1 строки матрицы, с которой начинается курсор, и номер S2 - строки, которой он заканчивается. Если S2 < S1, то курсор состоит из двух частей: от S1 до 13 включительно и от 0 до S2 включительно. Управление формой обеспечивается функцией 1, начальная строка задается в CH, конечная - в CL. Если бит 5 CH при этом установлен в 1, то курсор становится невидимым (другой способ сделать курсор невидимым - установить его на строку 25 при помощи функции 2). Вторая часть примера 9.5 демонстрирует все возможные формы курсора. Обратите внимание на то, что при некоторых значениях номеров строк s1, s2 происходит скачкообразное изменение формы. Функция фактически отрабатывает только 8 значений номера строки, чтобы обеспечить совместимость с CGA, имеющим размер матрицы по вертикали 8.


/*==                     ПРИМЕР 9.5                     ==*/
/*=================== Управление курсором ================*/
#include <dos.h>
#define byte unsigned char
#define word unsigned int
#define Esc 27
#define Enter 13
#define Up 0x48
#define Down 0x50
#define Left 0x4b
#define Right 0x4d
#define Home 0x47
union REGS rr;
main()
  {
  word posc; /* Позиция курсора в линейных координатах */
  word post; /* Позиция вывода текста в линейных коорд. */
  byte y,x;  /* Позиция в координатах x,y */
  byte s1, s2;  /* Нач.и кон.строки образа курсора */
  byte mode=0;  /* Атрибут мигания */
  char *modes[]= { "нормальный", "невидимый" };
  byte flag;    /* признак окончания */
  /*== 1. Перемещение курсора ==*/
  for(clrscr(),posc=post=0; ;posc++) {
    y=post/80; x=post%80; setcurpos(x,y);
    printf("текст->%d,курсор->%d ",post,posc);
    getcurpos(&x,&y);  post=y*80+x;
    y=posc/80; x=posc%80; setcurpos(x,y);
    if (getch()==27) break;
    }
  /*== 2. Удаление курсора ==*/
  clrscr(); printf("Курсор удален\n");
  setcurpos(1,26);
  getch();
  /*== 3. Управление формой курсора ==*/
  clrscr();
  printf("Изменение формы курсора\n");
  printf(" \\ s1 ");
  for(s1=0; s1<10; s1++) printf("%2d ",s1);
  printf("\ns2 \\\n");
  for(s2=0;s2<14;s2++) {
    printf("%2d    ",s2);
    for(s1=0; s1<10; s1++) printf("%2d ",s1); printf("\n");
    }
  setcurpos(0,18);
  printf("Up, Down, Left, Right, Home, Enter, Esc");
  setcurpos(0,20); printf("изменяется клавишей Enter");
  for (s1=7,s2=13,flag=0; flag==0;) {
    setcurpos(0,19);
    printf("Атрибут мигания - %d - %s   ",mode,modes[mode]);
    curform(s1,s2,mode);
    gotoxy(s1*3+8,s2+4);
    switch(getch()) {
      case Esc: flag++; break;
      case Enter: mode=1-mode; break;
      case 0: switch (getch()) {
          case Left: if (s1>0) s1--; else s1=8; break;
          case Up: if (s2>0) s2--; else s2=13; break;
          case Right: if (++s1>9) s1=0; break;
          case Down: if (++s2>13) s2=0; break;
          case Home: s1=7; s2=13; break;
          }
      }
    }
}
/*==== Установка курсора ====*/
setcurpos(byte x,byte y) {
  rr.h.ah=2;  /* функция 2 */
  rr.h.bh=0;  /* страница 0 */
  rr.h.dh=y;  /* координата y */
  rr.h.dl=x;  /* координата x */
  int86(0x10,&rr,&rr);
}
/*==== Чтение позиции курсора ====*/
getcurpos(byte *x,byte *y) {
  rr.h.ah=3;   /* функция 3 */
  rr.h.bh=0;   /* страница 0 */
  int86(0x10,&rr,&rr);
  *y=rr.h.dh;  /* координата y */
  *x=rr.h.dl;  /* координата x */
}
/*==== Изменение формы курсора ==*/
curform(byte s1, byte s2, byte mode) {
  rr.h.ah=1;            /* функция 1 */
  rr.h.ch=(mode<<4)|s1; /* нач.строка и атрибут */
  rr.h.cl=s2;           /* конечная строка */
  int86(0x10,&rr,&rr);
}

Набор средств BIOS и DOS для вывода на терминал весьма богат.

В прерывании BIOS 0x10 функции 8 и 9 - соответственно чтение и вывод символа и атрибута в текущей позиции курсора (курсор при этом не сдвигается); в BH задается номер страницы, в AL получается/задается код символа, в AH - цветовой атрибут. В примере 9.6 байты атрибутов на всей видеостранице заполняются случайными числами, функция putstring9 определяет атрибут позиции начала строки и использует его при выводе остальных символов строки.
Функция 0xA отличается от функции 9 тем, что цветовой атрибут здесь не управляем, сохраняется тот атрибут, который был в этом байте видеопамяти.
Функция 0xE называется выводом в режиме телетайпа, атрибут неуправляем, курсор сдвигается.
Функция 0x13 (доступна только для AT) - вывод строки, содержащий признак конца - 0 (ASCIIZ-строка). В зависимости от заданного подрежима курсор может сдвигаться или нет, может использоваться либо общий для всей строки цветовой атрибут, задаваемый в регистре, либо строка может задаваться в виде последовательности пар "символ-атрибут" - то есть, в формате видеопамяти.

Далее - о функциях DOS.


/*==                     ПРИМЕР 9.6                     ==*/
/*================ Функции прерывания 0x10 ===============*/
/* функции  6, 7 - сдвиг экрана; 8, 9 - читать/писать сим-
            вол/атрибут, 0x0A, 0x0E - вывод символа */
#include <dos.h>
#include <stdlib.h>
#define byte unsigned char
#define word unsigned int
#define Esc 27
#define Up 0x48
#define Down 0x50
#define Left 0x4b
#define Right 0x4d
union REGS rr;
struct SREGS sr;
byte x1=34, y1=7; /* координаты окна (левый,верхний) */
byte x2, y2;      /* координаты окна (правый,нижний) */
byte xc, yc;      /* координаты курсора */
char *msg[]={"=========Вывод по функции 0x0A==========",
             "=========Вывод по функции 0x0E==========",
             "=========Вывод по функции 0x09==========",
             "=========Вывод по функции 0x13==========",
             "========Вывод по функции DOS 2==========",
             "========Вывод по функции DOS 6==========",
             "========Вывод по функции DOS 9==========$"
              };
word save[5]; /* для сохранения регистров */
word i;
char *s;
main()
  {
  xc=0; yc=0;
  /*== Демонстрация функций вывода ==*/
  /* очистка экрана и заполн. его случайными атрибутами */
  randomize();
  for (i=0; i<4000;) {
    pokeb(0xb800,i++,' '); pokeb(0xb800,i++,random(128));
    }
  setcurpos(xc,yc);
  getch();
  for (i=0; i<6; i++) putstring0A(); getch();
  for (i=0; i<6; i++) putstring0E(); getch();
  for (i=0; i<6; i++) putstring09(); getch();
  if ((byte)peekb(0xf000,0xfffe)==0xfc)
    /* только для AT */
    { for (i=0; i<6; i++) putstring13(); getch(); }
  for (i=0; i<6; i++) putstringdos2(); getch();
  for (i=0; i<6; i++) putstringdos6(); getch();
  for (i=0; i<6; i++) putstringdos9(); getch();
  /*== Демонстрация сдвига текста ==*/
  x2=x1+10; y2=y1+10;
  /* очистка экрана и заполнение окна */
  ClrScr(); setcurpos(0,0);
  printf(" Демонстрация сдвига текста\n");
  printf(" Up, Down, Left, Right; Esc - выход.");
  for (i=0; i<11; i++)  putstring(x1,i+y1,i);
  for (i=0; i==0;) {
    setcurpos(0,26);  /* удаление курсора */
    switch(getch()) {
      case Esc: i=1; break;
      case 0: switch(getch()) {
        case Up: ScrollUp(1); break;
        case Down: ScrollDown(); break;
        case Left: ScrollLeft(); break;
        case Right: ScrollRight(); break;
        }
      }
    }
}
/*==== Вывод на экран строки с заданным атрибутом ====*/
putstring(byte x, byte y, byte n) {
char st[20], *s;
byte xend;
byte attr;
  attr=((n<<4)|(n+3))&0x7f;    /* формирование атрибута */
  sprintf(st," Строка %d ",n); /* формирование текста */
  /* посимвольный вывод текста */
  xend=x+10; s=st;
  while (x1) {
    for(y=y1; y<=y2; y++) {
      movestr(1,x1,y,x1-1,y);
      putca(x2,y,' ',0x0f);
      }
    x1--; x2--;
    }
  else printf("\7");
}
/*==== Сдвиг вправо (выполняется построчно) ====*/
ScrollRight() {
byte y;
  if (x2<78) {
    for(y=y1; y<=y2; y++) {
      movestr(0,x1,y,x1+1,y);
      putca(x1,y,' ',0x0f);
      }
    x1++; x2++;
    }
  else printf("\7");
}
/*==== Перемещение строки ===*/
movestr(byte op,byte x1,byte y1,byte x2,byte y2) {
byte xx1, xx2, c, a;
  if (op) { /* перемещение вперед */
    for (xx1=x1,xx2=x2; xx1=x1; xx1--,xx2--) {
      getca(xx1,y1,&c,&a);
      putca(xx2,y2,c,a);
      }
    }
}
/*==== Посимвольный вывод строки функцией 0A ====*/
putstring0A() {
  for (s=msg[0]; *s; s++) {
    rr.h.ah=0x0a;   /* функция 0A */
    rr.h.bh=0;      /* страница 0 */
    rr.h.al=*s;     /* символ */
    rr.x.cx=1;      /* число повторений */
    int86(0x10,&rr,&rr);
    /* перевычисление координат и сдвиг курсора */
    if (++xc>79) { xc=0; yc++; }
    setcurpos(xc,yc);
    }
}
/*==== Посимвольный вывод строки функцией 0E ====*/
putstring0E() {
  for (s=msg[1]; *s; s++) {
    rr.h.ah=0x0e;   /* функция 0E */
    rr.h.al=*s;     /* символ */
    int86(0x10,&rr,&rr);
    }
  /* перевычисление координат, курсор сдвигается сам */
  xc+=40; yc+=xc/80; xc=xc%80;
}
/*==== Посимвольный вывод строки функцией 9 ====*/
putstring09() {
char a, c;
  getca(xc,yc,&c,&a); /* определение атрибута 1-й позиции */
  for (s=msg[2]; *s; s++) {
    putca(xc,yc,*s,a);
    /* перевычисление коорд., курсор сдвигается в putca */
    if (++xc>79) { xc=0; yc++; }
    }
  setcurpos(xc,yc);
}
/*==== Посимвольный вывод строки функцией 13 ====*/
putstring13() {
char a, c;
  getca(xc,yc,&c,&a); /* определение атрибута 1-й позиции */
  save[0]=_BP; save[1]=_DI; save[2]=_SI;
  save[3]=_DS; save[4]=_ES;
  _BP=FP_SEG(msg[3]);  /* адрес строки */
  _ES=FP_OFF(msg[3]);
  _BH=0;            /* страница 0 */
  _BL=a;            /* атрибут */
  _DH=xc; _DL=yc;   /* координаты начала */
  _AX=0x1300;       /* функция 13, подфункция 0 */
  geninterrupt(0x10);
  _BP=save[0]; _DI=save[1]; _SI=save[2];
  _DS=save[3]; _ES=save[4];
  /* перевычисление координат, курсор сдвигается сам */
  xc+=40; yc+=xc/80; xc=xc%80;
}
/*==== Посимвольный вывод строки функцией DOS 2 ====*/
putstringdos2() {
  for (s=msg[4]; *s; s++) {
    rr.h.ah=2;      /* функция 2 */
    rr.h.dl=*s;     /* символ */
    intdos(&rr,&rr);
    }
  /* перевычисление координат, курсор сдвигается сам */
  xc+=40; yc+=xc/80; xc=xc%80;
}
/*==== Посимвольный вывод строки функцией DOS 6 ====*/
putstringdos6() {
  for (s=msg[5]; *s; s++) {
    rr.h.ah=6;      /* функция 6 */
    rr.h.dl=*s;     /* символ */
    intdos(&rr,&rr);
    }
  /* перевычисление координат, курсор сдвигается сам */
  xc+=40; yc+=xc/80; xc=xc%80;
}
/*==== Вывод строки функцией DOS 9 ====*/
putstringdos9() {
  rr.h.ah=9;               /* функция 9 */
  rr.x.dx=FP_OFF(msg[6]);  /* адрес строки */
  sr.ds=FP_SEG(msg[6]);     /* символ */
  intdosx(&rr,&rr,&sr);
  /* перевычисление координат, курсор сдвигается сам */
  xc+=40; yc+=xc/80; xc=xc%80;
}
/*==== Установка курсора ====*/
setcurpos(byte x,byte y) {
  rr.h.ah=2; rr.h.bh=0; rr.h.dh=y; rr.h.dl=x;
  int86(0x10,&rr,&rr);
}

В BIOS имеются средства, позволяющие осуществлять быстрый сдвиг изображения по вертикали: функции 6 (сдвиг окна вверх) и 7 (сдвиг вниз). В регистрах CH и CL задаются соответственно y- и x-координаты левого верхнего угла сдвигаемого окна, в DH и DL - правого нижнего угла; в AL - число строк сдвига (при AL=0 функция 6 очищает окно); а в BH - атрибут, которым заполняются освобождающиеся строки. Во второй части примера 9.6 при сдвиге вверх самая верхняя строка посимвольно копируется под самую нижнюю, а затем все окно, начиная со 2-й строки сдвигается; при сдвиге вверх - наоборот. При сдвигах вправо и влево каждая строка изображения посимвольно (функции 8 и 9) переносится на новое место. При выполнении этой программы хорошо заметно, что эти сдвиги выполняются гораздо медленнее, чем сдвиги по вертикали, лучше для этих целей использовать прямое копирование областей видеопамяти.

9.5. Средства драйвера ANSI.SYS

Драйвер ANSI.SYS, рассмотренный нами в главе, посвященной вводу, имеет также средства управления выводом на консоль. Ниже приведены основные Esc-последовательности, применяемые для этих целей (их применение иллюстрируется примером 9.7).


/*==                     ПРИМЕР 9.7                     ==*/
/*===== Управление выводом средствами драйвера ANSI ======*/
#include <dos.h>
#define byte unsigned char
#define Esc 27
#define Up 'A'
#define Down 'B'
#define Left 'D'
#define Right 'C'
byte xc, yc, y0;  /* координаты курсора */
byte dx,dy;       /* приращения координат */
byte mode;        /* видеорежим */
byte attr[2];     /* цвета текста [0] и фона [1] */
int i,j;
main()
  {
  y0=1; message("Очистка экрана"); getch();
  attr[0]=37; attr[1]=40; set_colors();
  clear_scr();
  attr[0]=30; attr[1]=42; set_colors();
  message("Установка цвета");
  message("Запоминание позиции курсора");
  message("Позиционирование курсора"); getch();
  xc=40; yc=12; dx=dy=1; set_cur_pos(xc,yc);
  save_cur();
  for(i=0; i<6; i++) {
    for(j=0; j<dy; j++) { yc--; set_cur_pos(xc,yc); } dy++;
    for(j=0; j<dx; j++) { xc++; set_cur_pos(xc,yc); } dx++;
    for(j=0; j<dy; j++) { yc++; set_cur_pos(xc,yc); } dy++;
    for(j=0; j<dx; j++) { xc--; set_cur_pos(xc,yc); } dx++;
    }
  message("Восстановление позиции курсора"); getch();
  attr[0]=30; attr[1]=41; set_colors();
  message("Перемещение курсора");
  restore_cur(); getch();
  for(dx=dy=0,i=0; i<6; i++) {
    move_cur(Up,dy++);
    move_cur(Right,dx++);
    move_cur(Down,dy++);
    move_cur(Left,dx++);
    }
  attr[0]=37; attr[1]=40; set_colors();
  message("Очистка до конца строки"); getch();
  restore_cur(); move_cur(Up,6);
  for (i=0; i<13; i++) {
    clreol(); move_cur(Down,1);
    }
  clear_eol();
  message("Переключение режимов");
  for(mode=0; (mode<7)&&(getch()!=Esc); mode++) {
    set_mode(mode);
    printf("Режим - %d",mode);
    }
  getch(); set_mode(3);
}
/*==== Установка цвета ====*/
set_colors() {
  printf("%c[%d;%dm",Esc,attr[0],attr[1]);
}
/*==== Очистка экрана ====*/
clear_scr() {
  printf("%c[2J",Esc);
}
/*==== Позиционирование курсора ====*/
set_cur_pos(byte x, byte y) {
  printf("%c[%d;%dH",Esc,y,x);
  printf("*");
  printf("%c[%d;%dH",Esc,y,x);
}
/*==== Запоминание позиции курсора ====*/
save_cur() {
  printf("%c[s",Esc);
}
/*==== Восстановление позиции курсора ====*/
restore_cur() {
  printf("%c[u",Esc);
}
/*==== Перемещение курсора ====*/
move_cur(char direct, byte step) {
  printf("%c[%d%c",Esc,step,direct);
  printf(".");
  printf("%c[1D",Esc);
}
/*==== Очистка до конца строки ====*/
clear_eol() {
  printf("%c[K",Esc);
}
/*==== Переключение режимов ====*/
set_mode(byte m) {
  printf("%c[=%dh",Esc,m);
}
/*==== Вывод сообщений ====*/
message(char *s) {
byte a[2];
  a[0]=attr[0]; a[1]=attr[1]; attr[0]=37; attr[1]=40;
  set_colors();
  set_cur_pos(1,y0++); printf("%s",s);
  attr[0]=a[0]; attr[1]=a[1]; set_colors();
}

9.6. Знакогенератор

В ПЗУ адаптера имеется таблица генератора знаков (в EGA таких таблиц четыре - две для матрицы 8 x 14 и две для 8 x 8). Однако, пользователь может сформировать собственные образы букв и загрузить их в знакогенератор. Образы составляются на матрице выбранного размера, образ одной буквы имеет размер 14 или 8 байт, каждый байт описывает одну строку образа (первый байт - верхнюю). Для работы со закогенератором используется функция 0x11.

Подфункция 0 (AL=0) загружает шрифт в знакогенератор. Она позволяет за один вызов перезагрузить как всю таблицу символов, так и какую-то ее часть. При обращении к ней регистры ES:BP содержат адрес загружаемой таблицы, CX - число символов, описываемых этой таблицей, DX - код первого символа в таблице, BH - число байт в образе (8 или 14). В примере 9.8 сформированная нами таблица содержит перевернутые образы четырех следующих подряд букв - TUVW. При загрузке этой таблицы в знакогенератор в тексте на экране эти буквы перевернутся.


/*==                   ПРИМЕР 9.8                   ==*/
/*======= Формирование пользовательского шрифта ======*/
#include <dos.h>
unsigned char font[56] = { /* пользовательский шрифт 8x14 */
 /* буква T */ 0x00,0x00,0x3c,0x18,0x18,0x18,0x18,0x18,0x5a,
               0x7e,0x7e,0x00,0x00,0x00,
 /* буква U */ 0x00,0x00,0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,
               0xc6,0xc6,0x00,0x00,0x00,
 /* буква V */ 0x00,0x00,0x10,0x38,0x6c,0xc6,0xc6,0xc6,0xc6,
               0xc6,0xc6,0x00,0x00,0x00,
 /* буква W */ 0x00,0x00,0x6c,0x7c,0xd6,0xd6,0xc6,0xc6,0xc6,
               0xc6,0xc6,0x00,0x00,0x00  };
unsigned save_es, save_bp;   /* для сохр.адреса */
unsigned reg[5];    /* для сохранения регистров */
main()
  {
   int  i;
   clrscr();
   for ( i = 'A'; i <= 'Z'; printf("%c ",i++));
   printf("\n");
   for ( i = 'А'; i <= 'Я'; printf("%c ",i++)); getch();
   reg[0]=_BP; reg[1]=_DI; reg[2]=_SI;
   reg[3]=_DS; reg[4]=_ES;
   /* сохр.адреса загруженного в данный момент шрифта */
   _AX=0x1130;  /* функция 11, подфункция 30 */
   _BX=0x0200;  /* шрифт 8x14 */
   geninterrupt(0x10);
   save_bp=_BP; save_es=_ES; /* сохранение */
   _BP=reg[0]; _DI=reg[1]; _SI=reg[2];
   _DS=reg[3]; _ES=reg[4];
   do {  /* загрузка части шрифта, сформированной нами */
     reg[0]=_BP; reg[1]=_DI; reg[2]=_SI;
     reg[3]=_DS; reg[4]=_ES;
     _ES=FP_SEG(font); _BP=FP_OFF(font);  /* адрес шрифта */
     _AX=0x1100; /* функция 11, подфункция 0 */
     _BX=0x0e00; /* шрифт 8x14 */
     _CX=4;      /* 4 символа */
     _DX='T';    /* начиная с буквы T */
     geninterrupt(0x10);
     _BP=reg[0]; _DI=reg[1]; _SI=reg[2];
     _DS=reg[3]; _ES=reg[4];
     getch();
     /* восстановление шрифта из ПЗУ */
     _AX=0x1101; /* функция 11, подфункция 1 */
     geninterrupt(0x10);
     } while (getch()!=27);  /* до клавиши Esc */
   /* восстановление ранее загруженного шрифта */
   reg[0]=_BP; reg[1]=_DI; reg[2]=_SI;
   reg[3]=_DS; reg[4]=_ES;
   _BP=save_bp; _ES=save_es; /* сохраненный адрес */
   _AX=0x1100;   /* функция 11, подфункция 0 */
   _CX=256;      /* 256 символов */
   _BX=0x0e00;   /* шрифт 8x14 */
   _DX=0;        /* начиная с кода 0 */
   geninterrupt(0x10);
   _BP=reg[0]; _DI=reg[1]; _SI=reg[2];
   _DS=reg[3]; _ES=reg[4];
   getch();
  }

Подфункции 1 и 2 могут использоваться для восстановления исходных обpазов в знакогенеpатоpе, пpи их выполнении из ПЗУ адаптеpа загpужаются таблицы символов 8 x 14 (подфункция 1) или 8 x 8 (подфункция 2). В нашей пpогpамме пpи нажатии клавиши загpуженный нами шpифт заменяется шpифтом из ПЗУ. Однако, может оказаться, что пpи этом будет испоpчена втоpая стpока вывода - символы киpиллицы. Действительно, обpазы букв киpиллицы необязательно пpисутствуют в ПЗУ адаптеpа заpубежного пpоизводства. Дpайвеp-pусификатоp создает свою таблицу обpазов, котоpую он загpужает в знакогенеpатоp, используя те же сpедства, что и мы (подфункцию 0). Для того, чтобы коppектно восстановить знакогенеpатоp, пpогpамма должна пpежде чем загpужать свой шpифт узнать и запомнить адpес той таблицы, котоpая загpужена в знакогенеpатоp в настоящий момент, что наша пpогpамма и делает пpи помощи подфункции 0x30. Пpи окончании pаботы с оpигинальным шpифтом пpогpамма загpужает в знакогенеpатоp таблицу, адpес котоpой она запомнила pанее.

9.7. Гpафика

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

В MS DOS нет сpедств (функций), поддеpживающих pаботу с гpафикой.

В BIOS pабота с гpафикой поддеpживается функциями пpеpывания 0x10, обеспечивающими установку pежима, упpавление палитpами, вывод символов. Стpого гpафических функций у пpеpывания 0x10 только две. Эти функции обеспечивают точечную гpафику - доступ к гpафической точке с кооpдинатами (x,y), где x - номеp столбца, y - номеp стpоки pазвеpтки (нумеpация начинается с 0). Функция 0xD возвpащает в AL цвет точки экpана, pасположенной на стpанице, заданной в BH, по кооpдинатам, заданным в (CX, DX). Функция 0xC записывает точку цвета AL на стpаницу BH по кооpдинатам (CX, DX). Если стаpший бит в AL пpи записи точки установлен в 1, то значение заданного цвета складывается по модулю 2 с пpежним значением цвета этой точки. Точно также pаботает стаpший бит атpибута цвета пpи использовании в гpафическом pежиме функций вывода символов.

Пpогpамма пpимеpа 9.9 иллюстpиpует возможности гpафических pежимов EGA. Она устанавливает гpафический pежим и в каждом pежиме pазмечает экpан сеткой с шагом в 10 точек (каждая десятая линия сетки выводится кpасным цветом). Линии сетки стpоятся по точкам с использованием функции 0xD (для того, чтобы веpтикальные белые линии не пеpекpывали гоpизонтальные кpасные, пpедваpительно пpовеpяется цвет каждой точки функцией 0xC). Затем пpи помощи функции 9 выводится стpока текста, содеpжащая инфоpмацию о pазpешающей способности экpана в этом pежиме. Такая же стpока выводится ниже, но со сложением цветов букв и сетки. На втоpой видеостpанице (EGA в гpафическом pежиме может иметь две стpаницы) стpоится и закpашивается (по точкам) окpужность с pадиусом 40 точек, и пpогpамма несколько pаз пеpеключает стpаницы. В гpафических pежимах пеpеключение видеостpаниц является весьма эффективным способом мгновенной смены изобpажения на экpане.


/*==                     ПРИМЕР 9.9                     ==*/
/*================ Графические режимы EGA ================*/
#include <dos.h>
#include <math.h>
#define byte unsigned char
#define word unsigned int
/* коды режимов и размеры экранов */
byte graph_modes[]={ 13,14,16 };
word x_modes[]={ 320,640,640 };
word y_modes[]={ 200,200,350 };
int n;        /* номер режима */
int x,y;      /* текущие координаты */
byte xc, yc;  /* координаты курсора */
char str[40], *s;
char any[] = "Нажмите любую клавишу...";
byte k, c;
int yy;
union REGS rr;
main()
  {
  for (n=0; n<3; n++) { /* перебор режимов */
    setGRmode(graph_modes[n]); /* установка режима */
    /* рисование горизонтальных линий на 0-й странице */
    for (c=0x0f,k=y=0; y<y_modes[n]; y+=10) {
      if (++k>10) { c=4; k=1; }
      for(x=0; x<x_modes[n]; x++) point(x,y,c,0);
      c=0x0f;
      }
    /* рисование вертикальных линий на 0-й странице */
    for(c=0x0f,k=x=0; x<x_modes[n]; x+=10) {
      if (++k>10) { c=4; k=1; }
      for (y=0; y<y_modes[n]; y++)
        /* чтобы не перечеркивать горизонталь */
        if (getpoint(x,y)!=4) point(x,y,c,0);
      c=0x0f;
      }
    sprintf(str,"Графический режим %02Xh %dx%d",
      graph_modes[n],x_modes[n],y_modes[n]);
    /* вывод текста поверх сетки */
    for (xc=5,yc=3,s=str; *s; s++) putca(xc++,yc,*s,0x02);
    /* вывод текста с совмещением цветов */
    for (xc=5,yc=12,s=str; *s; s++) putca(xc++,yc,*s,0x82);
    /* рисование и закрашивание окружности на 1 странице */
    for (x=-40; x<40; x++) {
      y=sqrt((double)(1600-x*x));
      for(yy=40-y; yy<=40+y; yy++) point(x+40,yy,2,1);
      }
    for(x=y=0; y<9; y++) { setpage(x); x=1-x; delay(500); }
    for (xc=5,yc=20,s=any; *s; s++) putca(xc++,yc,*s,0x04);
    getch();
    }
  }
/*==== Установка режима ====*/
setGRmode(byte m) {
  rr.h.ah=0;            /* функция 0 */
  rr.h.al=m;            /* код режима */
  int86(0x10,&rr,&rr);
}
/*==== Вывод одной графической точки ====*/
point(word x, word y, byte c,byte page) {
  rr.h.ah=0x0c;            /* функция C */
  rr.x.dx=y; rr.x.cx=x;    /* граф.координаты */
  rr.h.bh=page;            /* страница 0 */
  rr.h.al=c;               /* цвет точки */

/*==== Чтение цвета одной графической точки ====*/
int getpoint(word x, word y) {
  rr.h.ah=0x0d;            /* функция D */
  rr.x.dx=y; rr.x.cx=x;    /* граф.координаты */
  rr.h.bh=0;               /* страница 0 */
  int86(0x10,&rr,&rr);
  return(rr.h.al);         /* цвет точки */
  }
/*==== Писать символ и атрибут ==*/
putca(byte x,byte y,char c,byte attr) {
  setcurpos(x,y);
  rr.h.ah=9; rr.h.bh=0; rr.x.cx=1;
  rr.h.bl=attr; rr.h.al=c; int86(0x10,&rr,&rr);
}
/*==== Установка курсора ====*/
setcurpos(byte x,byte y) {
  rr.h.ah=2; rr.h.bh=0; rr.h.dh=y; rr.h.dl=x;
  int86(0x10,&rr,&rr);
}
setpage(byte page) {
  rr.h.ah=5;
  rr.h.al=page;
  int86(0x10,&rr,&rr);
  }

Для читателя имеет смысл здесь веpнуться к pассмотpению пpогpаммы пpимеpа 7.10, в котоpой более сложное гpафическое изобpажение (символ дpевнекитайской натуpфилософии) фоpмиpовалось теми же сpедствами (функция getpoint оттуда пеpенесена в пpимеp 9.9 без изменений, в функции point добавлен паpаметp - номеp стpаницы).

Выполнив пpогpаммы этих пpимеpов, читатель может убедиться в невысоком быстpодействии описанных гpафических сpедств. К сожалению, ничего более эффективного сpедствами BIOS осуществить нельзя.


КаталогИндекс раздела
НазадОглавлениеВперед