Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

5295

.pdf
Скачиваний:
0
Добавлен:
21.11.2023
Размер:
582.73 Кб
Скачать

есть в стандартный вывод. Но прежде, чем переходить к чтению следующего раздела, попробуйте самостоятельно записать что-нибудь (при помощи write(), естественно) в обычный файл. Когда будете открывать файл для записи, обратите пожалуйста внимание на флаги O_TRUNC, O_CREAT и O_APPEND. Подумайте, все ли флаги сочетаются между собой по смыслу.

Произвольный доступ: системный вызов lseek()

Как уже говорилось, с каждым открытым файлом связано число, указывающее на текущую позицию чтения-записи. При открытии файла позиция равна нулю. Каждый вызов read() или write() увеличивает текущую позицию на значение, равное числу прочитанных или записанных байт. Благодаря этому механизму, каждый повторный вызов read() читает следующие данные, и каждый повторный write() записывает данные в продолжение предыдущих, а не затирает старые. Такой механизм последовательного доступа очень удобен, однако иногда требуется получить произвольный доступ к содержимому файла, чтобы, например, прочитать или записать файл заново.

Для изменения текущей позиции чтения-записи используется системный вызов lseek(). Ниже представлен его прототип.

off_t lseek (int fd, ott_t offset, int against);

Первый аргумент, как всегда, - файловый дескриптор. Второй аргумент - смещение, как положительное (вперед), так и отрицательное (назад). Третий аргумент обычно передается в виде одной из трех констант SEEK_SET, SEEK_CUR и SEEK_END, которые показывают, от какого места отсчитывается смещение. SEEK_SET - означает начало файла, SEEK_CUR - текущая позиция, SEEK_END - конец файла. Рассмотрим следующие вызовы:

lseek (fd, 0, SEEK_SET);

lseek (fd, 20, SEEK_CUR);

lseek (fd, -10, SEEK_END);

Первый вызов устанавливает текущую позицию в начало файла. Второй вызов смещает позицию вперед на 20 байт. В третьем случае текущая позиция перемещается на 10 байт назад относительно конца файла.

В случае удачного завершения, lseek() возвращает значение установленной "новой" позиции относительно начала файла. В случае ошибки возвращается -1.

Я долго думал, какой бы пример придумать, чтобы продемонстрировать работу lseek() наглядным образом. Наиболее подходящим примером мне показалась идея создания программы рисования символами. Программа оказалась не слишком простой, однако если вы сможете разобраться в ней, то можете считать, что успешно овладели азами низкоуровневого ввода-вывода Linux. Ниже представлен исходный код этой программы.

/* draw.c */

#include <stdlib.h>

#include <stdio.h>

#include <unistd.h>

#include <fcntl.h>

 

 

 

#include <sys/types.h>

 

 

 

#include <sys/stat.h>

 

 

 

#include <string.h>

/* memset() */

 

#define N_ROWS

15

/* Image height */

#define N_COLS

40

/* Image width */

#define FG_CHAR

'O'

/* Foreground character */

#define IMG_FN

"image"

/* Image filename */

#define N_MIN(A,B) ((A)<(B)?(A):(B)) #define N_MAX(A,B) ((A)>(B)?(A):(B)) static char buffer[N_COLS];

void init_draw (int fd)

{

ssize_t bytes_written = 0; memset (buffer, ' ', N_COLS); buffer [N_COLS] = '\n';

while (bytes_written < (N_ROWS * (N_COLS+1))) bytes_written += write (fd, buffer, N_COLS+1);

}

void draw_point (int fd, int x, int y)

{

char ch = FG_CHAR;

lseek (fd, y * (N_COLS+1) + x, SEEK_SET); write (fd, &ch, 1);

}

void draw_hline (int fd, int y, int x1, int x2)

{

size_t bytes_write = abs (x2-x1) + 1; memset (buffer, FG_CHAR, bytes_write);

lseek (fd, y * (N_COLS+1) + N_MIN (x1, x2), SEEK_SET); write (fd, buffer, bytes_write);

}

void draw_vline (int fd, int x, int y1, int y2)

{

int i = N_MIN(y1, y2);

while (i <= N_MAX(y2, y1)) draw_point (fd, x, i++);

}

int main (void)

{

int a, b, c, i = 0; char ch;

int fd = open (IMG_FN, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) {

fprintf (stderr, "Cannot open file\n"); exit (1);

}

init_draw (fd);

char * icode[] = { "v 1 1 11", "v 11 7 11", "v 14 5 11",

"v 18 6 11", "v 21 5 10", "v 25 5 10", "v 29 5 6", "v 33 5 6", "v 29 10 11", "v 33 10 11", "h 11 1 8", "h 5 16 17",

"h 11 22 24", "p 11 5 0", "p 15 6 0", "p 26 11 0", "p 30 7 0", "p 32 7 0", "p 31 8 0", "p 30 9 0", "p 32 9 0", NULL };

while (icode[i] != NULL) {

sscanf (icode[i], "%c %d %d %d", &ch, &a, &b, &c); switch (ch) {

case 'v': draw_vline (fd, a, b, c); break; case 'h': draw_hline (fd, a, b, c); break; case 'p': draw_point (fd, a, b); break;

default: abort();

}

i++;

}

close (fd);

exit (0);

}

Теперь разберемся, как работает эта программа. Изначально "полотно" заполняется пробелами. Функция init_draw() построчно записывает в файл пробелы, чтобы получился "холст", размером N_ROWS на N_COLS. Массив строк icode в функции main() - это набор команд рисования. Команда начинается с одной из трех литер: 'v' - нарисовать вертикальную линию, 'h' - нарисовать горизонтальную линию, 'p' - нарисовать точку. После каждой такой литеры следуют три числа. В случае вертикальной линии первое число - фиксированная координата X, а два других числа - это начальная и конечная координаты Y. В случае горизонтальной линии фиксируется координата Y (первое число). Два остальных числа - начальная координата X и конечная координата X. При рисовании точки используются только два первых числа: координата X и координата Y. Итак, функция draw_vline() рисует вертикальную линию, функция draw_hline() рисует горизонтальную линию, а draw_point() рисует точку.

Функция init_draw() пишет в файл N_ROWS строк, каждая из которых содержит N_COLS пробелов, заканчивающихся переводом строки. Это процедура подготовки "холста".

Функция draw_point() вычисляет позицию (исходя из значений координат), перемещает туда текущую позицию ввода-вывода файла, и записывает в эту позицию символ (FG_CHAR), которым мы рисуем "картину".

Функция draw_hline() заполняет часть строки символами FG_CHAR. Так получается горизонтальная линия. Функция draw_vline() работает иначе. Чтобы записать вертикальную линию, нужно записывать по одному символу и каждый раз "перескакивать" на следующую строку. Эта функция работает медленнее, чем draw_hline(), но иначе мы не можем.

Полученное изображение записывается в файл image. Будьте внимательны: чтобы разгрузить исходный код, из программы исключены многие проверки (read(), write(), close(), диапазон координат и проч.). Попробуйте включить эти проверки самостоятельно.

Формат BMP

Формат bmp (от слов BitMaP - битовая карта, или, говоря по-русски, битовый массив) представляет из себя несжатое (в основном) изображение, которое довольно легко читается и выводится в ОС Windows, в которой есть специальные функции API, которые в этом помогают.

В начале стоит заголовок файла (BITMAPFILEHEADER). Он описан следующим образом:

typedef struct tagBITMAPFILEHEADER

{

WORD bfType;

DWORD bfSize;

WORD bfReserved1;

WORD bfReserved2;

DWORD bfOffBits;

} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

bfType определяет тип файла. Здесь он должен быть BM. Если Вы откроете любой файл BMP в текстовом (а лучше в 16-ричном редакторе), то увидите, что первые два символа - это BM (от слова BitMap).

bfSize - это размер самого файла в байтах.

bfReserved1 и bfReserved2 зарезервированы и должны быть нулями.

bfOffBits. Это один из самых важных полей в этой структуре. Он показывает, где начинается сам битовый массив относительно начала файла (или, как написано в MSDN, "от начала структуры BITMAPFILEHEADER"), который и описывает картинку. То есть, чтобы гарантированно попадать на начало массива вы должны писать:

SetFilePointer (hFile, bfh.bfOffBits, NULL, FILE_BEGIN);

Здесь и далее будем считать, что переменная bfh объявлена как BITMAPFILEHEADER bfh;

А дальше идет структура BITMAPINFOHEADER, которая объявлена так:

typedef struct tagBITMAPINFOHEADER

{

DWORD biSize;

LONG biWidth;

LONG biHeight;

WORD biPlanes;

WORD biBitCount;

DWORD biCompression;

DWORD biSizeImage;

LONG biXPelsPerMeter;

LONG biYPelsPerMeter;

DWORD biClrUsed;

DWORD biClrImportant;

} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

biSize - это размер самой структуры. Ее нужно инициализировать следующим образом: bih.biSize = sizeof

(BITMAPINFOHEADER);

Снова здесь и дальше будем считать, что bih объявлена следующим образом: BITMAPINFOHEADER bih;

biWidth и biHeight задают соответственно ширину и высоту картинки в пикселях.

biPlanes задает количество плоскостей. Пока оно всегда устанавливается в 1.

biBitCount - Количество бит на один пиксель. Подробнее про это поговорим ниже.

biCompression обозначает тип сжатия. Не удивляйтесь и не пугайтесь, что в bmp и вдруг сжатие. Я лично не видел не одной сжатой bmp (но я не говорю, что таких не существует). Если сжатия нет, то этот флаг надо устанавливать в BI_RGB. В этой статье мы говорим про несжатый формат, поэтому другие флаги я даже не буду перечислять. Похоже, что эта же структура используется и в файлах JPEG и PNG, потому что, начиная с Windows 98 тут появились варианты BI_JPEG, которая показывает, что эта картинка - JPEG и BI_PNG, что это PNG (про формат Jpeg я ничего не знаю, я только сделал эти выводы исходя из того, что написано в MSDN).

biSizeImage обозначает размер картинки в байтах. Если изображение несжато (то есть предыдущее поле установлено в BI_RGB), то здесь должен быть записан ноль. biXPelsPerMeter и biYPelsPerMeter обозначают соответственно горизонтальное и вертикальное разрешение (в пикселях на метр) конечного устройства, на которое будет выводиться битовый массив (растр). Приложение может использовать это значение для того, чтобы выбирать из группы ресурсов наиболее подходящий битовый массив для нужного устройства. Дело в том, что формат bmp - это по сути аппаратно-независимый растр, то есть когда внешний вид того, что получается не зависит от того, на что этот растр проецируется (если можно так выразится). Например, картинка будет выглядеть одинаково вне зависимости от того, рисуется она на экране монитора или печатается на принтере. Но вот разрешение у устройств разное, и именно для того, чтобы выбрать наиболее подходящую картинку из имеющихся и используют эти параметры.

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

biClrImportant - это количество важных цветов. Определяет число цветов, которые необходимы для того, чтобы изобразить рисунок. Если это значение равно 0 (как это обычно и бывает), то все цвета считаются важными.

После структур BITMAPFILEHEADER и BITMAPINFOHEADER идет палитра. Причем, если формат беспалитровый, то ее может и не быть, однако, на это рассчитывать не надо. Палитра представляет из себя массив структур RGBQUAD идущих друг за другом. Даже если в палитре используются не все цвета (а только, например, 16), то часто все равно под палитру отводят 256 полей. А 256 * 4 = 1024, где 4 - размер структуры RGBQUAD, то есть и получается тот самый один килобайт.

Сразу за палитрой идет сам растр. Тут уже более запутано. Во-первых, пиксели тут описываются так, как написано в таблице выше в зависимости от формата. И могут сами содержать значение компонентов цвета (для беспалитровых), а могут быть индексами массива-палитры. Сама картинка записывается построчно. Во-вторых, картинка идет как бы перевернутая вверх ногами. То есть сначала записана нижняя строка, потом предпоследняя и так далее до самого верха. И, в-третьих, как написано в *1+, если размер строки растра не кратен 4, то она дополняется от 1 до 3 пустыми (нулевыми) байтами, чтобы длина строки оказалась кратна параграфу. Вот это и есть самое неприятное. Дело в том, что для каждого формата приходится подстраивать это число пустых байтов (правда, я люблю туда записывать часть палитры, просто мне не хочется заводить лишние "нулевые" переменные, если все-равно эти байты пропускают и никому они не нужны). Я привожу таблицу с формулами, которые показывают для какого формата сколько байт надо дописывать в конец строки.

Задания.

1.Напишите программу, которая создаёт «капчу». То есть, получает на входе число и преобразует его в картинку формата bmp, содержащую это число.

2.Напишите программу, которая сравнивает 2 рисунка одинакового размера в формате bmp и выдаёт в результате сколько точек в них не совпадают по цвету.

3.Напишите программу, которая преобразует рисунок в формате bmp в 2 рисунка, каждый из которых содержит половину исходного рисунка.

4.Напишите программу, которая получает на входе рисунок в формате bmp и координаты углов прямоугольника, по которым из этого рисунка нужно вырезать изображение. Вырезанное изображение нужно сохранить в новый файл.

5.Напишите программу, которая получает на входе рисунок в формате bmp, содержащий два цвета и создаёт два новых рисунка, на первый из них она переносит те точки изображения, которые окрашены одним цветом, а на другой рисунок те точки, которые окрашены другим цветом.

6.Создайте программу, которая преобразует изображение в формате bmp из цветного в чёрно-белое.

7.Создайте программу, которая «склеивает» два рисунка в формате bmp одинакового размера. На выходе получается изображение, с шириной в 2 раза больше, чем исходные рисунки.

8.Создайте программу, которая «склеивает» два рисунка в формате bmp одинакового размера. На выходе получается изображение, с высотой в 2 раза больше, чем исходные рисунки.

9.Создайте программу, которая уменьшает изображение в формате bmp. Например, из изображения размером 600х800 точек делает изображение размером 300х400.

10.Создайте программу, которая увеличивает изображение в формате bmp. Например, из изображения размером 300х400 точек делает изображение размером 600х800.

11.Напишите программу, которая преобразует рисунок в формате bmp в 3 рисунка, каждый из которых содержит четверть исходного рисунка.

12.Создайте программу, которая поворачивает квадратное изображение в формате bmp на 90 градусов.

13.Создайте программу, которая создаёт негатив изображения. То есть, тёмные точки делает светлыми и наоборот.

14.Создайте программу, которая принимает в качестве параметра изображение в формате bmp и его размеры в метрах и подсчитывает, сколько необходимо разных красок на создание этого изображения (какая площадь изображения закрашена каждым цветом).

15.Создайте программу, которая будет растягивать изображение в формате bmp по вертикали или горизонтали.

16.Создайте программу, которая будет строить изображение закрашенного прямоугольника по введённым параметрам на рисунке в формате bmp.

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

18.Напишите программу, которая получает на входе рисунок в формате bmp, содержащий горизонтальные полосы и подсчитывает количество этих полос.

19.Напишите программу, которая получает на входе два рисунка в формате bmp одинакового размера и получает на выходе изображение, образованное наложением второго рисунка на первый

20.Напишите программу, которая получает на входе рисунок в формате bmp и заменяет в нём красный цвет на зелёный

21.Напишите программу, которая получает на входе изображение в формате bmp, а на выходе выдаёт перевёрнутое изображение

22.Напишите программу, которая получает на входе изображение в формате bmp и рисует на нём

«сетку». То есть, горизонтальные и вертикальные полосы, чёрного цвета. Количество полос задаёт пользователь.

Домрачев Анатолий Иванович

ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ

Учебно-методическое пособие

по выполнению курсовой работы для обучающихся по дисциплине «ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ»

по направлению подготовки 09.03.02 Информационные системы и технологии, без профиля

Федеральное государственное бюджетное образовательное учреждение высшего образования «Нижегородский государственный архитектурно-строительный университет»

603950, Нижний Новгород, ул. Ильинская, 65. http://www.nngasu.ru, srec@nngasu.ru

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]