4646T-2 - Programação de Baixo Nível
Prof. Márcio Sarroglia Pinho


CONVERSOR DE IMAGENS PPM


DESCRIÇÃO GERAL

O objetivo deste trabalho é "CONSERTAR"  uma imagem, no formato PPM 16 bits, que foi gravada em disco de maneira incorreta, e gerar um arquivo de saída em PPM 8 bits.

Para vizualizar a imagem, você utilizar um programa do Linux como o display, do ImageMagik. Na Figura 1, por exemplo,  pode-se observar a exibição de uma imagem incorreta e de sua correspondente imagem "consertada".


Imagem Incorreta
Imagem Consertada
Figura 1 - Exemplos de Imagens PPM


DESCRIÇÃO DO FORMATO PPM
Uma imagem PPM é formada por duas partes.
A primeira, um cabeçalho, em formato texto, e a segunda, que descreve as cores dos pontos da imagem, em formato binário. A Figura 2, a seguir, mostra um exemplo do PPM da Figura 1, aberto em um editor de textos.


Figura 2 - Exemplo de Imagem PPM carregado em um editor de textos

Cabeçalho
Uma imagem no formato PPM inicia-se como 3 linhas de texto que formam um cabeçalho, conforme o exemploda
Figura 2.
A primeira linha contém a string "P6".
Na segunda linha, estão duas strings, separadas por um espaço em branco, que definem a largura e a altura da imagem em pixels.
Na última linha do cabeçalho aparece uma string contendo o um número que pode ser 255 ou 65535. O primeiro indica uma imagens de 8 bits e o segundo, uma imagem de 16 bits.
Por exemplo, a seguinte sequência, define o cabeçalho de uma imagem PPM de 320 por 200 pixels.

P6
320 200
255


Pixels da Imagem
Depois do cabeçalho, inicia-se a descrição das cores dos pontos, no formato R G B, onde  R, G ou B são valores inteiros(em binário) de 16 ou 8 bits, que representam as chamadas "componentes de cor".
As cores dos pontos são armazenadas da esquerda para a direita, em sequência, uma linha após a outra, sem nehum tipo de marcação entre uma linha e outra.
Note que estes valores estão armazenados em binário e não em texto, como no cabeçalho. Por esta razão, na Figura 2, o editor de texto exibe caracteres estranhos após o final do cabeçalho. 

Componentes de Cor
As componentes de cor mencionadas na seção anterior definem a cor de um ponto a partir das quantidades de vermelho(R), verde(G) e azul(B) que irão formar a cor. Os valores válidos para cada componente R,G e B dependem do tamanho do número usado para representar cada componente. No caso de uma imagem de 8 bits, os valores válidos para R, G e B ficam entre 0 e 255, já numa imagem de 16 bits, os valores variam entre 0 e 65535. Exemplos de cores e seus respectivos valores em RGB de 8 bits podem ser vistos em páginas como esta.


LEITURA DO ARQUIVO PPM
As imagens a serem lidas neste trabalho estão no formato PPM de 16 bits e você deverá convertê-las para 8 bits. Este processo é necessário pois os monitores convencionais que temos à disposição exibem apenas pixels formados por componentes de cor de 8 bits. A conversão de 16 para 8 bits deve ser feita convertendo-se o valor de cada
componente de cor do intervalo [0..65535] para [0..255].

Além desta conversão, os bytes dos números que representam as componentes de cor foram gravados com a ordem invertida com relação à ordem usada em uma variável de um programa em C.
Por exemplo, se uma componente de cor tem o valor 255, o primeiro byte tem valor 0 e o segundo tem valor 255, quando em uma variável C o correto seria 255 e depois 0. Veja a seção sobre
ENDIANNESS para ter exemplos desta organização.

De posse destas informações, seu programa deverá ser capaz de ler quaisquer das imagens PPM 16 bits fornecidas neste link e  convertê-las para o formato PPM 8 bits, com a correção da ordem dos bytes.

Outro problema nas imagens PPM 16 bits é que estas encontram-se "de cabeça para baixo". Este problema também deverá ser corrigido antes da gravação.

Leitura do arquivo de disco

Os trechos de códido que lêem e gravam os dados dos arquivos PPM já estão disponíveis no programa apresentado a seguir.

Com isto, para desenvolver o trabalho, seu papel será manipular o vetor que armazenam os dados, sem se preocupar com a gravação propriamente dita.

  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// *******************************************************
// Programa que lê um arquivo, e coloca todo o conteúdo
// em um vetor de bytes
//
// Para compilar:
// gcc Carrega.c -o Carrega
// Para executar
// ./Carrega Arquivo.ppm
//
// *******************************************************
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


// *******************************************************
// Função que grava os bytes em um aquivo
// *******************************************************
void GravaArquivo(FILE* f, unsigned char* ptr, int tamanho)
{
unsigned long NroDeBytesGravados;
NroDeBytesGravados = fwrite(ptr, sizeof(unsigned char), tamanho, f);
if(NroDeBytesGravados != tamanho) { // verifica se a gravacao funcionou
printf("Erro na gravacao do arquivo!\n");
exit(1);
} else
printf("Gravacao realizada com sucesso! (%ld)\n", NroDeBytesGravados);

}

// *******************************************************
// Determina o novo tamanho da imagem, após a correção
// Esta função deverá ser alterada
// *******************************************************
void CalculaTamanhoDaImagemDeSaida(unsigned char *dados, unsigned long TamanhoDoArquivo, unsigned long* NovoTamanho)
{
*NovoTamanho = TamanhoDoArquivo;
}
// *******************************************************
// Realiza o processamento da imagem de entrada
// Esta função deverá ser alterada
// *******************************************************
unsigned char* ProcessaImagem(unsigned char* Entrada, unsigned long TamanhoInicial, unsigned long TamanhoFinal)
{
printf("%s\n", Entrada);

// Aloca memória para a imagem de saída
unsigned char *ptr;
ptr = (unsigned char*)malloc(sizeof(unsigned char) * TamanhoFinal);
if(ptr == NULL) { // Testa se conseguiu alocar
printf("Erro na alocação da memória!\n");
exit(1);
}

// printf("Processando...\n");
memcpy(ptr, Entrada, TamanhoFinal);
return ptr;
}
// *******************************************************
// Função que le os bytes de um aquivo
// *******************************************************
void LeArquivo(FILE* f, unsigned char* ptr, unsigned long TamanhoEsperado)
{
unsigned long NroDeBytesLidos;
NroDeBytesLidos = fread(ptr, sizeof(unsigned char), TamanhoEsperado, f);

if(NroDeBytesLidos != TamanhoEsperado) { // verifica se a leitura funcionou
printf("Erro na Leitura do arquivo!\n");
printf("Nro de bytes lidos: %ld", NroDeBytesLidos);
exit(1);
} else
printf("Leitura realizada com sucesso!\n");
}
// *******************************************************
// Função que obtém o tamanho de um arquivo em bytes.
// *******************************************************
unsigned long ObtemTamanhoDoArquivo(FILE* f)
{
fseek(f, 0, SEEK_END);
unsigned long len = (unsigned long)ftell(f);
fseek(f, SEEK_SET, 0);
return len;
}

// *******************************************************
// Função que lê a imagem PPM "incorreta"
// *******************************************************
unsigned char* LeImagemDeEntrada(char *nomeEntrada, unsigned long *tamEntrada)
{
FILE* arq;
// Tenta abrir o arquivo
arq = fopen(nomeEntrada, "rb");
if(arq == NULL) {
printf("Arquivo %s não existe.\n", nomeEntrada);
exit(1);
}
*tamEntrada = ObtemTamanhoDoArquivo(arq);
printf("O tamanho do arquivo %s é %ld bytes.\n", nomeEntrada, *tamEntrada);

// Aloca memória para ler todos os bytes do arquivo
unsigned char *ptr;
ptr = (unsigned char*)malloc(sizeof(unsigned char) * *tamEntrada);
if(ptr == NULL) { // Testa se conseguiu alocar
printf("Erro na alocação da memória!\n");
exit(1);
}
LeArquivo(arq, ptr, *tamEntrada);
fclose(arq); // fecha o arquivo
return ptr;
}
// *******************************************************
// Função que grava a imagem PPM "corrigida"
// *******************************************************
void GravaImagemProcessada(char *nome, unsigned char* NovosDados, unsigned long NovoTamanho)
{
printf("Criando arquivo %s.\n", nome);
// Cria o arquivo
FILE* arqSaida;
arqSaida = fopen(nome, "wb");
if(arqSaida == NULL) {
printf("Erro na criacao do arquivo %s.\n", nome);
exit(1);
} else
printf("Inciando gravacao do arquivo %s.\n", nome);
// Grava o vetor em disco
GravaArquivo(arqSaida, NovosDados, NovoTamanho);
fclose(arqSaida);
}
// *******************************************************
// *******************************************************
int main(int argc, char** argv)
{
// Testa se o programa recebeu o nome do arquivo na linha de comando
if(argc != 2) {
printf("CorrigePPM [filename]\n");
exit(1);
}

unsigned long TamanhoDoArquivo;
unsigned char* Dados;
Dados = LeImagemDeEntrada(argv[1], &TamanhoDoArquivo);

unsigned char* NovosDados;
unsigned long NovoTamanho;

CalculaTamanhoDaImagemDeSaida(Dados, TamanhoDoArquivo, &NovoTamanho);
NovosDados = ProcessaImagem(Dados, TamanhoDoArquivo, NovoTamanho);
free(Dados);

GravaImagemProcessada("Saida.ppm", NovosDados, NovoTamanho);
free(NovosDados);
return 0;
}

 



ENDIANNESS - Organização Interna de uma variável em C
A tabela a apresentada a seguir mostra as formas correta e invertida de representar um número em C.

Representação little-endian
(Utilizada em C)
Representação big-endian
(ordem inversa)
Número
Valor do byte no endereço menor
Valor do byte  no endereço maior Valor do byte no endereço menor Valor do byte  no endereço maior
1
1
0
0
1
255
255
0
0
255
256
0
1
1
0
257
1
1
1
1
2048
0
8
8
0
32768
0
128
128
0

Já este programa permite que se visualize a organização interna dos números em C.


 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int main()
{
int i;
unsigned short int a = 257; // altere o valor a ser decomposto em bytes
char *ptr;
ptr = (char*)&a;
int tam = sizeof(unsigned short int); // obtém o tamanho, em bytes, da variável a ser decomposta
unsigned char u;
for(i=0;i < tam; i++)
{
u = *ptr;
printf("%d ",u);
ptr++;
}

}



ORGANIZAÇÃO DO PROGRAMA
O programa deve, obrigatoriamente, usar como base o código fonte apresentado nesta página.


OBSERVAÇÕES

As funções do programa não poderão ter mais do que 20 linhas, cada uma
(-2.0 pontos).



FIM.