Tes2Mod:Image formats/Texture

Texture files are all files in the ARENA2 game directory that match TEXTURE.??? where ??? is a 3-digit ASCII encoded number, ranging from "000" through "511", with some numbers missing for a total of 472 files.  

General File Layout
The overall texture format is slightly more complicated than the usual image format.  With only four exceptions, a texture file begins with a 26 byte TextureFileHeader structure, followed by a contiguous list of RecordHeader structures, followed by the actual data which composes each record.  The record data itself is not in a single, uniform format, but instead comes in different formats, detailed below.  



TextureFileHeader


RecordHeader


Record Data
Immediately following the RecordHeaderList is the data which composes the individual records of the file.  Sections of this file require different decoding mechanisms (described below) in order to read the individual records.

TextureRecord
Starting at each RecordHeader.RecordPosition is a TextureRecord structure, which describes the various properties and attributes of the record, such as the number of frames or compression used to store the frame data.  

A TextureRecord structure is very similar to an ImgHeader structure, but most notably the offset and size fields are transposed.  To decode the data referenced by the DataOffset field, one must be aware there are different subformats.  The appropriate decoder is selected by consulting the Compression, IsNormal, and FrameCount fields.  The reader is reminded that while most records encountered will define an image with but a single frame, there are several textures with multiframe animation records.  The swirling animation which frames enchanted items in the inventory screen is one such example of a multiframe animation texture record.  Most of the light-sources encountered in the game (torches, candles, fires, etc.) are more such examples.  

After navigating to the address specified by the DataOffset field, one may begin reading and decoding the record data.  



UncompressedSingleFrame Records
The majority of records follow this type.  If the TextureRecord.Compression field is Uncompressed (or not within the defined range of the Compression enumeration), and the FrameCount is 1, then this algorithm defines the proper decoder.  

The record defines a single frame image, but the image data is stored in a manner where it is interleaved with other records in 256 byte chunks.  In order to successfully navigate the image data, one must read TextureRecord.Width bytes of pixel data (uncompressed, palette index values as per RCI files) and then skip ahead  256 -    bytes from the present position.  One must do this TextureRecord.Height times, once for each row of the image.  

Example code: 
 
for ( i = 0; i  

Clearly this places a maximum width of Uncompressed images at 256 pixels.

UncompressedMultiframe Records
The next most common subformat of records are decoded with this algorithm.  If the Compression field is Uncompressed (or not within the defined range of the Compression enumeration) and the framecount is greater than 1, then this algorithm defines the proper decoder.  

While the Compression field may indicate Uncompressed, there is a minor form of compression present.  Fortunately the compression is fairly trivial to implement.  Since the majority of images are bordered by several transparent pixels (palette index 0x00), a system of zero/non-zero alternations is used to read the row data for the record's image frames.  

The data referenced by the TextureRecord.DataOffset field is a contiguous list of Int32 values, TextureRecord.FrameCount elements long.  Each of these values is an offset relative to the TextureRecord.DataOffset field's value.  Each of these offsets refers to a FrameData structure, which declares the dimensions of the animation frame.  

FrameData


Reading UncompressedMultiframe Records
So first one navigates to TextureRecord.DataOffset and reads a number of Int32 offsets, TextureRecord.FrameCount in count.  At each offset a FrameData structure is defined.  For each FrameData structure, one reads the FrameData.Width and FrameData.Height, and then reads a variable number of bytes according to the decoding algorithm.  

Example code for decoding frames: 
 
Int16 Width = file.ReadUInt16();
Int16 Height = file.ReadUInt16();
Byte[] row = new Byte[ Width ];
Int32 c = file.ReadByte();
Boolean isZero = true;
Int32 p = 0;
do { 
  for ( i = 0; i  

RecordRle Records
This subformat is indicated by a value of RecordRle for the record's Compression field.  The DataOffset field refers to a contiguous list of SpecialFrameHeader structure values, TextureRecord.FrameCount &times; TextureRecord.Height elements long.  Since no RecordRle Records are known to have a TextureRecord.FrameCount other than 1, there should be exactly TextureRecord.Height elements.  

After reading the list of SpecialFrameHeader structures, comes the trouble of decoding the data referenced by the SpecialFrameHeader.FrameOffset field.  Depending on the SpecialFrameHeader.RowEncoding field's value, the data will either be read in a modified Rle format or it will be read as raw pixel data (as per RCI files).  Each row of the frame is independent of all other rows of the frame, meaning the decision must be made on a row-by-row basis; One row might be compressed and the following row might be Uncompressed.  

If the SpecialFrameHeader.RowEncoding field is equal to NotRleRowEncoded, one simply reads TextureRecord.Width bytes from the file as raw pixel data (Uncompressed palette index values).  If the SpecialFrameHeader.RowEncoding field is equal to IsRleRowEncoded then one must rely on a modified Rle decoding routine for that row.  Such row data is prefixed with the length of the row data, followed by the rle-compressed row data.  

SpecialFrameHeader


RowEncoding


Reading modified Rle row data
Example code for reading modified Rle row data: 
 
Int32 w = file.ReadInt16();

Int32 p = 0;
Int32 probe;
Byte pixel;
do { 
  probe = file.ReadInt16();
  if ( probe  

Reading RecordRle Records becomes something along the lines of: 
 

ImageRle Records
If the Compression field is ImageRle, then this algorithm defines the proper decoder.  The DataOffset field refers to a contiguous list of SpecialFrameHeader structures, TextureRecord.FrameCount &times; TextureRecord.Height elements long.  ImageRle Records are otherwise handled identically to RecordRle Records.

Special Texture Files
Note that there are 4 special texture files which are not described by the format and contain no images: 
;TEXTURE.000 
;TEXTURE.001
: These two texture files each contain 128 (0x80) images but with no TextureRecord or PixelData structures; just a TextureFileHeader and the TextureRecordHeaderList.  The high byte of the RecordHeader.Unknown1 field assumedly holds the palette index for the color to use for the texture.  The texture is used for any objects colored with a single, solid color rather than a bitmap.  The file can be identified by an image offset of 0.
;TEXTURE.215
;TEXTURE.217
: Each of these textures contain one image but with only the TextureFileHeader and TextureRecordHeaderList list, but no further data.  These files are most likely not used.