Nesse artigo, iremos explanar sobre uma classe C++ que tem por objetivo abrir um arquivo PNG, armazenando a imagem na memória para podermos manipulá-la, salvando o resultando em um novo arquivo PNG.
O ponto de partida para nossa classe é bem básico: temos alguns atributos (que armazenarão os dados lidos do arquivo) e dois métodos – read_png_file e write_png_file. A partir desse modelo básico, podemos ampliar nossa classe adicionando métodos para manipulação da imagem, ou construtores para podermos gerar um arquivo PNG a partir de fontes diversas, como um array de pontos gerados pelo computador.
A estrutura básica de nossa classe seria a seguinte:
class PNG { private: int width, height, rowbytes; png_byte color_type; png_byte bit_depth; png_structp png_ptr; png_infop info_ptr; int number_of_passes; png_bytep * row_pointers; public: void write_png_file (char * filename); int read_png_file (char * filename); };
Os atributos da classe tem as funções que seus nomes descrevem: width e height armazenam a largura e altura da imagem, respectivamente; rowbytes armazena o tamanho da linha, que é a largura multiplicada pelo numero de bytes por pixel (que varia de imagem para imagem, e é armazenado em bit_depth); color_type, png_str, info_ptr e number_of_passes são estruturas de dados que armazenam dados especificos do formato PNG (esses dados não são necessários para o propósito desse arqtigo, mas você pode se aprofundar sobre o assunto se deseja manipulações mais complexas); finalmente, row_pointers é uma matriz que armazena os pixels da imagem (apenar da declaração do atributo indicar ser um vetor de png_bytep, esse tipo é um alias para para unsigned char*, o que torna esse tipo uma matriz de valores unsigned char).
A função read_png_image recebe como parâmetro o nome de um arquivo PNG, e lê os dados desse arquivo para armazená-los nos atributos da classe. Isso em feito da seguinte forma:
1º passo:
Primeiro, precisamos abrir o arquivo que iremos ler e associá-lo a um stream de dados de onde serão lidos os dados:
/* open file and test for it being a png */ FILE *fp = fopen(file_name, "rb"); if (!fp) abort_("[read_png_file] File %s could not be opened for reading", file_name); fread(header, 1, 8, fp); if (png_sig_cmp(header, 0, 8)) abort_("[read_png_file] File %s is not recognized as a PNG file", file_name);
2º passo:
Em seguida, inicializaremos as estruturas de dados especificas do formato PNG – png_str e info_ptr – que foram declaradas como atributos da classe; essas estruturas guardarão dados necessários à manipulação da imagem e leitura dos dados restantes da imagem:
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) abort_("[read_png_file] png_create_read_struct failed"); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) abort_("[read_png_file] png_create_info_struct failed"); if (setjmp(png_jmpbuf(png_ptr))) abort_("[read_png_file] Error during init_io"); png_init_io(png_ptr, fp); png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr);
3º passo:
Após termos inicializado as estruturas, agora iremos ler do arquivo os demais dados da imagem, como por exemplo a largura e a altura, dentre outros:
width = png_get_image_width(png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); number_of_passes = png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); /* read file */ if (setjmp(png_jmpbuf(png_ptr))) abort_("[read_png_file] Error during read_image");
4º passo:
Iremos agora alocar espaço na memória para row_pointers e ler do arquivo os dados relativos aos pixels da imagem. Isso é feito da seguinte forma:
row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height); if (bit_depth == 16) rowbytes = width*8; else rowbytes = width*4; for (y=0; y<height; y++) row_pointers[y] = (png_byte*) malloc(rowbytes); png_read_image(png_ptr, row_pointers); fclose(fp);
A função write_png_image recebe como parâmetro o nome de um arquivo PNG, e salva os dados armazenados nos atributos da classe em um arquivo. Isso em feito da seguinte forma:
1º passo:
Para começar, precisamos criar um arquivo onde serão salvos os dados armazenados na memória, como o nome fornecido à função como parâmetro:
/* create file */ FILE *fp = fopen(file_name, "wb"); if (!fp) abort_("[write_png_file] File %s could not be opened for writing", file_name);
2º passo:
Em seguida, inicializamos as estruturas necessárias à formatação dos dados da imagem, png_ptr e info_ptr, da seguinte forma:
/* initialize stuff */ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) abort_("[write_png_file] png_create_write_struct failed"); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) abort_("[write_png_file] png_create_info_struct failed"); if (setjmp(png_jmpbuf(png_ptr))) abort_("[write_png_file] Error during init_io"); png_init_io(png_ptr, fp);
3º e 4º passos:
Nesse momento, iremos escrever no arquivo os dados do cabeçalho da imagem PNG e os pontos da imagem. Isso é feito dessa forma:
/* write header */ if (setjmp(png_jmpbuf(png_ptr))) abort_("[write_png_file] Error during writing header"); png_set_IHDR(png_ptr, info_ptr, width, height, 8, 6, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png_ptr, info_ptr); /* write bytes */ if (setjmp(png_jmpbuf(png_ptr))) abort_("[write_png_file] Error during writing bytes"); png_write_image(png_ptr, row_pointers);
4º passo:
Por fim, precisamos finalizar todos os ponteiros usados e liberar os recursos utilizados pela função.
/* end write */ if (setjmp(png_jmpbuf(png_ptr))) abort_("[write_png_file] Error during end of write"); png_write_end(png_ptr, NULL); /* cleanup heap allocation */ for (y=0; y<height; y++) free(row_pointers[y]); free(row_pointers); fclose(fp);
Depois de implementar esses dois métodos, você pode adicionar outros métodos para manipular a imagem. Dois tipos de método se destacam aqui: o primeiro tipo são métodos para executar algum tipo de efeito na imagem, e o segundo tipo são construtores que recebem como argumento vetores de pixels de diversos tipos e armazenam esses vetores em row_pointer. Nesse ultimo caso, lembre que o tipo png_bytep é um alias para unsigned char*, então na hora de armazenar algum vetor cujo tipo de dado seja diferente, lembre-se de fazer a devida conversão entre tipos (cast).