Leer archivo PPM en C++

Abdul Mateen 15 febrero 2024
  1. Archivo PPM
  2. Leer archivo PPM en C++
Leer archivo PPM en C++

En este tutorial, aprenderemos sobre los archivos PPM y cómo leerlos usando C++.

Primero discutiremos y comprenderemos el formato de archivo PPM. Más adelante, aprenderemos paso a paso el procedimiento para leer un archivo PPM en C++.

Archivo PPM

Cada archivo de imagen tiene un encabezado/metadatos adjuntos a los datos de la imagen. El encabezado contiene información diferente sobre la imagen y el archivo.

Al comienzo de cada archivo de imagen, existe una identificación del formato de archivo. Por ejemplo, BM se escribe al principio de cada archivo BMP.

El resto del encabezado contiene información como ancho, alto, número de colores, tipo de imagen (binario, escala de grises, 16 colores, 256 colores, colores de 24 bits), compresión, desplazamiento, etc.

El formato Portable Pixel Map (PPM) es una imagen en color con un formato relativamente simple. Hay dos variantes de PPM: una es P3, y la otra es P6.

P3 es formato ASCII, mientras que P6 es un formato binario; el formato ASCII suele ocupar más espacio que el formato binario. La mayoría de las imágenes tienen muchos datos; por lo tanto, el espacio es crucial.

Por ejemplo, una imagen pequeña de 100x100 tiene 10000 píxeles. En el caso de una imagen en color de 24 bits, cada píxel ocupa 3 bytes; por lo tanto, se requerirán 30000 bytes para almacenar la imagen.

En este tutorial, discutiremos el formato P6. Es relativamente más fácil de leer y comprender P3; sin embargo, le resultará más fácil después de aprender el formato P6.

P6 es una imagen en color de 24 bits. El encabezado de P6 es relativamente más sencillo; puede compararlo con el formato BMP.

El formato de archivo PPM tiene tres líneas de encabezado, que ocupan 15 bytes de espacio. La primera línea tiene un identificador/firma del PPM (ya sea P3 o P6).

La segunda línea tiene información sobre el ancho y el espacio de la imagen (separados por espacios), y la tercera línea tiene información sobre el valor de color máximo (por ejemplo, 15 o 255).

Por ejemplo, mira la siguiente imagen:

Archivo PPM de imagen de casa

Veamos la metainformación de la imagen de la casa (puede que no creas que los datos pertenecen a esta imagen).

P6
111 132
255
U`6Xe8Xk8Ul9Tg:Ve<Wd7Wd5Td5N_0MY/NZ3P^5Ub5Wc4T`2R`4T`4[d6Yd7NY1CM,@J-FQ/O\2Vg8Ra5FW.?O+@M,BQ/:F-0:**5)*1(6@-CQ5=H1;H-

Como se discutió anteriormente, las primeras tres líneas tienen información de encabezado. P6 en la primera línea identifica el tipo de imagen.

Los programadores pueden comprobar el identificador y leer la imagen según el formato.

La segunda línea describe el ancho como 111 y la altura como 132. La tercera línea indica 255 como valor de color máximo.

A partir de la cuarta línea tenemos los datos de la imagen almacenados en formato binario (presentados en pantalla en forma de caracteres, algunos de ellos reconocibles pero no todos).

Leer archivo PPM en C++

Para leer un archivo PPM, en primer lugar, debemos leer su encabezado para obtener información sobre el ancho y el alto. Esto es importante ya que debemos declarar la memoria dinámica en consecuencia y leer los datos de la imagen.

P6 es un formato binario; por lo tanto, tenemos que abrir la imagen en formato binario y hacer la lectura correspondiente:

FILE *read;
read = fopen("west_1.ppm", "rb");

En la primera línea, el puntero ARCHIVO se declara para almacenar un controlador del archivo en la segunda línea. Tenga en cuenta que el segundo parámetro en la función fopen es rb, donde r es para lectura y b es para binario (es decir, modo de lectura binario para el archivo).

Si no se siente cómodo con el almacenamiento binario, lea este artículo sobre archivos binarios. Una vez que haya abierto el archivo, debe leer el encabezado del archivo.

Aquí está el código para eso:

unsigned char header[15];
fread(header, 15, 1, read);

Puede encontrar más información sobre fread aquí.

Una vez que el encabezado se lee con éxito, lo siguiente es extraer la información de ancho y alto para continuar. Como esta información se almacena en forma de caracteres, podemos leer la información de los caracteres uno por uno y convertirla en un valor entero usando el siguiente código (puede usar la función atoi alternativamente):

int x = 0;
for (pos = 3; header[pos] != '\n' && header[pos] != ' '; pos++)
  x = x * 10 + (header[pos] - '0');

Como se discutió anteriormente, los dos primeros bytes del encabezado contienen un valor P6, y el tercero es el separador de la siguiente línea. Por lo tanto, comenzaremos desde el 4º elemento, que comienza desde el índice 3.

Leemos carácter por carácter para completar un valor entero (separado por un carácter de espacio o carácter de avance de línea). Para cada carácter, tenemos que restar el valor ASCII de 0 porque el valor ASCII de los dígitos comienza desde 48 o 32 hexadecimal (valor ASCII de 0).

Por ejemplo, el valor ASCII de 6 es 54 en decimal; por lo tanto, si restamos 48 de 54, obtendremos 6. Usando este hecho, podemos obtener un valor entero de una matriz de caracteres.

Vea un pequeño ejemplo para convertir una matriz de caracteres 135 en un entero equivalente.

entero x = 0                              // valor entero inicial
    x = x* 10 + 1 = 1 x = x* 10 + 3 = 13  // descubre 10s por
    x = x* 10 + 5 = 135  // descubre 100 y x tendrá un valor entero final

Después de obtener la información de ancho y alto, declararemos una matriz dinámica de tamaño “ancho x alto x 3” ya que hay píxeles de “ancho x alto” en la imagen, y cada imagen tiene 3 bytes (rojo, verde y azul, conocido como RGB).

unsigned char *image;
image = new unsigned char[width * height * 3];

Usamos caracteres sin signo en lugar de caracteres porque, en el tipo de caracteres simples, el primer bit se usa para el signo (0 para un número positivo y 1 para un número negativo).

En el caso de char, el rango máximo de valores positivos es de 0 a 127, mientras que tenemos que leer información de 0 a 255. Por lo tanto, necesitamos un char sin firmar.

El paso final es leer la imagen, que es bastante simple. Simplemente llame a la función fread como:

fread(image, size, 1, file);  // where size is width x height x 3

Nuevamente, el primer parámetro es una matriz de caracteres para almacenar datos de imagen, y el tamaño ya está bien explicado.

La variable imagen tiene todos los píxeles: valores rojo, verde y azul. Podemos declarar tres matrices separadas de tamaño “ancho x alto”, o podemos manejar las operaciones recordando que el primer valor es rojo, el segundo es verde y el tercero es azul.

Por ejemplo, si queremos eliminar el componente rojo de la imagen, debemos asignar 0 en el primer índice de todos los píxeles.

Podemos hacer eso de la siguiente manera:

for (i = 0; i < size; i = i + 3) {
  image[i] = 0;

Tenga en cuenta el paso de incremento en el bucle for, donde hemos aumentado i con 3 en lugar de 1. Es porque los valores segundo y tercero son para verde y azul, y no queremos cambiar los valores de verde y azul.

Vea la imagen de ejemplo a continuación para ver la diferencia después de eliminar un componente rojo de la imagen. Tenemos la imagen original a la izquierda, y la imagen modificada sin el componente rojo está a la derecha.

Original VS Sin Componente Rojo

Esperamos que tenga la idea completa sobre la lectura de archivos PPM. Al final, veamos un programa completo en C++ para leer, modificar y escribir un archivo PPM:

#include <cstdio>
#include <iostream>

using namespace std;

void readPPMHeader(FILE *file, unsigned char *header) {
  fread(header, 15, 1, file);
}
void readPPMImage(FILE *file, unsigned char *image, int size) {
  fread(image, size, 1, file);
}
void writePPM(FILE *file, unsigned char *header, unsigned char *image,
              int size) {
  fwrite(header, 15, 1, file);   // writing header information
  fwrite(image, size, 1, file);  // writing image information
}
void removeRed(unsigned char *image, unsigned char *withoutredimage, int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutredimage[i] = 0;  // red component is set to 0
    withoutredimage[i + 1] = image[i + 1];
    withoutredimage[i + 2] = image[i + 2];
  }
}
void removeGreen(unsigned char *image, unsigned char *withoutgreenimage,
                 int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutgreenimage[i] = image[i];
    withoutgreenimage[i + 1] = 0;  // green component is set to 0
    withoutgreenimage[i + 2] = image[i + 2];
  }
}
void removeBlue(unsigned char *image, unsigned char *withoutblueimage,
                int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutblueimage[i] = image[i];
    withoutblueimage[i + 1] = image[i + 1];
    withoutblueimage[i + 2] = 0;  // blue component is set to 0
  }
}
// To extract width & height from header
int getDimension(unsigned char *header, int &pos) {
  int dim = 0;
  for (; header[pos] != '\n' && header[pos] != ' '; pos++)
    dim = dim * 10 + (header[pos] - '0');
  return dim;
}
int main() {
  FILE *read, *write1, *write2, *write3;
  read = fopen("west_1.ppm", "rb");
  unsigned char header[15], *image;
  readPPMHeader(read, header);
  if (header[0] != 'P' || header[1] != '6') {
    cout << "Wrong file format\n";
    return 0;
  }
  write1 = fopen("west_1_without_red.ppm", "wb");
  write2 = fopen("west_1_without_green.ppm", "wb");
  write3 = fopen("west_1_without_blue.ppm", "wb");
  int width, height, clrs, pos = 3;
  width = getDimension(header, pos);
  pos++;
  height = getDimension(header, pos);
  cout << "Width:" << width << "\tHeight:" << height << '\n';
  image = new unsigned char[width * height * 3];
  unsigned char *withoutredimage, *withoutgreenimage, *withoutblueimage;
  withoutredimage = new unsigned char[width * height * 3];
  withoutgreenimage = new unsigned char[width * height * 3];
  withoutblueimage = new unsigned char[width * height * 3];
  readPPMImage(read, image, width * height * 3);
  removeRed(image, withoutredimage, width * height * 3);
  writePPM(write1, header, withoutredimage, width * height * 3);
  removeGreen(image, withoutgreenimage, width * height * 3);
  writePPM(write2, header, withoutgreenimage, width * height * 3);
  removeBlue(image, withoutblueimage, width * height * 3);
  writePPM(write3, header, withoutblueimage, width * height * 3);
  fclose(read);
  fclose(write1);
  fclose(write2);
  fclose(write3);
  return 0;
}

En este programa, leemos un archivo PPM y escribimos tres archivos PPM después de la modificación.

Veamos las imágenes de salida:

Sin Rojo VS Sin Verde VS Sin Azul

El color rojo se elimina en la imagen más a la izquierda. El del medio no tiene verde y el tercero no tiene componente azul.