README.md 10.2 KB

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.

Пример:

  1. Изображение имеет ширину 12 пикселей = 12 * 3 байт = 36 байт. Ширина кратна четырём, каждая следующая строчка начинается сразу после предыдущей.
  2. Изображение имеет ширину 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 файла пересобрать часть проекта не пересобирая всё остальное.