Ну а теперь — следующее испытание! Давайте напишем в файле resize.c программу, называемую resize. Она будет менять размеры несжатого 24-битного изображения BMP с шагом в n. Ваше приложение должно принимать ровно три аргумента командной строки, причем первый (n) должен быть целым числом не более 100, второй — именем файла, который будет изменен, а третий — названием сохраненной версии измененного файла.

&ltcodeUsage: ./resize n infile outfile>

С помощью такой программы мы могли бы создать large.bmp из файла small.bmp путем изменения размера последнего на 4 (т.е. путем умножения и ширины, и высоты на 4), как показано ниже.

./resize 4 small.bmp large.bmp

Для простоты можно начать задание скопировав еще раз copy.c и назвав копию resize.c.

Но сначала задайте себе вопросы и ответьте на них: что значит изменить размер BMP (можно предположить, что n помноженное на размер файла infile не будет превышать 232 — 1).

Определите, какие поля в BITMAPFILEHEADER и BITMAPINFOHEADER вам нужно изменить.

Подумайте, нужно ли вам добавить или удалить поля scanlines.

И, да, скажите спасибо, что мы не предлагаем вам рассмотреть все возможные значения n от 0 до 1! (хотя, если интересно — это задачка из хакерского задачника;)). Тем не менее, мы предполагаем, что при n = 1 программа будет работать правильно, и выходной файл outfile будет таких же размеров как и исходный infile.

Хотите проверить программку с помощью check50? Наберите следующую команду:

check50 2015.fall.pset4.resize bmp.h resize.c

Есть желание поиграться с реализацией приложения, выполненной ассистентами CS50? Выполните следующее:

~cs50/pset4/resize

Ну а если хотите посмотреть, например, заголовки large.bmp (в более дружественном для пользователя виде, чем позволяет xxd), нужно выполнить следующую команду:

~cs50/pset4/peek large.bmp

Еще лучше, если вы захотите сравнить свои заголовки с заголовками файлов ассистентов CS50. Вы можете выполнить команды внутри вашей директории ~/workspace/pset4/bmp (подумайте, что делает каждая команда).

./resize 4 small.bmp student.bmp
~cs50/pset4/resize 4 small.bmp staff.bmp
~cs50/pset4/peek student.bmp staff.bmp

Если вы использовали malloc, не забудьте использовать free, чтобы предотвратить утечку памяти. Попробуйте использовать valgrind чтобы проверить наличие утечек.

Как решать?

  • Открыть файл, который нам нужно увеличить, а также создать и открыть новый файл, в котором будет записано увеличенное изображение.
  • Обновить информацию заголовка для выходного файла. Поскольку наше изображение в формате BMP и мы меняем его размер, нам нужно записать заголовок нового файла с новыми размерами. Что поменяется? Размер файла, а также размер изображения — его ширина и высота.
  • Задание №2: resize - 1

    Если мы заглянем в описание заголовка, то увидите переменную biSizeImage. Она указывает на общий размер изображения в байтах, biWidth — ширина изображения минус выравнивание, biHeight — высота. Эти переменные находятся в структурах BITMAPFILEHEADER и BITMAPINFOHEADER. Вы можете найти их, если откроете файл bmp.h.

    Задание №2: resize - 2

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

    • Читаем исходящий файл, строка за строкой, пиксель за пикселем. Для этого мы снова обращаемся к нашей библиотеке файлового ввода/вывода и функции fread. Она принимает указатель на структуру, которая будет содержать прочитанные байты, размер одного элемента, который мы собираемся прочитать, количество таких элементов и указатель на файл, из которого мы будем читать.
    • Задание №2: resize - 3
    • Увеличиваем каждую строку по горизонтали согласно заданному масштабу, записываем результат в выходной файл
    • Задание №2: resize - 4

      Как мы записываем файлы? У нас есть функция fwrite, которой мы передаем указатель на структуру (там находятся данные для записи в файл), размер элемента, их количество и указатель на выходной файл. Для организации цикла можем воспользоваться уже привычным нам циклом for.

      Задание №2: resize - 5
    • Заполнить пробелы! Если количество пикселей в строке не кратно четырем, мы должны добавить «выравнивание» – нулевые байты. Нам понадобится формула для расчета размера выравнивания. Для записи нулевых байтов в выходной файл вы можете использовать функцию fputc, передав ей символ, который вы хотите записать и указатель на выходной файл.
    • Задание №2: resize - 6
    • Теперь, когда мы растянули строку по горизонтали и добавили выравнивание в выходной файл, нам нужно переместить текущую позицию в исходящем файле, поскольку нам нужно перескочить через выравнивание.
    • Задание №2: resize - 7
    • Увеличить размер по вертикали. Это сложнее, но мы можем использовать пример кода из файла copy.c(copy.c открывает исходящий файл, записывает заголовок в выходной файл, читает изображение из исходного файла по строкам, пиксель за пикселем, и записывает их в файл выхода). Исходя из этого, первым делом можно выполнить такую команду: cp copy.c resize.c

    Растянуть изображение по вертикали означает скопировать каждую строку несколько раз. Есть несколько разных способов это сделать. Например, с помощью перезаписи, когда мы сохраняем все пиксели одной строки в памяти и в цикле записываем эту строку в выходной файл столько раз, сколько это нужно. Другой метод – перекопирование: после чтения строки исходящего файла, записи его в выходной файл и выравнивания вернуться функцией fseek назад в начало строки в исходящем файле и повторить все заново несколько раз.

    Задание №2: resize - 8