Изучение растровой графики на примере XPM

Для наглядной демонстрации свойств растровой графики можно использовать текстовый формат XPM, представляющий собой фрагмент кода на Си.

Формат растровых графических файлов XPM изобретён довольно давно. Это полностью текстовый формат, то есть графические XPM-файлы можно редактировать в текстовом редакторе. Что ещё интереснее, XPM-файл представляет собой фрагмент кода на языке Си, коорый можно включать в программу с помощью «#include "…"». Всё это делает XPM удобным инструментом для начального изучения темы.

XPM: растровое изображение своими руками

Возьмём какую-нибудь тему из школьной информатики. Например, изучение растровой графики.

О чём по этой теме стоит разговаривать?

  • О матричном представлении изображения, в том числе — о том, что одной только матрицы недостаточно: размеры, способ кодирования цвета, другая дополнительная информация
  • О кодировании цветов и цветовых пространствах (как минимум — RGB, CMYK и HLS/HLV)
  • Об упаковке изображения и об упаковке «с потерями»
  • О проблемах геометрических преобразований

Ставить точки руками или обрабатывать области магией?

Среди этих тем некоторые — явно выше того, что можно в школе объяснить до конца (та же упаковка с потерями, например). Некоторые темы можно осветить только поверхностно. Можно запустить любой приличный редактор растровой графики и показать диалог выбора цвета в различных цветовых пространствах — какой при изменении параметров получается в результате цвет. Однако объяснять, почему цветовые пространства именно такие, придётся всё равно «на пальцах» (колбочки, палочки, складывание цветов, вычитание, а на освещённости/светлоте начинается уже математика).

colorpick.jpg

Диалог выбора цвета в редакторе GIMP. Довольно поучительно начать изменять один параметр из пространства HSV и пронаблюдать, как движется «прицел» и вращается маркер цвета.

Геометрические преобразования стоит исследовать уже после близкого знакомства с предыдущими темами.

А вот теме «матричное представление изображения», как это ни странно, не хватает программной наглядности. С одной стороны, имеется растровый редактор, в котором всё вполне наглядно, своими руками делается. А с другой стороны, со стороны программиста — разнообразные прекрасные библиотеки для работы с разнообразными прекрасными растровыми форматами.

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

Просто строки

Чего не хватает — это наглядного представления изображения в виде двумерного массива байтов и сопутствующей информации. Например, в «проекте рисования графика» мы использовал для этой цели… двумерный массив байтов! Более точно — массив строк, каждый символ которых соответствует отдельной точке «экрана». Обновление такого «экрана» — просто вывод всех строк на текстовый терминал.

Довольно интересный компромисс в этом плане — формат X Pixmap.

Вот, например, маленькая картинка в формате xpm: rbomb.png А вот тот же самый файл непосредственно:

   1 static char * rbomb_xpm[] = {
   2 /* width height ncolors chars_per_pixel */
   3 "32 32 5 1",
   4 /* colors */
   5 "       c None          s Transparent",
   6 "o      c gray50        s Cord",
   7 "O      c black",
   8 "$      c red",
   9 "%      c #D0D0A0       s Gold",
  10 /* pixels */
  11 "                                ",
  12 "                                ",
  13 "                                ",
  14 "                                ",
  15 "                                ",
  16 "                                ",
  17 "                                ",
  18 "                                ",
  19 "           oOoOo                ",
  20 "          O     Oo              ",
  21 "         o        O             ",
  22 "         O        o             ",
  23 "       OOOOO       O            ",
  24 "       OOOOO       o            ",
  25 "      OOOOOOO       O           ",
  26 "    oOOOOOOOOOo     o  $       $",
  27 "   oOOOOOOOOOOOo    O      $  $ ",
  28 "  oOOOOOOOOOOOOOo    o  $       ",
  29 "  OOOOOOOO%ooOOOO    O   $  $   ",
  30 " oOOOOOOOOO%ooOOOo    o   $ $   ",
  31 " OOOOOOOOOOOOOOOOO     OoO $ $ $",
  32 " OOOOOOOOOOO%ooOOO        $$    ",
  33 " OOOOOOOOOOO%ooOOO      $   $   ",
  34 " OOOOOOOOOOO%ooOOO        $  $  ",
  35 " OOOOOOOOOOOOOOOOO     $        ",
  36 " oOOOOOOOOOOOOOOOo    $   $    $",
  37 "  OOOOOOOOOOOOOOO               ",
  38 "  oOOOOOOOOOOOOOo         $     ",
  39 "   oOOOOOOOOOOOo                ",
  40 "    oOOOOOOOOOo                 ",
  41 "      oOOOOOo                   ",
  42 "                                "};

То есть сам формат — строго текстовый. Зная структуру XPM-файла, его несложно создать, и ещё проще — редактировать в произвольном текстовом редакторе.

Вот так, например, выглядит «подсветка синтаксиса», когда редактируешь XPM-файл с помощью редактора gvim:

gvimxpm.jpg

Нетрудно заметить, что XPM-файл — это просто массив строк, записанный на языке программирования Си. Первая строка массива содержит (в строковом виде) метаинформацию: ширину и высоту картинки в точках растра, количество цветов в палитре и «количество символов на цвет». Вот этот последний параметр особенно забавен: дело в том, что последние строки XPM-файла представляют собственно изображение, причём одна точка растра соответствует фиксированному числу символов (удобнее всего, когда одному). При этом сами символы задаются произвольно в палитре, начинающейся со второй строки массива. В частности, в примере чёрному цвету соответствует символ 'O', а красному — '$'.

Цвета в палитре можно задавать в пространстве RBG (24 бита на точку), как это сделано для золотистого цвета, а можно по именам. Имена цветов X.Org (графической подсистеме, используемой в большинстве дистрибутивов Linux) заданы примерно в том же формате в файле /usr/share/X11/rgb.txt. Среди них встречаются весьма поэтично названные, например LavenderBlush или PeachPuff.

Ещё одна особенность XPM — «значимые» имена цветов (задаются в форме «s имя»); в примере значимые имена даны всем цветам, кроме красного и чёрного. Соответствующая библиотека (libxpm) позволяет программам работать с этими значащими именами вместо непосредственно символов, так что нет необходимости пользоваться ровно теми символами, которые заданы в палитре. Например, при разработке графического интерфейса можно выделить цвет фона, цвет печати, цвета обычной, затенённой и высветленной областей границы и т. п. «Кнопочка» будет нарисована независимо от того, какая в действительности задана палитра. Но и без библиотеки такие имена полезны — они указывают на назначение цвета. При описании цвета можно ещё задавать, как отображать точки этого цвета в монохромном и чёрно-белом пространстве (когда-то давно это было насущной необходимостью, т. к. далеко не все графические устройства были цветными).

Одно из имён цвета — «None» — особенное. Это не значащее имя, а именно задание цвета (значащее имя для цвета None в примере — Transparent). При обработке изображения точки цвета None считаются прозрачными, выводятся и обрабатываются соответственно.

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

Программа на Си

Рассмотрим простую учебную программу на языке Си. В программе

  • XPM-файл с картинкой просто включается в исходный текст (он же тоже написан на Си!),
  • затем создаётся доступная на запись копия (заметим, что исходный код имеет тип const char **),

  • в этой копии модифицируется содержимое растровой матрицы,
  • результат записывается в новый XPM-файл (формат XPM при этом соблюдается вручную, это несложно :) )

   1 #include <stdio.h>
   2 #include <string.h>
   3 #include <stdlib.h>
   4 #include "face.xpm"
   5 
   6 int main(int argc, char *argv[]) {
   7   int Width, Height, PSize, BPP;
   8   int size;
   9   int beg, end, y, x, len;
  10   char **newface;
  11   FILE *fout;
  12 
  13   /* Извлекаем размеры картинки и палитры */
  14   /* ВНИМАНИЕ! Для простоты считаем, что BPP=1 */
  15   sscanf(face[0], "%d %d %d %d", &Width, &Height, &PSize, &BPP);
  16 
  17   /* Область картинки */
  18   beg = PSize+1;
  19   end = PSize+Height+1;
  20 
  21   /* Копия картинки, доступная для модификации */
  22   newface = calloc(end, sizeof(char *));
  23   for(y=size=0; y<end; y++) {
  24     len = strlen(face[y]);
  25     strcpy(newface[y]=malloc(len+1), face[y]);
  26   }
  27 
  28   /* Удаляем все '*' (красные точки) */
  29   for(y=beg; y<end; y++) 
  30     for(x=0; x<Width; x++)
  31       if(newface[y][x] == '*')
  32         newface[y][x] = ' ';
  33 
  34   /* Посмотрим на безротую картинку */
  35   for(y=beg; y<end; y++)
  36     puts(newface[y]);
  37 
  38   /* Перевернём рот. ВНИМАНИЕ! Мы заранее знали, в каких он строках */
  39   for(y=0; y<4; y++)
  40     for(x=0; x<Width; x++)
  41       if(face[end-8+y][x] == '*')
  42         newface[end-7-y][x] = '*';
  43 
  44   /* Создадим XPM-файл. ВНИМАНИЕ! Индентификатор картинки задан заранее */
  45   fout = fopen("sadface.xpm","w");
  46   fputs("/* XPM */",fout);
  47   fputs("static char * sadface[] = {",fout);
  48   for(y=0; y<end-1; y++)
  49     fprintf(fout,"\"%s\",\n",newface[y]);
  50   fprintf(fout,"\"%s\"};\n",newface[y]);
  51   fclose(fout);
  52 
  53   return 0;
  54 }

Обратите внимание на то, что программа несложная, однако затрагивает сразу несколько тем:

  • работа со строками (включая преобразование числовых форматов)
  • работа с памятью и указателями
  • работа с файлами
  • работа с растровой графикой :)

Вот такой файл включается в прогармму:

   1 /* XPM */
   2 static char * face[] = {
   3 "32 32 5 1",
   4 "       c none s mask",
   5 "#      c #55aad4 s HiShadow",
   6 "*      c #ff0000",
   7 "+      c #4488aa s Foreground",
   8 ".      c #33667f s LoShadow",
   9 "                                ",
  10 "            .........           ",
  11 "         ....       ....        ",
  12 "        ..             ..       ",
  13 "      ...               ...     ",
  14 "     ..                   ..    ",
  15 "    ..                     .#   ",
  16 "    .   ###          ###    #   ",
  17 "   ..  ## ##        ## ##   ##  ",
  18 "  ..   #   .        #   .    ## ",
  19 "  .    .. ..        .. ..     # ",
  20 "  .     ...          ...      # ",
  21 " ..                           ##",
  22 " .                             #",
  23 " .                             #",
  24 " .                             #",
  25 " .              .              #",
  26 " .                             #",
  27 " .                             #",
  28 " .                             #",
  29 " ..                           ##",
  30 "  .                           # ",
  31 "  .                           # ",
  32 "  ..                         ## ",
  33 "   ..  **               **  ##  ",
  34 "    .   ***           ***   #   ",
  35 "    .#    **         **    ##   ",
  36 "     ##    ***********    ##    ",
  37 "      ###               ###     ",
  38 "        ##             ##       ",
  39 "         ####       ####        ",
  40 "            #########           "};

Вот такой файл получается в результате:

   1 /* XPM */static char * sadface[] = {"32 32 5 1",
   2 "       c none s mask",
   3 "#      c #55aad4 s HiShadow",
   4 "*      c #ff0000",
   5 "+      c #4488aa s Foreground",
   6 ".      c #33667f s LoShadow",
   7 "                                ",
   8 "            .........           ",
   9 "         ....       ....        ",
  10 "        ..             ..       ",
  11 "      ...               ...     ",
  12 "     ..                   ..    ",
  13 "    ..                     .#   ",
  14 "    .   ###          ###    #   ",
  15 "   ..  ## ##        ## ##   ##  ",
  16 "  ..   #   .        #   .    ## ",
  17 "  .    .. ..        .. ..     # ",
  18 "  .     ...          ...      # ",
  19 " ..                           ##",
  20 " .                             #",
  21 " .                             #",
  22 " .                             #",
  23 " .              .              #",
  24 " .                             #",
  25 " .                             #",
  26 " .                             #",
  27 " ..                           ##",
  28 "  .                           # ",
  29 "  .        ***********        # ",
  30 "  ..      **         **      ## ",
  31 "   ..   ***           ***   ##  ",
  32 "    .  **               **  #   ",
  33 "    .#                     ##   ",
  34 "     ##                   ##    ",
  35 "      ###               ###     ",
  36 "        ##             ##       ",
  37 "         ####       ####        ",
  38 "            #########           "};

Что в итоге

При всё своей «игрушечности» формат XPM:

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

  • Позволяет сразу зацепить несколько тем:
    • Растр как матрица точек + дополнительная информация

    • Представление цветов в пространстве RGB и именование
    • Палитрованое изображение
    • Абстракции «прозрачность», «цвет фона», «цвет рисования»
    • Работа с точками растра при преобразовании
  • Неплохо подходит для изучения в курсе программирования на Си :)

P.S. Я 15 лет хотел написать эту статью!


CategoryArticle

FrBrGeorge/News/2016-11-09 (последним исправлял пользователь FrBrGeorge 2016-11-10 15:43:49)