0

我一直在尝试让这个程序将彩色的 tga 图像转换为黑白。但我不知道该怎么做。我对 C 非常陌生,还没有掌握语法甚至正确使用 ubuntu。

我认为我的问题是无法读取 tga 文件标题。因为我在 tga 文件上尝试这个程序时得到的结果是一张没有高度的无法打开的图片。“高度 = 0”。

是否有一些很好的链接可供人们阅读 C 语言?

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct pixel {
    uint8_t r, g, b, a;
};

static uint8_t *load_image(char *filename, int *sizex, int *sizey)
{
    uint8_t *image;
    char buf[512];
    char *bufptr;
    int ret;

    FILE *fp = fopen(filename, "r");
    bufptr = fgets(buf, 512, fp);
    ret = fscanf(fp, "%d %d\n", sizex, sizey);
    bufptr = fgets(buf, 512, fp);

    image = malloc(*sizex * *sizey * 4);

    int i;
    uint8_t *ptr = image;
    for (i=0; i<*sizex * *sizey; ++i) {
        ret = fread(ptr, 1, 3, fp);
        ptr += 4;
    }

    fclose(fp);
    return image;
}

static int save_image(const char *filename, uint8_t *image, int sizex, int sizey)
{
    FILE *fp = fopen(filename, "w");
    fprintf(fp, "P6\n%d %d\n255\n", sizex, sizey);

    int i;
    uint8_t *ptr = image;
    for (i=0; i<sizex * sizey; ++i) {
        fwrite(ptr, 1, 3, fp);
        ptr += 4;
    }
    fclose(fp);

    return 1;
}

void convert_grayscale(uint8_t *input, uint8_t *output, int sizex, int sizey)
{
    // Y = 0.299 * R + 0.587 * G + 0.114 * B

    int i;

    for (i = 0; i < sizex * sizey; ++i)
        {
            struct pixel *pin = (struct pixel*) &input[i*4];
            struct pixel *pout = (struct pixel*) &output[i*4];

            float luma = 0.299 * pin->r + 0.587 * pin->g + 0.114 * pin->b;

            if (luma > 255)
                luma = 255;

            uint8_t intluma = (int) luma;

            pout->r = intluma;
            pout->g = intluma;
            pout->b = intluma;
            pout->a = 255;
        }

}

int main()
{
    uint8_t *inputimg, *outputimg;
    int sizex, sizey;

    inputimg = load_image("image.tga", &sizex, &sizey);

    outputimg = malloc(sizex * sizey * 4);

    convert_grayscale(inputimg, outputimg, sizex, sizey);

    save_image("output.tga", outputimg, sizex, sizey);
}
4

1 回答 1

1

(个人说明:阅读为什么 Stackoverflow 很烂之后的更长答案。对于获得版主权限的每个人来说,这应该是必读的内容。)

问题是您的load_image代码似乎旨在读取 PPM(基于 ASCII)图像:

每个 PPM 映像包含以下内容: 1. 用于识别文件类型的“幻数”。ppm 图像的幻数是两个字符“P6”。2. 空白(空格、TAB、CR、LF)。3. 宽度,格式为十进制的 ASCII 字符。4. 空白。5. 高度,同样以 ASCII 十进制表示。6. 空白。7. 最大颜色值(Maxval),同样以 ASCII 十进制表示。必须小于 65536 且大于零。8. 单个空白字符(通常是换行符)。9. 高度行的栅格 [...]

-- 您首先fgets读取,然后丢弃“幻数”行,然后读取宽度和高度,然后丢弃“maxval”行。

它应该适用于 PPM 图像(并且您可以重命名此例程load_ppm_image),如果它不是针对一个重要问题:在所有 ASCII 内容之后,您切换到fread,因此这里是警告 #1

在打开您的文件之前,请确定您是要读取ASCII 文本,还是可能需要读取二进制数据。

问题是“文本模式”“w”在读写时会将某些字符转换为其他字符。这是所有常见 C 库中的内置行为;它试图修复上一代程序员留给我们的行尾字符混乱。现在,以文本模式读取文本文件变得更简单了,但读取二进制数据是不可能的。你不能确定你得到了文件中的确切内容。

让我们继续警告 #2:并非所有文件格式都相同。

上述例程(大部分)适用于 PPM 图像,但它会在 TGA 上失败,因为它的标题组织方式不同。此处对 TGA 标头进行了很好的描述(随机选择的 Google 结果)。

规范描述了字节,所以首先要做的就是将你的fopen行改为

FILE *fp = fopen(filename, "rb");

顺便说一句,一个好的做法是测试它是否成功:

if (fp == NULL)
{
   printf ("Opening the file '%s' failed\n", filename);
   return NULL;
}

然后您可以使用fgetcorfread来读取一个或多个字节。警告#3:fread小心使用。

fread按照它们存储到文件中的顺序读取多个字节,因此您会认为它可能会在一个“读取”操作中读取一个项目,例如widthand height—— 每个 2 字节的整数值。但是fread不知道系统中字节的顺序(也不知道文件本身),因此它可能会读取“lo-hi”,正如我指出的规范中那样,而在您的计算机中,字节的顺序是一个整数是“hi-lo”。澄清:如果文件包含这个

80 00

然后你读取,然后存储,这fread (&width,1,2, fp)两个字节以相同的顺序存储到计算机内存中。字节按 Big-Endian 顺序;“大”字节位于末尾。但是,如果您的计算机恰好是 Little-Endian 订单系统,那么您将不会获得价值0x0080 = 128,而是0x8000 = 32768

避免这种情况的方法是一次读取一个字节

width = fgetc(fp) + (fgetc(fp)<<8);

始终以正确的顺序读取数据:先低,后高。只有总和被存储(按照您的系统的顺序,但这现在无关紧要!)。

有了以上内容,我想我已经没有警告了。使用 TGA 规范作为指导,您现在可以打开文件,一次读取一个字节的标题,直到您获得所需的所有信息,然后继续将fread原始图像数据存入内存。您可以安全地fread一次读取三个图像字节,因为它们将以与读取时相同的顺序出现在内存中(它们不是整数或更大,因此“内存顺序”不是问题)。

确保您阅读正确信息的一个好方法是:

  1. 一次读取一个字节,以防止字节顺序问题。
  2. 在您的代码中添加注释,详细说明它是什么
  3. 打印出值
  4. 如果允许该值,请检查规范。

为了让您开始,在fopen线路之后(以及所需的检查是否有效):

int idLength = fgetc(fp); /* length of id string after header */
printf ("id length: %u bytes\n", idLength);
int colorMapType = fgetc(fp); /* 0 = RGB */
printf ("color map type: %u\n", colorMapType);
if (colorMapType != 0)
{
   printf ("unexpected color map type!\n");
   return NULL;
}
int imageType = fgetc(fp); /* 0 = None, 1 = Indexed, 2 = RGB, 3 = Greyscale */

.. 等等。当整个标题被读取并且您没有遇到意外时,您就可以设置读取实际图像数据的东西了。那里不需要更改,您现有的代码应该可以正常工作。


后期编辑:我看到我用过

int colorMapType = fgetc(fp);

其中“颜色映射类型”实际上是一个字节,而不是整数。那就是允许采用腰带和吊带的方法。如果在读取文件头时遇到文件末尾,则fgetc返回的代码是EOF. EOF不能存储到 achar中,因为它是一个整数值:(0xFFFFFFFF更准确地说:)(int)-1。如果将其存储到 achar中,则无法将其与完全正常0x000000FF的值(值 255)区分开来。

传送带和挂起方法是检查每个字节:

if (colorMapType == EOF)
{
   printf ("encountered unexpected end of file!\n");
   return NULL;
}

如果您正在使用已知文件,并且您知道它是有效的 TGA(您可以使用位图编辑器查看和编辑它),那么过度杀伤力,但是如果您打算处理您不知道它们是否有效的文件,你可能需要这个。

于 2013-11-24T15:56:16.690 回答