Assignment: Image rotation
Лабораторная работа: Поворот картинки
Подготовка
- Прочитайте главу 12 (стр. 221, 231–239) и 13 (целиком) "Low-level programming: C, assembly and program execution".
На защите мы можем обсуждать любые вопросы из учебника из глав 8–13 включительно.
Структура BMP файла
BMP файл состоит из заголовка и растрового массива.
Заголовок задаётся следующей структурой (обратите внимание на атрибут packed
):
// Описание для gcc и clang
#include <stdint.h>
struct bmp_header __attribute__((packed))
{
uint16_t bfType;
uint32_t bfileSize;
uint32_t bfReserved;
uint32_t bOffBits;
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
Сразу после него (всегда ли?) идёт растровый массив, в котором последовательно хранятся пиксели по строчкам. Каждый пиксель задаётся структурой размером 3 байта:
struct pixel { uint8_t b, g, r; };
Padding
Если ширина изображения в пикселах кратна четырём, то строчки идут одна за другой без пропусков. Если ширина не кратна четырём, то она дополняется мусорными байтами до ближайшего числа байтов, кратного четырём. Эти байты называются padding.
Пример:
- Изображение имеет ширину 12 пикселей = 12 * 3 байт = 36 байт. Ширина кратна четырём, каждая следующая строчка начинается сразу после предыдущей.
- Изображение имеет ширину 5 пикселей. 5 * 3 = 15 байт, ближайшее число кратное четырём (округление вверх) это 16. После каждой строчки будет отступ в один мусорный байт перед началом следующей.
Обратите внимание: отступы в байтах, не в пикселях.
Пользователям компилятора от Microsoft
Вам придётся задать структуру по-другому, без атрибута packed
:
#include <stdint.h>
#pragma pack(push, 1)
struct bmp_header
{
uint16_t bfType;
uint32_t bfileSize;
uint32_t bfReserved;
uint32_t bOffBits;
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
#pragma pack(pop)
Объяснение этого прочтите находится на страницах 235–239 учебника.
Задание
-
Необходимо реализовать поворот изображения в формате BMP на 90 градусов.
-
Архитектура приложения должна содержать три чётко разделённых части:
- Описание внутреннего представления картинки
struct image
, очищенное от деталей формата, и функции для работы с ним: создание, деинициализация и т.д.
struct image { uint64_t width, height; struct pixel* data; };
- Функции для работы с BMP-файлами и сериализации/десериализации внутреннего представления.
/* deserializer */ enum read_status { READ_OK = 0, READ_INVALID_SIGNATURE, READ_INVALID_BITS, READ_INVALID_HEADER /* коды других ошибок */ }; enum read_status from_bmp( FILE* in, struct image* img ); /* serializer */ enum write_status { WRITE_OK = 0, WRITE_ERROR /* коды других ошибок */ }; enum write_status to_bmp( FILE* out, struct image const* img );
Как только мы считали изображение во внутренний формат, мы должны забыть, из какого формата оно было считано! Иначе будет сложнее добавлять новые входные форматы, отличные от BMP (почему?).
-
Функции
from_bmp
иto_bmp
принимают уже открытый файл, что позволяет им работать с заранее открытыми файламиstdin
,stdout
,stderr
. Они не должны ни открывать, ни закрывать файлы. -
Вам потребуются функции, аналогичные
from_bmp
иto_bmp
, которые будут принимать имена файлов и заниматься корректным открытием (fopen
) и закрытием (fclose
) файлов; на открытых файлах они могут запускатьfrom_bmp
иto_bmp
.Имеет смысл разделять открытие/закрытие файлов и работу с ними. Уже открытие и закрытие могут сопровождаться ошибками (см.
man fopen
иman fclose
) и хочется отделить обработку ошибок открытия/закрытия и обработку ошибок чтения/записи. -
Для ошибок открытия/закрытия, возможно, вам захочется ввести отдельные типы перечислений.
-
Вам потребуется функция для поворота картинки в её внутреннем представлении:
/* создаёт копию изображения, которая повёрнута на 90 градусов */ struct image rotate( struct image const source );
Эти части имеет смысл держать в разных модулях (файлах с расширением
.c
). Разумеется, для каждого нужен соответствующий заголовочный файл.- Кроме того, дополнительные функции, которые вы ввели для удобства, но
которые не относятся по смыслу ни к одному из этих модулей, можно выделить
в отдельный модуль. Часто его называют
util.c
или как-то похоже.
- Описание внутреннего представления картинки
Для самопроверки:
-
Функции должны получать все необходимые им данные через аргументы.
-
Нельзя смешивать логику вычислений и ввод-вывод.
-
Нельзя использовать
typedef
для определения структур (объяснение), кроме структур из одного поля, которые являются аналогомtypedef
, но без неявных преобразований. -
Ваш код должен компилироваться с флагами
-std=c18 -pedantic -Wall -Werror
(gcc) или-std=c18 -pedantic -Wall -Werror
(clang). Пользователям MS Visual Studio придётся тяжко, поддержка C11/C17 пока есть только в Visual Studio 2019 version 16.8 Preview 3. Можете попробовать использоватьcl-clang
.Проверять Ваш код мы будем с помощью
gcc
иMakefile
. -
Типы:
- Проверьте, что вы максимально возможным образом расставили
const
. - Проверьте, что индексы используют только тип
size_t
. - Проверьте, что вы используете только платформо-независимые типы, такие, как
int64_t
илиint_fast64_t
.
- Проверьте, что вы максимально возможным образом расставили
-
Проверьте, что вы используете правильные спецификаторы ввода и вывода.
-
Вы можете добавлять любое число вспомогательных функций для удобства, это поощряется.
-
Проверьте архитектуру. Решение внутри одного файла приниматься не будет. Думайте о том, как бы вы хотели организовать код, чтобы легко добавлять входные форматы (не только BMP) и трансформации (не только поворот на 90 градусов).
-
Пожалуйста, присылайте решение в виде ссылки на репозиторий на https://gitlab.se.ifmo.ru или https://github.com .
-
Не забудьте написать
Makefile
. Он должен позволять при изменении одного.c
файла пересобрать часть проекта не пересобирая всё остальное.