Задания третьей недели курса CS50 (Whodunit, Resize легкий, Resize, Recover)

4.7 (93.33%) 9 vote[s]

Третья неделя курса CS50 «введение в информатику» посвящена изображениям и манипуляциям с ними. Включает задачи для решения Whodunit, Resize легкий, Resize, Recover. Растровые изображения представляются в виде двумерной таблицы пикселей разных цветов. Черно-белые изображения используют 1 бит на каждый пиксель: 0 – это черный, 1 – белый.

Чтобы получить цветное изображение, нужно использовать больше битов на каждый пиксель. Этим определяются форматы файлов: GIF поддерживает 8-битный цвет, т.е на один пиксель используется 8 бит. Формат BMP, JPEG,  PNG поддерживают 24-битный цвет (хотя BMP может поддерживать  1-, 4-, 8-, 16-, 24- и 32-битный цвет).

Как известно, любой цвет можно создать, смешивая 3 основных цвета RGB: красный (red), зеленый (green) и синий (blue). Поэтому BMP, используя по 8 бит для указания количества каждого цвета, и поддерживает 24-битный формат.

Чисто красный цвет в шестнадцатеричной системе можно записать так: значение R = 0xff, G =  0x00 и B = 0x00. В десятичной системе – 255,  0 и 0. Означают отсутствие зеленого и голубого соответственно.

В исходном файле задания Whodunit имеются такие пиксели. Но в нем также есть и пиксели с другими значениями!

HTML и CSS (языки, с помощью которых создаются веб-страницы) моделируют цвета так же.

Разбираемся в структуре растрового изображения

Таким образом, файл изображения – это последовательность 24-битных блоков (почти всегда) представляющие информацию о цвете определенного пикселя. Кроме того, BMP файл т содержит информацию о высоте и ширине изображения (метаданные»). Они хранятся в начале файла в виде двух структур данных (заголовков).

Первый заголовок называется BITMAPFILEHEADER. Его размер 14 байт (1 байт = 8 бит). Второй заголовок называется BITMAPINFOHEADER. Его длина  40 байт. После заголовков содержится «карта битов». Она представляет собой  массив байтов, каждая тройка из которых представляет цвет пикселя.

 В 1-, 4-, и 16-битных BMP (но не в 24-и 32-битных) присутствует еще один заголовок RGBQUAD, который  идет сразу за BITMAPINFOHEADER. Это массив с информацией об  интенсивности каждого цвета в палитре.

BMP сохраняет тройки байтов задом наперед (то есть в виде BGR). В некоторых файлах BMP задом наперед сохраняется и  «карта бит».

Структурные особенности

В нашем примере используем библиотеку  bmp.h. Откройте bmp.h и вы увидите собственное определение этих заголовков, адаптированные из реализаций от Microsoft. Кроме того, этот файл определяет типы данных BYTE, DWORD, LONG, и WORD которые, как правило, встречаются в среде Windows программирования. Обратите внимание на то, что они просто псевдонимы для примитивов. BITMAPFILEHEADER и BITMAPINFOHEADER используют эти типы. Этот файл также определяет структуру struct, которая называется RGBTRIPLE, что, попросту говоря, «инкапсулирует» три байта: один синий, один зеленый и один красный (это и есть порядок цветов, в котором мы будем искать RGB-тройки на диске).

Чем полезны struct (структуры)? Байты упорядочены таким образом, что первые несколько из них представляют то, следующие несколько представляют что-то еще и так далее. «Форматы» существуют только потому, что мир стандартизировал, которые байта что значат.

Каждый из этих  байтов имеет имена, чтобы их легче было доставать из памяти. Это именно то, что позволяют нам делать структуры в bmp.h. Вместо того, чтобы думать о какой-то файл как об одной длинную последовательность байтов, мы можем вместо того думать о нем как о последовательности struct (структур).

Файл .bmp размером 8 на 8 пикселей  занимает 14 + 40 + (8 × 8) × 3 = 246 байт на диске. Вот, как это выглядит на диске, в соответствии с Microsoft:

Как видно из этого рисунка, порядок имеет значение, когда дело доходит до полей структуры struct. Байт 57 представляет rgbtBlue (а не, скажем, rgbtRed), потому что rgbtBlue определен в RGBTRIPLE сначала. Использование атрибута packed, гарантирует, что clang не попытается выравнивать по слову поля структуры (при этом адрес первого байта каждого поля является кратной 4). Это может привести к появлению пробелов в структурах, фактически не существуют на диске.

Подсказка

      • Благодаря bmp.h, RGBTRIPLE — это просто новый тип данных, с которым можно работать, как с int или float.
      • Если есть переменная типа RGBTRIPLE, можно получить доступ к ее свойствам, таких как rgbtRed, использовав запись с точкой.
      • Помните, что triple это переменная типа RGBTRIPLE.
      • Помните, что мы можем писать числа в шестнадцатеричном формате в C, написав перед ними 0x.
      • Помните, что пиксель определяется как имеющий компоненты красного, зеленого и синего (RGB).
      • Помните, что первые два шестнадцатеричных числа в шестнадцатеричном коде цветов определяют красный компонент, вторые два — зеленый, а третьи два — синий.

Народная мудрость

Whodunit

В файле whodunit.c. нужно попробовать изменить пиксели в процессе между прочтением исходного файла и записи результирующего.

Как можно получить скрытую запись? Придется поэкспериментировать. Может, нужно превратить все белые пиксели на черные, потому что больший контраст может улучшить читабельность? Или, может, нужно превратить в белое весь красный «шум» на картинке? Или, может, следует дать всему красную тень? Можно использовать логику условного перехода, чтобы, возможно, сначала проверить различные свойства triple, а затем решить, что делать.

Подсказки для решения задания Whodunit

  • Помните, что мы можем создавать более сложные булевы выражения с помощью && и ||.
  • Помните, что == проверяет на равенство, а <= и> = могут быть также подходящими операторами в этом случае.

После завершения работы над кодом, выполните команды для компиляции файла, как описано в первом  задании, и запустите программу

./whodunit clue.bmp verdict.bmp

Предлагается 2 варианта решения, сравните, попробуйте поэкспериментировать.

Первый вариант

Вот исходное изображение:

clue
clue

Данное изображение в формате .jpeg. Для работы с этим заданием необходимо скачать исходники с библиотекой bmp.h и изображением clue в формате .bmp.

Файл bmp.h, который должен быть в папке с проектом:

#include <stdint.h>

// aliases for C/C++ primitive data types
// https://msdn.microsoft.com/en-us/library/cc230309.aspx
typedef uint8_t  BYTE;
typedef uint32_t DWORD;
typedef int32_t  LONG;
typedef uint16_t WORD;

// information about the type, size, and layout of a file
// https://msdn.microsoft.com/en-us/library/dd183374(v=vs.85).aspx
typedef struct
{
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
} __attribute__((__packed__))
BITMAPFILEHEADER;

// information about the dimensions and color format
// https://msdn.microsoft.com/en-us/library/dd183376(v=vs.85).aspx
typedef struct
{
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
} __attribute__((__packed__))
BITMAPINFOHEADER;

// relative intensities of red, green, and blue
// https://msdn.microsoft.com/en-us/library/dd162939(v=vs.85).aspx
typedef struct
{
    BYTE rgbtBlue;
    BYTE rgbtGreen;
    BYTE rgbtRed;
} __attribute__((__packed__))
RGBTRIPLE;

 

Код реализации whodunit.c:

#include <stdio.h>
#include <stdlib.h>

#include "bmp.h"

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        printf("Usage: ./whodunit infile outfile\n");
        return 1;
    }
    char* infile = argv[1];
    char* outfile = argv[2];

    FILE* inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        printf("Could not open %s.\n", infile);
        return 2;
    }
    FILE* outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        fprintf(stderr, "Could not create %s.\n", outfile);
        return 3;
    }
    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);

    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);

    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 4;
    }
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);

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

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

    for (int i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++)
    {
        for (int j = 0; j < bi.biWidth; j++)
        {
            RGBTRIPLE triple;

            fread(&triple, sizeof(RGBTRIPLE), 1, inptr);

         if (triple.rgbtRed == 255)
           {
              triple.rgbtRed = 255;
              triple.rgbtBlue = 255;
              triple.rgbtGreen = 255;
           }

           fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
           }
            fseek(inptr, padding, SEEK_CUR);

        for (int k = 0; k < padding; k++)
           {
            fputc(0x00, outptr);
           }
       }
      fclose(inptr);

      fclose(outptr);

      return 0;
    }

 

Результат работы такого варианта программы Whodunit будет выглядеть так:

Второй вариант

Поэкспериментировав с фильтрами и с цветами (а текст в исходнике выполнен шумом), можно получить более четкое изображение. Создаем новый файл whodunit2.c записываем новый код, и реализуем его командой ./whodunit2 clue.bmp verdict2.bmp. При этом после выполнения в папке с проектом будет создан новый файл verdict2.bmp, который можно сравнить с полученным «Whodunit» в первом варианте.

#include <stdio.h>
#include <stdlib.h>

#include "bmp.h"

int main(int argc, char *argv[])
{
   if (argc != 3)
    {
        fprintf(stderr, "Usage: copy infile outfile\n");
        return 1;
    }
    char *infile = argv[1];
    char *outfile = argv[2];

    FILE *inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        fprintf(stderr, "Could not open %s.\n", infile);
        return 2;
    }
    FILE *outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        fprintf(stderr, "Could not create %s.\n", outfile);
        return 3;
    }
    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);

    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);

    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 4;
    }
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);
    int padding = (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;

    for (int i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++)
    {
        for (int j = 0; j < bi.biWidth; j++)
        {
            RGBTRIPLE triple;

            fread(&triple, sizeof(RGBTRIPLE), 1, inptr);

          if (triple.rgbtRed < 255)
            {
                triple.rgbtBlue = 0;
                triple.rgbtRed = 128;
                triple.rgbtGreen = 0;
            }

          if (triple.rgbtRed == 255)
            {
                triple.rgbtBlue = 255;
                triple.rgbtRed = 255;
                triple.rgbtGreen = 255;
            }
            fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
        }
        fseek(inptr, padding, SEEK_CUR);

        for (int k = 0; k < padding; k++)
        {
            fputc(0x00, outptr);
        }
    }

    fclose(inptr);

    fclose(outptr);

    return 0;
}

 

После выполнения получим такое изображение:

Основываясь на данной информации, можно сразу приступать и к реализации вариантов изменения размера изображения.

Resize (легкий)

Для работы с этим и следующим заданием необходимо загрузить архив с исходными файлами.

В том случае, когда количество байтов в строке не кратно 4, BMP-файлы хранят их по-другому. Если маленький 24-битный файл имеет размер 3 пикселя в ширину на 3 пикселя в высоту (файл
small.bmp из архива), то каждая строка будет занимать  (3 пикселя) × (3 байта на пиксель) = 9 байтов. Это число не кратно 4, поэтому строка дополнятся минимальным количеством нулевых байтов, чтобы общее количество байт стало кратным 4.

В таком 24-битном BMP файле для каждой строки нужно дополнение размером от 0 до 3 байтов. Это нам пригодится при изменении размера файла и поиска нулевых байтов.

Спецификация задания

Реализуйте программу resize, которая изменяет (то есть увеличивает) размер 24-битного не сжатого BMP файла в n раз.

Программа должна принимать ровно три аргумента командной строки, а именно:

      • первый из них (n) должен быть положительным целым числом, которое меньше или равно 100,
      • второй из них должен быть именем файла, размер которого нужно изменить,
      • третий из них должен быть именем файла, куда будет записано версию файла с измененным размером.

Если эти условия не выполняются, программа с помощью printf  должна напомнить пользователю о том, как ее корректно использовать, а main должен вернуть 1.

Если ваша программа использует malloc (наведите курсор и смотрите во всплывающем окне), она не должна иметь утечек памяти. Убедитесь, что вы вызвали free.

#include <stdio.h>
#include <stdlib.h>

#include "bmp.h"

int main(int argc, char *argv[])
{
    // обеспечиваем правильное использование
    if (argc != 4)
    {
        fprintf(stderr, "Usage: copy infile outfile\n");
        return 1;
    }

    //Получаем n из пользовательского ввода
    int n = atoi(argv[1]);

    //Убеждаемся, что n от 0 до 100
    if (n < 0 || n > 100)
    {
        printf("Usage: ./resize n infile outfile\n");

        return 5;
    }

    // Запомнаем имена файлов.
    char *infile = argv[2];
    char *outfile = argv[3];

    // Открываем входной файл.
    FILE *inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        fprintf(stderr, "%s.\n", infile);
        return 2;
    }

    // Открываем выходной файл.
    FILE *outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        fprintf(stderr, "%s.\n", outfile);
        return 3;
    }

    // Читаем BITMAPFILEHEADER infile и устанавливаем равный ему BITMAPHEADERFILE.
    BITMAPFILEHEADER bf, bfR;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);
    bfR = bf;

    // Читаем BITMAPINFOHEADER infile и устанавливаем равный ему BITMAPINFOHEADER.
    BITMAPINFOHEADER bi, biR;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);
    biR = bi;

    //Задаем файлы заголовка выходного файла для настройки по значению изменения размера.
    biR.biWidth = bi.biWidth * n;
    biR.biHeight = bi.biHeight * n;

    // Проверяем, что infile (вероятно) является 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 4;
    }
  
     // Определяем отступы для строк развертки для infile и outfile.
    int padding = (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;
    int outPadding = (4 - (biR.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;

    //Новый размер изображения.
    bfR.bfSize = 54 + biR.biWidth * abs(biR.biHeight) * 3 + abs(biR.biHeight) *  outPadding;
	biR.biSizeImage = ((((biR.biWidth * biR.biBitCount) + 31) & ~31) / 8) * abs(biR.biHeight);

    // Записываем файл BITMAPFILEHEADER.
    fwrite(&bfR, sizeof(BITMAPFILEHEADER), 1, outptr);

    // Записываем файл BITMAPINFOHEADER.
    fwrite(&biR, sizeof(BITMAPINFOHEADER), 1, outptr);

    // Перебираем сканы строк в infile.
    for (int i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++)
    {
        // Перебираем пиксели в линии сканирования.
        for (int j = 0; j < n; j++)
        {
            for (int k = 0; k < bi.biWidth; k++)
            {
                // Создаем временное хранилище.
                RGBTRIPLE triple;

                // Читаем тройной RGB из infile.
                fread(&triple, sizeof(RGBTRIPLE), 1, inptr);

                // Пишем тройной RGB в выходной файл n раз.
                for (int l = 0; l < n; l++)
                {
                    fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
                }
            }

            // Добавляем отступы.
            for (int m = 0; m < outPadding; m++)
            {
                fputc(0x00, outptr);
            }

            if (j < n - 1)
            {
                fseek(inptr, -bi.biWidth * sizeof(RGBTRIPLE), SEEK_CUR);
            }
        }

        // Пропускаем заполнение, если они есть
        fseek(inptr, padding, SEEK_CUR);
      }
      fclose(inptr);

     fclose(outptr);

    return 0;
}

 

Программа должна вести себя, как указано в примерах:

$ ./resize  
Usage: ./resize n infile outfile
$ echo $?
1

$ ./resize 2 small.bmp larger.bmp
$ echo $?
0

Для сравнения своего решения с решением команды курса, выполняем команды:

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

Народная мудрость

Resize

В этом задании изменяется тип вводных данных. Теперь будем работать с числом с плавающей запятой.

Спецификация

Реализуйте программу resize которая изменяет (то есть увеличивает или уменьшает) размер 24-битного несжатого BMP в f раз.

Программа должна принимать ровно три аргумента командной строки, а именно:

      • первый из них (f) должно быть числом с плавающей точкой, принадлежащей промежутке (0.0, 100],
      • второй из них должен быть именем файла, размер которого нужно изменить,
      • третий из них должен быть именем файла, куда будет записано версию файла с измененным размером.

Если эти условия не выполняются, программа должна напомнить пользователю о том, как ее корректно использовать с помощью printf, а main должен вернуть 1.

Если программа использует malloc, она не должна иметь утечек памяти.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/stat.h>
#include <time.h>
#include "bmp.h"

// функциональные прототипы.
int resizeLarger(FILE* inptr, FILE* outptr, double factor);
int resizeSmaller(FILE* inptr, FILE* outptr, float factor);

int main(int argc, char* argv[])
{
    // Начинаем отсчет времени выполнения программы.
    clock_t startTime, endTime, totalTime;    
    startTime = clock();
    
    // Обеспечиваем правильное использование.
    if (argc != 4)
    {
        printf("Usage: copy factor[0-100] infile outfile\n");
        return 1;
    }
    
    // Запоминаем имена файлов.
    char* infile = argv[2];
    char* outfile = argv[3];
    
    // Если либо infile, либо outfile не заканчиваются на .bmp, отменить.
    if (strstr(infile, ".bmp") == 0)
    {
        printf("Infile does not have a .bmp extension. Aborting.\n");
        return 1;
    }
    else if (strstr(outfile, ".bmp") == 0)
    {
        printf("Outfile does not have a .bmp extension. Aborting.\n");
        return 1;
    }
    
    // Открываем входной файл.
    FILE* inptr = fopen(infile, "r");
    if (inptr == NULL)
    {
        printf("Could not open %s.\n", infile);
        return 2;
    }
    
    // Открываем выходной файл.
    FILE* outptr = fopen(outfile, "w");
    if (outptr == NULL)
    {
        fclose(inptr);
        fprintf(stderr, "Could not create %s.\n", outfile);
        return 3;
    }
    
    // Проверяем правильность ввода.
    if (!atof(argv[1]))
    {
        printf("The factor must be a float or integer.\n");
        return 4;
    }
    else if (atof(argv[1]) <= 0 || atof(argv[1]) >= 100)
    {
        printf("The factor must be of type float or integer and larger than zero and smaller or equal to 100. Aborting.\n");
        return 5;
    }
    
    // Коэффициент ввода для умножения размеров изображения на ...
    float factor = atof(argv[1]);
    
    // Начинаем изменять размер изображения в отдельной функции, назначенной для каждого типа изменения размера.
    if (factor >= 1.0)
    {
        if(resizeLarger(inptr, outptr, factor) != 0)
        {
            printf("An error occured during the resize process. Aborting.\n");
            return 6;
        }
    }
    else if (factor < 1.0)
    {
        if(resizeSmaller(inptr, outptr, factor) != 0)
        {
            printf("An error occured during the resize process. Aborting.\n");
            return 6;
        }
    }

    // Закрываем
    fclose(inptr);
    
    // Закрываем
    fclose(outptr);
    
    // Временное хранилище для файлов.
    struct stat st;
    
    // Рассчитываем размер байта для infile.
    stat(infile, &st);
    int originalSizeInBytes = st.st_size;
     
    // Рассчитываем размер выходного файла.
    stat(outfile, &st);
    int resizedSizeInBytes = st.st_size;
    
    // Конвертируем байты в мегабайты.
    int originalSizeInMB = (originalSizeInBytes / 1024) / 1024; 
    int resizedSizeInMB = (resizedSizeInBytes / 1024) / 1024;
    
    // Стоп счетчик времени работы.
    endTime = clock();

    // Рассчитываем окончательное время выполнения в секундах.
    totalTime = (double)(endTime - startTime) / CLOCKS_PER_SEC;
  
    // Проверяем, единственное или множественное число.
    char* secondCorrectVerb = (totalTime == 1) ? "second" : "seconds";   
    
    return 0;
}

/*
 *В этом блоке увеличиваем размеры исходного изображения с заданным коэффициентом от 1 до 100 и сохраняет его в другом файле.
  * @param inptr Указатель файла на исходное изображение, размер которого изменяется.
  * @param outptr Указатель файла на вновь созданное изображение с измененным размером.
  * @param factor Коэффициент ввода от 1 до 100, с которым изменяется исходное изображение.
  * @return resize Возвращает 0, если ошибок не было, или! 0, если ошибки произошли.
 */
int resizeLarger(FILE* inptr, FILE* outptr, double factor)
{
    // Перепроверяем входной коэффициент.
    if (factor < 1.00 || factor > 100.00)
    {
        printf("The resizeLarger() function only handles values larger than 0 and smaller than 1. Aborting.\n");
        return 9;
    }
    
    // Читаем входящий BITMAPFILEHEADER.
    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);
    
    // Читаем входящий BITMAPINFOHEADER.
    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);
    
    // Сохраняем оригинал BiHeight и originalBiWidth исходного изображения
    // мы можем использовать его позже.
    int originalBiHeight = bi.biHeight;
    int originalBiWidth = bi.biWidth;
    
    // Перезаписываем значения BITMAPINFOHEADER для biWidth и biHeight 
    bi.biWidth = ceil(originalBiWidth * factor); // new Width is original width of the infile times the input factor
    
    // Если originalBiHeight отрицателен, ceil округляет (например,) от -2,3 до -2, в то время как мы ожидаем -3, поэтому вместо этого мы полагаем:
    bi.biHeight = (originalBiHeight >= 0) ? ceil(originalBiHeight * factor) : floor(originalBiHeight * factor); // Новая высота является исходной высотой фактора.

    // Определяем отступы для строк.
    int originalPadding =  (4 - (originalBiWidth * sizeof(RGBTRIPLE)) % 4) % 4; // padding in the original file
    int padding =  (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4; // padding in the to be resized file
    
    // Рассчитываем biSizeImage для изображения с измененным размером. biHeight может быть отрицательным, поэтому с помощью abs () мы делаем его положительным.
    bi.biSizeImage = abs(bi.biHeight) * ((bi.biWidth * sizeof(RGBTRIPLE)) + padding); // width times the size of each RGBTRIPLE plus the padding at the end of line
    
    // Рассчитываем bfSize для изображения с измененным размером. Это равно biSizeImage одного и того же файла, плюс размер двух заголовков.
    bf.bfSize = bi.biSizeImage + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
     
    // Убеждаемся, что infile (вероятно) является 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 7;
    }
    
    // Записываем BITMAPFILEHEADER outfile, используя новые значения, которые мы присвоили ранее.
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);
    
    // Записываем BITMAPINFOHEADER outfile, используя новые значения, которые мы присваивали ранее.
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);
    
    // float, который хранит размер каждого вертикального шага.
    float fractionOfLinesHeight = factor;
    
    // float, который хранит размер каждого горизонтального шага.
    float fractionOfLinesWidth = factor;

    // Объявляем float, который хранит начальное вертикальное местоположение.
    float verticalLocation = 0.00;
    
    // int, который содержит начальное количество пропущенных вертикальных линий.
    int numOfVerticalLines = 0;
    
    // nt, который содержит начальный размер вертикального шага.
    int verticalStep = 0;
        
    // Проверяем по вертикали, пока я не совпаду с originalBiHeight of infile, деленным на размер каждого вертикального шага.
    // Используем abs (), потому что согласно спецификации MSDN высота может быть отрицательной.
    for (int i = 0, newBiHeight = ceil(abs(originalBiHeight) * factor); i < newBiHeight; i++)
    {         
        // Сохраняем ячейку текущего вертикального местоположения плюс размер каждого дополнительного шага (например, дробное_физическоеВыделение),
        // минус предыдущее установленное значение вертикального местоположения (которое установлено в предыдущей итерации).
        verticalStep = (ceil(verticalLocation + fractionOfLinesHeight) - ceil(verticalLocation));      
 
        // Объявляем число с плавающей точкой, в котором хранится начальное горизонтальное положение (сбрасывается до 0,00 при достижении следующей строки).
        float horizontalLocation = 0.00;
     
        // Повторяем по горизонтали до тех пор, пока j не совпадет с originalBiWidth значения infile, деленного на размер каждого горизонтального шага.
        for (int j = 0; j < originalBiWidth; j++)
        {
            // Сохраняем текущее значение текущего горизонтального расположения плюс размер каждого дополнительного шага(fractionOfLinesWidth),
            // минус предыдущий ceiled HorizontalLocation (который установлен в предыдущей итерации).
            int horizontalStep = (ceil(horizontalLocation + fractionOfLinesWidth) - ceil(horizontalLocation));           
            
            // Временное хранилище для каждого пикселя, состоящего из красного, зеленого и синего
            RGBTRIPLE triple;
            
            // Читаем тройной RGB из infile.
            fread(&triple, sizeof(RGBTRIPLE), 1, inptr);
            
            // Для каждого RGBTRIPLE в исходном файле записываем в горизонтальном шаге столько раз, сколько RGBTRIPLE.
            for (int k = 0; k < horizontalStep; k++)
                // Записываемписываем тройки RGB в файл.
                fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
           
            // Новое горизонтальное расположение равно текущему горизонтальному положению плюс размер каждой дополнительной горизонталиstep (fractionOfLinesWidth).
            horizontalLocation += fractionOfLinesWidth;
        }
        
        // Пропускаем отступы в исходном изображении, если есть.
        fseek(inptr, originalPadding, SEEK_CUR);
        
        // Добавляем отступ.
        for (int k = 0; k < padding; k++)
            fputc(0x00, outptr);   
        
        // Размер каждой строки в байтах.
        int originalLineSizeInBytes = ((originalBiWidth * sizeof(RGBTRIPLE)) + originalPadding);
        
        if (numOfVerticalLines <= verticalStep)
        {
            // Пропускаем одну строку назад.
            fseek(inptr, -originalLineSizeInBytes, SEEK_CUR);
            
            // Увеличиваем количество дублирующихся вертикальных линий.
            numOfVerticalLines++;
        }

        // Если число вертикальных линий, которые необходимо дублировать, сделано, сбрасываем.
        if (numOfVerticalLines == verticalStep)
        {
            numOfVerticalLines = 0;
            fseek(inptr, originalLineSizeInBytes, SEEK_CUR);
        }
        
        // Новое вертикальное расположение равно текущему вертикальному местоположению плюс размер каждого дополнительного вертикального шага(fractionOfLinesHeight).
        if (numOfVerticalLines == 0)
            verticalLocation += fractionOfLinesHeight;
    }
    
    return 0;
}

/*
 *  Тперь уменьшаем размер исходного изображения с заданным коэффициентом больше 0 и меньше 1 и сохраняем его в другом файле.
  * @param inptr Указатель файла на исходное изображение, размер которого изменяется.
  * @param outptr Указатель файла на вновь созданное изображение с измененным размером.
  * @param factor Коэффициент ввода, с которым изменяется исходное изображение.
  * @return resize Возвращает 0, если ошибок не было, или! 0, если ошибки произошли.
 */
int resizeSmaller(FILE* inptr, FILE* outptr, float factor)
{   
    // Дважды проверяем, что мы можем работать с этим фактором.
    if (factor <= 0.00 || factor >= 1.00)
    {
        printf("The resizeSmaller() function only handles values larger than 0 and smaller than 1. Aborting.\n");
        return 10;
    }
    
    // Читаем входящий BITMAPFILEHEADER
    BITMAPFILEHEADER bf;
    fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);
    
    // Читаем входящий BITMAPINFOHEADER
    BITMAPINFOHEADER bi;
    fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);
    
    // Сохраняем исходный BiHeight и OriginalBiWidth исходного изображения.
    // мы можем использовать его позже.
    int originalBiHeight = bi.biHeight;
    int originalBiWidth = bi.biWidth;
    
    // Перезаписываем значения BITMAPINFOHEADER для biWidth и biHeight.  
    bi.biWidth = ceil(originalBiWidth * factor); // Новая ширина - исходная ширина входного файла, умноженная на коэффициент ввода.
    // Если originalBiHeight отрицателен, ceil округляет (например,) от -2,3 до -2, в то время как мы ожидаем -3, поэтому вместо этого мы полагаем:
    bi.biHeight = (originalBiHeight >= 0) ? ceil(originalBiHeight * factor) : floor(originalBiHeight * factor); // new Height is original height of original times the factor

    // Определяем отступы для строк.
    int originalPadding =  (4 - (originalBiWidth * sizeof(RGBTRIPLE)) % 4) % 4; // отступ в исходном файле.
    int padding =  (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4; // заполнение в файле, который будет изменен.
    
    // Рассчитываем biSizeImage для изображения с измененным размером. biHeight может быть отрицательным, поэтому с помощью abs () мы делаем его положительным.
    bi.biSizeImage = abs(bi.biHeight) * ((bi.biWidth * sizeof(RGBTRIPLE)) + padding); // ширина умножается на размер каждого RGBTRIPLE плюс отступ в конце строки.
    
    // Рассчитываем bfSize для изображения с измененным размером. Это равно biSizeImage одного и того же файла, плюс размер двух заголовков.
    bf.bfSize = bi.biSizeImage + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

    // Убеждаемся, что infile (вероятно) является 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, "Usage: ./resize f infile outfile\n");
        return 7;
    }
    
    // Записываем BITMAPFILEHEADER outfile, используя новые значения, которые мы присвоили ранее
    fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr).
    
    // Записываем BITMAPINFOHEADER outfile, используя новые значения, которые мы присваивали ранее
    fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr).

    // Итерация по каждой горизонтальной строке файла (оригинальный файл).
    for (int j = 0, originalBiHeightWithAbs = abs(originalBiHeight); j < originalBiHeightWithAbs; j++)
    {
        // Если полученное произведение числа итераций и фактора не равно следующей итерации
        // продукт тех же факторов, вертикальная линия должна быть включена в файл.
        if ( ceil(j * factor) != ceil((j+1) * factor) )
        {     
            // Перебираем каждый пиксель по ширине файла (оригинальный файл).    
            for (int i = 0; i < originalBiWidth; i++)
            {   
                // То же, что и выше, но теперь с учетом горизонтального числа итераций.
                if (ceil(i * factor) != ceil((i+1) * factor))
                {
                    // Временное хранилище для трехместных.
                    RGBTRIPLE triple;
                    
                    // Читаем один пиксель (также известный как 1 RGBTRIPLE) из входящего файла.
                    fread(&triple, sizeof(RGBTRIPLE), 1, inptr);
                    
                    // Записываем один пиксель в файл.
                    fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
                }
                else if (ceil(i * factor) == ceil((i+1) * factor))
                {
                    // Пропускаем более 1 пикселя.
                    fseek(inptr, sizeof(RGBTRIPLE), SEEK_CUR);
                }
            }
            
            // Пропускаем заполнение в infile.
            fseek(inptr, originalPadding, SEEK_CUR);
            
            // Добавляем его обратно в файл.
            for (int k = 0; k < padding; k++)
                fputc(0x00, outptr); // each byte of padding consists of 2 zero's, in hex
        }
        else if (ceil(j * factor) == ceil((j+1) * factor))
        {
            // Пропускаем 1 горизонтальную линию.
            // Размер строки в байтах является произведением количества пикселей на размер каждого пикселя (3 или размер RGBTRIPLE)
            // Добавляем отступы, если они есть в исходном файле.
            fseek(inptr, (sizeof(RGBTRIPLE) * abs(originalBiWidth) + originalPadding), SEEK_CUR);
        }
    }
    return 0;
}
Проверка работы программы:

 
$ ./resize  
Usage: ./resize f infile outfile  
$ echo $?  
1 

$ ./resize .5 large.bmp smaller.bmp  
$ echo $?  
0 

$  ./resize 2 small.bmp larger.bmp  
$ echo $?  
0

Recover

Как восстановить случайно удаленные jpeg файлы с карты памяти?

К счастью, в компьютерном мире, «удален», как правило, означает «забытый». Несмотря на то, что камера настаивает на том, что карта памяти теперь пуста, мы уверены, что это не совсем правда.

Несмотря на то, что формат JPEG сложнее BMP, JPEG имеет «подписи», последовательности байтов, которые отличают их от других форматов файлов. В частности, первые три байта JPEG-файлов выглядят вот так (от первого байта к третьему слева направо):

0xff  0xd8 0xff

Четвертый байт равен одном из байтов в списке: 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, или  0xef. Иначе говоря, первые четыре бита четвертого байта равны 1110.

Скорее всего, если вы найдете одну из этих последовательностей на диске, на котором хранятся фотографии (например, карта памяти), они покажут начало JPEG-файла. Конечно, вы можете столкнуться с этим на каком диске случайно, поэтому восстановление данных не является точной наукой.

К счастью, цифровые камеры, как правило, сохраняют фотографии на картах памяти непрерывно одна за другой, в результате чего каждая фотография сохраняется сразу же после того фото, которое было сохранено ранее. Соответственно, начало JPEG-файла обычно идет сразу за концом другого. Однако, обычно карты памяти отформатированы в файловую систему FAT, «размер блока» которой составляет 512 байтов (B).

Поэтому камеры записывают данные на эти карты в единицах по 512 B. Если фото имеет размер 1 МБ (то есть 1048576 B), то по этому принципу оно занимает 1048576 ÷ 512 = 2048 «блоков» на карте. Такое же количество блоков займет файл размером в один байт меньше (т.е. 1048575 B)! Незанятое пространство на диске называется «остаточным пространством». Судебные следователи часто используют эту особенность для нахождения остатков подозрительных данных.

Следствием всех этих деталей является то, что вы можете написать программу, которая будет проходить по копии моей карты памяти, ища подписи (сигнатуры) JPEG-файлов. Каждый раз, когда вы находите эту сигнатуру, вы можете открыть новый файл для записи и начать заполнять его байтами с карты памяти, закрывая этот файл тогда, когда найдется следующий сигнатура.

Более того, вместо того, чтобы читать по одному байту за раз, вы можете прочитать в буфер 512 байт за раз для эффективности. Благодаря файловой системе FAT вы с уверенностью можете знать, что сигнатуры JPEG-файлов будут выровненными по блокам. То есть, вам нужно искать эти подписи только в четырех первых байтах блока.

Для эксперимента предлагается скачать копию «стертой» карты памяти и попытаться ее восстановить.

Спецификация

Требования к коду:

      • Реализуйте программу recover, которая восстанавливает JPEG-изображения со «стертой карты памяти».
      • Реализуйте вашу программу в файле recover.c, что должно находиться в папке recover.
      • Программа должна принимать ровно один аргумент командной строки — название файла, из которого нужно восстановить изображение. Если это условие не выполняется, программа должна напомнить пользователю о том, как ее корректно использовать с помощью fprintf (до stderr), а main должен вернуть 1.
      • Если файл card.raw (или любой другой ваш) не может быть открыт, программа должна сообщить об этом пользователю с помощью fprintf (до stderr), а main должен вернуть 2.

Если программа использует malloc, она не должна иметь утечек памяти.

Вот вариант реализации кода:

#include <stdio.h>
#include <stdbool.h>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        fprintf(stderr, "Please enter file to open.\n");
        return 1;
    }

    FILE *file = fopen(argv[1], "r");

    if (file == NULL)
    {
        fprintf(stderr, "Could not open %s.\n", argv[1]);
        return 1;
    }

    FILE *img = NULL;

    unsigned char buffer[512];
    char filename[8];

    int counter = 0;

    bool flag = false;

    while (fread(buffer, 512, 1, file) == 1)
    {
        if (buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff && (buffer[3] & 0xf0) == 0xe0)
        {
            if (flag == true)
            {
                fclose(img);
            }
            else
            {
                flag = true;
            }

            sprintf(filename, "%03i.jpg", counter);
            img = fopen(filename, "w");
            counter++;
        }

        if (flag == true)
        {
            fwrite(&buffer, 512, 1, img);
        }
    }
    fclose(file);
    fclose(img);

    return 0;
}

 

При правильной реализации при выполнении команды

$ ./recover card.raw
$ echo $?
0

в папке с проектом будут записаны все обнаруженные файлы.

Читайте больше по теме:

Подписаться
Уведомление о
guest
0 комментариев
Inline Feedbacks
View all comments
Просмотры: 2279

Популярные записи