0

I'm trying to load a file and turn it into an image. The file format is called an Infernal Machine MAT and is used for textures in the game Star Wars Jedi Knight Dark Forces 2. The file comes in 2 color depths, 8-bit and 16-bit. They are pretty much Bitmap images except that they lack various headers and palette information.

Specs about the Infernal Machine MAT can be found by typing in Google: "Jedi Knight Unofficial Specs Millennium" and "JK Hub mat2".

Now, I'm not the best of programmers, but slowly I managed to make sense of this file format. I wanted to learn this because I edit this game and I create some textures to it, and thought it would be nice to display them on my website. The way I wanted to display them was not to convert them to PNGs on the side, but rather to load them straight off (for example if there would be a change I wouldn't have to upload both the MAT file and remember to upload another PNG).

However, I seem to run into a little snag. The best I can do is to load/convert the image pixel by pixel, which is time consuming and times-out on large MAT files. I tried feeding the data straight in as imagecreatefromstring() but that failed (I'm guessing due to the lack of palette information). Is there a way of speeding up the processes any rather than doing it dot by dot?

My code looks like this: http://www.edwardleuf.org/Games/JK/IM_MAT_Loader.zip

// Create our palette
$colormap=imagecreate(256,1);
for($i=0; $i<256; $i++)
{
    $color=imagecolorallocate($colormap,$cmpR[$i],$cmpG[$i],$cmpB[$i]);
    imagesetpixel($colormap,$i,0,$color);
}

// Read whole mat into one string.
$wholemat = "";
if($zipfile!="")
{
    $zip = zip_open($zipfile);
    if(is_resource($zip))
    {
        While ($zip_entry = zip_read($zip))
        {
            $zip_ename = zip_entry_name($zip_entry);
            if($matfile!="" && $zip_ename==$matfile)
            {
                $wholemat = zip_entry_read($zip_entry,zip_entry_filesize($zip_entry));
            }
            else if($matfile=="" && strtoupper(substr(strrchr($zip_ename,"."),1)) == "MAT")
            {
                $wholemat = zip_entry_read($zip_entry,zip_entry_filesize($zip_entry));
            }
        }
    }
    zip_close($zip);
}
else
{
    if($matfile!="")
    {
        $mat = fopen($matfile,'r');
        $wholemat = fread($mat,filesize($matfile));
        fclose($mat);
    }
}
if($wholemat=="" || substr($wholemat,0,5)!="MAT 2")
{    // If we weren't successful in procuring a proper MAT file
    // produce a 2x2 blank
    header('Content-type: image/png');
    $img = imagecreatetruecolor(2,2);
    imagepng($img,'',9);
    imagedestroy($img);
    return;
}

// Type: 0 = single color, 1 = ?, 2 = full texture
$u = unpack("L",substr($wholemat,8,4));
$matType = $u[1];

// Number of textures or colors
$u = unpack("L",substr($wholemat,12,4));
$matRecordCount = $u[1];

// If single color, it is 0. If full, same as RecordCount
$u = unpack("L",substr($wholemat,16,4));
$matTextureCount = $u[1];

// 8 or 16 bits
$u = unpack("L",substr($wholemat,24,4));
$matBitDepth = $u[1];

$u = unpack("L",substr($wholemat,28,4));
$matBlueBits = $u[1];    // 0, 5, 8
$u = unpack("L",substr($wholemat,32,4));
$matGreenBits = $u[1];    // 0, 6 (16-bit 565), 5 (16-bit 1555), 8
$u = unpack("L",substr($wholemat,36,4));
$matRedBits = $u[1];    // 0, 5, 8

// The shift offset for the location of the R, G and B color values
// in the bitmap data. The color data is extracted by shifting
// the opposite direction of these values.
$u = unpack("L",substr($wholemat,40,4));
$matRedShiftL = $u[1];        // 11 for RGB565, 10 for ARGB1555
$u = unpack("L",substr($wholemat,44,4));
$matGreenShiftL = $u[1];    // 5
$u = unpack("L",substr($wholemat,48,4));
$matBlueShiftL = $u[1];        // 0

// The amount to shift the extracted color values to expand them from
// 5 or 6 bit values to 8 bit values. Unsure if JK actually uses these.
$u = unpack("L",substr($wholemat,52,4));
$matRedShiftR = $u[1];        // 3
$u = unpack("L",substr($wholemat,56,4));
$matGreenShiftR = $u[1];    // 2
$u = unpack("L",substr($wholemat,60,4));
$matBlueShiftR = $u[1];        // 3
$u = unpack("L",substr($wholemat,80,4));
$matTransColor = $u[1];

if($matType==0)
{    // Single Color Mat
    if($matBitDepth==8)
    {
        $img = imagecreate(64*$matRecordCount,64) or die("Cannot Initialize new GD image stream");
        imagepalettecopy($img,$colormap);
    }
    else if($matBitDepth==16)
    {
        $img = imagecreatetruecolor(64*$matRecordCount,64) or die("Cannot Initialize new GD image stream");
    }
    for($i=0; $i<$matRecordCount; $i++)
    {
        $u = unpack("L",substr($wholemat,80+($i*24),4));
        if($matBitDepth==8)
        {
            $carray = imagecolorsforindex($img,$u[1]);
            $color = imagecolorclosest($img,$carray[red],$carray[green],$carray[blue]);
        }
        else if($matBitDepth==16)
        {
            $color = $u[1];
        }
        imagefilledrectangle($img,$i*64,0,$i*64+64,64,$color);
    }

}
else if($matType==2)
{    // Full Texture
    $starttex = intval(76+$matRecordCount*40);
    $u = unpack("L",substr($wholemat,$starttex,4));
    $matSizeX = $u[1];
    $u = unpack("L",substr($wholemat,$starttex+4,4));
    $matSizeY = $u[1];
    if($matBitDepth==8)
    {
        $img = imagecreate($matSizeX*$matRecordCount,$matSizeY) or die("Cannot Initialize new GD image stream");
        imagepalettecopy($img,$colormap);
    }
    else if($matBitDepth==16)
    {
        $img = imagecreatetruecolor($matSizeX*$matRecordCount,$matSizeY) or die("Cannot Initialize new GD image stream");
    }
    $matTransparency=0;
    for($i=0; $i<$matRecordCount; $i++)
    {    // Each animation cel can in theory have different sizes.
        $u = unpack("L",substr($wholemat,$starttex,4));
        $matSizeX = $u[1];
        $u = unpack("L",substr($wholemat,$starttex+4,4));
        $matSizeY = $u[1];
        if($matTransparency==0)
        {
            $u = unpack("L",substr($wholemat,$starttex+8,4));
            $matTransparency = $u[1];
        }
        $u = unpack("L",substr($wholemat,$starttex+20,4));
        $matMipMaps = $u[1];
        if($matBitDepth==8)
        {
            $strimg = substr($wholemat,($starttex+24),($matSizeX*$matSizeY));
        }
        else if($matBitDepth==16)
        {
            $strimg = substr($wholemat,($starttex+24),($matSizeX*$matSizeY*2));
        }
        $j=0;
        for($y=0; $y<$matSizeY; $y++)
        {
            for($x=$matSizeX*$i; $x<$matSizeX+$matSizeX*$i; $x++)
            {
                if($matBitDepth==8)
                {
                    $carray = imagecolorsforindex($img,ord(substr($strimg,$j,1)));
                    $color = imagecolorclosest($img,$carray[red],$carray[green],$carray[blue]);
                    $j=$j+1;
                }
                else if($matBitDepth==16)
                {
                    if(strlen(substr($strimg,$j,2))==2)
                    {
                        $u = unpack("S",substr($strimg,$j,2));
                        $xr = ($u[1] & 0xf800) >> 11;
                        $xg = ($u[1] & 0x07e0) >> 5;
                        $xb = $u[1] & 0x001f;
                        $br = pow(2,$matRedBits)-1;
                        $bg = pow(2,$matGreenBits)-1;
                        $bb = pow(2,$matBlueBits)-1;
                        if($br>0) $nr = 255/$br * $xr; else $nr = $xr*8;
                        if($bg>0) $ng = 255/$bg * $xg; else $ng = $xg*4;
                        if($bb>0) $nb = 255/$bb * $xb; else $nb = $xb*8;
                        $color = imagecolorallocate($img,$nr,$ng,$nb);
                    //    echo $nr."\t".$ng."\t".$nb."\n";
                        $j=$j+2;
                    }
                }
                imagesetpixel($img,$x,$y,$color);
            }
        }
        // Jump over MipMaps...
        if($matMipMaps>1)
        {
            $j=$j+(($matSizeX/2)*($matSizeY/2));
            if($matMipMaps>2)
            {
                $j=$j+(($matSizeX/4)*($matSizeY/4));
                if($matMipMaps>3)
                {
                    $j=$j+(($matSizeX/8)*($matSizeY/8));
                }
            }
        }
        $starttex=$starttex+$j+24;
    }

}

if($matBitDepth==8)
{
    if($matTransparency!=0)
    {
        /* Not sure about Transparency Information.
           According to sources, this should be the index to mask out,
           but when looking at 08tgrate.mat it says index 85
           which is not the black that should be transparent.

        $carray = imagecolorsforindex($img,$matTransColor);
        $color = imagecolorclosest($img,$carray[red],$carray[green],$carray[blue]);

           Does JK ignore this completely? */

        $color = imagecolorclosest($img,0,0,0);
        imagecolortransparent($img,$color);
    }
}
else if($matBitDepth==16)
{
    if($matTransparency!=0)
    {
        imagecolortransparent($img,$matTransColor);
    }
}

if($thumbW!=0 && $thumbH!=0)
{
    $newwidth=$thumbW;
    $newheight=(imagesy($img)/imagesx($img))*$newwidth;
    if($newheight>$thumbH)
    {
        $newheight=$thumbH;
        $newwidth=(imagesx($img)/imagesy($img))*$newheight;
    }
    $tmp=imagecreatetruecolor($newwidth,$newheight);
    imagecopyresampled($tmp,$img,0,0,0,0,$newwidth,$newheight,imagesx($img),imagesy($img));
    header('Content-type: image/png');
    imagejpeg($tmp,'',50);
    imagedestroy($tmp);
}
else
{
    header('Content-type: image/png');
    imagepng($img,'',9);
}
imagedestroy($img);
imagedestroy($colormap);
unset($wholemat);

And this is the website using it: http://www.edwardleuf.org/Games/JK/MATs/

/Edward

Edward
  • 495
  • 1
  • 6
  • 20
  • Specs for 8-bit mats: http://millennium.massassi.net/jkspec/jkspecs.htm – Edward Nov 04 '13 at 13:51
  • Specs for 16-bit mats: http://www.jkhub.net/library/index.php?title=Reference:Material – Edward Nov 04 '13 at 13:51
  • `substr($wholemat,($starttex+24),($starttex+24)+($matSizeX*$matSizeY));` <-- this looks wrong. The second param for `substr()` is the *length* to extract, this looks like you are treating it as the end index for the chunk. In this case, you are probably processing *way* more data than you need to for a large block of data (also the result would likely be wrong). Also there are probably some efficiency gains to be had be interchanging some ifs/loops etc, can you show how the variables that are referenced but not initialised in the code above (`$img`, `$wholemat`, `$starttex` etc) are created? – DaveRandom Nov 04 '13 at 14:09
  • @DaveRandom Hah, thanks for pointing that out. Seeing as I just run up to the needed length in the for loop I hadn't noticed. A little less in the `strimg`. As to knowing how the variables are created, would that mean I have to post the whole code (1087 lines)? – Edward Nov 04 '13 at 16:28
  • Well, turns out I cannot post the entire code because there is a limit of 30000 characters. To know more about the initiazation of the variables you will have to download the ZIP. – Edward Nov 05 '13 at 19:57
  • Ahh I didn't spot the link to the zip, I'll try and take a look at it later on – DaveRandom Nov 06 '13 at 14:37

0 Answers0