mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-11 19:54:03 +00:00
448 lines
12 KiB
C++
448 lines
12 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 "glk/comprehend/pics.h"
|
|
#include "common/memstream.h"
|
|
#include "glk/comprehend/charset.h"
|
|
#include "glk/comprehend/comprehend.h"
|
|
#include "glk/comprehend/draw_surface.h"
|
|
#include "glk/comprehend/file_buf.h"
|
|
#include "glk/comprehend/game.h"
|
|
#include "glk/comprehend/game_data.h"
|
|
|
|
namespace Glk {
|
|
namespace Comprehend {
|
|
|
|
#define IMAGES_PER_FILE 16
|
|
|
|
enum Opcode {
|
|
OPCODE_END = 0,
|
|
OPCODE_SET_TEXT_POS = 1,
|
|
OPCODE_SET_PEN_COLOR = 2,
|
|
OPCODE_TEXT_CHAR = 3,
|
|
OPCODE_SET_SHAPE = 4,
|
|
OPCODE_TEXT_OUTLINE = 5,
|
|
OPCODE_SET_FILL_COLOR = 6,
|
|
OPCODE_END2 = 7,
|
|
OPCODE_MOVE_TO = 8,
|
|
OPCODE_DRAW_BOX = 9,
|
|
OPCODE_DRAW_LINE = 10,
|
|
OPCODE_DRAW_CIRCLE = 11,
|
|
OPCODE_DRAW_SHAPE = 12,
|
|
OPCODE_DELAY = 13,
|
|
OPCODE_PAINT = 14,
|
|
OPCODE_RESET = 15
|
|
};
|
|
|
|
enum SpecialOpcode {
|
|
RESETOP_0 = 0,
|
|
RESETOP_RESET = 1,
|
|
RESETOP_OO_TOPOS_UNKNOWN = 3
|
|
};
|
|
|
|
/*-------------------------------------------------------*/
|
|
|
|
uint32 Pics::ImageContext::getFillColor() const {
|
|
uint color = _fillColor;
|
|
|
|
// FIXME: Properly display text color in Crimson Crown
|
|
if (g_vm->getGameID() == "crimsoncrown" && color == 0x000000ff)
|
|
color = G_COLOR_WHITE;
|
|
|
|
return color;
|
|
}
|
|
|
|
void Pics::ImageContext::lineFixes() {
|
|
// WORKAROUND: Fix lines on title screens so floodfill works correctly
|
|
if (g_vm->getGameID() == "transylvania" && _picIndex == 9999) {
|
|
_drawSurface->drawLine(191, 31, 192, 31, G_COLOR_BLACK); // v
|
|
_drawSurface->drawLine(196, 50, 197, 50, G_COLOR_BLACK); // a
|
|
_drawSurface->drawLine(203, 49, 204, 49, G_COLOR_BLACK);
|
|
_drawSurface->drawLine(197, 53, 202, 53, G_COLOR_BLACK);
|
|
_drawSurface->drawLine(215, 51, 220, 51, G_COLOR_BLACK); // n
|
|
_drawSurface->drawLine(221, 51, 222, 51, G_COLOR_BLACK);
|
|
_drawSurface->drawLine(228, 50, 229, 50, G_COLOR_BLACK);
|
|
_drawSurface->drawLine(217, 59, 220, 59, G_COLOR_BLACK);
|
|
_drawSurface->drawLine(212, 49, 212, 50, G_COLOR_BLACK);
|
|
_drawSurface->drawLine(213, 49, 213, 52, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(235, 52, 236, 61, G_COLOR_BLACK); // i
|
|
_drawSurface->drawLine(237, 61, 238, 61, G_COLOR_BLACK);
|
|
}
|
|
|
|
if (g_vm->getGameID() == "crimsoncrown" && _picIndex == 9999 && _x == 67 && _y == 55) {
|
|
_drawSurface->drawLine(78, 28, 77, 29, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(71, 43, 69, 47, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(67, 57, 68, 56, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(79, 101, 80, 101, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(183, 101, 184, 100, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(193, 47, 193, 48, G_COLOR_WHITE);
|
|
_drawSurface->drawLine(68, 48, 71, 48, G_COLOR_BLACK);
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------*/
|
|
|
|
Pics::ImageFile::ImageFile(const Common::String &filename, bool isSingleImage) : _filename(filename) {
|
|
Common::File f;
|
|
uint16 version;
|
|
int i;
|
|
|
|
if (!f.open(_filename))
|
|
error("Could not open file - %s", filename.c_str());
|
|
|
|
if (isSingleImage) {
|
|
// It's a title image file, which has only a single image with no
|
|
// table of image offsets
|
|
_imageOffsets.resize(1);
|
|
_imageOffsets[0] = 4;
|
|
return;
|
|
}
|
|
|
|
version = f.readUint16LE();
|
|
if (version == 0x1000)
|
|
f.seek(4);
|
|
else
|
|
f.seek(0);
|
|
|
|
// Get the image offsets in the file
|
|
_imageOffsets.resize(IMAGES_PER_FILE);
|
|
for (i = 0; i < IMAGES_PER_FILE; i++) {
|
|
_imageOffsets[i] = f.readUint16LE();
|
|
if (version == 0x1000)
|
|
_imageOffsets[i] += 4;
|
|
}
|
|
}
|
|
|
|
void Pics::ImageFile::draw(uint index, ImageContext *ctx) const {
|
|
if (!ctx->_file.open(_filename))
|
|
error("Opening image file");
|
|
|
|
ctx->_file.seek(_imageOffsets[index]);
|
|
|
|
for (bool done = false; !done;) {
|
|
done = doImageOp(ctx);
|
|
}
|
|
}
|
|
|
|
bool Pics::ImageFile::doImageOp(Pics::ImageContext *ctx) const {
|
|
uint8 opcode;
|
|
uint16 a, b;
|
|
|
|
opcode = ctx->_file.readByte();
|
|
debugCN(kDebugGraphics, " %.4x [%.2x]: ", (int)ctx->_file.pos() - 1, opcode);
|
|
|
|
byte param = opcode & 0xf;
|
|
opcode >>= 4;
|
|
|
|
switch (opcode) {
|
|
case OPCODE_END:
|
|
case OPCODE_END2:
|
|
// End of the rendering
|
|
debugC(kDebugGraphics, "End of image");
|
|
return true;
|
|
|
|
case OPCODE_SET_TEXT_POS:
|
|
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
|
|
b = imageGetOperand(ctx);
|
|
debugC(kDebugGraphics, "set_text_pos(%d, %d)", a, b);
|
|
|
|
ctx->_textX = a;
|
|
ctx->_textY = b;
|
|
break;
|
|
|
|
case OPCODE_SET_PEN_COLOR:
|
|
debugC(kDebugGraphics, "set_pen_color(%.2x)", opcode);
|
|
if (!(ctx->_drawFlags & IMAGEF_NO_FILL))
|
|
ctx->_penColor = ctx->_drawSurface->getPenColor(param);
|
|
break;
|
|
|
|
case OPCODE_TEXT_CHAR:
|
|
case OPCODE_TEXT_OUTLINE:
|
|
// Text outline mode draws a bunch of pixels that sort of looks like the char
|
|
// TODO: See if the outline mode is ever used
|
|
if (opcode == OPCODE_TEXT_OUTLINE)
|
|
warning("TODO: Implement drawing text outlines");
|
|
|
|
a = imageGetOperand(ctx);
|
|
if (a < 0x20 || a >= 0x7f) {
|
|
warning("Invalid character - %c", a);
|
|
a = '?';
|
|
}
|
|
|
|
debugC(kDebugGraphics, "draw_char(%c)", a);
|
|
ctx->_font->drawChar(ctx->_drawSurface, a, ctx->_textX, ctx->_textY, ctx->getFillColor());
|
|
ctx->_textX += ctx->_font->getCharWidth(a);
|
|
break;
|
|
|
|
case OPCODE_SET_SHAPE:
|
|
debugC(kDebugGraphics, "set_shape_type(%.2x)", param);
|
|
|
|
if (param == 8) {
|
|
// FIXME: This appears to be a _shape type. Only used by OO-Topos
|
|
warning("TODO: Shape type 8");
|
|
ctx->_shape = SHAPE_PIXEL;
|
|
} else {
|
|
ctx->_shape = (Shape)param;
|
|
}
|
|
break;
|
|
|
|
case OPCODE_SET_FILL_COLOR:
|
|
a = imageGetOperand(ctx);
|
|
debugC(kDebugGraphics, "set_fill_color(%.2x)", a);
|
|
ctx->_fillColor = ctx->_drawSurface->getFillColor(a);
|
|
break;
|
|
|
|
case OPCODE_MOVE_TO:
|
|
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
|
|
b = imageGetOperand(ctx);
|
|
|
|
debugC(kDebugGraphics, "move_to(%d, %d)", a, b);
|
|
ctx->_x = a;
|
|
ctx->_y = b;
|
|
break;
|
|
|
|
case OPCODE_DRAW_BOX:
|
|
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
|
|
b = imageGetOperand(ctx);
|
|
|
|
debugC(kDebugGraphics, "draw_box (%d, %d) - (%d, %d)",
|
|
ctx->_x, ctx->_y, a, b);
|
|
|
|
ctx->_drawSurface->drawBox(ctx->_x, ctx->_y, a, b, ctx->_penColor);
|
|
break;
|
|
|
|
case OPCODE_DRAW_LINE:
|
|
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
|
|
b = imageGetOperand(ctx);
|
|
|
|
debugC(kDebugGraphics, "draw_line (%d, %d) - (%d, %d)",
|
|
ctx->_x, ctx->_y, a, b);
|
|
ctx->_drawSurface->drawLine(ctx->_x, ctx->_y, a, b, ctx->_penColor);
|
|
|
|
ctx->_x = a;
|
|
ctx->_y = b;
|
|
break;
|
|
|
|
case OPCODE_DRAW_CIRCLE:
|
|
a = imageGetOperand(ctx);
|
|
debugC(kDebugGraphics, "draw_circle (%d, %d) diameter=%d",
|
|
ctx->_x, ctx->_y, a);
|
|
|
|
ctx->_drawSurface->drawCircle(ctx->_x, ctx->_y, a, ctx->_penColor);
|
|
break;
|
|
|
|
case OPCODE_DRAW_SHAPE:
|
|
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
|
|
b = imageGetOperand(ctx);
|
|
debugC(kDebugGraphics, "draw_shape(%d, %d), style=%.2x, fill=%.2x",
|
|
a, b, ctx->_shape, ctx->_fillColor);
|
|
|
|
if (!(ctx->_drawFlags & IMAGEF_NO_FILL))
|
|
ctx->_drawSurface->drawShape(a, b, ctx->_shape, ctx->_fillColor);
|
|
break;
|
|
|
|
case OPCODE_DELAY:
|
|
// The original allowed for rendering to be paused briefly. We don't do
|
|
// that in ScummVM, and just show the finished rendered image
|
|
(void)imageGetOperand(ctx);
|
|
break;
|
|
|
|
case OPCODE_PAINT:
|
|
a = imageGetOperand(ctx) + (param & 1 ? 256 : 0);
|
|
b = imageGetOperand(ctx);
|
|
if (opcode & 0x1)
|
|
a += 255;
|
|
|
|
debugC(kDebugGraphics, "paint(%d, %d)", a, b);
|
|
ctx->lineFixes();
|
|
if (!(ctx->_drawFlags & IMAGEF_NO_FILL))
|
|
ctx->_drawSurface->floodFill(a, b, ctx->_fillColor);
|
|
break;
|
|
|
|
#if 0
|
|
// FIXME: The reset case was causing room outside cell to be drawn all white
|
|
case OPCODE_RESET:
|
|
a = imageGetOperand(ctx);
|
|
doResetOp(ctx, a);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
//ctx->_drawSurface->dumpToScreen();
|
|
|
|
return false;
|
|
}
|
|
|
|
void Pics::ImageFile::doResetOp(ImageContext *ctx, byte param) const {
|
|
switch (param) {
|
|
case RESETOP_0:
|
|
// In Transylvania this sub-opcode is a do nothing
|
|
break;
|
|
|
|
case RESETOP_RESET:
|
|
// TODO: Calls same reset that first gets called when rendering starts.
|
|
// Figure out what the implication of resetting the variables does
|
|
break;
|
|
|
|
case RESETOP_OO_TOPOS_UNKNOWN:
|
|
// TODO: This is called for some scenes in OO-Topis. Figure out what it does
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint16 Pics::ImageFile::imageGetOperand(ImageContext *ctx) const {
|
|
return ctx->_file.readByte();
|
|
}
|
|
|
|
/*-------------------------------------------------------*/
|
|
|
|
Pics::Pics() : _font(nullptr) {
|
|
if (Common::File::exists("charset.gda"))
|
|
_font = new CharSet();
|
|
else if (g_comprehend->getGameID() == "talisman")
|
|
_font = new TalismanFont();
|
|
}
|
|
|
|
Pics::~Pics() {
|
|
delete _font;
|
|
}
|
|
|
|
void Pics::clear() {
|
|
_rooms.clear();
|
|
_items.clear();
|
|
}
|
|
|
|
void Pics::load(const Common::StringArray &roomFiles,
|
|
const Common::StringArray &itemFiles,
|
|
const Common::String &titleFile) {
|
|
clear();
|
|
|
|
for (uint idx = 0; idx < roomFiles.size(); ++idx)
|
|
_rooms.push_back(ImageFile(roomFiles[idx]));
|
|
for (uint idx = 0; idx < itemFiles.size(); ++idx)
|
|
_items.push_back(ImageFile(itemFiles[idx]));
|
|
|
|
if (!titleFile.empty())
|
|
_title = ImageFile(titleFile, true);
|
|
}
|
|
|
|
int Pics::getPictureNumber(const Common::String &filename) const {
|
|
// Ensure prefix and suffix
|
|
if (!filename.hasPrefixIgnoreCase("pic") ||
|
|
!filename.hasSuffixIgnoreCase(".raw"))
|
|
return -1;
|
|
|
|
// Get the number part
|
|
Common::String num(filename.c_str() + 3, filename.size() - 7);
|
|
if (num.empty() || !Common::isDigit(num[0]))
|
|
return -1;
|
|
|
|
return atoi(num.c_str());
|
|
}
|
|
|
|
bool Pics::hasFile(const Common::Path &path) const {
|
|
Common::String name = path.baseName();
|
|
int num = getPictureNumber(name);
|
|
if (num == -1)
|
|
return false;
|
|
|
|
if (num == DARK_ROOM || num == BRIGHT_ROOM || num == TITLE_IMAGE)
|
|
return true;
|
|
if (num >= ITEMS_OFFSET && num < (int)(ITEMS_OFFSET + _items.size() * IMAGES_PER_FILE))
|
|
return true;
|
|
if (num < ITEMS_OFFSET && (num % 100) < (int)(_rooms.size() * IMAGES_PER_FILE))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int Pics::listMembers(Common::ArchiveMemberList &list) const {
|
|
return list.size();
|
|
}
|
|
|
|
const Common::ArchiveMemberPtr Pics::getMember(const Common::Path &path) const {
|
|
if (!hasFile(path))
|
|
return Common::ArchiveMemberPtr();
|
|
|
|
return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(path, *this));
|
|
}
|
|
|
|
Common::SeekableReadStream *Pics::createReadStreamForMember(const Common::Path &path) const {
|
|
Common::String name = path.baseName();
|
|
// Get the picture number
|
|
int num = getPictureNumber(name);
|
|
if (num == -1 || !hasFile(path))
|
|
return nullptr;
|
|
|
|
// Draw the image
|
|
drawPicture(num);
|
|
|
|
// Create a stream with the data for the surface
|
|
Common::MemoryReadWriteStream *stream =
|
|
new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
|
|
const DrawSurface &ds = *g_comprehend->_drawSurface;
|
|
stream->writeUint16LE(ds.w);
|
|
stream->writeUint16LE(ds.h);
|
|
stream->writeUint16LE(0); // Palette size
|
|
stream->write(ds.getPixels(), ds.w * ds.h * 4);
|
|
|
|
return stream;
|
|
}
|
|
|
|
void Pics::drawPicture(int pictureNum) const {
|
|
ImageContext ctx(g_comprehend->_drawSurface, _font, g_comprehend->_drawFlags, pictureNum);
|
|
|
|
if (pictureNum == DARK_ROOM) {
|
|
ctx._drawSurface->clearScreen(G_COLOR_BLACK);
|
|
|
|
} else if (pictureNum == BRIGHT_ROOM) {
|
|
ctx._drawSurface->clearScreen(G_COLOR_WHITE);
|
|
|
|
} else if (pictureNum == TITLE_IMAGE) {
|
|
ctx._drawSurface->clearScreen(G_COLOR_WHITE);
|
|
_title.draw(0, &ctx);
|
|
|
|
} else if (pictureNum >= ITEMS_OFFSET) {
|
|
pictureNum -= ITEMS_OFFSET;
|
|
ctx._drawSurface->clear(0);
|
|
_items[pictureNum / IMAGES_PER_FILE].draw(
|
|
pictureNum % IMAGES_PER_FILE, &ctx);
|
|
|
|
} else {
|
|
if (pictureNum < LOCATIONS_NO_BG_OFFSET) {
|
|
ctx._drawSurface->clearScreen((ctx._drawFlags & IMAGEF_REVERSE) ? G_COLOR_BLACK : G_COLOR_WHITE);
|
|
if (ctx._drawFlags & IMAGEF_REVERSE)
|
|
ctx._penColor = RGB(255, 255, 255);
|
|
} else {
|
|
ctx._drawSurface->clear(0);
|
|
}
|
|
pictureNum %= 100;
|
|
_rooms[pictureNum / IMAGES_PER_FILE].draw(
|
|
pictureNum % IMAGES_PER_FILE, &ctx);
|
|
}
|
|
}
|
|
|
|
} // namespace Comprehend
|
|
} // namespace Glk
|