JavaRush /Курсы /Harvard CS50 /Задание №2: resize

Задание №2: resize

Harvard CS50
4 уровень , 6 лекция
Открыта

Ну а теперь — следующее испытание! Давайте напишем в файле 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
Комментарии (22)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
FeatHonnar Уровень 16
17 октября 2022
Боже, наконец-то я это доделал. Целый день в итоге ушел на поиск малюсенький ошибки, но отныне все работает. Что мне только не наговорили на форумах, и что открывать файлы в каком-то бинарном виде, и куча еще сложных вещей, которые на деле для задания не нужны совсем. Полный код в ответе на комментарий выставлю (и ссылку на чужой вариант с другим способом увеличения, тоже вполне понятный). Думаю, кому-то помочь может, ибо больше в обсуждении здесь на "человеческом" уровне программирования не выставлял, как-то сложно все (Но, пожалуйста, подумайте сперва сами, читайте чужой код только в ситуации полного ступора)
FeatHonnar Уровень 16
17 октября 2022
Это 1-я часть, никак не отличается от copy.c, но оставлю для целостности.

#include <stdio.h>
#include <stdlib.h>
                    //БИБЛИОТЕКИ
#include "bmp.h"

int main(int argc, char* argv[])
{
    // обеспечивание корректного использования программы // {
    if (argc != 4)
    {
        printf("Usage: ./copy infile outfile\n");
        return 1;
    }
                                                            // ПРОВЕРКА АРГУМЕНТОВ
    int n = atoi(argv[1]);

    if (n < 0 || n > 100)
    {
        return 2;
    }                                                   // }

    // запомнить имена файлов                                   // {
    char* infile = argv[2];
    char* outfile = argv[3];

    // открыть входной файл
    FILE* inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        printf("Could not open %s.\n", infile);
        return 3;
    }
                                                                // ПОДГОТОВКА ФАЙЛОВ
    // открыть выходной файл
    FILE* outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        fprintf(stderr, "Could not create %s.\n", outfile);
        return 4;
    }                                                           // }

    // читает BITMAPFILEHEADER из входного файла                // {
    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);
                                                                    // ЧТЕНИЕ ЗАГОЛОВКОВ
    // читает BITMAPINFOHEADER из входного файла
    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);             // }

    // убедиться, что входной файл - 24-битный несжатый BMP 4.0
    if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 ||
        bi.biBitCount != 24 || bi.biCompression != 0)
    {
        fclose(outptr);
        fclose(inptr);
        fprintf(stderr, "Unsupported file format.\n");
        return 5;<
FeatHonnar Уровень 16
17 октября 2022
Продолжение - определение заголовков и отступов.


    // определить отступы во входном файле
    int inPadding = (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;

    // изменить часть заголовков для выходного файла, посчитать отступы             // {
    bi.biWidth = bi.biWidth * n;
    bi.biHeight = bi.biHeight * n;

    int outPadding = (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;

    bf.bfSize = 54 + (bi.biWidth * sizeof(RGBTRIPLE) + outPadding) * abs(bi.biHeight);
    bi.biSizeImage = bf.bfSize - 54;
                                                                                            // ПЕРЕПИСЫВАЕМ ЗАГОЛОВКИ НА ОБНОВЛЁННЫЕ
    // записать BITMAPFILEHEADER в выходной файл
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);

    // записать BITMAPINFOHEADER в выходной файл
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);                               // }
FeatHonnar Уровень 16
17 октября 2022
3 часть - увеличение.


    // перебрать все строки сканирования
    for (int d = 0, i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++)
    {
        // перебрать все пиксели внутри строки сканирования                             // {
        for (int l = 0, j = 0; j < bi.biWidth; j++)
        {
            // временное хранилище
            RGBTRIPLE triple;

            // прочитать RGB triple из входного файла
            fread(&triple, sizeof(RGBTRIPLE), 1, inptr);

            // записать RGB triple в выходной файл
            fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
                                                                                                // ПЕРЕПИСЫВАНИЕ И УВЕЛИЧЕНИЕ СТРОКИ
            // возвращает указатель в inptr на 1 пиксель для дальнйшего увеличения по горизонтали
            if ((l < n - 1) && (n > 1)) {
                l++;
                fseek(inptr, -sizeof(RGBTRIPLE), SEEK_CUR);
            }
            else {
                l = 0;
            }
        }                                                                              // }
        // пропустить отступы во входном файле, если таковые имеются                    // {
        fseek(inptr, inPadding, SEEK_CUR);
                                                                                                // РАБОТА С ОТСТУПАМИ
        // вписать отступы в выходной файл
        for (int k = 0; k < outPadding; k++)
        {
            fputc(0x00, outptr);
        }                                                                               // }

        // возвращает указатель в inptr на строку назад (включая отступы) для дальнейшего увеличения по вертикали
        if ((d < n - 1) && (n > 1)) {
            d++;
            fseek(inptr, -(bi.biWidth / n * sizeof(RGBTRIPLE) + inPadding), SEEK_CUR);
        }
        else {
            d = 0;
        }
    }
FeatHonnar Уровень 16
17 октября 2022

    // закрыть infile
    fclose(inptr);

    // закрыть outfile
    fclose(outptr);

    // на этом всё
    return 0;
}
Ссылка на чужую версию - https://gist.github.com/CraigRodrigues/8cf42cb785e4d6468ec201a5e0323069#file-resize-c
Karen Уровень 1
19 апреля 2022
Уменьшение.

    int biWidthN = bi.biWidth / n;
    int biHeightN = abs(bi.biHeight / n);

    int biWidthO = bi.biWidth;
    int biHeightO = abs(bi.biHeight);

    int paddingN =  (4 - (biWidthN * sizeof(RGBTRIPLE)) % 4) % 4;
    int paddingO =  (4 - (biWidthO * sizeof(RGBTRIPLE)) % 4) % 4;

    bi.biSizeImage = ((biWidthN * sizeof(RGBTRIPLE) + paddingN) * biHeightN);
    bi.biWidth /= n;
    bi.biHeight /= n;

    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);

    RGBTRIPLE triple;

    fseek(inptr, 54, SEEK_SET);
    fseek(outptr, 54, SEEK_SET);

        for(int u = 0; u < biHeightN; u++)  // на n раз сокращаем и  записываем строку
        {   
            for(int j = 0; j < biWidthN; j++) // записываем пиксели в ряду
            {   
                fread(&triple, sizeof(triple), 1, inptr);
                fwrite(&triple, sizeof(triple), 1, outptr);
                fseek(inptr, sizeof(triple) * (n -1), SEEK_CUR);
            }
            for (int k = 0; k < paddingN; k++)  // Дописываем байты кратно четырем
            {
                fputc(0x00, outptr);
            }
            fseek(inptr, paddingO, SEEK_CUR);
            fseek(inptr, ((biWidthO * sizeof(RGBTRIPLE) + paddingO) * (n - 1)), SEEK_CUR);
        }
Karen Уровень 1
19 апреля 2022
Увеличение через массив.

 bi.biWidth *= n;
    bi.biHeight *= n;
    int biHeight = abs(bi.biHeight);
    int paddingS = (4 - (bi.biWidth / n * sizeof(RGBTRIPLE)) % 4) % 4;
    int padding =  (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;
    bi.biSizeImage = ( (bi.biWidth * sizeof(RGBTRIPLE) + paddingS) * biHeight );

    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);

    // determine padding for scanlines
   
    
    int xx[(biHeight/n * bi.biWidth/n) * 3 + 6]; 
    RGBTRIPLE triple;

    fseek(inptr, 54, SEEK_SET);
    
    for (int i = 0, k = 0, nn = biHeight / n; i < nn; i++) 
    {
        for (int j = 0, ww = bi.biWidth/n; j < ww; j++)
        {
            fread(&triple, sizeof(triple), 1, inptr);
            xx[k    ] = triple.rgbtBlue;
            xx[k + 1] = triple.rgbtGreen;
            xx[k + 2] = triple.rgbtRed;

            k += 3;
        }
        fseek(inptr, (paddingS), SEEK_CUR);
    }
    fseek(inptr, 54, SEEK_SET);
    fseek(outptr, 54, SEEK_SET);
    for (int i = 0, dd = 0, ll = 0; i < biHeight; dd += bi.biWidth / n * 3)  /
    {
        for(int u = 0; u < n; u++, i++)  // n раз дублируем строку
        { 
            ll = 0;
            for(int j = 0, kf = (bi.biWidth / n); j < kf; j++) 
            {
                for(int a = 0; a < n ; a++) 
                {
                    fread(&triple, sizeof(RGBTRIPLE), 1, outptr);
                    triple.rgbtBlue  = xx[dd + ll    ];
                    triple.rgbtGreen = xx[dd + ll + 1];
                    triple.rgbtRed   = xx[dd + ll + 2];
                    fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
                }
                    ll += 3; 
            }
            for (int k = 0; k < padding; k++)  // Дописываем байты кратно четырем
            {
                fputc(0x00, outptr);
            } 
        }
    }
Karen Уровень 1
19 апреля 2022
Увеличение через передвижение курсора.

    bi.biWidth *= n;
    bi.biHeight *= n;
    int biHeight = abs(bi.biHeight);
    int padding =  (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;
    int paddingS =  (4 - (bi.biWidth / n * sizeof(RGBTRIPLE)) % 4) % 4;
    bi.biSizeImage = ( (bi.biWidth * sizeof(RGBTRIPLE) + paddingS) * biHeight);
    
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);

    RGBTRIPLE triple;

    fseek(inptr, 54, SEEK_SET);
    fseek(outptr, 54, SEEK_SET);
    int kf = (bi.biWidth / n);

    for (int i = 0, dd = 0; i < biHeight;)  
    {
        for(int u = 0; u < n; u++, i++ )  // n раз дублируем строку
        {   
            for(int j = 0; j < kf; j++) // дублируем пиксели в ряду
            {
                for(int a = 0; a < n ; a++) // дублируем пиксель n раз
                {
                    fread(&triple, sizeof(triple), 1, inptr);
                    fwrite(&triple, sizeof(triple), 1, outptr);
                    fseek(inptr, -sizeof(triple), SEEK_CUR);
                }
                fseek(inptr, (sizeof(triple)), SEEK_CUR);
            }
            for (int k = 0; k < padding; k++)  // Дописываем байты кратно четырем
            {
                fputc(0x00, outptr);
            } 
            fseek(inptr, (54 + (kf * sizeof(triple) + paddingS) * dd), SEEK_SET);
        }
        dd++;
        fseek(inptr, (54 + (kf * sizeof(triple) + paddingS) * dd), SEEK_SET);
    }
Yoty Уровень 39
23 июля 2021
"Если количество пикселей в строке не кратно четырем, мы должны добавить «выравнивание» – нулевые байты." - не пикселей, а байт!
Ярослав Уровень 0
10 сентября 2020
Сначала опечатки и неточности, потом ход решения: 1 - 232 это опечатка!!! You may assume that f times the size of infile will not exceed 2^32 - 1. original http 2 - Неточность, которая запутывает: "Если мы заглянем в описание заголовка, то увидите переменную biSizeImage. Она указывает на общий размер изображения в байтах" Вообще bfSize; это общий размер. А biSize - это размер второй структуры Bmp файла BITMAPINFOHEADER. Да там написано biSizeImage, но лучше называть вещи правильно bfSize Решение: Вся сложность программы в том, что бы понять что в циклах надо ставить границы с входящего файла. + отступы надо контролировать! Пропускать для входящего и добавлять для нового. Отступов теперь 2 переменные. abs используем для biWidth! что бы число было с +. Ну и простейший счетчик внутри каждого цикла для n-раз. j2 = j2 - 1; if (j2 == 0) // если правда - новый круг цикла / новый пиксель { j = j + 1; j2 = n; } else fseek(inptr, - sizeof(triple), SEEK_CUR); // возвращаемся назад на пиксель.
Александр # Уровень 0
17 апреля 2021
Ярослав, большое спасибо за указание опечатки в переводе разрядности. По поводу biSizeImage - на самом деле в задании написано верно. Эта переменная в структуре bi существует, и именно её значение как раз и нужно изменить. Действительно, есть и просто bi.biSize, но она в данном задании не участвует.
FeatHonnar Уровень 16
15 октября 2022
abs ведь нужен для biHeight, а не biWidth, т.к. второе - всегда положительное. А первое необязательно
Михаил Уровень 20
10 февраля 2019
Может кто объяснить, как вычислить фактический размер. А если точнее как заполнение вычислить сколько будет? При 3х кратном увеличении умножаем размер на 3 + RGB triple * на заполнение?
Serg Уровень 2
5 марта 2019
bi.biWidth = (bi.biWidth-padding)*size; bi.biHeight = bi.biHeight*size; bi.biSizeImage = abs(bi.biWidth*3*bi.biHeight); bf.bfSize = (abs(bi.biWidth*3*bi.biHeight)+54); новая ширина берется с вычетом выравнивания, размер изображения в байтах, а не пикселях, и по модулю, в размер всего файла добавляется еще заголовочная информация из 54 байт .
Артем Уровень 0
8 июня 2020
Добрый день Serg. Думаю что вы неправильно формулу составили. Это легко потом проверить с помощью команды diff в терминале. Сравните large.bmp и Ваш файл который получился после вашей программы. Используйте 16 ричный редактор и увидите где ошибка. padding не нужно отнимать.
Alexander Levin Уровень 4
14 октября 2018
Главное следить за курсором..
Roman Andreev Уровень 1
18 октября 2017
Интересно, кто нибудь делал хакерскую версию, где изображение не только увеличивается, но еще и сжимается? Понятно, что если мы , например увеличили изображение сначала в N раз, то обратными действиями его можно на столько же уменьшить. Но как быть, если нам, скажем изображение 3х3 пикселя нужно уменьшить в 5 раз? Пока даже понять не могу по какому принципу это реализуется. Пиксель это же цельная величина, как мы можем отрисовать 0,6 пикселя, да еще и сохранив рисунок при этом?
Дарек Цыдик Уровень 11
10 ноября 2017
как я понимаю(еще не прочитав лекцию), то при уменьшении ты не пиксели уменьшаешь, а уменьшаешь их количество
2 февраля 2018
Если алгоритм уменьшения работает правильно, то из изображения 3х3 останется 2 пикселя.
George Tushinsky Уровень 4
30 августа 2017
check50 cs50/2017/x/resize/less, теперь так надо писать. (http://docs.cs50.net/problems/resize/less/resize.html)
12 ноября 2017
У меня просит ввести логин и пароль от гитхаба. Как загрузить на гитхаб не знаю. Подскажите если не трудно.