Каждый пиксель задаётся структурой размером 3 байта:
```c
structpixel{uint7_tb,g,r;};
structpixel{uint8_tb,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)
structbmp_header
{
uint16_tbfType;
uint32_tbfileSize;
uint32_tbfReserved;
uint32_tbOffBits;
uint32_tbiSize;
uint32_tbiWidth;
uint32_tbiHeight;
uint16_tbiPlanes;
uint16_tbiBitCount;
uint32_tbiCompression;
uint32_tbiSizeImage;
uint32_tbiXPelsPerMeter;
uint32_tbiYPelsPerMeter;
uint32_tbiClrUsed;
uint32_tbiClrImportant;
};
#pragma pack(pop)
```
Объяснение этого прочтите находится на страницах 235–239 учебника.
# Задание
- Необходимо реализовать поворот изображения в формате BMP на 90 градусов.
- Архитектура приложения должна содержать три чётко разделённых части:
- Описание внутреннего представления картинки `struct image`, очищенное от деталей формата
- Описание внутреннего представления картинки `struct image`, очищенное от деталей формата, и функции для работы с ним: создание, деинициализация и т.д.
```c
structimage{
uint64_twidth,height;
structpixel*data;
};
```
- Функции для работы с BMP-файлами и сериализации/десериализации внутреннего представления.
Как только мы считали изображение во внутренний формат, мы должны забыть, из какого формата оно было считано! Иначе будет сложнее добавлять новые входные форматы, отличные от BMP (почему?).
Как только мы считали изображение во внутренний формат, мы должны забыть, из какого формата оно было считано!
- Функции `from_bmp` и `to_bmp` принимают уже открытый файл, что позволяет
им работать с заранее открытыми файлами `stdin`, `stdout`, `stderr`.
Они не должны ни открывать, ни закрывать файлы.
- Вам потребуются функции, аналогичные `from_bmp` и `to_bmp`, которые будут
принимать имена файлов и заниматься корректным открытием (`fopen`) и
закрытием (`fclose`) файлов; на открытых файлах они могут запускать `from_bmp`
и `to_bmp`.
- Функции для трансформации внутреннего представления, которые работают с `struct image`.
Имеет смысл разделять открытие/закрытие файлов и работу с ними. Уже
открытие и закрытие могут сопровождаться ошибками (см. `man fopen` и
`man fclose`) и хочется отделить обработку ошибок открытия/закрытия и
обработку ошибок чтения/записи.
```c
/* создаёт копию изображения, которая повёрнута на 90 градусов */
struct image rotate( struct image const source );
```
- Для ошибок открытия/закрытия, возможно, вам захочется ввести отдельные типы перечислений.
# Пользователям компилятора от Microsoft
- Вам потребуется функция для поворота картинки в её внутреннем представлении:
Вам придётся задать структуру по-другому, без атрибута `packed`:
```c
/* создаёт копию изображения, которая повёрнута на 90 градусов */
struct image rotate( struct image const source );
```
```c
#include <stdint.h>
#pragma pack(push, 1)
structbmp_header
{
uint16_tbfType;
uint32_tbfileSize;
uint32_tbfReserved;
uint32_tbOffBits;
uint32_tbiSize;
uint32_tbiWidth;
uint32_tbiHeight;
uint16_tbiPlanes;
uint16_tbiBitCount;
uint32_tbiCompression;
uint32_tbiSizeImage;
uint32_tbiXPelsPerMeter;
uint32_tbiYPelsPerMeter;
uint32_tbiClrUsed;
uint32_tbiClrImportant;
};
#pragma pack(pop)
```
Объяснение этого прочтите находится на страницах 235–239 учебника.
Эти части имеет смысл держать **в разных модулях (файлах с расширением `.c`)**. Разумеется, для каждого нужен соответствующий заголовочный файл.
- Кроме того, дополнительные функции, которые вы ввели для удобства, но
которые не относятся по смыслу ни к одному из этих модулей, можно выделить
в отдельный модуль. Часто его называют `util.c` или как-то похоже.
# Для самопроверки:
- Функции должны получать все необходимые им данные через аргументы.
- Нельзя смешивать логику вычислений и ввод-вывод.
- Нельзя использовать `typedef` для определения структур ([объяснение](https://stepik.org/lesson/408350/step/2)).
- Ваш код должен компилироваться с флагами `-std=c18 -pedantic -Wall -Werror`.
- Нельзя использовать `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).
Можете попробовать использовать [`cl-clang`](https://clang.llvm.org/docs/MSVCCompatibility.html).
Проверять Ваш код мы будем с помощью `gcc` и `Makefile`.
- Типы:
- Проверьте, что вы максимально возможным образом расставили `const`.
- Проверьте, что индексы используют только тип `size_t`.
- Проверьте, что вы используете только платформо-независимые типы, такие, как `int64_t` или `int_fast64_t`.
- Проверьте, что вы используете правильные спецификаторы ввода и вывода.
- Вы можете добавлять любое число вспомогательных функций для удобства, это поощряется.
- Проверьте архитектуру. Думайте о том, как бы вы хотели организовать код, чтобы легко добавлять входные форматы (не только BMP) и трансформации (не только поворот на 90 градусов).
- Проверьте архитектуру. **Решение внутри одного файла приниматься не будет**.
Думайте о том, как бы вы хотели организовать код, чтобы легко добавлять входные форматы (не только BMP) и трансформации (не только поворот на 90 градусов).
- Пожалуйста, присылайте решение в виде ссылки на репозиторий на https://gitlab.se.ifmo.ru или https://github.com .
- Не забудьте написать `Makefile`. Он должен позволять при изменении одного `.c` файла пересобрать часть проекта не пересобирая всё остальное.