SCI/Specifications/Graphics/SCI0-SCI01 pic resource
The pic (background picture) resource format used in SCI0 is rather complex in comparison to the other graphical resource formats. It is best described as a sequence of drawing operations on a set of four 320x200 canvases, three of which are later used in the game (visual, priority, and control), and one of which is used during the drawing process for auxiliary purposes[1]
In order to describe the process, we will first need to define a set of operations we base them on:
<syntax type="pascal"> FUNCTION peek_input(): Byte; /* returns the byte pointed to by the input pointer */ FUNCTION get_input(): Byte; /* works like peek_input(), but also increminates the
** input pointer */
FUNCTION skip_input(x): Byte; /* skips x input bytes */ </syntax>
Using these pre-defined functions, we will now define additional helper functions used for reading specifically encoded data tuples:
<syntax type="pascal"> FUNCTION GetAbsCoordinates(): (Integer, Integer) VAR
x, y, coordinate_prefix : Integer;
BEGIN
coordinate_prefix := get_input(); x := get_input(); y := get_input(); x |= (coordinate_prefix & 0xf0) << 4; y |= (coordinate_prefix & 0x0f) << 8;
RETURN (x,y)
END
FUNCTION GetRelCoordinates(x : Integer, y: Integer): (Integer, Integer)
VAR
input : Integer;
BEGIN
input := get_input(); IF (input & 0x80) THEN x -= (input >> 4); ELSE x += (input >> 4); FI
IF (input & 0x08) THEN y -= (input & 0x7); ELSE y += (input & 0x7); FI
RETURN (x,y)
END </syntax>
We also need some data types based on EGACOLOR and PRIORITY, which can be thought of as integers:
<syntax type="pascal"> TYPE Palette = ARRAY[0..39] of EGACOLOR[0..1] TYPE Priority_Table = ARRAY[0..39] of PRIORITY
Palette default_palette =
<(0,0), (1,1), (2,2), (3,3), (4,4), (5,5), (6,6), (7,7), (8,8), (9,9), (a,a), (b,b), (c,c), (d,d), (e,e), (8,8), (8,8), (0,1), (0,2), (0,3), (0,4), (0,5), (0,6), (8,8), (8,8), (f,9), (f,a), (f,b), (f,c), (f,d), (f,e), (f,f), (0,8), (9,1), (2,a), (3,b), (4,c), (5,d), (6,e), (8,8)>;
- define DRAW_ENABLE_VISUAL 1
- define DRAW_ENABLE_PRIORITY 2
- define DRAW_ENABLE_CONTROL 4
- define PATTERN_FLAG_RECTANGLE 0x10
- define PATTERN_FLAG_USE_PATTERN 0x20
</syntax>
And now for the actual algorithm:
<syntax type="pascal"> FUNCTION DrawPic (cumulative, fill_in_black : Boolean; default_palette: Integer; visual_map, priority_map, control_map, aux_map : Map): Map^4 VAR
palette : Array [0..3] of Palette; drawenable, priority, col1, col2, pattern_nr, pattern_code : Integer;
BEGIN
palette := (default_palette * 4); drawenable := DRAW_ENABLE_VISUAL | DRAW_ENABLE_PRIORITY priority := 0; col1 := col2 := 0; pattern_nr := 0; pattern_code := 0;
IF (!cumulative) THEN BEGIN visual_map := (0xf * 320 * 200); map control := map priority := map aux := (0 * 320 * 200); END
FOREVER DO BEGIN
opcode := get_input();
COND opcode: 0xf0 => /* PIC_OP_SET_COLOR */ code := get_input(); (col1, col2) := palette[default_palette + (code / 40)][code % 40]; drawenable |= DRAW_ENABLE_VISUAL;
0xf1 => /* PIC_OP_DISABLE_VISUAL */ drawenable &= ~DRAW_ENABLE_VISUAL;
0xf2 => /* PIC_OP_SET_PRIORITY */ code := get_input(); priority := code & 0xf; drawenable |= DRAW_ENABLE_PRIORITY;
0xf3 => /* PIC_OP_DISABLE_PRIORITY */ drawenable &= ~DRAW_ENABLE_PRIORITY;
0xf4 => /* PIC_OP_RELATIVE_PATTERNS */ IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN pattern_nr := (get_input() >> 1) & 0x7f FI
(x,y) := GetAbsCoordinates();
DrawPattern(x, y, col1, col2, priority, control, drawenable, pattern_code & PATTERN_FLAG_USE_PATTERN, pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);
WHILE (peek_input() < 0xf0) DO BEGIN IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN pattern_nr := (get_input() >> 1) & 0x7f FI (x,y) = GetRelCoordinates(x,y); DrawPattern(x, y, col1, col2, priority, control, drawenable, pattern_code & PATTERN_FLAG_USE_PATTERN, pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE); END
0xf5 => /* PIC_OP_RELATIVE_MEDIUM_LINES */ (oldx, oldy) := GetAbsCoordinates(); WHILE (peek_input() < 0xf0) DO BEGIN temp := get_input(); IF (temp & 0x80) THEN y := oldy - (temp & 0x7f) ELSE y := oldy + temp FI x = oldx + get_input(); DitherLine(oldx, oldy, x, y, col1, col2, priority, special, drawenable); (oldx, oldy) := (x, y); END
0xf6 => /* PIC_OP_RELATIVE_LONG_LINES */ (oldx, oldy) := GetAbsCoordinates() WHILE (peek_input() < 0xf0) DO BEGIN (x, y) := GetAbsCoordinates(); DitherLine(oldx, oldy, x, y, col1, col2, priority, special, drawenable); (oldx, oldy) := (x, y); END
0xf7 => /* PIC_OP_RELATIVE_SHORT_LINES */ (oldx, oldy) = GetAbsCoordinates() WHILE (peek_input() < 0xf0) DO BEGIN (x, y) := GetRelCoordinates(oldx, oldy); DitherLine(oldx, oldy, x, y, col1, col2, priority, special, drawenable); (oldx, oldy) := (x, y); END
0xf8 => /* PIC_OP_FILL */ IF (fill_in_black) THEN (oldc1, oldc2) := (c1, c2); FI
WHILE (peek_unput() < 0xf0) DO BEGIN (x, y) := GetAbsCoordinates(); DitherFill(x, y, col1, col2, priority, special, drawenable); END
IF (fill_in_black) THEN (c1, c2) := (oldc1, oldc2); FI
0xf9 => /* PIC_OP_SET_PATTERN */ pattern_code := get_input() & 0x37; pattern_size := pattern_code & 0x7;
0xfa => /* PIC_OP_ABSOLUTE_PATTERNS */ WHILE (peek_input() < 0xf0) DO IF (pattern_code & PATTERN_FLAG_USE_PATTERN) pattern_nr := (get_input() >> 1) & 0x7f FI (x, y) := GetAbsCoordinates(); DrawPattern(x, y, col1, col2, priority, control, drawenable, pattern_code & PATTERN_FLAG_USE_PATTERN, pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE); END
0xfb => /* PIC_OP_SET_CONTROL */ control := get_input() & 0x0f; drawenable |= DRAW_ENABLE_CONTROL;
0xfc => /* PIC_OP_DISABLE_CONTROL */ drawenable &= ~DRAW_ENABLE_CONTROL;
0xfd => /* PIC_OP_RELATIVE_MEDIUM_PATTERNS */ IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN pattern_nr := (get_input() >> 1) & 0x7f; FI
(oldx, oldy) := GetAbsCoordinates();
DrawPattern(x, y, col1, col2, priority, control, drawenable, pattern_code & PATTERN_FLAG_USE_PATTERN, pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE);
WHILE (peek_input() < 0xf0) DO BEGIN IF (pattern_code & PATTERN_FLAG_USE_PATTERN) THEN pattern_nr := (get_input() >> 1) & 0x7f; FI temp := get_input(); IF (temp & 0x80) y := oldy - (temp & 0x7f) ELSE y := oldy + temp FI x := oldx + get_input(); DrawPattern(x, y, col1, col2, priority, control, drawenable, pattern_code & PATTERN_FLAG_USE_PATTERN, pattern_size, pattern_nr, pattern_code & PATTERN_FLAG_RECTANGLE); END
0xfd => /* PIC_OP_OPX */ COND get_input(): 0x00 => /* PIC_OPX_SET_PALETTE_ENTRY */ WHILE peek_input() < 0xf0 DO BEGIN index := get_input(); color := get_input(); palette[index / 40][color % 40] := color; END
0x01 => /* PIC_OPX_SET_PALETTE */ palette_number := get_input(); FOR i := 0 TO 39 DO palette[palette_number][i] := get_input(); OD
0x02 => /* PIC_OPX_MONO0 */ skip_input(41);
0x03 => /* PIC_OPX_MONO1 */ skip_input(1);
0x04 => /* PIC_OPX_MONO2 */ 0x05 => /* PIC_OPX_MONO3 */ skip_input(1);
0x06 => /* PIC_OPX_MONO4 */ 0x07 => /* PIC_OPX_EMBEDDED_VIEW */ /* SCI01 operation */ 0x08 => /* PIC_OPX_SET_PRIORITY_TABLE */ /* SCI01 operation */
0xff => return (visual, control, priority, aux); END OF COND END
END </syntax>
This algorithm uses three auxiliary algorithms, DrawPattern, DitherLine, and DitherFill, which are sketched below. All of these functions are supposed to take the four maps as implicit parameters.
<syntax type="pascal"> PROCEDURE DrawPattern(x, y, col1, col2, priority, control, drawenable : Integer; solid : Boolean ; pattern_size, pattern_nr : Integer; rectangle : Boolean) </syntax>
Alters (x,y) so that 0 <= (x - pattern_size), 319 >= (x + pattern_size), 189 >= (y + pattern_size) and 0 <= (y - pattern_size), then draws a rectangle or a circle filled with col1, col2, priority, control, as determined by drawenable.
If rectangle is not set, it will draw a rectangle, otherwise a circle of size pattern_size. pattern_nr is used to specify the start index in the random bit table (256 random bits)
<syntax type="pascal">
PROCEDURE DitherLine(x, y, xend, yend, color1, color2, priority, control, drawenable : Integer)</syntax>
Draws a dithered line between (x, y+10) and (xend, yend+10). If the appropriate drawenable flags are set, it draws 'priority' to the priority map, 'control' to the control map, and 'color1' and 'color2' (alternating) to the visual map. The auxiliary map is bitwise-or'd with the drawenable flag while this is done.
<syntax type="pascal">
PROCEDURE DitherFill(x, y, col0, col1, priority, control, drawenable : Integer)</syntax>
Fills all layers for which drawenable is set with the appropriate content. Diagonal filling is not allowed. Boundaries are determined as follows: x<0, x>319, y<10, y>199 are hard boundaries. We now determine the 'boundary map' bound_map and the allowed color legal_color. If bound_map[coordinates] = legal_color, then the pixel may be filled.
<syntax type="pascal">IF (drawenable & DRAW_ENABLE_VISUAL)
bound_map = visual; legal_color = 0xf;
ELSIF (drawenable & DRAW_ENABLE_PRIORITY)
bound_map = priority; legal_color = 0;
ELSIF (drawenable & DRAW_ENABLE_CONTROL)
bound_map = control; legal_color = 0;
ELSE
return;
FI </syntax>
- ↑ Due to the vector graphics nature of these drawing operations, they are inherently more scaleable than pixmaps.