Well a converted TIM i downloaded months ago shows 32x224. I tweaked the code to be:
width = (width * 16) / BPP
Better results when tested, however, something's a little funky:

Also, started work on an experimental Qt image plugin for Chrono Cross's altered TIM format used in battle textures. Until if the unknown data interspersed in the CLUT and image headers is ever identified, the plugin will only be read only.
Little update: wrote a Free Pascal unit that implements a TIM reader based on the TFPCustomImageReader class as well as a tim2bmp program. This is based on my Qt plugin, so expect that strange output if working with CLUT images. Should only need to type `fpc tim2bmp.pas` to compile both if you have Free Pascal installed and in your Windows PATH
fpreadtim.pas
{$mode objfpc}{$h+}
unit FPReadTIM;
interface
uses classes, fpimage;
type
TFPReaderTIM = class (TFPCustomImageReader)
protected
procedure InternalRead(Stream: TStream; Img: TFPCustomImage); override;
function InternalCheck(Stream: TStream): boolean; override;
public
constructor Create; override;
destructor Destroy; override;
end;
function RGBA5551ToFPColor(RGBA: word): TFPColor;
function RGBA5551ToDWord(RGBA: word): dword;
implementation
function Scale5To8(c: byte): byte;
begin
result := (c shl 3) or (c shr 2);
end;
constructor TFPReaderTIM.Create;
begin
inherited create;
end;
destructor TFPReaderTIM.Destroy;
begin
inherited destroy;
end;
function RGBA5551ToFPColor(RGBA: word): TFPColor;
begin
RGBA := leton(RGBA);
with result do
begin
red := swap(word(Scale5To8(RGBA and $1f)));
green := swap(word(Scale5To8((RGBA shr 5) and $1f)));
blue := swap(word(Scale5To8((RGBA shr 10) and $1f)));
alpha := swap(word(not Scale5To8((RGBA shr 15) and $1f)));
end;
end;
function RGBA5551ToDWord(RGBA: word): dword;
var
a, r, g, b: byte;
begin
RGBA := leton(RGBA);
r := Scale5To8(RGBA and $1f);
g := Scale5To8((RGBA shr 5) and $1f);
b := Scale5To8((RGBA shr 10) and $1f);
a := not Scale5To8((RGBA shr 15) and $1f);
result := swap(word((a shl 24) or (r shl 16) or (g shl 8) or b));
end;
function TFPReaderTIM.InternalCheck(Stream: TStream): boolean;
var
magic: dword;
begin
magic := leton(Stream.readdword);
result := (magic = $10);
end;
procedure TFPReaderTIM.InternalRead(Stream: TStream; Img: TFPCustomImage);
var
flags, clutsize, imgsize: dword;
colors, npals, i, w, h, x, y: word;
hasCLUT: boolean;
bpp, p: byte;
begin
flags := leton(Stream.readdword);
hasCLUT := (flags and $8) <> 0;
if (flags and $7) > 0 then bpp := (flags and $7)*8 else bpp := 4;
if hasCLUT then
begin
clutsize := leton(Stream.readdword)-12;
Stream.seek(4, socurrent);
colors := leton(Stream.readword);
npals := leton(Stream.readword);
Img.usepalette := true;
Img.palette.clear;
for i := 0 to colors*npals-1 do
Img.palette.add(RGBA5551ToFPColor(leton(Stream.readword)));
end;
imgsize := leton(Stream.readdword)-12;
Stream.seek(4, socurrent);
w := (leton(Stream.readword)*16) div bpp;
h := leton(Stream.readword);
Img.setsize(w, h);
x := 0;
y := 0;
while y < h do
begin
while x < w do
begin
case bpp of
4:
begin
p := Stream.readbyte;
Img.pixels[x,y] := swap(dword(p and $f0));
inc(x);
Img.pixels[x,y] := swap(dword(p and $f));
end;
8: Img.pixels[x,y] := swap(dword(Stream.readbyte));
16: Img.pixels[x,y] := swap(RGBA5551ToDWord(leton(Stream.readword)));
else
end;
inc(x);
end;
x := 0;
inc(y);
end;
end;
end.
tim2bmp.pas
{$mode objfpc}{$h+}
{$apptype console}
program tim2bmp;
uses FPReadTIM, FPImage, classes, FPWriteBMP, sysutils;
var
img: TFPMemoryImage;
r: TFPCustomImageReader;
w: TFPCustomImageWriter;
rf, wf: string;
begin
if paramcount <> 2 then
begin
writeln('tim2bmp [source] [dest]');
exit;
end;
try
r := TFPReaderTIM.Create;
w := tfpwriterbmp.create;
tfpwriterbmp(w).bitsperpixel := 32;
img := TFPMemoryImage.create(0,0);
rf := paramstr(1);
wf := paramstr(2);
img.loadfromfile(rf, r);
img.savetofile(wf, w);
r.free;
w.free;
img.free;
except
on e: exception do
writeln('error: ', e.message);
end;
end.