ImageMagick颜色深度转换

时间:2017-03-06 15:15:10

标签: imagemagick imagemagick-convert

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

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

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

谢谢,

皮尔

编辑:

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

3 个答案:

答案 0 :(得分:2)

更新了答案。

reading few documents TARGA format之后illu_evolution_01.tga。我已修改+简化了一个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即可执行。

N_birthday_0000.tga N_cactushit_0031.tga N_enter_0015.tga T_bark1_0000.tga W_clock_0000.tga This answer

原始答案。

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

enter image description here提供了一种快速阅读图像数据的方法;因此与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 https://stackoverflow.com/a/7050007/438117
 * 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;
}

enter image description here enter image description here {{3}}

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

答案 1 :(得分:2)

哦,我看到埃里克打败了我:-)

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

我之所以选择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

并在命令行中使用 ImageMagick PAM转换为PNG

convert someImage.pam someImage.png

如果您想避免将中间PAM文件写入磁盘,可以将其直接导入convert,如下所示:

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

enter image description here

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

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

如果您有数千个文件要做,并且您使用的是Mac或Linux,则可以使用 GNU Parallel 并以更快的速度完成所有文件:

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

如果你有超过几千个文件,你可能会得到“参数列表太长”错误,在这种情况下,使用稍微更难的语法:

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

在Mac上,您可以使用自制程序安装 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;
}

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

enter image description here

答案 2 :(得分:1)

我一直在考虑这个问题,并且应该可以在没有任何特殊软件的情况下重建图像 - 我现在也很难看出我的错误可能是@emcconville可以把你的专家视为它,并指出我的错误!好吗?

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

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

RGBA4444

enter image description here

因此,下图表示1位像素的16位:

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

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