Question

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

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top