662 lines
21 KiB
C
662 lines
21 KiB
C
// AGI v2 PIC (picture / background) decoder.
|
|
//
|
|
// Paints to two private 160x168 chunky 8bpp buffers (visual + priority).
|
|
// The blit step doubles each AGI source column into two stage columns
|
|
// so the natural 160x168 internal coordinate system can be used by the
|
|
// drawing primitives, and the 320-wide stage receives the canonical
|
|
// AGI look.
|
|
//
|
|
// All algorithms here -- line interpolation, corner drawing, scanline
|
|
// flood fill, pen plotting, opcode dispatch -- are re-implementations
|
|
// written against the published AGI v2 picture-stream specification.
|
|
// No third-party source is referenced.
|
|
|
|
#include "agi.h"
|
|
|
|
#include "joey/draw.h"
|
|
#include "surfaceInternal.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
// PIC opcodes. Anything < 0xF0 is an argument byte; >= 0xF0 ends the
|
|
// current opcode's argument list.
|
|
#define OP_FIRST 0xF0u
|
|
#define OP_SET_PIC_COLOR 0xF0u
|
|
#define OP_DISABLE_PIC 0xF1u
|
|
#define OP_SET_PRI_COLOR 0xF2u
|
|
#define OP_DISABLE_PRI 0xF3u
|
|
#define OP_Y_CORNER 0xF4u
|
|
#define OP_X_CORNER 0xF5u
|
|
#define OP_ABS_LINE 0xF6u
|
|
#define OP_REL_LINE 0xF7u
|
|
#define OP_FILL 0xF8u
|
|
#define OP_SET_PEN 0xF9u
|
|
#define OP_PLOT_PEN 0xFAu
|
|
#define OP_END 0xFFu
|
|
|
|
// Pen control byte bits (set via OP_SET_PEN, consumed by OP_PLOT_PEN).
|
|
#define PEN_PATTERN_BIT 0x20u
|
|
|
|
// Scanline-fill seed stack. Each fill is reentrant-free, so a single
|
|
// static stack serves both the visual and priority passes. The bound
|
|
// is a safe over-estimate of typical PIC fill complexity; KQ3 PICs
|
|
// don't approach it.
|
|
#define FILL_STACK_MAX 512
|
|
|
|
|
|
typedef struct {
|
|
uint8_t visualEnabled;
|
|
uint8_t priorityEnabled;
|
|
uint8_t visualColor;
|
|
uint8_t priorityColor;
|
|
uint8_t penPattern;
|
|
} PicStateT;
|
|
|
|
|
|
// AGI 16-color CGA-derived palette. Entry 6's (R=A, G=5, B=0) keeps the
|
|
// classic CGA "brown fix" -- pure (R=A, G=A, B=0) would read as a vivid
|
|
// yellow-green on the host, which is wrong for AGI's intended look.
|
|
const uint16_t kAgiPalette[16] = {
|
|
0x000u, 0x00Au, 0x0A0u, 0x0AAu,
|
|
0xA00u, 0xA0Au, 0xA50u, 0xAAAu,
|
|
0x555u, 0x55Fu, 0x5F5u, 0x5FFu,
|
|
0xF55u, 0xF5Fu, 0xFF5u, 0xFFFu
|
|
};
|
|
|
|
|
|
// ----- Prototypes -----
|
|
|
|
static void drawCorner(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos, bool yFirst);
|
|
static void drawLine(AgiPicT *pic, const PicStateT *state, int16_t x0, int16_t y0, int16_t x1, int16_t y1);
|
|
static void fillPlane(uint8_t *plane, int16_t x, int16_t y, uint8_t bgValue, uint8_t newValue);
|
|
static bool isOpcode(uint8_t byte);
|
|
static void opAbsLine(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos);
|
|
static void opFill(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos);
|
|
static void opPlotPen(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos);
|
|
static void opRelLine(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos);
|
|
static void plot(AgiPicT *pic, const PicStateT *state, int16_t x, int16_t y);
|
|
|
|
|
|
#ifdef JOEYLIB_PLATFORM_IIGS
|
|
segment "AGIPIC";
|
|
#endif
|
|
|
|
// ----- Internal helpers (alphabetical) -----
|
|
|
|
static void drawCorner(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos, bool yFirst) {
|
|
uint16_t p;
|
|
int16_t curX;
|
|
int16_t curY;
|
|
bool readY;
|
|
|
|
p = *pos;
|
|
if (p + 1 >= length) {
|
|
*pos = length;
|
|
return;
|
|
}
|
|
curX = (int16_t)data[p++];
|
|
curY = (int16_t)data[p++];
|
|
plot(pic, state, curX, curY);
|
|
|
|
readY = yFirst;
|
|
while (p < length && !isOpcode(data[p])) {
|
|
if (readY) {
|
|
int16_t newY = (int16_t)data[p++];
|
|
drawLine(pic, state, curX, curY, curX, newY);
|
|
curY = newY;
|
|
} else {
|
|
int16_t newX = (int16_t)data[p++];
|
|
drawLine(pic, state, curX, curY, newX, curY);
|
|
curX = newX;
|
|
}
|
|
readY = !readY;
|
|
}
|
|
*pos = p;
|
|
}
|
|
|
|
|
|
// Documented AGI line algorithm. Major axis steps in unit increments;
|
|
// the minor axis is linearly interpolated with round-half-away-from-
|
|
// zero. All math stays in 16-bit -- AGI coordinates are 8-bit so the
|
|
// worst product (dy * i = 167 * 167 = 27889) fits in int16, and the
|
|
// 65816 build avoids ORCA-C's long-arithmetic helpers entirely.
|
|
static void drawLine(AgiPicT *pic, const PicStateT *state, int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
|
|
int16_t dx;
|
|
int16_t dy;
|
|
int16_t absDx;
|
|
int16_t absDy;
|
|
int16_t steps;
|
|
int16_t i;
|
|
int16_t half;
|
|
int16_t px;
|
|
int16_t py;
|
|
int16_t sx;
|
|
int16_t sy;
|
|
|
|
dx = (int16_t)(x1 - x0);
|
|
dy = (int16_t)(y1 - y0);
|
|
if (dx == 0 && dy == 0) {
|
|
plot(pic, state, x0, y0);
|
|
return;
|
|
}
|
|
|
|
absDx = (int16_t)((dx >= 0) ? dx : -dx);
|
|
absDy = (int16_t)((dy >= 0) ? dy : -dy);
|
|
sx = (int16_t)((dx >= 0) ? 1 : -1);
|
|
sy = (int16_t)((dy >= 0) ? 1 : -1);
|
|
|
|
if (absDx >= absDy) {
|
|
steps = absDx;
|
|
half = (int16_t)(steps / 2);
|
|
for (i = 0; i <= steps; i++) {
|
|
px = (int16_t)(x0 + sx * i);
|
|
py = (int16_t)(y0 + (dy * i + sy * half) / steps);
|
|
plot(pic, state, px, py);
|
|
}
|
|
} else {
|
|
steps = absDy;
|
|
half = (int16_t)(steps / 2);
|
|
for (i = 0; i <= steps; i++) {
|
|
py = (int16_t)(y0 + sy * i);
|
|
px = (int16_t)(x0 + (dx * i + sx * half) / steps);
|
|
plot(pic, state, px, py);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Scanline flood fill on one plane. Replaces every cell of value
|
|
// `bgValue` 4-connected to (x, y) with `newValue`. The seed stack is
|
|
// bounded; runs that don't fit are silently dropped, which mirrors
|
|
// the original interpreter's behavior under pathological PIC inputs.
|
|
static void fillPlane(uint8_t *plane, int16_t x, int16_t y, uint8_t bgValue, uint8_t newValue) {
|
|
static int16_t stackX[FILL_STACK_MAX];
|
|
static int16_t stackY[FILL_STACK_MAX];
|
|
uint16_t sp;
|
|
int16_t cx;
|
|
int16_t cy;
|
|
int16_t left;
|
|
int16_t right;
|
|
int16_t scanX;
|
|
bool aboveOpen;
|
|
bool belowOpen;
|
|
|
|
if (x < 0 || x >= AGI_PIC_WIDTH || y < 0 || y >= AGI_PIC_HEIGHT) {
|
|
return;
|
|
}
|
|
if (plane[y * AGI_PIC_WIDTH + x] != bgValue) {
|
|
return;
|
|
}
|
|
if (bgValue == newValue) {
|
|
return;
|
|
}
|
|
|
|
sp = 0;
|
|
stackX[sp] = x;
|
|
stackY[sp] = y;
|
|
sp++;
|
|
|
|
while (sp > 0u) {
|
|
sp--;
|
|
cx = stackX[sp];
|
|
cy = stackY[sp];
|
|
|
|
if (plane[cy * AGI_PIC_WIDTH + cx] != bgValue) {
|
|
continue;
|
|
}
|
|
|
|
// Expand the current row to the bg run's full extent.
|
|
left = cx;
|
|
while (left > 0 && plane[cy * AGI_PIC_WIDTH + (left - 1)] == bgValue) {
|
|
left--;
|
|
}
|
|
right = cx;
|
|
while (right < (int16_t)(AGI_PIC_WIDTH - 1) && plane[cy * AGI_PIC_WIDTH + (right + 1)] == bgValue) {
|
|
right++;
|
|
}
|
|
for (scanX = left; scanX <= right; scanX++) {
|
|
plane[cy * AGI_PIC_WIDTH + scanX] = newValue;
|
|
}
|
|
|
|
// Push one seed per bg run on the row above.
|
|
if (cy > 0) {
|
|
aboveOpen = false;
|
|
for (scanX = left; scanX <= right; scanX++) {
|
|
if (plane[(cy - 1) * AGI_PIC_WIDTH + scanX] == bgValue) {
|
|
if (!aboveOpen) {
|
|
if (sp < FILL_STACK_MAX) {
|
|
stackX[sp] = scanX;
|
|
stackY[sp] = (int16_t)(cy - 1);
|
|
sp++;
|
|
}
|
|
aboveOpen = true;
|
|
}
|
|
} else {
|
|
aboveOpen = false;
|
|
}
|
|
}
|
|
}
|
|
// And one seed per bg run on the row below.
|
|
if (cy < (int16_t)(AGI_PIC_HEIGHT - 1)) {
|
|
belowOpen = false;
|
|
for (scanX = left; scanX <= right; scanX++) {
|
|
if (plane[(cy + 1) * AGI_PIC_WIDTH + scanX] == bgValue) {
|
|
if (!belowOpen) {
|
|
if (sp < FILL_STACK_MAX) {
|
|
stackX[sp] = scanX;
|
|
stackY[sp] = (int16_t)(cy + 1);
|
|
sp++;
|
|
}
|
|
belowOpen = true;
|
|
}
|
|
} else {
|
|
belowOpen = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static bool isOpcode(uint8_t byte) {
|
|
return byte >= OP_FIRST;
|
|
}
|
|
|
|
|
|
static void opAbsLine(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos) {
|
|
uint16_t p;
|
|
int16_t prevX;
|
|
int16_t prevY;
|
|
int16_t curX;
|
|
int16_t curY;
|
|
bool have;
|
|
|
|
p = *pos;
|
|
have = false;
|
|
prevX = 0;
|
|
prevY = 0;
|
|
while (p + 1 < length && !isOpcode(data[p])) {
|
|
curX = (int16_t)data[p++];
|
|
curY = (int16_t)data[p++];
|
|
if (!have) {
|
|
plot(pic, state, curX, curY);
|
|
have = true;
|
|
} else {
|
|
drawLine(pic, state, prevX, prevY, curX, curY);
|
|
}
|
|
prevX = curX;
|
|
prevY = curY;
|
|
}
|
|
*pos = p;
|
|
}
|
|
|
|
|
|
static void opFill(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos) {
|
|
uint16_t p;
|
|
int16_t x;
|
|
int16_t y;
|
|
|
|
p = *pos;
|
|
while (p + 1 < length && !isOpcode(data[p])) {
|
|
x = (int16_t)data[p++];
|
|
y = (int16_t)data[p++];
|
|
if (state->visualEnabled) {
|
|
fillPlane(pic->visual, x, y, AGI_VISUAL_BG, state->visualColor);
|
|
}
|
|
if (state->priorityEnabled) {
|
|
fillPlane(pic->priority, x, y, AGI_PRIORITY_BG, state->priorityColor);
|
|
}
|
|
}
|
|
*pos = p;
|
|
}
|
|
|
|
|
|
// Plot-with-pen. With pen patterning off, each iteration consumes an
|
|
// X,Y pair and plots a single pixel. With patterning on, each
|
|
// iteration consumes a texture byte first (we discard it -- proper
|
|
// patterned brushes are deferred to a later pass). Size and shape
|
|
// are likewise stubbed to single-pixel; KQ3 PICs use pens sparingly
|
|
// and dropping the brush footprint just thins occasional grass / sky
|
|
// dithering.
|
|
static void opPlotPen(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos) {
|
|
uint16_t p;
|
|
int16_t x;
|
|
int16_t y;
|
|
|
|
p = *pos;
|
|
while (p < length && !isOpcode(data[p])) {
|
|
if (state->penPattern) {
|
|
// Skip the texture-selector byte.
|
|
p++;
|
|
if (p >= length || isOpcode(data[p])) {
|
|
break;
|
|
}
|
|
}
|
|
if (p + 1 >= length) {
|
|
break;
|
|
}
|
|
x = (int16_t)data[p++];
|
|
y = (int16_t)data[p++];
|
|
plot(pic, state, x, y);
|
|
}
|
|
*pos = p;
|
|
}
|
|
|
|
|
|
static void opRelLine(AgiPicT *pic, const PicStateT *state, const uint8_t *data, uint16_t length, uint16_t *pos) {
|
|
uint16_t p;
|
|
int16_t curX;
|
|
int16_t curY;
|
|
int16_t newX;
|
|
int16_t newY;
|
|
uint8_t disp;
|
|
int16_t dx;
|
|
int16_t dy;
|
|
|
|
p = *pos;
|
|
if (p + 1 >= length) {
|
|
*pos = p;
|
|
return;
|
|
}
|
|
curX = (int16_t)data[p++];
|
|
curY = (int16_t)data[p++];
|
|
plot(pic, state, curX, curY);
|
|
|
|
while (p < length && !isOpcode(data[p])) {
|
|
disp = data[p++];
|
|
// High nibble = X disp (bit 7 sign, bits 4-6 magnitude 0..7).
|
|
// Low nibble = Y disp (bit 3 sign, bits 0-2 magnitude 0..7).
|
|
dx = (int16_t)((disp >> 4) & 0x07u);
|
|
if (disp & 0x80u) {
|
|
dx = (int16_t)-dx;
|
|
}
|
|
dy = (int16_t)(disp & 0x07u);
|
|
if (disp & 0x08u) {
|
|
dy = (int16_t)-dy;
|
|
}
|
|
newX = (int16_t)(curX + dx);
|
|
newY = (int16_t)(curY + dy);
|
|
drawLine(pic, state, curX, curY, newX, newY);
|
|
curX = newX;
|
|
curY = newY;
|
|
}
|
|
*pos = p;
|
|
}
|
|
|
|
|
|
static void plot(AgiPicT *pic, const PicStateT *state, int16_t x, int16_t y) {
|
|
uint16_t idx;
|
|
|
|
if (x < 0 || x >= (int16_t)AGI_PIC_WIDTH) {
|
|
return;
|
|
}
|
|
if (y < 0 || y >= (int16_t)AGI_PIC_HEIGHT) {
|
|
return;
|
|
}
|
|
idx = (uint16_t)(y * (int16_t)AGI_PIC_WIDTH + x);
|
|
if (state->visualEnabled) {
|
|
pic->visual[idx] = state->visualColor;
|
|
}
|
|
if (state->priorityEnabled) {
|
|
pic->priority[idx] = state->priorityColor;
|
|
}
|
|
}
|
|
|
|
|
|
// ----- Public API (alphabetical) -----
|
|
|
|
bool agiPicAlloc(AgiPicT *pic) {
|
|
pic->visual = (uint8_t *)malloc(AGI_PIC_PIXELS);
|
|
pic->priority = (uint8_t *)malloc(AGI_PIC_PIXELS);
|
|
if (pic->visual == NULL || pic->priority == NULL) {
|
|
agiPicFree(pic);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void agiPicBlit(const AgiPicT *pic, jlSurfaceT *stage, int16_t destY) {
|
|
int16_t sy;
|
|
int16_t sx;
|
|
int16_t dx;
|
|
uint8_t color;
|
|
|
|
// Fast path: chunky shadow exists -> write packed bytes direct,
|
|
// mark the whole region dirty once at the end. ~30x faster than
|
|
// calling jlDrawPixel 53,760 times (each does bounds + 1-pixel
|
|
// dirty mark). Required for state-2 / state-3 title transitions
|
|
// to render under a second on period-correct DOSBox CPU cycles.
|
|
if (stage != NULL && stage->pixels != NULL) {
|
|
const uint8_t *src = pic->visual;
|
|
uint8_t *dst;
|
|
int16_t rowOff;
|
|
|
|
for (sy = 0; sy < (int16_t)AGI_PIC_HEIGHT; sy++) {
|
|
rowOff = (int16_t)((int16_t)(destY + sy) * (int16_t)SURFACE_BYTES_PER_ROW);
|
|
dst = &stage->pixels[rowOff];
|
|
for (sx = 0; sx < (int16_t)AGI_PIC_WIDTH; sx++) {
|
|
color = (uint8_t)(src[sy * AGI_PIC_WIDTH + sx] & 0x0Fu);
|
|
// Pixel-doubled: each source pixel = 1 dst byte
|
|
// (both nibbles = same color).
|
|
dst[sx] = (uint8_t)((color << 4) | color);
|
|
}
|
|
}
|
|
surfaceMarkDirtyRect(stage, 0, destY,
|
|
(int16_t)SURFACE_WIDTH, (int16_t)AGI_PIC_HEIGHT);
|
|
return;
|
|
}
|
|
|
|
// Planar fallback (s->pixels NULL: Phase-9 Amiga). Goes through
|
|
// jlDrawPixel so the port's c2p / plane-sync path stays correct.
|
|
for (sy = 0; sy < (int16_t)AGI_PIC_HEIGHT; sy++) {
|
|
for (sx = 0; sx < (int16_t)AGI_PIC_WIDTH; sx++) {
|
|
color = pic->visual[sy * AGI_PIC_WIDTH + sx];
|
|
dx = (int16_t)(sx << 1);
|
|
jlDrawPixel(stage, dx, (int16_t)(destY + sy), color);
|
|
jlDrawPixel(stage, (int16_t)(dx + 1), (int16_t)(destY + sy), color);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void agiPicClear(AgiPicT *pic) {
|
|
if (pic->visual != NULL) {
|
|
memset(pic->visual, AGI_VISUAL_BG, AGI_PIC_PIXELS);
|
|
}
|
|
if (pic->priority != NULL) {
|
|
memset(pic->priority, AGI_PRIORITY_BG, AGI_PIC_PIXELS);
|
|
}
|
|
}
|
|
|
|
|
|
bool agiPicDecode(AgiPicT *pic, const uint8_t *data, uint16_t length) {
|
|
PicStateT state;
|
|
uint16_t pos;
|
|
uint8_t op;
|
|
|
|
state.visualEnabled = 0u;
|
|
state.priorityEnabled = 0u;
|
|
state.visualColor = 0u;
|
|
state.priorityColor = 0u;
|
|
state.penPattern = 0u;
|
|
|
|
pos = 0u;
|
|
while (pos < length) {
|
|
op = data[pos++];
|
|
switch (op) {
|
|
case OP_SET_PIC_COLOR:
|
|
if (pos >= length) {
|
|
return false;
|
|
}
|
|
state.visualColor = data[pos++];
|
|
state.visualEnabled = 1u;
|
|
break;
|
|
case OP_DISABLE_PIC:
|
|
state.visualEnabled = 0u;
|
|
break;
|
|
case OP_SET_PRI_COLOR:
|
|
if (pos >= length) {
|
|
return false;
|
|
}
|
|
state.priorityColor = data[pos++];
|
|
state.priorityEnabled = 1u;
|
|
break;
|
|
case OP_DISABLE_PRI:
|
|
state.priorityEnabled = 0u;
|
|
break;
|
|
case OP_Y_CORNER:
|
|
drawCorner(pic, &state, data, length, &pos, true);
|
|
break;
|
|
case OP_X_CORNER:
|
|
drawCorner(pic, &state, data, length, &pos, false);
|
|
break;
|
|
case OP_ABS_LINE:
|
|
opAbsLine(pic, &state, data, length, &pos);
|
|
break;
|
|
case OP_REL_LINE:
|
|
opRelLine(pic, &state, data, length, &pos);
|
|
break;
|
|
case OP_FILL:
|
|
opFill(pic, &state, data, length, &pos);
|
|
break;
|
|
case OP_SET_PEN:
|
|
if (pos >= length) {
|
|
return false;
|
|
}
|
|
state.penPattern = (uint8_t)((data[pos++] & PEN_PATTERN_BIT) ? 1u : 0u);
|
|
break;
|
|
case OP_PLOT_PEN:
|
|
opPlotPen(pic, &state, data, length, &pos);
|
|
break;
|
|
case OP_END:
|
|
return true;
|
|
default:
|
|
// Unknown opcode -- stop cleanly rather than misparsing.
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// Bake a single VIEW cel into the visual + priority planes (the
|
|
// implementation of add.to.pic). Pixels outside the picture window
|
|
// or set to the cel's transparent color are skipped. priColor in
|
|
// 4..15 overwrites the priority pixels where the visual is opaque;
|
|
// 0 preserves the existing priority. RLE format matches agiViewDraw
|
|
// (per-row 0-byte EOR marker after a variable run).
|
|
void agiPicAddView(AgiPicT *pic, const AgiViewT *view,
|
|
uint8_t loop, uint8_t cel,
|
|
int16_t baseX, int16_t baseY,
|
|
uint8_t priColor, uint8_t margin) {
|
|
const AgiCelInfoT *info;
|
|
const AgiCelInfoT *resolved;
|
|
const uint8_t *rle;
|
|
uint8_t actualLoop;
|
|
int16_t width;
|
|
int16_t height;
|
|
uint8_t trans;
|
|
bool mirrored;
|
|
int16_t topY;
|
|
int16_t cy;
|
|
int16_t cx;
|
|
int16_t drawCx;
|
|
int16_t agiX;
|
|
int16_t agiY;
|
|
uint8_t rleByte;
|
|
uint8_t color;
|
|
uint8_t runLen;
|
|
uint8_t r;
|
|
|
|
(void)margin;
|
|
if (view == NULL || pic == NULL || pic->visual == NULL) {
|
|
return;
|
|
}
|
|
if (loop >= view->loopCount) {
|
|
return;
|
|
}
|
|
if (cel >= view->loops[loop].celCount) {
|
|
return;
|
|
}
|
|
info = &view->loops[loop].cels[cel];
|
|
actualLoop = loop;
|
|
mirrored = false;
|
|
if (info->mirrored != 0u) {
|
|
if (info->mirrorSourceLoop != loop && info->mirrorSourceLoop < view->loopCount
|
|
&& cel < view->loops[info->mirrorSourceLoop].celCount) {
|
|
actualLoop = info->mirrorSourceLoop;
|
|
mirrored = true;
|
|
}
|
|
}
|
|
resolved = &view->loops[actualLoop].cels[cel];
|
|
|
|
width = (int16_t)resolved->width;
|
|
height = (int16_t)resolved->height;
|
|
trans = resolved->transparentColor;
|
|
rle = resolved->rleData;
|
|
topY = (int16_t)(baseY - height + 1);
|
|
|
|
for (cy = 0; cy < height; cy++) {
|
|
cx = 0;
|
|
for (;;) {
|
|
rleByte = *rle++;
|
|
if (rleByte == 0u) {
|
|
// End-of-row marker.
|
|
break;
|
|
}
|
|
color = (uint8_t)(rleByte >> 4);
|
|
runLen = (uint8_t)(rleByte & 0x0Fu);
|
|
for (r = 0u; r < runLen; r++) {
|
|
if (cx >= width) {
|
|
break;
|
|
}
|
|
if (color != trans) {
|
|
drawCx = mirrored ? (int16_t)(width - 1 - cx) : cx;
|
|
agiX = (int16_t)(baseX + drawCx);
|
|
agiY = (int16_t)(topY + cy);
|
|
if (agiX >= 0 && agiX < (int16_t)AGI_PIC_WIDTH && agiY >= 0 && agiY < (int16_t)AGI_PIC_HEIGHT) {
|
|
int16_t idx = (int16_t)(agiY * (int16_t)AGI_PIC_WIDTH + agiX);
|
|
bool paint = true;
|
|
|
|
// AGI v2 priority masking: when priColor is a
|
|
// real priority value (>= AGI_PRIORITY_BG),
|
|
// skip the pixel if the existing priority is
|
|
// strictly higher -- the new art is behind
|
|
// existing higher-priority art. This is how
|
|
// KQ3's title "III" ends up behind the KQ
|
|
// logo (III priColor=4 = background, KQ logo
|
|
// in PIC 45 sits at a higher band).
|
|
if (priColor >= AGI_PRIORITY_BG && priColor < 16u && pic->priority != NULL) {
|
|
if (pic->priority[idx] > priColor) {
|
|
paint = false;
|
|
}
|
|
}
|
|
if (paint) {
|
|
pic->visual[idx] = color;
|
|
if (priColor >= AGI_PRIORITY_BG && priColor < 16u && pic->priority != NULL) {
|
|
pic->priority[idx] = priColor;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
cx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void agiPicFree(AgiPicT *pic) {
|
|
if (pic->visual != NULL) {
|
|
free(pic->visual);
|
|
pic->visual = NULL;
|
|
}
|
|
if (pic->priority != NULL) {
|
|
free(pic->priority);
|
|
pic->priority = NULL;
|
|
}
|
|
}
|