Files
2026-02-02 04:50:13 +01:00

625 lines
16 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "graphics/surface.h"
#include "awe/video.h"
#include "awe/bitmap.h"
#include "awe/gfx.h"
#include "awe/resource.h"
#include "awe/resource_3do.h"
#include "awe/system_stub.h"
namespace Awe {
Video::Video(Resource *res) : _res(res) {
}
void Video::init() {
_nextPal = _currentPal = 0xFF;
_buffers[2] = getPagePtr(1);
_buffers[1] = getPagePtr(2);
setWorkPagePtr(0xFE);
_pData.byteSwap = (_res->getDataType() == DT_3DO);
}
void Video::setDefaultFont() {
_graphics->setFont(nullptr, 0, 0);
}
void Video::setFont(const uint8 *font) {
int w, h;
uint8 *buf = decode_bitmap(font, true, -1, &w, &h);
if (buf) {
_graphics->setFont(buf, w, h);
free(buf);
}
}
void Video::setHeads(const uint8 *src) {
int w, h;
uint8 *buf = decode_bitmap(src, true, 0xF06080, &w, &h);
if (buf) {
_graphics->setSpriteAtlas(buf, w, h, 2, 2);
free(buf);
_hasHeadSprites = true;
}
}
void Video::setDataBuffer(uint8 *dataBuf, uint16 offset) {
_dataBuf = dataBuf;
_pData.pc = dataBuf + offset;
}
void Video::drawShape(uint8 color, uint16 zoom, const Point *pt) {
uint8 i = _pData.fetchByte();
if (i >= 0xC0) {
if (color & 0x80) {
color = i & 0x3F;
}
fillPolygon(color, zoom, pt);
} else {
i &= 0x3F;
if (i == 1) {
warning("Video::drawShape() ec=0x%X (i != 2)", 0xF80);
} else if (i == 2) {
drawShapeParts(zoom, pt);
} else {
warning("Video::drawShape() ec=0x%X (i != 2)", 0xFBB);
}
}
}
void Video::drawShapePart3DO(int color, int part, const Point *pt) {
assert(part < (int)ARRAYSIZE(VERTICES_3DO));
const uint8 *vertices = VERTICES_3DO[part];
const int w = *vertices++;
const int h = *vertices++;
const int x = pt->x - w / 2;
const int y = pt->y - h / 2;
QuadStrip qs;
qs.numVertices = 2 * h;
assert(qs.numVertices < QuadStrip::MAX_VERTICES);
for (int i = 0; i < h; ++i) {
qs.vertices[i].x = x + *vertices++;
qs.vertices[i].y = y + i;
qs.vertices[2 * h - 1 - i].x = x + *vertices++;
qs.vertices[2 * h - 1 - i].y = y + i;
}
_graphics->drawQuadStrip(_buffers[0], color, &qs);
}
void Video::drawShape3DO(int color, int zoom, const Point *pt) {
const int code = _pData.fetchByte();
debugC(kDebugVideo, "Video::drawShape3DO() code=0x%x pt=%d,%d", code, pt->x, pt->y);
if (color == 0xFF) {
color = code & 31;
}
switch (code & 0xE0) {
case 0x00:
{
const int x0 = pt->x - _pData.fetchByte() * zoom / 64;
const int y0 = pt->y - _pData.fetchByte() * zoom / 64;
int count = _pData.fetchByte() + 1;
do {
uint16 offset = _pData.fetchWord();
Point po;
po.x = x0 + _pData.fetchByte() * zoom / 64;
po.y = y0 + _pData.fetchByte() * zoom / 64;
color = 0xFF;
if (offset & 0x8000) {
color = _pData.fetchByte();
const int num = _pData.fetchByte();
if (color & 0x80) {
drawShapePart3DO(color & 0xF, num, &po);
continue;
}
}
offset <<= 1;
uint8 *bak = _pData.pc;
_pData.pc = _dataBuf + offset;
drawShape3DO(color, zoom, &po);
_pData.pc = bak;
} while (--count != 0);
}
break;
case 0x20:
{ // rect
const int w = _pData.fetchByte() * zoom / 64;
const int h = _pData.fetchByte() * zoom / 64;
const int x1 = pt->x - w / 2;
const int y1 = pt->y - h / 2;
const int x2 = x1 + w;
const int y2 = y1 + h;
if (x1 > 319 || x2 < 0 || y1 > 199 || y2 < 0) {
break;
}
QuadStrip qs;
qs.numVertices = 4;
qs.vertices[0].x = x1;
qs.vertices[0].y = y1;
qs.vertices[1].x = x1;
qs.vertices[1].y = y2;
qs.vertices[2].x = x2;
qs.vertices[2].y = y2;
qs.vertices[3].x = x2;
qs.vertices[3].y = y1;
_graphics->drawQuadStrip(_buffers[0], color, &qs);
}
break;
case 0x40:
{ // pixel
if (pt->x > 319 || pt->x < 0 || pt->y > 199 || pt->y < 0) {
break;
}
_graphics->drawPoint(_buffers[0], color, pt);
}
break;
case 0xC0:
{ // polygon
const int w = _pData.fetchByte() * zoom / 64;
const int h = _pData.fetchByte() * zoom / 64;
const int count = _pData.fetchByte();
QuadStrip qs;
qs.numVertices = count * 2;
assert(qs.numVertices < QuadStrip::MAX_VERTICES);
const int x0 = pt->x - w / 2;
const int y0 = pt->y - h / 2;
if (x0 > 319 || pt->x + w / 2 < 0 || y0 > 199 || pt->y + h / 2 < 0) {
break;
}
for (int i = 0, j = count * 2 - 1; i < count; ++i, --j) {
const int x1 = _pData.fetchByte() * zoom / 64;
const int x2 = _pData.fetchByte() * zoom / 64;
const int y = _pData.fetchByte() * zoom / 64;
qs.vertices[i].x = x0 + x2;
qs.vertices[(i + 1) % count].y = y0 + y;
qs.vertices[j].x = x0 + x1;
qs.vertices[count * 2 - 1 - (i + 1) % count].y = y0 + y;
}
_graphics->drawQuadStrip(_buffers[0], color, &qs);
}
break;
default:
warning("Video::drawShape3DO() unhandled code 0x%X", code);
break;
}
}
void Video::fillPolygon(uint16 color, uint16 zoom, const Point *pt) {
const uint8 *p = _pData.pc;
const uint16 bbw = (*p++) * zoom / 64;
const uint16 bbh = (*p++) * zoom / 64;
const int16 x1 = pt->x - bbw / 2;
const int16 x2 = pt->x + bbw / 2;
const int16 y1 = pt->y - bbh / 2;
const int16 y2 = pt->y + bbh / 2;
if (x1 > 319 || x2 < 0 || y1 > 199 || y2 < 0)
return;
QuadStrip qs;
qs.numVertices = *p++;
if ((qs.numVertices & 1) != 0) {
warning("Unexpected number of vertices %d", qs.numVertices);
return;
}
assert(qs.numVertices < QuadStrip::MAX_VERTICES);
for (int i = 0; i < qs.numVertices; ++i) {
Point *v = &qs.vertices[i];
v->x = x1 + (*p++) * zoom / 64;
v->y = y1 + (*p++) * zoom / 64;
}
if (qs.numVertices == 4 && bbw == 0 && bbh <= 1) {
_graphics->drawPoint(_buffers[0], color, pt);
} else {
_graphics->drawQuadStrip(_buffers[0], color, &qs);
}
}
void Video::drawShapeParts(uint16 zoom, const Point *pgc) {
Point pt;
pt.x = pgc->x - _pData.fetchByte() * zoom / 64;
pt.y = pgc->y - _pData.fetchByte() * zoom / 64;
int16 n = _pData.fetchByte();
debugC(kDebugVideo, "Video::drawShapeParts n=%d", n);
for (; n >= 0; --n) {
uint16 offset = _pData.fetchWord();
Point po(pt);
po.x += _pData.fetchByte() * zoom / 64;
po.y += _pData.fetchByte() * zoom / 64;
uint16 color = 0xFF;
if (offset & 0x8000) {
color = _pData.fetchByte();
const int num = _pData.fetchByte();
if (Gfx::_is1991) {
if (!_hasHeadSprites && (color & 0x80) != 0) {
_graphics->drawSprite(_buffers[0], num, &po, color & 0x7F);
continue;
}
} else if (_hasHeadSprites && _displayHead) {
switch (num) {
case 0x4A:
{ // facing right
Point pos(po.x - 4, po.y - 7);
_graphics->drawSprite(_buffers[0], 0, &pos, color);
}
case 0x4D:
return;
case 0x4F:
{ // facing left
Point pos(po.x - 4, po.y - 7);
_graphics->drawSprite(_buffers[0], 1, &pos, color);
}
case 0x50:
return;
default:
break;
}
}
color &= 0x7F;
}
offset <<= 1;
uint8 *bak = _pData.pc;
_pData.pc = _dataBuf + offset;
drawShape(color, zoom, &po);
_pData.pc = bak;
}
}
static const int NTH_EDITION_STRINGS_COUNT = 157;
static const char *findString(const StrEntry *stringsTable, int id) {
for (const StrEntry *se = stringsTable; se->id != 0xFFFF; ++se) {
if (se->id == id) {
return se->str;
}
}
return nullptr;
}
void Video::drawString(uint8 color, uint16 x, uint16 y, uint16 strId) {
bool escapedChars = false;
const char *str = nullptr;
if (_res->getDataType() == DT_15TH_EDITION || _res->getDataType() == DT_20TH_EDITION) {
for (int i = 0; i < NTH_EDITION_STRINGS_COUNT; ++i) {
if (Video::STRINGS_ID_15TH[i] == strId) {
str = _res->getString(i);
if (str) {
escapedChars = true;
} else {
str = Video::STRINGS_TABLE_15TH[i];
}
break;
}
}
} else if (_res->getDataType() == DT_WIN31) {
str = _res->getString(strId);
} else if (_res->getDataType() == DT_3DO) {
str = findString(STRINGS_TABLE_3DO, strId);
} else if (_res->getDataType() == DT_ATARI_DEMO && strId == 0x194) {
str = _str0x194AtariDemo;
} else {
if (_res->getDataType() == DT_AMIGA || _res->getDataType() == DT_ATARI) {
str = findString(STRINGS_TABLE_AMIGA_CODES, strId);
}
if (!str) {
str = findString(_stringsTable, strId);
}
if (!str && _res->getDataType() == DT_DOS) {
str = findString(STRINGS_TABLE_DEMO, strId);
}
}
if (!str) {
warning("Unknown string id %d", strId);
return;
}
debugC(kDebugVideo, "drawString(%d, %d, %d, '%s')", color, x, y, str);
const uint16 xx = x;
const int len = strlen(str);
for (int i = 0; i < len; ++i) {
if (str[i] == '\n' || str[i] == '\r') {
y += 8;
x = xx;
} else if (str[i] == '\\' && escapedChars) {
++i;
if (i < len) {
switch (str[i]) {
case 'n':
y += 8;
x = xx;
break;
default:
break;
}
}
} else {
Point pt(x * 8, y);
_graphics->drawStringChar(_buffers[0], color, str[i], &pt);
++x;
}
}
}
uint8 Video::getPagePtr(uint8 page) {
uint8 p;
if (page <= 3) {
p = page;
} else {
switch (page) {
case 0xFF:
p = _buffers[2];
break;
case 0xFE:
p = _buffers[1];
break;
default:
p = 0; // XXX check
warning("Video::getPagePtr() p != [0,1,2,3,0xFF,0xFE] == 0x%X", page);
break;
}
}
return p;
}
void Video::setWorkPagePtr(uint8 page) {
debugC(kDebugVideo, "Video::setWorkPagePtr(%d)", page);
_buffers[0] = getPagePtr(page);
}
void Video::fillPage(uint8 page, uint8 color) {
debugC(kDebugVideo, "Video::fillPage(%d, %d)", page, color);
_graphics->clearBuffer(getPagePtr(page), color);
}
void Video::copyPage(uint8 src, uint8 dst, int16 vscroll) {
debugC(kDebugVideo, "Video::copyPage(%d, %d)", src, dst);
if (src >= 0xFE || ((src &= ~0x40) & 0x80) == 0) { // no vscroll
_graphics->copyBuffer(getPagePtr(dst), getPagePtr(src));
} else {
const uint8 sl = getPagePtr(src & 3);
const uint8 dl = getPagePtr(dst);
if (sl != dl && vscroll >= -199 && vscroll <= 199) {
_graphics->copyBuffer(dl, sl, vscroll);
}
}
}
static void decode_amiga(const uint8 *src, uint8 *dst) {
static const int plane_size = 200 * 320 / 8;
for (int y = 0; y < 200; ++y) {
for (int x = 0; x < 320; x += 8) {
for (int b = 0; b < 8; ++b) {
const int mask = 1 << (7 - b);
uint8 color = 0;
for (int p = 0; p < 4; ++p) {
if (src[p * plane_size] & mask) {
color |= 1 << p;
}
}
*dst++ = color;
}
++src;
}
}
}
static void decode_atari(const uint8 *src, uint8 *dst) {
for (int y = 0; y < 200; ++y) {
for (int x = 0; x < 320; x += 16) {
for (int b = 0; b < 16; ++b) {
const int mask = 1 << (15 - b);
uint8 color = 0;
for (int p = 0; p < 4; ++p) {
if (READ_BE_UINT16(src + p * 2) & mask) {
color |= 1 << p;
}
}
*dst++ = color;
}
src += 8;
}
}
}
static void deinterlace555(const uint8 *src, int w, int h, uint16 *dst) {
for (int y = 0; y < h / 2; ++y) {
for (int x = 0; x < w; ++x) {
dst[x] = READ_BE_UINT16(src) & 0x7FFF; src += 2;
dst[w + x] = READ_BE_UINT16(src) & 0x7FFF; src += 2;
}
dst += w * 2;
}
}
static void yflip(const uint8 *src, int w, int h, uint8 *dst) {
for (int y = 0; y < h; ++y) {
memcpy(dst + (h - 1 - y) * w, src, w);
src += w;
}
}
void Video::scaleBitmap(const uint8 *src, int fmt) {
_graphics->drawBitmap(0, src, BITMAP_W, BITMAP_H, fmt);
}
void Video::copyBitmapPtr(const uint8 *src, uint32 size) {
if (_res->getDataType() == DT_DOS || _res->getDataType() == DT_AMIGA) {
decode_amiga(src, _tempBitmap);
scaleBitmap(_tempBitmap, FMT_CLUT);
} else if (_res->getDataType() == DT_ATARI) {
decode_atari(src, _tempBitmap);
scaleBitmap(_tempBitmap, FMT_CLUT);
} else if (_res->getDataType() == DT_WIN31) {
yflip(src, BITMAP_W, BITMAP_H, _tempBitmap);
scaleBitmap(_tempBitmap, FMT_CLUT);
} else if (_res->getDataType() == DT_3DO) {
deinterlace555(src, BITMAP_W, BITMAP_H, _bitmap555);
scaleBitmap((const uint8 *)_bitmap555, FMT_RGB555);
} else { // .BMP
if (Gfx::_is1991) {
const int w = READ_LE_UINT32(src + 0x12);
const int h = READ_LE_UINT32(src + 0x16);
if (w == BITMAP_W && h == BITMAP_H) {
const uint8 *data = src + READ_LE_UINT32(src + 0xA);
yflip(data, w, h, _tempBitmap);
scaleBitmap(_tempBitmap, FMT_CLUT);
}
} else {
int w, h;
uint8 *buf = decode_bitmap(src, false, -1, &w, &h);
if (buf) {
_graphics->drawBitmap(_buffers[0], buf, w, h, FMT_RGB);
free(buf);
}
}
}
}
static void readPaletteWin31(const uint8 *buf, int num, Color pal[16]) {
const uint8 *p = buf + num * 16 * sizeof(uint16);
for (int i = 0; i < 16; ++i) {
const uint16 index = READ_LE_UINT16(p); p += 2;
const uint32 color = READ_LE_UINT32(buf + 0xC04 + index * sizeof(uint32));
pal[i].r = color & 255;
pal[i].g = (color >> 8) & 255;
pal[i].b = (color >> 16) & 255;
}
}
static void readPalette3DO(const uint8 *buf, int num, Color pal[16]) {
const uint8 *p = buf + num * 16 * sizeof(uint16);
for (int i = 0; i < 16; ++i) {
const uint16 color = READ_BE_UINT16(p); p += 2;
const int r = (color >> 10) & 31;
const int g = (color >> 5) & 31;
const int b = color & 31;
pal[i].r = (r << 3) | (r >> 2);
pal[i].g = (g << 3) | (g >> 2);
pal[i].b = (b << 3) | (b >> 2);
}
}
static void readPaletteEGA(const uint8 *buf, int num, Color pal[16]) {
const uint8 *p = buf + num * 16 * sizeof(uint16);
p += 1024; // EGA colors are stored after VGA (Amiga)
for (int i = 0; i < 16; ++i) {
const uint16 color = READ_BE_UINT16(p); p += 2;
if (1) {
const uint8 *ega = &Video::PALETTE_EGA[3 * ((color >> 12) & 15)];
pal[i].r = ega[0];
pal[i].g = ega[1];
pal[i].b = ega[2];
} else { // lower 12 bits hold other colors
const uint8 r = (color >> 8) & 0xF;
const uint8 g = (color >> 4) & 0xF;
const uint8 b = color & 0xF;
pal[i].r = (r << 4) | r;
pal[i].g = (g << 4) | g;
pal[i].b = (b << 4) | b;
}
}
}
static void readPaletteAmiga(const uint8 *buf, int num, Color pal[16]) {
const uint8 *p = buf + num * 16 * sizeof(uint16);
for (int i = 0; i < 16; ++i) {
const uint16 color = READ_BE_UINT16(p); p += 2;
const uint8 r = (color >> 8) & 0xF;
const uint8 g = (color >> 4) & 0xF;
const uint8 b = color & 0xF;
pal[i].r = (r << 4) | r;
pal[i].g = (g << 4) | g;
pal[i].b = (b << 4) | b;
}
}
void Video::changePal(uint8 palNum) {
if (palNum < 32 && palNum != _currentPal) {
Color pal[16];
if (_res->getDataType() == DT_WIN31) {
readPaletteWin31(_res->_segVideoPal, palNum, pal);
} else if (_res->getDataType() == DT_3DO) {
readPalette3DO(_res->_segVideoPal, palNum, pal);
} else if (_res->getDataType() == DT_DOS && _useEGA) {
readPaletteEGA(_res->_segVideoPal, palNum, pal);
} else {
readPaletteAmiga(_res->_segVideoPal, palNum, pal);
}
_graphics->setPalette(pal, 16);
_currentPal = palNum;
}
}
void Video::updateDisplay(uint8 page, SystemStub *stub) {
debugC(kDebugVideo, "Video::updateDisplay(%d)", page);
if (page != 0xFE) {
if (page == 0xFF) {
SWAP(_buffers[1], _buffers[2]);
} else {
_buffers[1] = getPagePtr(page);
}
}
if (_nextPal != 0xFF) {
changePal(_nextPal);
_nextPal = 0xFF;
}
_graphics->drawBuffer(_buffers[1], stub);
}
void Video::setPaletteColor(uint8 color, int r, int g, int b) {
Color c;
c.r = r;
c.g = g;
c.b = b;
_graphics->setPalette(&c, 1);
}
void Video::drawRect(uint8 page, uint8 color, int x1, int y1, int x2, int y2) {
Point pt;
pt.x = x1;
pt.y = y1;
_graphics->drawRect(page, color, &pt, x2 - x1, y2 - y1);
}
void Video::drawBitmap3DO(const char *name, SystemStub *stub) {
assert(_res->getDataType() == DT_3DO);
int w, h;
uint16 *data = _res->_3do->loadShape555(name, &w, &h);
if (data) {
Graphics::Surface s;
s.setPixels(data);
s.w = s.pitch = w;
s.h = h;
s.format = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0);
_graphics->drawBitmapOverlay(s, FMT_RGB555, stub);
free(data);
}
}
} // namespace Awe