0

I have several thousands of tga files (without palette) which contain RGBA4444 data (I know usualy tga files don't contain RGBA4444 data). I would like to convert them into RGBA8888 data. I use the following command line:

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

In this case, woody4.tga is the original RGBA4444 file, and woody8.tga the target RGBA8888 file but it doesn't change the colors of my pictures, what am I missing?

Thanks,

Pierre

Edit:

Thanks very much Mark, I have successfully converted more than 10 000 TGA with your program, the result is very good and correct to the original TGA ! this would has been impossible without the parallel command ! Just a last point, I have around 50 TGA larger (the backgrounds of the game) which are coded with RGBA5650 and not RGBA4444, how can I modify your program to manage the RGBA5650 ? Thanks very much !

PGibouin
  • 241
  • 1
  • 3
  • 14

3 Answers3

2

Updated answer.

After reading a few documents about TARGA format. I've revised + simplified a C program to convert.

// 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;
}

Which can be compiled with clang $(MagickWand-config --cflags --libs) -o tga2im tga2im.c, and can be executed simply by ./tga2im N_birthday_0000.tga N_birthday_0000.tga.png.

illu_evolution_01.tga N_birthday_0000.tga N_cactushit_0031.tga N_enter_0015.tga T_bark1_0000.tga W_clock_0000.tga

Original answer.

The only way I can think of converting the images is to author a quick program/script to do the bitwise color-pixel logic.

This answer offers a quick way to read the image data; so combining with MagickWand, can be converted easily. (Although I know there'll be better solutions found on old game-dev forums...)

#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 enter image description here

The order of the color channels may need to be switch around.

Community
  • 1
  • 1
emcconville
  • 23,800
  • 4
  • 50
  • 66
  • I'd upvote you twice if I could - top quality answer! – Mark Setchell Mar 07 '17 at 17:39
  • Thanks very much for your help !! You are experts in TGA manipulations ! Bravo !! The sprites are taken from the game Neighbours from hell (for educational purposes, not to stole the graphics), you can find a video from the game here: – PGibouin Mar 07 '17 at 18:13
  • It seems your results are way to much green than the originals wich are more colorfull, I don't know if it's a question about order of the different color channels. Anayway, thanks for the work and your help ! – PGibouin Mar 07 '17 at 18:15
  • Yeah. The green ones are my first attempt. Please ignore as `Updated answer` segment at the top replace the previous solution. – emcconville Mar 07 '17 at 18:25
  • You did it emcconville, thanks !! This is that !! Exactly, I will be able to study the graphics for another animation project. I will compile the code tomorrow and test it on other graphics – PGibouin Mar 07 '17 at 18:27
  • Do you know why the developpers of the game did this RGBA modification ? Is it to protect the graphics from piracy ? (the game was created in 2003) – PGibouin Mar 07 '17 at 18:32
  • No way of telling with out reading code. I can speculate packing the data this way to improve gl textures (space or performance I couldn't say). Might be worth digging into old game-dev forums. – emcconville Mar 07 '17 at 18:37
  • Is it better to compile the code on MacOSX Sierra or Windows 10 ? I use XCode on MacOSX and devcpp on Windows 10. – PGibouin Mar 07 '17 at 19:53
  • Just a last point... Is it possible to add the opposite function into the code to convert RGBA 8888 to TGA RGBA 4444 ? Thanks very much – PGibouin Mar 07 '17 at 20:56
  • C program provided was compiled with `clang` on OS X. You should be mindful of data types/sizes if porting to windows. As for creating a program that converts it back, that comes down to how comfortable you are with written C. – emcconville Mar 07 '17 at 21:06
2

Oh, I see Eric beat me to it:-)

Hey ho! I did it a different way anyway and got a different answer so you can see which one you like best. I also wrote some C but I didn't rely on any libraries, I just read the TGA and converted it to a PAM format and let ImageMagick make that into PNG afterwards at command-line.

I chose PAM because it is the simplest file to write which supports transparency - 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;
}

I the compile that with gcc:

gcc targa.c -o targa

Or you could use clang:

clang targa.c -o targa

and run it with

./targa someImage.tga > someImage.pam

and convert the PAM to PNG with ImageMagick at the command-line:

convert someImage.pam someImage.png

If you want to avoid writing the intermediate PAM file to disk, you can pipe it straight into convert like this:

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

enter image description here

You can, equally, make a BMP output file if you wish:

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

If you have thousands of files to do, and you are on a Mac or Linux, you can use GNU Parallel and get them all done in parallel much faster like this:

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

If you have more than a couple of thousand files, you may get "Argument list too long" errors, in which case, use the slightly harder syntax:

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

On a Mac, you would install GNU Parallel with homebrew using:

brew install parallel

For your RGBA5650 images, I will fall back to PPM as my intermediate format because the alpha channel of PAM is no longer needed. The code will now look like this:

#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;
}

And will compile and run exactly the same way.

enter image description here

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • Wow! Much cleaner & straight forward. Good job! – emcconville Mar 06 '17 at 23:24
  • Great !! Thanks very much for your answer and all that code without any external library ! The result is too much blue I think from the original (see the reply I did to emcconville with the original video here: https://www.youtube.com/watch?v=Phs2xpOHE8c – PGibouin Mar 07 '17 at 18:18
  • your work is great ! another expert from TGA manipulation ! Bravo ! – PGibouin Mar 07 '17 at 18:19
  • I had a couple of bit-twiddles messed up - should be ok now! – Mark Setchell Mar 07 '17 at 19:03
  • Yes, you did it, this is it !! Thanks very much for your code ! – PGibouin Mar 07 '17 at 19:45
  • Just a last point... Is it possible to add the opposite function into the code to convert RGBA 8888 to TGA RGBA 4444 ? Thanks very much – – PGibouin Mar 07 '17 at 20:57
  • Everything is working fine except the parallel, I get an error message when I launch it: -bash: /usr/local/bin/parallel: Argument list too long. What am I missing ? Thanks by advance for your help, – PGibouin Mar 08 '17 at 14:53
  • I have added a way to solve it of you have too many files for the maximum argument list length - please see the end of my answer – Mark Setchell Mar 08 '17 at 15:11
  • This is strange because I get this kind of error for all the files in the directory: /bin/bash: line 1: 1107 Broken pipe: 13 targa ./N_speedrope_0000.tga 1108 Abort trap: 6 | convert - ./N_speedrope_0000.png ETA: 39s Left: 4187 AVG: 0.01s local:4/6606/100%/0.0s dyld: Library not loaded: /ImageMagick-7.0.5/lib/libMagickCore-7.Q16HDRI.2.dylib Referenced from: /opt/local/bin/convert Reason: image not found – PGibouin Mar 08 '17 at 15:25
  • It's working fine with the line ./targa illu_evolution_01.tga | convert - result.bmp but not with the parallel option – PGibouin Mar 08 '17 at 15:26
  • I've put the targa program into the bin directory of /opt/local/ with the others programme from ImageMagick. The pictures to convert are into a different subdirectory Extract – PGibouin Mar 08 '17 at 15:28
  • It depends how your PATH is set. You may need `find ... | parallel -0 '/opt/local/bin/targa ... | /opt/local/bin/convert ...'` – Mark Setchell Mar 08 '17 at 15:46
  • Try using `parallel --dry-run ...` and it will just show you the commands it would run, without actually running anything. – Mark Setchell Mar 08 '17 at 15:48
  • Thanks very much Mark, I have successfully converted more than 10 000 TGA with your progs, the result is very goodand correct to the original TGA ! this would has been impossible without the parallel command ! I have around 50 TGA larger (the backgrounds of the game) which are coded with RGBA5650 and not RGBA4444, how can I modify your program to manage the RGBA5650 ? Thanks very much – PGibouin Mar 09 '17 at 09:31
  • Brilliant! :-) If you upload an RGBA5650 image, and hopefully a JPEG showing how it should look, I will see what I can do - it may not be today though. Ping me with a comment when you have uploaded it so I see it. – Mark Setchell Mar 09 '17 at 09:38
  • I am sure @OleTange who is the wizard that develops **GNU Parallel**, will be delighted to read your comments. – Mark Setchell Mar 09 '17 at 09:48
  • Here are the TGA probably coded with RGBA5650: http://www.maison-sartrouville.fr/voisin_back/ – PGibouin Mar 09 '17 at 10:27
  • On this video, you can see all the backgrounds with the correct colors: https://www.youtube.com/watch?v=cel2SJeb0wk – PGibouin Mar 09 '17 at 10:39
  • The house you can see in the video contains the house*.tga backgrounds files – PGibouin Mar 09 '17 at 10:40
  • I have added a new version of the program for the RGBA5650 images. Good luck with your project! – Mark Setchell Mar 10 '17 at 12:51
  • Thanks very much Mark ! You have all my gratitude. Have a nice weekend. – PGibouin Mar 10 '17 at 19:28
1

I have been thinking about this some more and it ought to be possible to reconstruct the image without any special software - I can't quite see my mistake for the moment by maybe @emcconville can cast your expert eye over it and point out my mistake! Pretty please?

So, my concept is that ImageMagick has read in the image size and pixel data correctly but has just allocated the bits according to the standard RGB5551 interpretation of a TARGA file rather than RGBA4444. So, we rebuild the 16-bits of data it read and split them differently.

The first line below does the rebuild into the original 16-bit data, then each subsequent line splits out one of the RGBA channels and then we recombine them:

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

enter image description here

So, the following diagram represents the 16-bits of 1 pixel:

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

Yes, I have disregarded the alpha channel for the moment.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432