Gire e amplie o cartão-postal

CBeginner
Pratique Agora

Introdução

O ateliê quer o mesmo cartão em outra direção e com um tamanho maior em pixels. Você acompanhará o deslocamento de um pixel, implementará o giro horário e depois adicionará uma ampliação inteira sem misturar cores.

Este laboratório começa de forma independente com sua própria arte e seus arquivos-fonte. Você precisa dos conhecimentos de grade, paleta, P3 e terminal ensinados no laboratório anterior. Um pequeno leitor de comandos fornecido processa as opções; você implementa as funções de coordenadas. Preserve a arte e o leitor de opções. O tempo de prática é uma estimativa do autor.

Gire um cartão retangular no sentido horário

Nesta etapa, você transformará uma imagem de cinco por três em outra de três por cinco sem alterar suas cores.

Abra Workspace. Este laboratório tem seu próprio /home/labex/project/postcards recém-preparado; não usa arquivos do laboratório anterior. base.c é o exemplo completo na orientação original. Primeiro compile e execute esse exemplo funcional:

cd /home/labex/project/postcards
gcc -std=c11 -Wall -Wextra base.c -o base
./base > original.ppm

Mude para Postcard Preview, clique em Refresh files, selecione original.ppm e clique em Open postcard. O status 5 × 3 e as linhas coloridas estabelecem a orientação inicial. Volte para Workspace e leia a nova estrutura:

cat art.h
cat card.c

Mantenha art.h, base.c e cli.h sem alterações; edite card.c. O main fornecido recebe dois argumentos: ROTATE é exatamente 0 (original) ou 1 (horário), e SCALE é um inteiro de 1 a 4. O código de apoio em cli.h os guarda nas variáveis inteiras rotation e scale. parse_options retorna verdadeiro apenas para argumentos compatíveis; caso contrário, main retorna status 2 antes de escrever uma imagem. As declarações argc e argv pertencem ao ponto de entrada C fornecido; não é necessário implementar aqui um analisador de argumentos baseado em ponteiros.

Um giro horário troca largura e altura. Acompanhe o pixel superior esquerdo da origem (0,0): ele vai para o canto superior direito da imagem girada, (HEIGHT - 1,0). O laço de saída faz a pergunta inversa: para o destino (x,y), qual pixel de origem deve ser lido? Sem ampliação, a resposta é source_x = y e source_y = HEIGHT - 1 - x. Subtrair a coluna de destino de HEIGHT - 1 inverte a ordem das linhas de origem. Usar WIDTH - 1 aqui seria incorreto para um retângulo.

Cada função de coordenadas recebe x e y como parâmetros inteiros e retorna um índice inteiro. Quando rotation vale zero, as funções retornam as coordenadas sem alterações. A expressão fornecida rotation ? HEIGHT : WIDTH significa “escolha HEIGHT se rotation não for zero; caso contrário, escolha WIDTH”.

Substitua card.c por esta primeira versão completa. Como no primeiro laboratório, o here-document com delimitador entre aspas copia literalmente o código C até a última linha EOF. Esta versão atende ao fator 1; a próxima etapa adicionará os fatores de 2 a 4.

cat > card.c <<'EOF'
#include <stdio.h>
#include "art.h"
#include "cli.h"

int source_x(int x, int y) {
    if (rotation) return y;
    return x;
}
int source_y(int x, int y) {
    if (rotation) return HEIGHT - 1 - x;
    return y;
}
int main(int argc, char **argv) {
    if (!parse_options(argc, argv)) return 2;
    int out_width = (rotation ? HEIGHT : WIDTH) * scale;
    int out_height = (rotation ? WIDTH : HEIGHT) * scale;
    printf("P3\n%d %d\n255\n", out_width, out_height);
    for (int y = 0; y < out_height; y++) {
        for (int x = 0; x < out_width; x++) {
            int c = pattern[source_y(x, y)][source_x(x, y)] - '0';
            printf("%d %d %d\n", palette[c][0], palette[c][1], palette[c][2]);
        }
    }
    return 0;
}
EOF

Compile. Execute a orientação original e o giro horário com fator 1; cada saída recebe um nome diferente:

gcc -std=c11 -Wall -Wextra card.c -o card
./card 0 1 > original.ppm
./card 1 1 > rotated.ppm

Examine o arquivo girado:

python3 /home/labex/.local/share/pixel-postcards/inspect.py rotated.ppm
3 x 5 pixels; 15 RGB triples; max value 255

Mude para Postcard Preview, clique em Refresh files, selecione rotated.ppm e clique em Open postcard. A antiga faixa escura da esquerda deve agora ser a linha superior. Só as dimensões não comprovam o giro: compare também os pixels coloridos. Se o resultado parecer espelhado, reveja a inversão HEIGHT - 1 - x, compile e gere o arquivo novamente.

Amplie cada pixel e rejeite opções inválidas

Nesta etapa, você ampliará o cartão girado copiando cada pixel de origem para um bloco do mesmo tamanho.

Comece em Workspace. No fator 2, as colunas de destino 0 e 1 leem ambas a coluna 0; as colunas 2 e 3 leem a coluna 1. A divisão inteira produz exatamente esse agrupamento: 0 / 2 e 1 / 2 resultam em 0. Dividir primeiro as coordenadas de destino por scale desfaz a ampliação. Depois, a mesma fórmula de giro inverso escolhe a posição de origem. Isso produz uma imagem girada no sentido horário e ampliada por um inteiro positivo, sem misturar nem interpolar cores.

No caso girado, retorne y / scale em source_x e HEIGHT - 1 - x / scale em source_y. No caso original, retorne x / scale e y / scale. O laço principal fornecido já multiplica as dimensões de saída por scale. Substitua o arquivo pela versão completa:

cat > card.c <<'EOF'
#include <stdio.h>
#include "art.h"
#include "cli.h"

int source_x(int x, int y) {
    if (rotation) return y / scale;
    return x / scale;
}
int source_y(int x, int y) {
    if (rotation) return HEIGHT - 1 - x / scale;
    return y / scale;
}
int main(int argc, char **argv) {
    if (!parse_options(argc, argv)) return 2;
    int out_width = (rotation ? HEIGHT : WIDTH) * scale;
    int out_height = (rotation ? WIDTH : HEIGHT) * scale;
    printf("P3\n%d %d\n255\n", out_width, out_height);
    for (int y = 0; y < out_height; y++) {
        for (int x = 0; x < out_width; x++) {
            int c = pattern[source_y(x, y)][source_x(x, y)] - '0';
            printf("%d %d %d\n", palette[c][0], palette[c][1], palette[c][2]);
        }
    }
    return 0;
}
EOF

Compile, atualize as saídas original e girada com este executável e crie a ampliação por dois do cartão girado:

gcc -std=c11 -Wall -Wextra card.c -o card
./card 0 1 > original.ppm
./card 1 1 > rotated.ppm
./card 1 2 > enlarged.ppm
python3 /home/labex/.local/share/pixel-postcards/inspect.py enlarged.ppm
6 x 10 pixels; 60 RGB triples; max value 255

Cada um dos 15 pixels originais virou um bloco de 2 por 2: 60 pixels ao todo. Mude para Postcard Preview, atualize a lista, selecione enlarged.ppm e clique em Open postcard. A direção e as proporções devem ser iguais às de rotated.ppm. A visualização ajusta ambos à tela, por isso leia o rótulo de dimensões para distinguir os tamanhos reais das imagens salvas. Baixe o arquivo ampliado usando Download PPM.

A captura mostra a vista girada no sentido horário com 6 × 10 pixels salvos. A faixa escura está agora na linha superior; a imagem conserva as proporções do arquivo girado.

O cartão girado e ampliado no Postcard Preview

Volte para Workspace. Um fator zero não produz uma imagem útil. O próximo comando é uma falha intencional. Use um nome novo porque > abre e trunca o arquivo antes que o executável possa rejeitar as opções. echo $? imprime imediatamente o status do comando anterior: 0 significa sucesso e 2 é o erro de argumentos deste programa.

./card 1 0 > rejected.ppm
echo $?

Você deverá ver Scale must be an integer from 1 to 4 e depois 2. Examine o arquivo rejeitado com o decodificador já aprendido:

python3 /home/labex/.local/share/pixel-postcards/inspect.py rejected.ppm

Este comando também falha intencionalmente com Cannot read postcard: Expected a P3 header. Não há pixels para decodificar. Isso difere de um arquivo ausente: o shell criou um arquivo vazio antes de executar o programa C. Em Postcard Preview, atualize os arquivos e abra rejected.ppm para ver o erro em vez da imagem anterior. Abra enlarged.ppm novamente para recuperar a visualização normal. Os três arquivos corretos permanecem intactos.

Mantenha card.c, o executável card, os arquivos fornecidos art.h e cli.h, além de original.ppm, rotated.ppm e enlarged.ppm. O gerador deve atender aos dois valores de rotação e a todos os fatores de 1 a 4 para padrões fornecidos de até 16 colunas por 16 linhas; não deve fixar no código as dimensões ou cores desta imagem. São aceitos laços e expressões de coordenadas equivalentes. Argumentos inválidos devem falhar com uma mensagem e sem escrever um cabeçalho de imagem. O analisador fornecido já oferece esse comportamento.

Resumo

Você acompanhou um pixel durante um giro horário, desfez a ampliação com divisão inteira e combinou esses mapeamentos em duas funções C. Criou imagens originais, giradas e ampliadas e distinguiu rejeição de argumentos de arquivos malformados ou ausentes.