ImageMagick 颜色深度转换

ImageMagick colors depth conversion

我有数千个 tga 文件(没有调色板),其中包含 RGBA4444 数据(我知道通常 tga 文件不包含 RGBA4444 数据)。我想将它们转换成 RGBA8888 数据。我使用以下命令行:

convert -depth 4 woody4.tga -depth 8 woody8.tga

在这种情况下,woody4.tga 是原始 RGBA4444 文件,woody8.tga 是目标 RGBA8888 文件,但它不会改变我图片的颜色,我错过了什么?

谢谢,

皮埃尔

编辑:

非常感谢马克,我已经用你的程序成功转换了超过 10 000 个 TGA,结果非常好并且与原始 TGA 正确!如果没有并行命令,这是不可能的!最后一点,我有大约 50 个更大的 TGA(游戏背景),它们是用 RGBA5650 而不是 RGBA4444 编码的,我如何修改你的程序来管理 RGBA5650?非常感谢!

更新答案。

reading a few documents about TARGA format 之后。我修改+简化了一个C程序来转换。

// tga2img.c
#include <stdio.h>
#include <stdlib.h>
#include <wand/MagickWand.h>

typedef struct {
    unsigned char  idlength;
    unsigned char  colourmaptype;
    unsigned char  datatypecode;
    short int colourmaporigin;
    short int colourmaplength;
    unsigned char  colourmapdepth;
    short int x_origin;
    short int y_origin;
    short int width;
    short int height;
    unsigned char  bitsperpixel;
    unsigned char  imagedescriptor;
} HEADER;

typedef struct {
    int extensionoffset;
    int developeroffset;
    char signature[16];
    unsigned char p;
    unsigned char n;
} FOOTER;

int main(int argc, const char * argv[]) {

    HEADER tga_header;
    FOOTER tga_footer;
    FILE
        * fd;
    size_t
        tga_data_size,
        tga_pixel_size,
        i,
        j;
    unsigned char
        * tga_data,
        * buffer;
    const char
        * input,
        * output;

    if (argc != 3) {
        printf("Usage:\n\t %s <input> <output>\n", argv[0]);
        return 1;
    }
    input = argv[1];
    output = argv[2];

    fd = fopen(input, "rb");
    if (fd == NULL) {
        fprintf(stderr, "Unable to read TGA input\n");
        return 1;
    }
    /********\
     * TARGA *
    \*********/
    #pragma mark TARGA
    // Read TGA header
    fread(&tga_header.idlength,        sizeof(unsigned char), 1, fd);
    fread(&tga_header.colourmaptype,   sizeof(unsigned char), 1, fd);
    fread(&tga_header.datatypecode,    sizeof(unsigned char), 1, fd);
    fread(&tga_header.colourmaporigin, sizeof(    short int), 1, fd);
    fread(&tga_header.colourmaplength, sizeof(    short int), 1, fd);
    fread(&tga_header.colourmapdepth,  sizeof(unsigned char), 1, fd);
    fread(&tga_header.x_origin,        sizeof(    short int), 1, fd);
    fread(&tga_header.y_origin,        sizeof(    short int), 1, fd);
    fread(&tga_header.width,           sizeof(    short int), 1, fd);
    fread(&tga_header.height,          sizeof(    short int), 1, fd);
    fread(&tga_header.bitsperpixel,    sizeof(unsigned char), 1, fd);
    fread(&tga_header.imagedescriptor, sizeof(unsigned char), 1, fd);
    // Calculate sizes
    tga_pixel_size = tga_header.bitsperpixel / 8;
    tga_data_size = tga_header.width * tga_header.height * tga_pixel_size;
    // Read image data
    tga_data = malloc(tga_data_size);
    fread(tga_data, 1, tga_data_size, fd);
    // Read TGA footer.
    fseek(fd, -26, SEEK_END);
    fread(&tga_footer.extensionoffset, sizeof(          int),  1, fd);
    fread(&tga_footer.developeroffset, sizeof(          int),  1, fd);
    fread(&tga_footer.signature,       sizeof(         char), 16, fd);
    fread(&tga_footer.p,               sizeof(unsigned char),  1, fd);
    fread(&tga_footer.n,               sizeof(unsigned char),  1, fd);
    fclose(fd);

    buffer = malloc(tga_header.width * tga_header.height * 4);
    #pragma mark RGBA4444 to RGBA8888
    for (i = 0, j=0; i < tga_data_size; i+= tga_pixel_size) {
        buffer[j++] = (tga_data[i+1] & 0x0f) << 4; // Red
        buffer[j++] =  tga_data[i  ] & 0xf0;       // Green
        buffer[j++] = (tga_data[i  ] & 0x0f) << 4; // Blue
        buffer[j++] =  tga_data[i+1] & 0xf0;       // Alpha
    }
    free(tga_data);

    /***************\
     * IMAGEMAGICK *
    \***************/
    #pragma mark IMAGEMAGICK
    MagickWandGenesis();
    PixelWand * background;
    background = NewPixelWand();
    PixelSetColor(background, "none");
    MagickWand * wand;
    wand = NewMagickWand();
    MagickNewImage(wand,
                   tga_header.width,
                   tga_header.height,
                   background);
    background = DestroyPixelWand(background);
    MagickImportImagePixels(wand,
                            0,
                            0,
                            tga_header.width,
                            tga_header.height,
                            "RGBA",
                            CharPixel,
                            buffer);
    free(buffer);
    MagickWriteImage(wand, argv[2]);
    wand = DestroyMagickWand(wand);
    return 0;
}

可以用clang $(MagickWand-config --cflags --libs) -o tga2im tga2im.c编译,./tga2im N_birthday_0000.tga N_birthday_0000.tga.png.

可以直接执行

原始答案。

我能想到的转换图像的唯一方法是编写一个快速 program/script 来执行按位颜色像素逻辑。

This answer 提供了一种快速读取图像数据的方法;所以结合MagickWand,可以轻松转换。 (虽然我知道在旧的游戏开发论坛上会找到更好的解决方案...)

#include <stdio.h>
#include <stdbool.h>
#include <wand/MagickWand.h>

typedef struct
{
    unsigned char imageTypeCode;
    short int imageWidth;
    short int imageHeight;
    unsigned char bitCount;
    unsigned char *imageData;
} TGAFILE;

bool LoadTGAFile(const char *filename, TGAFILE *tgaFile);

int main(int argc, const char * argv[]) {

    const char
        * input,
        * output;
    if (argc != 3) {
        printf("Usage:\n\t%s <input> <output>\n", argv[0]);
    }
    input = argv[1];
    output = argv[2];

    MagickWandGenesis();
    TGAFILE header;

    if (LoadTGAFile(input, &header) == true) {
        // Build a blank canvas image matching TGA file.
        MagickWand * wand;
        wand = NewMagickWand();
        PixelWand * background;
        background = NewPixelWand();
        PixelSetColor(background, "NONE");
        MagickNewImage(wand, header.imageWidth, header.imageHeight, background);
        background = DestroyPixelWand(background);
        // Allocate RGBA8888 buffer
        unsigned char * buffer = malloc(header.imageWidth * header.imageHeight * 4);
        // Iterate over TGA image data, and convert RGBA4444 to RGBA8888;
        size_t pixel_size = header.bitCount / 8;
        size_t total_bytes = header.imageWidth * header.imageHeight * pixel_size;
        for (int i = 0, j = 0; i < total_bytes; i+=pixel_size) {
            // Red
            buffer[j++] = (header.imageData[i  ] & 0x0f) << 4;
            // Green
            buffer[j++] = (header.imageData[i  ] & 0xf0);
            // Blue
            buffer[j++] = (header.imageData[i+1] & 0xf0) << 4;
            // Alpha
            buffer[j++] = (header.imageData[i+1] & 0xf0);
        }
        // Import image data over blank canvas
        MagickImportImagePixels(wand, 0, 0, header.imageWidth, header.imageHeight, "RGBA", CharPixel, buffer);
        // Write image
        MagickWriteImage(wand, output);
        wand = DestroyMagickWand(wand);
    } else {
        fprintf(stderr, "Could not read TGA file %s\n", input);
    }
    MagickWandTerminus();
    return 0;
}

/*
 * Method copied verbatim from 
 * Show your love by +1 to Wroclai answer.
 */
bool LoadTGAFile(const char *filename, TGAFILE *tgaFile)
{
    FILE *filePtr;
    unsigned char ucharBad;
    short int sintBad;
    long imageSize;
    int colorMode;
    unsigned char colorSwap;

    // Open the TGA file.
    filePtr = fopen(filename, "rb");
    if (filePtr == NULL)
    {
        return false;
    }

    // Read the two first bytes we don't need.
    fread(&ucharBad, sizeof(unsigned char), 1, filePtr);
    fread(&ucharBad, sizeof(unsigned char), 1, filePtr);

    // Which type of image gets stored in imageTypeCode.
    fread(&tgaFile->imageTypeCode, sizeof(unsigned char), 1, filePtr);

    // For our purposes, the type code should be 2 (uncompressed RGB image)
    // or 3 (uncompressed black-and-white images).
    if (tgaFile->imageTypeCode != 2 && tgaFile->imageTypeCode != 3)
    {
        fclose(filePtr);
        return false;
    }

    // Read 13 bytes of data we don't need.
    fread(&sintBad, sizeof(short int), 1, filePtr);
    fread(&sintBad, sizeof(short int), 1, filePtr);
    fread(&ucharBad, sizeof(unsigned char), 1, filePtr);
    fread(&sintBad, sizeof(short int), 1, filePtr);
    fread(&sintBad, sizeof(short int), 1, filePtr);

    // Read the image's width and height.
    fread(&tgaFile->imageWidth, sizeof(short int), 1, filePtr);
    fread(&tgaFile->imageHeight, sizeof(short int), 1, filePtr);

    // Read the bit depth.
    fread(&tgaFile->bitCount, sizeof(unsigned char), 1, filePtr);

    // Read one byte of data we don't need.
    fread(&ucharBad, sizeof(unsigned char), 1, filePtr);

    // Color mode -> 3 = BGR, 4 = BGRA.
    colorMode = tgaFile->bitCount / 8;
    imageSize = tgaFile->imageWidth * tgaFile->imageHeight * colorMode;

    // Allocate memory for the image data.
    tgaFile->imageData = (unsigned char*)malloc(sizeof(unsigned char)*imageSize);

    // Read the image data.
    fread(tgaFile->imageData, sizeof(unsigned char), imageSize, filePtr);

    // Change from BGR to RGB so OpenGL can read the image data.
    for (int imageIdx = 0; imageIdx < imageSize; imageIdx += colorMode)
    {
        colorSwap = tgaFile->imageData[imageIdx];
        tgaFile->imageData[imageIdx] = tgaFile->imageData[imageIdx + 2];
        tgaFile->imageData[imageIdx + 2] = colorSwap;
    }

    fclose(filePtr);
    return true;
}

可能需要调换颜色通道的顺序。

哦,我看到埃里克先于我:-)

嘿嘿!无论如何,我以不同的方式做了它并得到了不同的答案,所以你可以看到你最喜欢哪一个。我也写了一些 C 但我不依赖任何库,我只是阅读 TGA 并将其转换为 PAM 格式并让 ImageMagick 制作它之后在命令行进入PNG

我选择 PAM 因为它是支持透明的最简单的文件编写 - see Wikipedia on PAM format.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc,char* argv[]){

    unsigned char buf[64];

    FILE* fp=fopen(argv[1],"rb");    
    if(fp==NULL){
       fprintf(stderr,"ERROR: Unable to open %s\n",argv[1]);
       exit(1);
    }

    // Read TGA header of 18 bytes, extract width and height
    fread(buf,1,18,fp);  // 12 bytes junk, 2 bytes width, 2 bytes height, 2 bytes junk
    unsigned short w=buf[12]|(buf[13]<<8);
    unsigned short h=buf[14]|(buf[15]<<8);

    // Write PAM header
    fprintf(stdout,"P7\n");
    fprintf(stdout,"WIDTH %d\n",w);
    fprintf(stdout,"HEIGHT %d\n",h);
    fprintf(stdout,"DEPTH 4\n");
    fprintf(stdout,"MAXVAL 255\n");
    fprintf(stdout,"TUPLTYPE RGB_ALPHA\n");
    fprintf(stdout,"ENDHDR\n");

    // Read 2 bytes at a time RGBA4444
    while(fread(buf,2,1,fp)==1){
       unsigned char out[4];
       out[0]=(buf[1]&0x0f)<<4;
       out[1]=buf[0]&0xf0;
       out[2]=(buf[0]&0x0f)<<4;
       out[3]=buf[1]&0xf0;
       // Write the 4 modified bytes out RGBA8888
       fwrite(out,4,1,stdout);
    }
    fclose(fp);
    return 0;
}

我用 gcc:

编译
gcc targa.c -o targa

或者您可以使用 clang:

clang targa.c -o targa

和运行它与

./targa someImage.tga > someImage.pam

并在命令行使用 ImageMagickPAM 转换为 PNG

convert someImage.pam someImage.png

如果你想避免将中间 PAM 文件写入磁盘,你可以像这样直接将它传送到 convert 中:

./targa illu_evolution_01.tga | convert - result.png

如果您愿意,您同样可以制作一个 BMP 输出文件:

./targa illu_evolution_01.tga | convert - result.bmp

如果您有数千个文件要做,并且您在 Mac 或 Linux 上,您可以使用 GNU Parallel 并获取所有文件像这样并行完成得更快:

parallel --eta './targa {} | convert - {.}.png' ::: *.tga

如果你有超过几千个文件,你可能会得到 "Argument list too long" 错误,在这种情况下,使用稍微难一点的语法:

find . -name \*tga -print0 | parallel -0 --eta './targa {} | convert - {.}.png'

在 Mac 上,您将使用 homebrew 安装 GNU Parallel 使用:

brew install parallel

对于您的 RGBA5650 图像,我将回退到 PPM 作为我的中间格式,因为不再需要 PAM 的 alpha 通道。代码现在看起来像这样:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc,char* argv[]){

    unsigned char buf[64];

    FILE* fp=fopen(argv[1],"rb");    
    if(fp==NULL){
       fprintf(stderr,"ERROR: Unable to open %s\n",argv[1]);
       exit(1);
    }

    // Read TGA header of 18 bytes, extract width and height
    fread(buf,1,18,fp);  // 12 bytes junk, 2 bytes width, 2 bytes height, 2 bytes junk
    unsigned short w=buf[12]|(buf[13]<<8);
    unsigned short h=buf[14]|(buf[15]<<8);

    // Write PPM header
    fprintf(stdout,"P6\n");
    fprintf(stdout,"%d %d\n",w,h);
    fprintf(stdout,"255\n");

    // Read 2 bytes at a time RGBA5650
    while(fread(buf,2,1,fp)==1){
       unsigned char out[3];
       out[0]=buf[1]&0xf8;
       out[1]=((buf[1]&7)<<5) | ((buf[0]>>3)&0x1c);
       out[2]=(buf[0]&0x1f)<<3;
       // Write the 3 modified bytes out RGB888
       fwrite(out,3,1,stdout);
    }
    fclose(fp);
    return 0;
}

并且将以与 运行 完全相同的方式进行编译。

我一直在考虑这个问题,应该 可以在没有任何特殊软件的情况下重建图像 - 我暂时看不出我的错误也许@emcconville 可以用专家的眼光审视它并指出我的错误!漂亮吗?

所以,我的想法是 ImageMagick 已正确读取图像大小和像素数据,但只是根据 RGB5551 的标准解释分配了位TARGA 文件而不是 RGBA4444。因此,我们重建它读取的 16 位数据并以不同方式拆分它们。

下面的第一行将重建为原始的 16 位数据,然后每一行都拆分出一个 RGBA 通道,然后我们将它们重新组合:

convert illu_evolution_01.tga -depth 16 -channel R -fx "(((r*255)<<10) | ((g*255)<<5) | (b*255) | ((a*255)<<15))/255" \
   \( -clone 0 -channel R -fx "((((r*255)>>12)&15)<<4)/255"     \) \
   \( -clone 0 -channel R -fx "((((r*255)>>8 )&15)<<4)/255"     \) \
   \( -clone 0 -channel R -fx "((((r*255)    )&15)<<4)/255"     \) \
   -delete 0 -set colorspace RGB -combine -colorspace sRGB result.png

# The rest is just debug so you can see the reconstructed channels in [rgba].png
convert result.png -channel R -separate r.png
convert result.png -channel G -separate g.png
convert result.png -channel B -separate b.png
convert result.png -channel A -separate a.png

所以,下图表示1个像素的16位:

A R R R R R G G G G G B B B B B           <--- what IM saw
R R R R G G G G B B B B A A A A           <--- what it really meant

是的,我暂时忽略了 alpha 通道。