Difference between revisions of "Picture format"

From DoomWiki.org

[checked revision][checked revision]
m (Earlier versions of the format: RoTT patch_t format)
m (Earlier versions of the format: That search for syntaxhighlight tags clearly did NOT find everything.)
Line 221: Line 221:
  
 
A minor variant on the beta {{c|patch_t}} format is also used by ''[[Rise of the Triad]]'':
 
A minor variant on the beta {{c|patch_t}} format is also used by ''[[Rise of the Triad]]'':
<syntaxhighlight lang="C">
+
 
typedef struct
+
typedef struct
{
+
{
  short          origsize;        // the orig size of "grabbed" gfx
+
    short          origsize;        // the orig size of "grabbed" gfx
  short          width;            // bounding box size
+
    short          width;            // bounding box size
  short          height;
+
    short          height;
  short          leftoffset;      // pixels to the left of origin
+
    short          leftoffset;      // pixels to the left of origin
  short          topoffset;        // pixels above the origin
+
    short          topoffset;        // pixels above the origin
  unsigned short collumnofs[320];  // only [width] used, the [0] is &collumnofs[width]
+
    unsigned short collumnofs[320];  // only [width] used, the [0] is &collumnofs[width]
} patch_t;
+
} patch_t;
</syntaxhighlight>
 
  
 
===Other early picture formats===
 
===Other early picture formats===

Revision as of 08:29, 19 October 2015

Many of the Doom engine graphics, including wall patches, sprites, and all menu graphics, are stored in the WAD files in a special picture format. Notably excepted are the textures for floors and ceilings, which are known as flats.

Details of the picture format in Doom are given in the Unofficial Doom Specs.

A picture header gives its width and height, and offset values. Following the header are pointers to data for each column of pixels; the number of these pointers is equal to the picture width.

The data for each column is divided into posts, which are lines of colored pixels going downward on the screen. Each post is described by its starting height (relative to the top of the picture) and number of pixels, followed by a value for each of the pixels. Picture descriptions can (and do) skip over some pixel positions; these pixels are transparent. (Since transparent pixels are not changed when drawing a particular picture, whatever was drawn into the frame buffer previously will show through.)

Each pixel is given as an unsigned byte (and thus is valued from 0 to 255). The pixel value is first used as an index into the current COLORMAP, which gives a new pixel value (from 0 to 255) adjusted for the desired light level. (At full brightness, the pixel value is unchanged.) Then this new pixel value is written into the frame buffer. The actual red, green, and blue values corresponding to the palette index in the current palette are stored in the VGA graphics card's 8-bit hardware palette.

Note that gamma correction, a user-adjustable setting that can lighten the colors for dark-looking monitors, is handled when setting the game's palette and not when actually drawing the graphics themselves. This avoids an additional indirection.

Programming algorithms

Converting to a doom picture

This algorithm will convert some pixel data (eg, from a windows bitmap file) to a doom picture.

Notes:
------

Byte = 0 - 255
Word = 0 - 65535
DWord = 0 - 4294967295

dummy_value = 	Byte, those unused bytes in the file (excerpt from UDS: "..left overs from NeXT machines?..")
picture_* = 	Word, the maximum width for an image in doom picture format is 256 pixels
pixel_count = 	Byte, the number of pixels in a post
Pixel = 	Byte, the pixel colour
column_array =	array of DWord, this holds all the post start offsets for each column


Algorithm:
----------

begin

write picture_width to file
write picture_height to file
write picture_top to file
write picture_left to file

while loop, exit on x = picture width
	increase column_array by 1

	write memory buffer position to end of column_array

	y = 0

	operator = true

	do while loop, until y = picture height
		get Pixel value
		
		if the pixel = transparent_colour and operator = false then
			dummy_value = 0
			
			write dummy_value to memory buffer

			operator = true

		otherwise, if pixel != transparent_colour and operator = true then
			row_start = y
			
			pixel_count = 0

			dummy value = 0

			write above post data to memory buffer

			offset = current post position in memory buffer

			operator = false

		otherwise, if pixel != transparent_colour and operator = false then
			increment current post pixel_count

			if offset > 0 and pixel count > 0 then
				previous_offset = current post position

				seek back in memory buffer by offset - 2

				write pixel_count to memory buffer

				seek back to previous_offset
			end block
			
			write pixel to memory buffer
		end block

		increment y by 1

	end block

	if operator = true or y = height then
		Pixel = 0

		write Pixel to memory buffer

		rowstart = 255
		
		write rowstart to memory buffer
	end block

	increment x by 1

end block

seek memory buffer position to 0

block_size = picture_width * size of dword

allocate block_memory, filled with 0's, with block_size

write block_memory to file, using block_size as size

offset = current file_position

free block_memory

seek to position 8 in file from start

for loop, count = 0, break on count = number of elements in column_array
	column_offset = column_array[count] + offset

	write column_offset to file
end block

write memory buffer to file

Converting from a doom picture

This algorithm will convert a doom picture to some pixel data.

Notes:
------

Byte = 0 - 255
Word = 0 - 65535
DWord = 0 - 4294967295

dummy_value = 	Byte, those unused bytes in the file (excerpt from UDS: "..left overs from NeXT machines?..")
picture_* = 	Word, the maximum width for an image in doom picture format is 256 pixels
pixel_count = 	Byte, the number of pixels in a post
Pixel = 	Byte, the pixel colour
column_array =	array of DWord, this holds all the post start offsets for each column

doom image = could be a file or memory stream

Algorithm:
----------

create a image with a pixel format of 8bit and the doom palette, set the background colour to a contrasting colour (cyan).

read width from doom image (word)
read height from doom image (word)
read left from doom image (word)
read top from doom image (word)

create column_array with width number of elements

for loop, i = 0, break on i = width - 1
	column_array[i] = read from doom image, 4 bytes
end block

for loop, i = 0, break on i = width - 1
	seek doom image to column_array[i] from beginning of doom image

	rowstart = 0

	while loop, rowstart != 255
		read rowstart from doom image, 1 byte
		
		if rowstart = 255, break from this loop

		read pixel_count from doom image, 1 byte

		read dummy_value from doom image, 1 byte

		for loop, j = 0, break on j = pixel_count - 1
			read Pixel from doom image, 1 byte
			
			write Pixel to image, j + rowstart = row, i = column
		end block
		
		read dummy_value from doom image, 1 byte
	end block
end block

Earlier versions of the format

The patch format originally (in the alpha and beta version) was more compact but limited. The alpha used bytes (instead of words) for dimension and starting offsets in the picture header. Both alpha and beta use words (instead of dwords) for locating column offsets, and neither features the dummy bytes that were added to the column spans in the final version.

For comparison, a table of the old and the new inserted in the patch_t struct from the Doom source code:

Alpha size Beta size Final size
typedef struct
{
byte short short width; // bounding box size
byte short short height;
byte short short leftoffset; // pixels to the left of origin
byte short short topoffset; // pixels below the origin
short short int columnofs[8]; // only [width] used
// the [0] is &columnofs[width]
} patch_t;

A minor variant on the beta patch_t format is also used by Rise of the Triad:

typedef struct
{
   short          origsize;         // the orig size of "grabbed" gfx
   short          width;            // bounding box size
   short          height;
   short          leftoffset;       // pixels to the left of origin
   short          topoffset;        // pixels above the origin
   unsigned short collumnofs[320];  // only [width] used, the [0] is &collumnofs[width]
} patch_t;

Other early picture formats

Since the alpha version of the picture format used a byte for height and width, it did not support images larger than 255 pixels in any dimension. Other formats were therefore needed for wide graphics. The following formats have been used in alpha versions before being discarded:

  • Alpha raw-and-header: Uses a header similar to that of final patches (four shorts for width, height, and presumably left and top offsets) followed by a raw dump of pixel data technically identical to flats (one byte per pixel, row-major). Index 255 is transparent. This is used notably for the Doom v0.2 PLAYSCREEN lump.
  • GNUM: A simple raw dump of pixel data technically identical to flats, but with a size 10x12 pixels. This is used in Doom v0.4 for a series of lumps called GNUM0 to GNUM9.
  • Snea: Uses a two-byte header indicating the quarter of the width and the full height. This is followed by an interleaved, column-major dump of pixel data: columns are described in the sequence 0, 4, 8, etc., then 1, 5, 9, etc. and so on. This format was introduced as a replacement of the raw-and-header one in Doom v0.4, and remained until the Beta where it was still used for the TITLEPIC (but nothing else).
  • HUFONT: Uses a 770-byte header indicating first the character height on a short, then the character width of each 256 characters in sequence, each on a single byte, then the start offset of each character data on a short. Character data is in column-major format, color index 0 is transparent. This was used in Doom v0.4 and Doom v0.5 for the system font.

SLADE3 can display all these graphic formats.

Tall patches

A centaur from Harmony. To the left, tall patches are not supported; to the right, they are.
A column starts with a two-byte header, the first byte containing the column's "topdelta", or its vertical offset from the top of the picture, and the second containing its length. This means that a column has severe limitations. Since the format uses a single byte to store a column's offset from the top of the image, if a picture is tall (more than 256 pixel-high) it is possible that a column's top offset becomes impossible to express normally. A top offset value of 255 will be interpreted to mean there is no column anymore, and a top offset value higher than 255 cannot be represented on a single byte.

A workaround was designed, which is known as DeePsea tall patches. A column's topdelta is compared to the previous column's topdelta (or to -1 if there is no previous column in this row). If the new topdelta is lesser than the previous, it is interpreted as a tall patch and the two values are added together, the sum serving as the current column's actual offset. Since a column is supposed to go below its predecessor, this trick is normally non-ambiguous. However, if support for tall patches was not implemented, then these columns end up overwriting previous columns by being put at their actual offset, rather than the intended one.