Как пикселизировать двоичный (P6) файл PPM

Я изо всех сил пытаюсь пикселизировать изображение, состоящее из значений RGB, хранящихся в двоичном (P6) файле PPM. Шаги по пикселизации изображения следующие:

  1. Считайте двоичные данные и сохраните их в одномерном массиве.
  2. Перебирать данные, хранящиеся в этом массиве, в ячейках размера «c» (строка x столбцов). Эта переменная 'c' может быть изменена пользователем, но для этой программы в настоящее время установлено значение int c = 4;, что означает, что она выполняет итерацию по пиксельным данным в блоках размером 4 x 4.
  3. Теперь найдите среднее значение цвета в каждой из этих ячеек размером 'c'.
  4. Выведите каждое среднее значение цвета в новый выходной файл PPM

Каждый двоичный файл PPM начинается с заголовка в следующем формате, состоящего из «магического числа», за которым следуют ширина, высота и, наконец, максимальное значение цвета 255. Комментарии заголовка игнорируются. Пример заголовка показывает изображение PPM формата P6 (следовательно, это двоичный файл), шириной 16, высотой 16 и максимальным значением цвета 255:

P6
16
16
255 

Где я борюсь:

  1. Я не уверен, как найти среднее значение RGB для каждой ячейки и затем вывести его в новый поток данных. Я нашел способ сделать это при линейном чтении данных (т.е. не считывая их в массив (или буфер)), разделив общее количество цветов в этой ячейке на количество итераций цикла, но это оказалось неверным.
  2. Упорядочены ли вложенные циклы for таким образом, чтобы изменить ориентацию выходного изображения, например. он поворачивает его на 180 градусов и т. д.? Я изо всех сил пытаюсь определить это при чтении необработанных двоичных данных.

Моя попытка:

#define _CRT_SECURE_NO_WARNINGS                 //preprocessor requirement 

#include <stdio.h>                              //library for I/O functions 
#include <stdlib.h>                             //library for general functions 

#define magic_size 10                           //macro for PPM character found within header

typedef struct {
    int t_r, t_g, t_b;                          //Struct to hold  RGB pixel data
} Pixel;

int main()
{
    char magic_number[magic_size];              //variable for PPM format
    int width = 0, height = 0, max_col = 0;     //imagine dimensions
    int c = 4;                                  //mosaic parameter

    /* INPUT FILE HANDLING */
    FILE *inputFile; 
    inputFile = fopen("Sheffield512x512.ppm", "r");
    //input file error handling
    if (inputFile == NULL)
    {
        printf(stderr, "ERROR: file cannot be opened");
        getchar();  //prevent cmd premature closure
        exit(1);    //exit program cleanly
    }

    /* OUTPUT FILE HANDLING */
    FILE *outputFile; 
    outputFile = fopen("mosaic.ppm", "w");
    //output file error handling
    if (outputFile == NULL)
    {
        printf(stderr, "ERROR: cannot write to file");
        getchar();  //prevent cmd premature closure
        exit(1);    //exit program cleanly
    }

    // Scan the header (these variables are used later on)
    fscanf(inputFile, "%s\n%d\n%d\n%d", &magic_number, &width, &height, &max_col);

    // Error handling. Program only supports binary files (i.e. of P6 format) 
    if (magic_number[1] != '6')
    {
        printf("Only Binary images supported!\n");
        getchar();  //prevent cmd premature closure
        return;
    }

    // Raw 1 dimensional store of pixel data
    Pixel *data = malloc(width*height * sizeof(Pixel));

    //2D index to access pixel data
    Pixel **pixels = malloc(height * sizeof(Pixel*));

    // Read the binary file data 
    size_t r = fread(data, width*height, sizeof(unsigned char), inputFile);

    // Build a 1-dimensional index for the binary data 
    for (unsigned int i = 0; i < height; ++i)
    {
        pixels[i] = data + (i * width); 
    }

    // Close the input file 
    fclose(inputFile);


    /* BEGIN PIXELATION PROCESS */

    // Print the OUTPUT file header 
    fprintf(outputFile, "%s\n%d\n%d\n%d", magic_number, width, height, max_col);

    //loop condition variables 
    int cw_x = ceil((double)(width / (float)c));
    int cw_y = ceil((double)(height / (float)c));

    //iterate through 2d array in cells of size c 
    for (int c_x = 0; c_x < cw_x; c_x += 1)
    {
        for (int c_y = 0; c_y < cw_y; c_y += 1)
        {

            //iterate within the cells
            for (int _x = 0; _x < c; _x++)
            {
                int x = c_x * c + _x;

                //bounds checking
                if (x < width)
                {
                    for (int _y = 0; _y < c; _y++)
                    {
                        int y = c_y * c + _y;

                        //bounds checking 
                        if (y < height)
                        {
                            //write data to the output FILE stream 
                            fwrite(data, width*height, sizeof(unsigned char), outputFile);
                        }
                    }
                }

            }
        }
    }

    //close the output file
    fclose(outputFile);

    return 0; 
}

person p.luck    schedule 11.05.2019    source источник
comment
Обратите внимание, что с fread(data, width*height, sizeof(unsigned char), ..) вы не читаете достаточно, потому что вы должны читать значения RGB, которые являются вашими Pixels.   -  person Paul Ogilvie    schedule 11.05.2019
comment
Обратите внимание, что sizeof(Pixel) может быть больше, чем RGB, потому что компилятор может дополнить структуру. Перед объявлением Pixel используйте #pragma pack (push n) с n размером целых чисел в файле / формате, а затем #pragma pop.   -  person Paul Ogilvie    schedule 11.05.2019
comment
Вы не используете pixels. Убери это.   -  person Paul Ogilvie    schedule 11.05.2019
comment
fwrite(data, width*height, .., конечно, всегда будет записывать первые байты изображения.   -  person Paul Ogilvie    schedule 11.05.2019
comment
@PaulOgilvie Спасибо, что выделили их. Если у вас есть время, не могли бы вы изменить мой код, чтобы он соответствовал пунктам, которые вы делаете в своих комментариях, пожалуйста, в форме ответа? Я был бы очень признателен за помощь.   -  person p.luck    schedule 11.05.2019
comment
Просто для удовольствия ... stackoverflow.com/a/47153407/2836621   -  person Mark Setchell    schedule 12.05.2019


Ответы (1)


В комментариях я дал вам отзывы об ошибках в вашем коде. Вы можете исправить это сами. Возьмите отладчик, чтобы протестировать / проверить все эти предварительные шаги. Например, прочтите файл и немедленно запишите его (и отобразите изображение), чтобы вы знали, что чтение в порядке.

Ваша основная проблема и вопрос - о петле.

По сути, у вас есть одномерный массив, который состоит из строки развертки после строки развертки, и каждая строка развертки содержит пиксели. В отличие от формата BMP, ваш формат, похоже, не использует байты заполнения для выравнивания строк развертки по границам слов. Это немного упрощает задачу.

Пиксель состоит из трех значений цвета: R, G и B, и я предполагаю, что каждое значение цвета - это один байт (беззнаковый символ). Распределение памяти и чтение затем становятся:

unsigned char *data = malloc(width*height*3);
r = fread(data, width*height*3, 1, inputFile);

Цикл теперь проходит по всем строкам с шагом по четыре и обрабатывает каждый пиксель с шагом по четыре. Таким образом, он обрабатывает один квадрат за раз, вычисляет среднее значение и записывает его:

    c= 4;
    for (y=0; y<height; y += c)
    {
        for (x=0; x<width; x += c)
        {
            unsigned int avgR=0, avgG=0, avgB= 0;

            for (dy=0; dy<c && y+dy<height; dy++)
            {
                for (dx=0; dx<c && x+dx<width; dx++)
                {
                    avgR += data[  y*width*3    // line in image
                                 + x*3          // pixel on line
                                 + dy*width*3   // line of square
                                 + dx*3         // R pixel in line of square
                                 ];
                    avgG += data[  y*width*3    // line in image
                                 + x*3          // pixel on line
                                 + dy*width*3   // line of square
                                 + dx*3 + 1     // G pixel in line of square
                                 ];
                    avgB += data[  y*width*3    // line in image
                                 + x*3          // pixel on line
                                 + dy*width*3   // line of square
                                 + dx*3 + 2     // B pixel in line of square
                                 ];
                }
            }
            unsigned char avgRb= avgR/(dx*dy);
            unsigned char avgGb= avgG/(dx*dy);
            unsigned char avgBb= avgB/(dx*dy);
            fwrite(&avgR,1,1,outputFile);
            fwrite(&avgG,1,1,outputFile);
            fwrite(&avgB,1,1,outputFile);
        }
    }

Это можно оптимизировать с помощью арифметики указателей, но это показывает основы необходимого цикла.

Примечания:

  • ..&& y+dy<height проверяет регистр границы, когда последний квадрат не соответствует высоте. То же по ширине.

  • как следствие, среднее значение рассчитывается путем деления на (dx*dy).

Заявление об ограничении ответственности

Я не мог проверить это, поэтому алгоритм - это ментальная конструкция.

person Paul Ogilvie    schedule 11.05.2019