Gerar arquivo ppm

Eu tenho o seguinte código pra gerar arquivos ppm, mas não consegui entender a lógica por trás do mesmo, como é que ele tá pintando as cores ? Quem me responder eu agradeço !

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

int main(void)
{
    const int dimx = 800, dimy = 600;//Dimensão da matriz
    int i, j;
    
    FILE *fp = fopen("first.ppm", "wb"); /* b - modo binário */
    
    //Descrição do arquivo
    fprintf(fp, "P6\n%d %d\n255\n", dimx, dimy);
    //Varre a matriz em coloca uma cor na posição
    for (j = 0; j < dimy; ++j)
    {
        for (i = 0; i < dimx; ++i)
        {
            //tente trocar o unsigned char por outros tipos
            //Verifique o que significa unsigned char
            static unsigned char color[3];
            color[0] = i+2*j % 256;  /* red */
            color[1] = i-j % 256;  /* green */
            color[2] = (i+j) % 256;  /* blue */
            fwrite(color, 1, 3, fp);//Escreve no arquivo a cor
        }
    }
    fclose(fp);//Salva o arquivo
    return EXIT_SUCCESS;
}

Você quer entender essa lógica das cores (tipo color[2] = (i + j) % 256) ou como esses números viram cores quando você abre o arquivo .ppm?

De qualquer forma, vou responder os 2 e você lê o que interessar.

O vetor unsigned char color tem espaço para 3 dados de 1 byte de 8 bits. Em cada espaço, cabem 256 números (2 ^ 8), que no caso são de 0 a 255 por causa do unsigned. Se não fosse unsigned, ia caber de -128 até 127. Como você quer representar cores RGB de 0 a 255, o unsigned cai como uma luva (e isso não é à toa).

A imagem PPM que você está criando tem 3 canais (RGB). O arquivo funciona assim:

valorR valorG valorB valorR valorG ValorB...

Cada conjunto de 3 valores separados espaços representam um pixel, são os 3 canais de cores. Essa linha mostra isso:

fwrite(color, 1, 3, fp);//Escreve no arquivo a cor

A assinatura da função fwrite é a seguinte:

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

Os argumentos para a escrita são:

  1. A fonte dos dados. O que você quer escrever na stream (que nesse caso é o arquivo);
  2. O tamanho de cada dado nessa fonte. Como você está usando unsigned char, o argumento é 1, pois esse é o tamanho do unsigned char (1 byte de 8 bits). Eu acho “mais certo” colocar sizeof(unsigned char) ao invés de 1;
  3. A quantidade de dados que devem ser escritos na stream. O total de bytes à escrever é calculado por tamanho do dado * quantidade, é assim que a função fwrite sabe quantos bytes escrever;
  4. A stream.

É basicamente isso, quanto à escrita. Para cada pixel da imagem (por isso os 2 fors), são gerados 3 valores (um para cada canal, RGB) e escritos no arquivo. É só isso, a imagem não é “pintada” no arquivo. São só números.

Quando você vai abrir essa imagem, o programa que a abre é responsável por interpreta-la. Por causa daqueles meta-dados que você escreve no começo do arquivo (P6, as dimensões e 255), o programa é capaz de percorrer o arquivo, guardando os números em uma estrutura em memória, representando a imagem. Em seguida, o programa pode utilizar uma biblioteca de gráficos para mostrar a imagem na tela, que nada mais é do que pegar as cores informadas pelo arquivo e acender cada pixel do monitor com as intensidades certas.