# Assignment: Image rotation --- Лабораторная работа: Поворот картинки # Подготовка - Прочитайте главу 12 (стр. 221, 231–239) и 13 (целиком) "Low-level programming: C, assembly and program execution". На защите мы можем обсуждать любые вопросы из учебника из глав 8–13 включительно. # Структура BMP файла BMP файл состоит из заголовка и растрового массива. Заголовок задаётся следующей структурой (обратите внимание на атрибут `packed`): ```c // Описание для 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 байта: ```c struct pixel { uint8_t b, g, r; }; ``` ## Padding Если ширина изображения в пикселах кратна четырём, то строчки идут одна за другой без пропусков. Если ширина не кратна четырём, то она дополняется мусорными байтами до ближайшего числа байтов, кратного четырём. Эти байты называются *padding*. Пример: 1. Изображение имеет ширину 12 пикселей = 12 * 3 байт = 36 байт. Ширина кратна четырём, каждая следующая строчка начинается сразу после предыдущей. 2. Изображение имеет ширину 5 пикселей. 5 * 3 = 15 байт, ближайшее число кратное четырём (округление вверх) это 16. После каждой строчки будет отступ в один мусорный байт перед началом следующей. Обратите внимание: отступы в *байтах*, не в пикселях. # Пользователям компилятора от Microsoft Вам придётся задать структуру по-другому, без атрибута `packed`: ```c #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`, очищенное от деталей формата, и функции для работы с ним: создание, деинициализация и т.д. ```c struct image { uint64_t width, height; struct pixel* data; }; ``` - Функции для работы с BMP-файлами и сериализации/десериализации внутреннего представления. ```c /* 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`) и хочется отделить обработку ошибок открытия/закрытия и обработку ошибок чтения/записи. - Для ошибок открытия/закрытия, возможно, вам захочется ввести отдельные типы перечислений. - Вам потребуется функция для поворота картинки в её внутреннем представлении: ```c /* создаёт копию изображения, которая повёрнута на 90 градусов */ struct image rotate( struct image const source ); ``` Эти части имеет смысл держать **в разных модулях (файлах с расширением `.c`)**. Разумеется, для каждого нужен соответствующий заголовочный файл. - Кроме того, дополнительные функции, которые вы ввели для удобства, но которые не относятся по смыслу ни к одному из этих модулей, можно выделить в отдельный модуль. Часто его называют `util.c` или как-то похоже. # Для самопроверки: - Функции должны получать все необходимые им данные через аргументы. - Глобальные переменные запрещены. - Сообщения об ошибках должны выводиться в `stderr`. Общее правило: результаты вычислений — в `stdout`, информация о том, *как* происходят вычисления — в `stderr`. - Нельзя смешивать логику вычислений и ввод-вывод. - Нельзя использовать `typedef` для определения структур ([объяснение](https://stepik.org/lesson/408350/step/2)), кроме структур из одного поля, которые являются аналогом `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](https://devblogs.microsoft.com/cppblog/c11-and-c17-standard-support-arriving-in-msvc). Установите флаги `/Wall` (all warnings) и `/WX` (warnings as errors). Можете попробовать использовать [`cl-clang`](https://clang.llvm.org/docs/MSVCCompatibility.html). Проверять Ваш код мы будем с помощью `gcc` и `Makefile`. - Типы: - Проверьте, что вы максимально возможным образом расставили `const`. - Проверьте, что индексы используют только тип `size_t`. - Проверьте, что вы используете только платформо-независимые типы, такие, как `int64_t` или `int_fast64_t`. - Проверьте, что вы используете правильные спецификаторы ввода и вывода. - Вы можете добавлять любое число вспомогательных функций для удобства, это поощряется. - Проверьте архитектуру. **Решение внутри одного файла приниматься не будет**. Думайте о том, как бы вы хотели организовать код, чтобы легко добавлять входные форматы (не только BMP) и трансформации (не только поворот на 90 градусов). - Пожалуйста, присылайте решение в виде pull-request. [Инструкция](https://gitlab.se.ifmo.ru/cse/main/-/wikis/%D0%9A%D0%B0%D0%BA-%D0%BF%D0%BE%D1%81%D0%BB%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%BD%D0%B0-%D0%BF%D1%80%D0%BE%D0%B2%D0%B5%D1%80%D0%BA%D1%83). В крайнем случае допускается ссылка на репозиторий на https://gitlab.se.ifmo.ru или https://github.com . - Не забудьте написать `Makefile`. Он должен позволять при изменении одного `.c` файла пересобрать часть проекта не пересобирая всё остальное.