Daggerfall Mod:Image formats/Texture

The UESPWiki – Your source for The Elder Scrolls since 1995
Jump to: navigation, search

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[edit]

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.

Texture file coarse structure
TextureFileHeader
?RecordHeaderList
?RecordData

TextureFileHeader[edit]

TextureFileHeader structure
Offset Type Name Description
0-1 Int16 RecordCount The count of RecordHeader elements which follow this header.
2-25 7-bit ASCII string Name A name or description of the texture file. This value is null-terminated.

RecordHeader[edit]

RecordHeader structure
0-1 UInt16 Unknown1 Unknown purpose.
2-5 Int32 RecordPosition The offset to the record's data position within the file, relative to the start of the file.
6-7 UInt16 Unknown2 Unknown purpose.
8-11 UInt32 Unknown3 Unknown purpose.
12-19 Int64 NullValue Always 0x0000000000000000

Record Data[edit]

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[edit]

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.

TextureRecord structure
Offset Type Name Description
0-1 Int16 OffsetX The X-axis display offset for the record's image.
2-3 Int16 OffsetY The Y-axis display offset for the record's image.
4-5 Int16 Width The width of the record's image.
6-7 Int16 Height The height of the record's image.
8-9 Compression (UInt16) Compression The Compression enumeration is detailed here. Any unknown values should be interpretted as Uncompressed.
10-13 UInt32 RecordSize The total size of the record's image data, including this 28 byte structure.
14-17 UInt32 DataOffset The position of the record's image data from the start of this structure.
18-19 Bool16 IsNormal This flag is required to decode the record's image data (detailed below).
20-21 UInt16 FrameCount The count of animation frames for this record's image.
22-23 UInt16 Unknown1 Unknown purpose.
24-25 Int16 XScale Divide this number by 256 to obtain a width modifier when the texture is used as a scene flat. For example, -0.5 means the width should be reduced by half, and 0.5 means the width should be enlarged by half. 0.0 means no change. Always equal to YScale.
26-27 Int16 YScale Divide this number by 256 to obtain a height modifier when the texture is used as a scene flat. For example, -0.5 means the height should be reduced by half, and 0.5 means the height should be enlarged by half. 0.0 means no change. Always equal to XScale.

UncompressedSingleFrame Records[edit]

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 - TextureRecord.Width 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 < Height; i++ ) { 
  buffer = file.ReadBytes( Width );
  file.Seek( 256 - Width, FromCurrentPosition );
}

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

UncompressedMultiframe Records[edit]

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[edit]

FrameData structure
Offset Type Name Description
0-1 UInt16 Width The width of the frame.
2-3 UInt16 Height The height of the frame.
4… Int8 PixelData The variable-length pixel data for the frame.

Reading UncompressedMultiframe Records[edit]

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 < c; i++ ) { 
    if ( true == isZero ) { 
      row[ p++ ] = 0;
    } else { 
      row[ p++ ] = file.ReadByte();
    }
  }
  if ( ( p < Width ) || ( ( p == Width ) && ( true == isZero ) ) ) { 
    c = file.ReadByte();
  }
  isZero = !isZero;
} while ( p < Width );

RecordRle Records[edit]

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 × 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[edit]

SpecialFrameHeader structure
Offset Type Name Description
0-1 Int16 RowOffset The offset to the actual frame-row data, relative to the record's DataOffset field.
2-3 RowEncoding (UInt16) RowEncoding This flag indicates that additional special handling is required.

RowEncoding[edit]

RowEncoding enumeration
Value Description
0x8000 IsRleEncoded The row will require a modified form of Rle decompression to decode.
0x0000 NotRleEncoded The row is not Rle compressed.

Reading modified Rle row data[edit]

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 < 0 ) { 
    probe = -probe;
    pixel = file.ReadByte();
    for ( i = 0; i < probe; i++ ) { 
      output[ p++ ] = pixel;
    }
  } else if ( 0 < probe ) { 
    for ( i = 0; i < probe; i++ ) { 
      output[ p++ ] = file.ReadByte();
    }
  }
} while ( p < w );

Reading RecordRle Records becomes something along the lines of:

foreach ( SpecialRowHeader header in specialFrameHeaderList ) { 
  file.Seek( header.RowOffset );
  if ( IsRleEncoded == header.RowEncoding ) { 
    row = GetModifiedRleEncodedRow( file );
  } else { 
    row = file.ReadBytes( Width );
  }
}

ImageRle Records[edit]

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 × TextureRecord.Height elements long. ImageRle Records are otherwise handled identically to RecordRle Records.

Special Texture Files[edit]

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.