把网格变成彩色图像

C 语言Beginner
立即练习

介绍

一家小型印刷工作室需要一张由颜色网格组成的明信片。你将先运行完整的四乘四示例,为它换上暖色油墨,再完成一个 C 程序,把矩形字符图案变成真正的图像文件。

你需要掌握 C 变量、条件、循环、数组、函数和基本终端导航。本实验会解释二维索引和图像输出。准备好的浏览器环境包含 Workspace 终端和 Postcard Preview。图片采用 P3 PPM 格式:内容是可读的数字,而不是 PNG。阅读与练习时长为作者估计,并非测得的初学者完成时间。

打印小型示例并更换颜色

在这一步中,你将看到完整 C 示例变成图片,再改变一种油墨颜色。

打开 Workspace Tab。终端位于 /home/labex/project/postcards;下面明确写出的 cd 也方便你在切换目录后返回。pwd 显示当前目录,ls 列出其中的文件。

cd /home/labex/project/postcards
pwd
ls

已准备的文件包括 sample.c、尚未完成的 card.c 和提供的 art.h。只修改 sample.c 与 card.c,保持 art.h 不变。cat 会显示文件,不会编辑它:

cat sample.c

示例使用四行四列的网格。在 grid[row][col] 中,第一个索引从上向下选择行,第二个索引从左向右选择列;两者都从零开始。每个元素是 0 或 1,用来选择 color 中的一个 RGB 三元组。RGB 分别表示红、绿、蓝,每个分量都是 0 到 255 的整数。color[c][0] 的第二个索引选择红色,[1] 选择绿色,[2] 选择蓝色。

printf 把文字写到标准输出,通常就是当前终端。\n 表示换行。P3 文件先写 P3、宽和高,再写最大的分量值 255。之后是 RGB 三元组:每行从左到右,整幅图从上到下读取。数字由空白分隔,因此每行写一个 RGB 三元组是合法的。P3 也允许 # 注释。

编译示例。gcc 是 C 编译器;-std=c11 选择 C11,-Wall -Wextra 开启有用的警告,-o sample 将可执行文件命名为 sample。

gcc -std=c11 -Wall -Wextra sample.c -o sample

./sample 运行当前目录中的可执行文件。> 将标准输出重定向到文件,而不是终端。shell 会在程序运行之前打开或覆盖这个文件。使用下面的新文件名:

./sample > sample.ppm

切换到 Postcard Preview Tab。点击 Refresh files,在 Postcard file 中选择 sample.ppm,再点击 Open postcard。状态应显示 4 × 4 pixels · sample.ppm。你会看到奶油色背景上的深色非对称图案。预览器显示的是刚生成的文件,不会补齐缺失像素。

返回 Workspace。把深色油墨从 {30, 76, 64} 改为暖色 {170, 70, 45}。下面的 sed 文字编辑命令使用 s/old/new/ 替换这段精确文字,-i 将修改保存到 sample.c。单引号让替换表达式作为一个不变的参数传给 sed。

sed -i 's/{30, 76, 64}/{170, 70, 45}/' sample.c

源文件修改后必须重新编译;旧的可执行文件不会自行更新。

gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample > sample.ppm

切换到 Postcard Preview,继续选择 sample.ppm,再次点击 Open postcard。只有前景颜色应发生变化,尺寸和奶油色像素都应保持不变。

如果终端提示找不到文件,用刚学的 cd 和 ls 返回工作区。如果预览器提示文件不存在,返回 Workspace,先确认编译成功,再运行 ./sample > sample.ppm。编译错误表示没有生成新的可执行文件:修正提示的源代码行,再重新编译。

把矩形图案映射为 RGB 像素

在这一步中,你将完成明信片生成器,并打开它的真实输出。

从 Workspace 的 /home/labex/project/postcards 开始。读取提供的图案和未完成的生成器:

cd /home/labex/project/postcards
cat art.h
cat card.c

#include "art.h" 让这些准备好的定义在编译 card.c 时可用。#define WIDTH 5 和 #define HEIGHT 3 为尺寸定义名称。pattern 有三行,每行五个数字字符。每个字符串行分配 WIDTH + 1 个字符,因为 C 会在可见字符后存放一个值为零的终止符;循环只处理五个可见位置。

第一行 "10000" 先使用调色板颜色 1,再使用四个颜色 0 的像素。字符 '2' 不是整数 2。减去字符 '0' 就能把提供的数字字符转成调色板索引:'2' - '0' 为 2。图案只包含数字 0 到 3,对应调色板的四行。保持图案、尺寸和颜色不变。循环应使用 WIDTH 与 HEIGHT,使程序重新编译后也能处理宽、高均在 1 到 16 之间、仍使用调色板索引 0 到 3 的矩形图案。

未完成的生成器只写文件头,没有像素。只有文件头不等于一张图片。用遍历 y(行)的外层循环和遍历 x(列)的内层循环补齐它。pattern[y][x] 选择字符,palette[c][0..2] 选择相应 RGB 值。printf 中的 %d 占位符按列出的顺序输出整数参数。

使用下面的完整源代码。cat > card.c 会覆盖该文件。<<'EOF' 开始一个 here-document:shell 将后面的行按原样复制,直到遇到只包含 EOF 的一行。带引号的结束标记防止 shell 展开 C 源码中的内容。复制完整代码块,包括最后的 EOF。

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

int main(void) {
    printf("P3\n%d %d\n255\n", WIDTH, HEIGHT);
    for (int y = 0; y < HEIGHT; y++) {
        for (int x = 0; x < WIDTH; x++) {
            int c = pattern[y][x] - '0';
            printf("%d %d %d\n", palette[c][0], palette[c][1], palette[c][2]);
        }
    }
    return 0;
}
EOF

编译并生成新的输出文件:

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

提供的检查工具只解码文件,不会生成或修复明信片。把文件名作为参数运行它:

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

预期输出:

5 x 3 pixels; 15 RGB triples; max value 255

这些数字有什么用?正方形可能掩盖宽、高互换的问题。这个五列三行的图案必须宽大于高,深色条带位于左侧。WIDTH 控制内层循环,HEIGHT 控制外层循环;交换它们可能越过一行的边界,或漏掉像素。

切换到 Postcard Preview。点击 Refresh files,选择 original.ppm,再点击 Open postcard。检查 5 × 3 状态,并把各行与 art.h 对照。点击 Download PPM 保存真实图像文件。浏览器可能会直接下载 PPM,而不是显示它;请使用此预览器查看图片。返回 Workspace 进行实验检查。

截图显示已选择的 original.ppm、它保存的 5 × 3 尺寸,以及图片下方的 Download PPM。对照左侧深色条带和全部三行。

Postcard Preview 中的矩形明信片

将 card.c、可执行文件 card、art.h 和 original.ppm 保留在此工作区。

如果预览器报告像素数量错误,表示文件存在但内容不完整:检查两个循环的边界,保存 card.c,重新编译,再生成 original.ppm。预览器在屏幕上的放大不会改变保存的尺寸。下一个实验将由你真正生成放大后的图像文件。

总结

你编译并更换了小示例的配色,再把矩形字符数组映射成 RGB 三元组。C 程序生成了真实 P3 文件,你对它进行了解码、预览和下载,并练习了先行后列的索引及标准输出重定向。