Rather than trying to explain the format in detail, I'm just going to post the code I use for reading it. This is adapted from various other snippets of code I've found in other places, I don't remember where anymore :)
LBXFile.FileName is the name of the LBX file (e.g. FIGURES1.LBX).
FileOffset is the offset from the start of the LBX file for this subfile (see other post about how to determine offsets inside an LBX file)
Implode.
Code: Select all
{===========================================================================
|
| Type : TGfxHeader
| Description : Header from all graphics files
|
==========================================================================}
TGfxHeader =
packed record
Width, Height, Unknown1, BitmapCount, Unknown2, Unknown3,
Unknown4, PaletteInfoOffset, Unknown5: Word;
end;
{===========================================================================
|
| Type : TGfxPaletteInfo
| Description : Structure describing the portion of the palette contained
| in the graphics file
|
==========================================================================}
TGfxPaletteInfo =
packed record
PaletteOffset, FirstPaletteColourIndex, PaletteColourCount,
Unknown: Word;
end;
{===========================================================================
|
| Type : TGfxPaletteEntry
| Description : Palettes are made up of multiple entries like this
|
==========================================================================}
TGfxPaletteEntry =
packed record
r, g, b: Byte;
end;
TGfxPalette = Array [0..255] of TColor;
const
{==========================================================================
|
| Constant : MOM_PALETTE
| Description : Default Master of Magic palette
|
=========================================================================}
MOM_PALETTE: Array [0..255] of TGfxPaletteEntry =
((r: $0; g: $0; b: $0),
(r: $8; g: $4; b: $4),
(r: $24; g: $1c; b: $18),
(r: $38; g: $30; b: $2c),
(r: $48; g: $40; b: $3c),
(r: $58; g: $50; b: $4c),
(r: $68; g: $60; b: $5c),
(r: $7c; g: $74; b: $70),
(r: $8c; g: $84; b: $80),
(r: $9c; g: $94; b: $90),
(r: $ac; g: $a4; b: $a0),
(r: $c0; g: $b8; b: $b4),
(r: $d0; g: $c8; b: $c4),
(r: $e0; g: $d8; b: $d4),
(r: $f0; g: $e8; b: $e4),
(r: $fc; g: $fc; b: $fc),
(r: $38; g: $20; b: $1c),
(r: $40; g: $2c; b: $24),
(r: $48; g: $34; b: $2c),
(r: $50; g: $3c; b: $30),
(r: $58; g: $40; b: $34),
(r: $5c; g: $44; b: $38),
(r: $60; g: $48; b: $3c),
(r: $64; g: $4c; b: $3c),
(r: $68; g: $50; b: $40),
(r: $70; g: $54; b: $44),
(r: $78; g: $5c; b: $4c),
(r: $80; g: $64; b: $50),
(r: $8c; g: $70; b: $58),
(r: $94; g: $74; b: $5c),
(r: $9c; g: $7c; b: $64),
(r: $a4; g: $84; b: $68),
(r: $ec; g: $c0; b: $d4),
(r: $d4; g: $98; b: $b4),
(r: $bc; g: $74; b: $94),
(r: $a4; g: $54; b: $7c),
(r: $8c; g: $38; b: $60),
(r: $74; g: $24; b: $4c),
(r: $5c; g: $10; b: $34),
(r: $44; g: $4; b: $20),
(r: $ec; g: $c0; b: $c0),
(r: $d4; g: $94; b: $94),
(r: $bc; g: $74; b: $74),
(r: $a4; g: $54; b: $54),
(r: $8c; g: $38; b: $38),
(r: $74; g: $24; b: $24),
(r: $5c; g: $10; b: $10),
(r: $44; g: $4; b: $4),
(r: $ec; g: $d4; b: $c0),
(r: $d4; g: $b4; b: $98),
(r: $bc; g: $98; b: $74),
(r: $a4; g: $7c; b: $54),
(r: $8c; g: $60; b: $38),
(r: $74; g: $4c; b: $24),
(r: $5c; g: $34; b: $10),
(r: $44; g: $24; b: $4),
(r: $ec; g: $ec; b: $c0),
(r: $d4; g: $d4; b: $94),
(r: $bc; g: $bc; b: $74),
(r: $a4; g: $a4; b: $54),
(r: $8c; g: $8c; b: $38),
(r: $74; g: $74; b: $24),
(r: $5c; g: $5c; b: $10),
(r: $44; g: $44; b: $4),
(r: $d4; g: $ec; b: $bc),
(r: $b8; g: $d4; b: $98),
(r: $98; g: $bc; b: $74),
(r: $7c; g: $a4; b: $54),
(r: $60; g: $8c; b: $38),
(r: $4c; g: $74; b: $24),
(r: $38; g: $5c; b: $10),
(r: $24; g: $44; b: $4),
(r: $c0; g: $ec; b: $c0),
(r: $98; g: $d4; b: $98),
(r: $74; g: $bc; b: $74),
(r: $54; g: $a4; b: $54),
(r: $38; g: $8c; b: $38),
(r: $24; g: $74; b: $24),
(r: $10; g: $5c; b: $10),
(r: $4; g: $44; b: $4),
(r: $c0; g: $ec; b: $d8),
(r: $98; g: $d4; b: $b8),
(r: $74; g: $bc; b: $98),
(r: $54; g: $a4; b: $7c),
(r: $38; g: $8c; b: $60),
(r: $24; g: $74; b: $4c),
(r: $10; g: $5c; b: $38),
(r: $4; g: $44; b: $24),
(r: $f4; g: $c0; b: $a0),
(r: $e0; g: $a0; b: $84),
(r: $cc; g: $84; b: $6c),
(r: $c8; g: $8c; b: $68),
(r: $a8; g: $78; b: $54),
(r: $98; g: $68; b: $4c),
(r: $8c; g: $60; b: $44),
(r: $7c; g: $50; b: $3c),
(r: $c0; g: $d8; b: $ec),
(r: $94; g: $b4; b: $d4),
(r: $70; g: $98; b: $bc),
(r: $54; g: $7c; b: $a4),
(r: $38; g: $64; b: $8c),
(r: $24; g: $4c; b: $74),
(r: $10; g: $38; b: $5c),
(r: $4; g: $24; b: $44),
(r: $c0; g: $c0; b: $ec),
(r: $98; g: $98; b: $d4),
(r: $74; g: $74; b: $bc),
(r: $54; g: $54; b: $a4),
(r: $3c; g: $38; b: $8c),
(r: $24; g: $24; b: $74),
(r: $10; g: $10; b: $5c),
(r: $4; g: $4; b: $44),
(r: $d8; g: $c0; b: $ec),
(r: $b8; g: $98; b: $d4),
(r: $98; g: $74; b: $bc),
(r: $7c; g: $54; b: $a4),
(r: $60; g: $38; b: $8c),
(r: $4c; g: $24; b: $74),
(r: $38; g: $10; b: $5c),
(r: $24; g: $4; b: $44),
(r: $ec; g: $c0; b: $ec),
(r: $d4; g: $98; b: $d4),
(r: $bc; g: $74; b: $bc),
(r: $a4; g: $54; b: $a4),
(r: $8c; g: $38; b: $8c),
(r: $74; g: $24; b: $74),
(r: $5c; g: $10; b: $5c),
(r: $44; g: $4; b: $44),
(r: $d8; g: $d0; b: $d0),
(r: $c0; g: $b0; b: $b0),
(r: $a4; g: $90; b: $90),
(r: $8c; g: $74; b: $74),
(r: $78; g: $5c; b: $5c),
(r: $68; g: $4c; b: $4c),
(r: $5c; g: $3c; b: $3c),
(r: $48; g: $2c; b: $2c),
(r: $d0; g: $d8; b: $d0),
(r: $b0; g: $c0; b: $b0),
(r: $90; g: $a4; b: $90),
(r: $74; g: $8c; b: $74),
(r: $5c; g: $78; b: $5c),
(r: $4c; g: $68; b: $4c),
(r: $3c; g: $5c; b: $3c),
(r: $2c; g: $48; b: $2c),
(r: $c8; g: $c8; b: $d4),
(r: $b0; g: $b0; b: $c0),
(r: $90; g: $90; b: $a4),
(r: $74; g: $74; b: $8c),
(r: $5c; g: $5c; b: $78),
(r: $4c; g: $4c; b: $68),
(r: $3c; g: $3c; b: $5c),
(r: $2c; g: $2c; b: $48),
(r: $d8; g: $dc; b: $ec),
(r: $c8; g: $cc; b: $dc),
(r: $b8; g: $c0; b: $d4),
(r: $a8; g: $b8; b: $cc),
(r: $9c; g: $b0; b: $cc),
(r: $94; g: $ac; b: $cc),
(r: $88; g: $a4; b: $cc),
(r: $88; g: $94; b: $dc),
(r: $fc; g: $f0; b: $90),
(r: $fc; g: $e4; b: $60),
(r: $fc; g: $c8; b: $24),
(r: $fc; g: $ac; b: $c),
(r: $fc; g: $78; b: $10),
(r: $d0; g: $1c; b: $0),
(r: $98; g: $0; b: $0),
(r: $58; g: $0; b: $0),
(r: $90; g: $f0; b: $fc),
(r: $60; g: $e4; b: $fc),
(r: $24; g: $c8; b: $fc),
(r: $c; g: $ac; b: $fc),
(r: $10; g: $78; b: $fc),
(r: $0; g: $1c; b: $d0),
(r: $0; g: $0; b: $98),
(r: $0; g: $0; b: $58),
(r: $fc; g: $c8; b: $64),
(r: $fc; g: $b4; b: $2c),
(r: $ec; g: $a4; b: $24),
(r: $dc; g: $94; b: $1c),
(r: $cc; g: $88; b: $18),
(r: $bc; g: $7c; b: $14),
(r: $a4; g: $6c; b: $1c),
(r: $8c; g: $60; b: $24),
(r: $78; g: $54; b: $24),
(r: $60; g: $44; b: $24),
(r: $48; g: $38; b: $24),
(r: $34; g: $28; b: $1c),
(r: $90; g: $68; b: $34),
(r: $90; g: $64; b: $2c),
(r: $94; g: $6c; b: $34),
(r: $94; g: $70; b: $40),
(r: $8c; g: $5c; b: $24),
(r: $90; g: $64; b: $2c),
(r: $90; g: $68; b: $30),
(r: $98; g: $78; b: $4c),
(r: $60; g: $3c; b: $2c),
(r: $54; g: $a4; b: $a4),
(r: $c0; g: $0; b: $0),
(r: $fc; g: $88; b: $e0),
(r: $fc; g: $58; b: $84),
(r: $f4; g: $0; b: $c),
(r: $d4; g: $0; b: $0),
(r: $ac; g: $0; b: $0),
(r: $e8; g: $a8; b: $fc),
(r: $e0; g: $7c; b: $fc),
(r: $d0; g: $3c; b: $fc),
(r: $c4; g: $0; b: $fc),
(r: $90; g: $0; b: $bc),
(r: $fc; g: $f4; b: $7c),
(r: $fc; g: $e4; b: $0),
(r: $e4; g: $d0; b: $0),
(r: $a4; g: $98; b: $0),
(r: $64; g: $58; b: $0),
(r: $ac; g: $fc; b: $a8),
(r: $74; g: $e4; b: $70),
(r: $0; g: $bc; b: $0),
(r: $0; g: $a4; b: $0),
(r: $0; g: $7c; b: $0),
(r: $ac; g: $a8; b: $fc),
(r: $80; g: $7c; b: $fc),
(r: $0; g: $0; b: $fc),
(r: $0; g: $0; b: $bc),
(r: $0; g: $0; b: $7c),
(r: $30; g: $30; b: $50),
(r: $28; g: $28; b: $48),
(r: $24; g: $24; b: $40),
(r: $20; g: $1c; b: $38),
(r: $1c; g: $18; b: $34),
(r: $18; g: $14; b: $2c),
(r: $14; g: $10; b: $24),
(r: $10; g: $c; b: $20),
(r: $a0; g: $a0; b: $b4),
(r: $88; g: $88; b: $a4),
(r: $74; g: $74; b: $90),
(r: $60; g: $60; b: $80),
(r: $50; g: $4c; b: $70),
(r: $40; g: $3c; b: $60),
(r: $30; g: $2c; b: $50),
(r: $24; g: $20; b: $40),
(r: $18; g: $14; b: $30),
(r: $10; g: $c; b: $20),
(r: $14; g: $c; b: $8),
(r: $18; g: $10; b: $c),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0),
(r: $0; g: $0; b: $0));
{===============================================================================
|
| Method : SaveGfxAsBmps
| Description : Converts this graphics file inside the LBX file to multiple
| BMPs
|
==============================================================================}
procedure TLBXFileContents.SaveGfxAsBmps (FileName: String);
var
fin: TFileStream;
BitmapNo, BitmapOffset, RLE_val, ColourNo, BitmapStart, BitmapEnd, x, y,
BitmapSize, BitmapIndex, next_ctl, long_data, n_r, last_pos, RleLength,
RleCounter: Integer;
BitmapNumberString: String;
GfxHeader: TGfxHeader;
GfxPaletteInfo: TGfxPaletteInfo;
GfxPaletteEntry: TGfxPaletteEntry;
BitmapOffsets: TList;
Palette: TGfxPalette;
b: TBitmap;
ImageBuffer: Array [0..65499] of Byte;
ColourValue: TColor;
begin
{ Open LBX file }
fin := TFileStream.Create (LBXFile.FileName, fmOpenRead or fmShareDenyWrite);
try
{ Find start of graphics file }
fin.Seek (FileOffset, soFromBeginning);
{ Read graphics header }
fin.ReadBuffer (GfxHeader, SizeOf (TGfxHeader));
{ Read file offsets of each bitmap }
BitmapOffsets := TList.Create;
try
{ Not -1 since there is an extra offset specifying the end of the
last image }
for BitmapNo := 0 to GfxHeader.BitmapCount do
begin
fin.ReadBuffer (BitmapOffset, 4);
BitmapOffsets.Add (Pointer (BitmapOffset));
end;
{ Default palette }
for ColourNo := 0 to 255 do
begin
GfxPaletteEntry := MOM_PALETTE [ColourNo];
Palette [ColourNo] :=
(GfxPaletteEntry.b shl 16) +
(GfxPaletteEntry.g shl 8) +
GfxPaletteEntry.r;
end;
{ Read palette info if present }
if GfxHeader.PaletteInfoOffset > 0 then
begin
fin.Seek (FileOffset + GfxHeader.PaletteInfoOffset, soFromBeginning);
fin.ReadBuffer (GfxPaletteInfo, SizeOf (TGfxPaletteInfo));
{ Read palette }
fin.Seek (FileOffset + GfxPaletteInfo.PaletteOffset, soFromBeginning);
for ColourNo := 0 to GfxPaletteInfo.PaletteColourCount - 1 do
begin
fin.ReadBuffer (GfxPaletteEntry, SizeOf (TGfxPaletteEntry));
{ Multiply colour values up by 4 }
Palette [GfxPaletteInfo.FirstPaletteColourIndex + ColourNo] :=
(GfxPaletteEntry.b shl 18) +
(GfxPaletteEntry.g shl 10) +
(GfxPaletteEntry.r shl 2);
end;
end else
begin
{ No palette info, use defaults }
GfxPaletteInfo.FirstPaletteColourIndex := 0;
GfxPaletteInfo.PaletteColourCount := 255;
end;
{ Reuse the same bitmap for each image }
b := TBitmap.Create;
try
b.Width := GfxHeader.Width;
b.Height := GfxHeader.Height;
{ Set background colour }
for x := 0 to GfxHeader.Width - 1 do
for y := 0 to GfxHeader.Height - 1 do
b.Canvas.Pixels [x, y] := $FF00FF;
{ Values of at least this indicate run length values }
RLE_val := GfxPaletteInfo.FirstPaletteColourIndex +
GfxPaletteInfo.PaletteColourCount;
{ Convert each bitmap }
for BitmapNo := 0 to GfxHeader.BitmapCount - 1 do
begin
BitmapStart := Integer (BitmapOffsets.Items [BitmapNo]);
BitmapEnd := Integer (BitmapOffsets.Items [BitmapNo + 1]);
BitmapSize := BitmapEnd - BitmapStart;
if BitmapSize > 65500 then
raise ELBXException.Create
('Does not support encoded images larger than 65500 bytes, found image of size ' +
IntToStr (BitmapSize));
{ Read in entire bitmap }
fin.Seek (FileOffset + BitmapStart, soFromBeginning);
fin.ReadBuffer (ImageBuffer, BitmapSize);
{ Byte 0 tells us whether to reset the image half way
through an animation }
if (ImageBuffer [0] = 1) and (BitmapNo > 0) then
for x := 0 to GfxHeader.Width - 1 do
for y := 0 to GfxHeader.Height - 1 do
b.Canvas.Pixels [x, y] := $FF00FF;
{ Decode bitmap }
BitmapIndex := 1; { Current index into the image buffer }
x := 0;
y := GfxHeader.Height;
next_ctl := 0;
long_data := 0;
n_r := 0;
last_pos := 0;
while (x < GfxHeader.Width) and (BitmapIndex < BitmapSize) do
begin
y := 0;
if (ImageBuffer [BitmapIndex] = $FF) then
begin
inc (BitmapIndex);
RLE_val := GfxPaletteInfo.FirstPaletteColourIndex +
GfxPaletteInfo.PaletteColourCount;
end else
begin
long_data := ImageBuffer [BitmapIndex + 2];
next_ctl := BitmapIndex + ImageBuffer [BitmapIndex + 1] + 2;
case ImageBuffer [BitmapIndex] of
$00: RLE_val := GfxPaletteInfo.FirstPaletteColourIndex +
GfxPaletteInfo.PaletteColourCount;
$80: RLE_val := $E0;
else
raise ELBXException.Create ('Unrecognized RLE value');
end;
y := ImageBuffer [BitmapIndex + 3];
inc (BitmapIndex, 4);
n_r := BitmapIndex;
while n_r < next_ctl do
begin
while (n_r < BitmapIndex + long_data) and (x < GfxHeader.Width) do
begin
if (ImageBuffer [n_r] >= RLE_val) then
begin
{ This value is an run length, the
next value is the value to repeat }
last_pos := n_r + 1;
RleLength := ImageBuffer [n_r] - RLE_val + 1;
{ if (RleLength + y > GfxHeader.Height) then
raise ELBXException.Create ('RLE length overrun on y');}
RleCounter := 0;
while (RleCounter < RleLength) and (y < GfxHeader.Height) do
begin
if (x < GfxHeader.Width) and (y < GfxHeader.Height) and
(x >= 0) and (y >= 0) then
begin
ColourValue := Palette [ImageBuffer [last_pos]];
if ColourValue = $B4A0A0 then
b.Canvas.Pixels [x, y] := $00FF00
else
b.Canvas.Pixels [x, y] := ColourValue;
end else
raise ELBXException.Create ('RLE length overrun on output');
inc (y);
inc (RleCounter);
end;
inc (n_r, 2);
end else
begin
{ Regular single pixel }
if (x < GfxHeader.Width) and (y < GfxHeader.Height) and
(x >= 0) and (y >= 0) then
begin
ColourValue := Palette [ImageBuffer [n_r]];
if ColourValue = $B4A0A0 then
b.Canvas.Pixels [x, y] := $00FF00
else
b.Canvas.Pixels [x, y] := ColourValue;
end;
{ else
raise ELBXException.Create ('Buffer overrun');}
inc (n_r);
inc (y);
end;
end;
if n_r < next_ctl then
begin
{ On se trouve sur un autre RLE sur la ligne
Some others data are here }
inc (y, ImageBuffer [n_r + 1]); { next pos Y to write pixels }
BitmapIndex := n_r + 2;
long_data := ImageBuffer [n_r]; { number of data to put }
inc (n_r, 2);
{ if n_r >= next_ctl then
raise ELBXException.Create ('More RLE but lines too short');}
end;
end;
BitmapIndex := next_ctl; { jump to next line }
end;
inc (x);
end;
{ Save bitmap }
BitmapNumberString := IntToStr (BitmapNo);
while Length (BitmapNumberString) < 3 do
BitmapNumberString := '0' + BitmapNumberString;
b.SaveToFile (ChangeFileExt (FileName, '') + '_' + BitmapNumberString + '.bmp');
end;
finally
b.Free;
end;
finally
BitmapOffsets.Free;
end;
finally
fin.Free;
end;
end;