Coming Heart 资源分析(五)

终于我们来到了最后,离画出标题 CG 只差一步了。

前情提要

上一期我们完成了事实上的读取过程,这个时候文件已经全部读取出来了, 然后得到这样一张图:

这个相当于绘图的线稿,后面我们只要按照程序逻辑填充颜色,就可以完成最终的绘图。

填充颜色

我们继续看 fcn_004010f0 的最后一部分,汇编的话是这段:

0x00401253      xor     ecx, ecx
0x00401255      mov     edx, dword [var_24h_2]
0x00401259      add     edx, 0x400 ; 1024
0x0040125f      mov     eax, 0x3e800
0x00401264      xor     ebx, ebx
0x00401266      mov     bl, byte [edx]
0x00401268      test    ebx, ebx
0x0040126a      je      0x40127b
0x0040126c      cmp     ecx, ebx
0x0040126e      jne     0x401277
0x00401270      xor     ecx, ecx
0x00401272      mov     byte [edx], 0
0x00401275      jmp     0x40127d
0x00401277      mov     ecx, ebx
0x00401279      jmp     0x40127d
0x0040127b      mov     byte [edx], cl
0x0040127d      inc     edx
0x0040127e      dec     eax
0x0040127f      jne     0x401264
0x00401281      pop     ebp
0x00401282      pop     edi
0x00401283      pop     esi
0x00401284      pop     ebx
0x00401285      add     esp, 0x10
0x00401288      ret     8

并不是很长,不过反编译的代码更容易理解,我也加好了注释, 注释中提到的「颜色」就是「调色板索引号」,简单起见我们就称之为「颜色」:

ecx = 0;                     // 填充颜色,初始为零
edx = var_24h_2;             // 读取图像内存位置
edx += 0x400;                // 跳过调色板
eax = 0x3e800;               // 循环次数
do {
    ebx = 0;                 // ebx 高位清零
    bl = *(edx);             // ebx 低位读出当前颜色
    if (ebx != 0) {          // 如果读出的颜色不为 0
        if (ecx == ebx) {    // 如果当前颜色和填充颜色相同
            ecx = 0;         // 填充颜色设为 0
            *(edx) = 0;      // 当前位置颜色重写为 0
        } else {             // 如果当前颜色和填充颜色不同
            ecx = ebx;       // 填充颜色设定为当前颜色
        }
    } else {                 // 如果读出的颜色为 0
        *(edx) = cl;         // 当前位置颜色重写为填充颜色
    }
    edx++;                   // 移到下一个像素位置
    eax--;                   // 循环次数减一
} while (eax != 0);          // 循环次数为 0 时终止循环
return eax;                  // 这个我们不理会

这个循环重复了 0x3e800 次,次数我们应该也熟悉,就是 640x400。 相比于上一期,这期我们转换为实际代码就太好写了,直接套就好了:

let mut default_color = 0;
for i in 0..image.data.len() {
    let current_color = image.data[i];
    if current_color != 0 {
        if default_color == current_color {
            default_color = 0;
            image.data[i] = 0;
        } else {
            default_color = current_color;
        }
    } else {
        image.data[i] = default_color;
    }
}

接下来我们就可以画图了,不过敏锐的读者可能会注意到有一个细节我没有提及, 就是图像的坐标系,根据程序读取出的数据,我们画一下就可以知道, 图像的原点是左下角,x 轴方向向右,y 轴向上,所以由于我们的图像库原点是左上角, 所以我们画图时要做一个变换:

for i in 0..self.data.len() {
    let x = i % width;
    let y = height - 1 - i / width; // 变换 y 轴坐标
    let color_index = self.data[i] as usize;
    let &(r, g, b) = self.pallete.get_color(color_index).unwrap();
    let pixel = image_buffer.get_pixel_mut(x as u32, y as u32);
    *pixel = Rgb([r, g, b]);
}

总结

我们已经完整实现了 fcn_004010f0 的全部内容,来画一下我们的成果吧: