Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert 32-bit TGA to PNG

Tags:

php

I am working from this code and taking into account the comments to "fix" it for 32-bit, but it seems to still not work. I am pretty sure it has something to do with the TGA descriptor. This will have bits 0-3 as the alpha channel depth which will always be 8 for 32 bit, and the code doesn't account for that.

I tried to understand how to piece it together using this C code as a guide, but no luck.

It seems the once you take into account the pixel being of length of 4 (as per the patch in comments) his dwordize only accounts for 3 of the 4 bytes, the 4th byte being the alpha bits I think.

I tried changing the function from

function dwordize($str)
{
    $a = ord($str[0]);
    $b = ord($str[1]);
    $c = ord($str[2]);
    return $c*256*256 + $b*256 + $a;
}

to

function dwordize($str)
{
    $a = ord($str[0]);
    $b = ord($str[1]);
    $c = ord($str[2]);
    $d = ord($str[3]);
    return $d*256*256*256 + $c*256*256 + $b*256 + $a;
}

which didn't work, and then tried

function dwordize($str)
{
    $a = ord($str[0]);
    $b = ord($str[1]);
    $c = ord($str[2]);
    $d = ord($str[3]);
    return $c*256*256 + $b*256 + $a + $d*256*256*256;
}

All of which I am trying to go off the C code which goes from like RBGA to BGRA and then the indices are all weird. I really don't understand the C code enough to apply it to the PHP code.

I also found this site which may help, I am reading it over now and if I come up with anything I will update.

like image 597
ParoX Avatar asked Oct 21 '22 05:10

ParoX


1 Answers

I have found your solution. There were missing adjustments that needed to be made in the RLE decode function to support the 32 bits pixel format: i.e: AARRGGBB

So first of all we need to bypass the color depth check of your library as suggested in the comments, so we change the check to this on line 99

if ($header['pixel_size'] != 24 && $header['pixel_size'] != 32) {
    die('Unsupported TGA color depth'); 
}

Then we need to change some variables that are color depth dependent. Most of them will be used for data formatting so we need them to have the correct value regardless of the color depth. So that would be:

line 104 : $bytes = $header['pixel_size'] / 8;

line 117 : $size = $header['width'] * $header['height'] * $bytes;

line 154 : $pixels = str_split($data, $bytes);

and remove the line 153: $num_bytes = $header['pixel_size']/8; We do not need it anymore.


Then we will need the rle_decode() function to know the pixel format size, so int the for loop when we call it we need to pass this parameter, so we need to change as well the following line:

line 141: $data = rle_decode($data, $size, $bytes);

Therefore the function prototypes changes into:

line 9: function rle_decode($data, $datalen, $pixel_size)

Now you can see on this function there are multiple 3 magic numbers (i.e: for ($j = 0; $j<3*$value; $j++) on line 29). All of those must be replaced by the pixel size parameter. (3 for 24 bits and 4 for 32 bits)

Moreover sometimes it will write only 3 bytes instead of 4 when creating the pixels so we must adjust that as well.

So finally this gives us the following algorithm:

function rle_decode($data, $datalen, $pixel_size)
{
    $len = strlen($data);

    $out = '';

    $i = 0;
    $k = 0;
    while ($i<$len)
    {
        dec_bits(ord($data[$i]), $type, $value);
        if ($k >= $datalen)
        {
            break;
        }

        $i++;

        if ($type == 0) //raw
        {
            for ($j=0; $j<$pixel_size*$value; $j++)
            {
                $out .= $data[$j+$i];
                $k++;           
            }
            $i += $value*$pixel_size;
        }
        else //rle
        {
            for ($j=0; $j<$value; $j++)
            {
                $out .= $data[$i] . $data[$i+1] . $data[$i+2];
                if ($pixel_size == 4) $out .= $data[$i+3];
                $k++;               
            }
            $i += $pixel_size;
        }   
    }
    return $out;
}

That's it, you will now get your TGA images perfectly converted. I'm giving you the full tga.php code so you can directly paste it and test it by yourself:

<?php

// Author: de77
// Licence: MIT
// First-version: 9.02.2010
// Version: 24.08.2010
// http://de77.com

function rle_decode($data, $datalen, $pixel_size)
{
    $len = strlen($data);

    $out = '';

    $i = 0;
    $k = 0;
    while ($i<$len)
    {
        dec_bits(ord($data[$i]), $type, $value);
        if ($k >= $datalen)
        {
            break;
        }

        $i++;

        if ($type == 0) //raw
        {
            for ($j=0; $j<$pixel_size*$value; $j++)
            {
                $out .= $data[$j+$i];
                $k++;           
            }
            $i += $value*$pixel_size;
        }
        else //rle
        {
            for ($j=0; $j<$value; $j++)
            {
                $out .= $data[$i] . $data[$i+1] . $data[$i+2];
                if ($pixel_size == 4) $out .= $data[$i+3];
                $k++;               
            }
            $i += $pixel_size;
        }   
    }
    return $out;
}

function dec_bits($byte, &$type, &$value)
{
    $type = ($byte & 0x80) >> 7;
    $value = 1 + ($byte & 0x7F);
}

function getimagesizetga($filename)
{
    $f = fopen($filename, 'rb');
    $header = fread($f, 18);
    $header = @unpack(  "cimage_id_len/ccolor_map_type/cimage_type/vcolor_map_origin/vcolor_map_len/" .
                        "ccolor_map_entry_size/vx_origin/vy_origin/vwidth/vheight/" .
                        "cpixel_size/cdescriptor", $header);
    fclose($f);

    $types = array(0,1,2,3,9,10,11,32,33);      
    if (!in_array($header['image_type'], $types))
    {
        return array(0, 0, 0, 0, 0);
    }

    if ($header['pixel_size'] > 32)
    {
        return array(0, 0, 0, 0, 0);
    }

    return array($header['width'], $header['height'], 'tga', $header['pixel_size'], $header['image_type']);
}

function imagecreatefromtga($filename)
{
    $f = fopen($filename, 'rb');
    if (!$f)
    {
        return false;
    }
    $header = fread($f, 18);
    $header = unpack(   "cimage_id_len/ccolor_map_type/cimage_type/vcolor_map_origin/vcolor_map_len/" .
                        "ccolor_map_entry_size/vx_origin/vy_origin/vwidth/vheight/" .
                        "cpixel_size/cdescriptor", $header);

    switch ($header['image_type'])
    {
        case 2:     echo "Image is not compressed\n";//no palette, uncompressed
        case 10:    //no palette, rle
                    break;
        default:    die('Unsupported TGA format');                  
    }

    if ($header['pixel_size'] != 24 && $header['pixel_size'] != 32)
    {
        die('Unsupported TGA color depth'); 
    }

    $bytes = $header['pixel_size'] / 8;

    if ($header['image_id_len'] > 0)
    {
        $header['image_id'] = fread($f, $header['image_id_len']);
    }
    else
    {
        $header['image_id'] = '';   
    }

    $im = imagecreatetruecolor($header['width'], $header['height']);

    $size = $header['width'] * $header['height'] * $bytes;

    //-- check whether this is NEW TGA or not
    $pos = ftell($f);
    fseek($f, -26, SEEK_END);   
    $newtga = fread($f, 26);
    if (substr($newtga, 8, 16) != 'TRUEVISION-XFILE')
    {
        $newtga = false;
    }

    fseek($f, 0, SEEK_END);
    $datasize = ftell($f) - $pos; 
    if ($newtga)
    {
        $datasize -= 26;
    }

    fseek($f, $pos, SEEK_SET);

    //-- end of check
    $data = fread($f, $datasize);
    if ($header['image_type'] == 10)
    {
        $data = rle_decode($data, $size, $bytes);                   
    }
    if (bit5($header['descriptor']) == 1)
    {
        $reverse = true;    
    }
    else
    {
        $reverse = false;
    }    


    $i = 0;
    $pixels = str_split($data, $bytes);

    //read pixels 
    if ($reverse)
    {   
        for ($y=0; $y<$header['height']; $y++)
        {       
            for ($x=0; $x<$header['width']; $x++)
            {
                imagesetpixel($im, $x, $y, dwordize($pixels[$i]));
                $i++;
            }
        }
    }
    else
    {
        for ($y=$header['height']-1; $y>=0; $y--)
        {       
            for ($x=0; $x<$header['width']; $x++)
            {
                imagesetpixel($im, $x, $y, dwordize($pixels[$i]));
                $i++;
            }
        }
    }
    fclose($f);         

    return $im;
}

function dwordize($str)
{
    $a = ord($str[0]);
    $b = ord($str[1]);
    $c = ord($str[2]);
    return $c*256*256 + $b*256 + $a;
}

function bit5($x)
{
    return ($x & 32) >> 5;  
}

And here is the output PNG image directly created from the script: Eyebrows PNG output

And if you would prefer a direct download link rather than pasting the fixed code I give you here the full php file: https://www.sendspace.com/file/92uir9

At the end of the day, just change the tga.php file and everything will automagically work.

like image 176
Valentin Mercier Avatar answered Oct 23 '22 23:10

Valentin Mercier