mirror of
https://github.com/scummvm/scummvm.git
synced 2025-04-02 10:52:32 -04:00
Replacing a cast member with a Picture will reset the _initialRect to a generic one positioned at (0, 0). Before we do that, untransform the registration offset so that it remains relative to the image coordinates. Fixes graphics in Dungeon Street.
953 lines
29 KiB
C++
953 lines
29 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 "common/config-manager.h"
|
|
#include "common/macresman.h"
|
|
#include "graphics/surface.h"
|
|
#include "graphics/macgui/macwidget.h"
|
|
#include "image/bmp.h"
|
|
#include "image/jpeg.h"
|
|
#include "image/pict.h"
|
|
#include "image/png.h"
|
|
|
|
#include "director/director.h"
|
|
#include "director/cast.h"
|
|
#include "director/images.h"
|
|
#include "director/movie.h"
|
|
#include "director/picture.h"
|
|
#include "director/score.h"
|
|
#include "director/window.h"
|
|
#include "director/castmember/bitmap.h"
|
|
#include "director/lingo/lingo-the.h"
|
|
|
|
namespace Director {
|
|
|
|
BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1)
|
|
: CastMember(cast, castId, stream) {
|
|
_type = kCastBitmap;
|
|
_picture = new Picture();
|
|
_ditheredImg = nullptr;
|
|
_matte = nullptr;
|
|
_noMatte = false;
|
|
_bytes = 0;
|
|
_pitch = 0;
|
|
_flags2 = 0;
|
|
_regX = _regY = 0;
|
|
_clut = CastMemberID(0, 0);
|
|
_ditheredTargetClut = CastMemberID(0, 0);
|
|
_bitsPerPixel = 0;
|
|
_external = false;
|
|
|
|
if (debugChannelSet(5, kDebugLoading)) {
|
|
stream.hexdump(stream.size());
|
|
}
|
|
|
|
if (version < kFileVer400) {
|
|
_flags1 = flags1; // region: 0 - auto, 1 - matte, 2 - disabled
|
|
|
|
_bytes = stream.readUint16();
|
|
// A little context about how bitmap bounding boxes are stored.
|
|
// In the Director editor, images can be edited on a big scrolling canvas with
|
|
// the image in the middle. _initialRect describes the location on that virtual
|
|
// canvas, with the top-left being the start position of the image.
|
|
// _regX and _regY is the registration offset, in canvas space.
|
|
// This means if a bitmap cast member is placed at (64, 64) on the score, the
|
|
// registration offset of the image is placed at (64, 64).
|
|
// By default the registration offset is the dead centre of the image.
|
|
// _boundingRect I think is used internally by the editor and not elsewhere.
|
|
_initialRect = Movie::readRect(stream);
|
|
_boundingRect = Movie::readRect(stream);
|
|
_regY = stream.readSint16();
|
|
_regX = stream.readSint16();
|
|
|
|
if (_bytes & 0x8000) {
|
|
_bitsPerPixel = stream.readUint16();
|
|
int clutId = stream.readSint16();
|
|
|
|
if (clutId <= 0) // builtin palette
|
|
_clut = CastMemberID(clutId - 1, -1);
|
|
else
|
|
_clut = CastMemberID(clutId, DEFAULT_CAST_LIB);
|
|
} else {
|
|
_bitsPerPixel = 1;
|
|
_clut = CastMemberID(kClutSystemMac, -1);
|
|
}
|
|
|
|
_pitch = _initialRect.width();
|
|
if (_pitch % 16)
|
|
_pitch += 16 - (_initialRect.width() % 16);
|
|
|
|
_pitch *= _bitsPerPixel;
|
|
_pitch >>= 3;
|
|
|
|
} else if (version >= kFileVer400 && version < kFileVer600) {
|
|
_flags1 = flags1;
|
|
_pitch = stream.readUint16();
|
|
_pitch &= 0x0fff;
|
|
|
|
_initialRect = Movie::readRect(stream);
|
|
_boundingRect = Movie::readRect(stream);
|
|
_regY = stream.readUint16();
|
|
_regX = stream.readUint16();
|
|
|
|
_bitsPerPixel = 0;
|
|
|
|
if (stream.pos() < stream.size()) {
|
|
// castSize is > 22 bytes
|
|
stream.readByte();
|
|
_bitsPerPixel = stream.readByte();
|
|
int clutCastLib = -1;
|
|
if (version >= kFileVer500) {
|
|
clutCastLib = stream.readSint16();
|
|
}
|
|
int clutId = stream.readSint16();
|
|
|
|
if (clutId <= 0) // builtin palette
|
|
_clut = CastMemberID(clutId - 1, -1);
|
|
else if (clutId > 0) {
|
|
if (clutCastLib == -1) {
|
|
clutCastLib = _cast->_castLibID;
|
|
}
|
|
_clut = CastMemberID(clutId, clutCastLib);
|
|
}
|
|
if (stream.pos() < stream.size()) {
|
|
// castSize > 28 bytes on D4, > 30 bytes on D5
|
|
stream.readUint16();
|
|
/* uint16 unk1 = */ stream.readUint16();
|
|
stream.readUint16();
|
|
|
|
stream.readUint32();
|
|
stream.readUint32();
|
|
|
|
_flags2 = stream.readUint16();
|
|
}
|
|
}
|
|
|
|
if (_bitsPerPixel == 0)
|
|
_bitsPerPixel = 1;
|
|
|
|
int tail = stream.size() - stream.pos();
|
|
if (tail > 0) {
|
|
warning("BUILDBOT: BitmapCastMember: %d bytes left", tail);
|
|
if (debugChannelSet(2, kDebugLoading)) {
|
|
byte buf[256];
|
|
tail = MIN(256, tail);
|
|
stream.read(buf, tail);
|
|
debug("BitmapCastMember: tail");
|
|
Common::hexdump(buf, tail);
|
|
}
|
|
}
|
|
} else {
|
|
warning("STUB: BitmapCastMember::BitmapCastMember(): Bitmaps not yet supported for version %d", version);
|
|
}
|
|
|
|
_tag = castTag;
|
|
}
|
|
|
|
BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1)
|
|
: CastMember(cast, castId) {
|
|
_type = kCastBitmap;
|
|
_matte = nullptr;
|
|
_noMatte = false;
|
|
_bytes = 0;
|
|
if (img != nullptr) {
|
|
_picture = new Picture(*img);
|
|
}
|
|
_ditheredImg = nullptr;
|
|
_clut = CastMemberID(0, 0);
|
|
_ditheredTargetClut = CastMemberID(0, 0);
|
|
_initialRect = Common::Rect(0, 0, img->getSurface()->w, img->getSurface()->h);
|
|
_pitch = img->getSurface()->pitch;
|
|
_bitsPerPixel = img->getSurface()->format.bytesPerPixel * 8;
|
|
_regY = img->getSurface()->h / 2;
|
|
_regX = img->getSurface()->w / 2;
|
|
_flags1 = flags1;
|
|
_flags2 = 0;
|
|
_tag = 0;
|
|
_external = false;
|
|
}
|
|
|
|
BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, BitmapCastMember &source)
|
|
: CastMember(cast, castId) {
|
|
_type = kCastBitmap;
|
|
// force a load so we can copy the cast resource information
|
|
source.load();
|
|
_loaded = true;
|
|
|
|
_initialRect = source._initialRect;
|
|
_boundingRect = source._boundingRect;
|
|
_children = source._children;
|
|
|
|
_picture = source._picture ? new Picture(*source._picture) : nullptr;
|
|
_ditheredImg = nullptr;
|
|
_matte = nullptr;
|
|
|
|
_pitch = source._pitch;
|
|
_regX = source._regX;
|
|
_regY = source._regY;
|
|
_flags2 = source._regY;
|
|
_bytes = source._bytes;
|
|
_clut = source._clut;
|
|
_ditheredTargetClut = source._ditheredTargetClut;
|
|
|
|
_bitsPerPixel = source._bitsPerPixel;
|
|
|
|
_tag = source._tag;
|
|
_noMatte = source._noMatte;
|
|
_external = source._external;
|
|
|
|
warning("BitmapCastMember(): Duplicating source %d to target %d! This is unlikely to work properly, as the resource loader is based on the cast ID", source._castId, castId);
|
|
}
|
|
|
|
BitmapCastMember::~BitmapCastMember() {
|
|
delete _picture;
|
|
|
|
if (_ditheredImg) {
|
|
_ditheredImg->free();
|
|
delete _ditheredImg;
|
|
}
|
|
|
|
if (_matte) {
|
|
_matte->free();
|
|
delete _matte;
|
|
}
|
|
}
|
|
|
|
Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) {
|
|
if (!_picture) {
|
|
warning("BitmapCastMember::createWidget: No picture");
|
|
return nullptr;
|
|
}
|
|
|
|
// skip creating widget when the bbox is not available, maybe we should create it using initialRect
|
|
if (!bbox.width() || !bbox.height())
|
|
return nullptr;
|
|
|
|
// Check if we need to dither the image
|
|
int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel;
|
|
int srcBpp = _picture->_surface.format.bytesPerPixel;
|
|
|
|
const byte *pal = _picture->_palette;
|
|
bool previouslyDithered = _ditheredImg != nullptr;
|
|
|
|
// _ditheredImg should contain a cached copy of the bitmap after any expensive
|
|
// colourspace transformations (e.g. palette remapping or dithering).
|
|
// We also want to make sure that
|
|
if (isModified() || (((srcBpp == 1) || (srcBpp > 1 && dstBpp == 1)) && !previouslyDithered)) {
|
|
if (_ditheredImg) {
|
|
_ditheredImg->free();
|
|
delete _ditheredImg;
|
|
_ditheredImg = nullptr;
|
|
_ditheredTargetClut = CastMemberID(0, 0);
|
|
}
|
|
|
|
if (dstBpp == 1) {
|
|
// ScummVM using 8-bit video
|
|
|
|
if (srcBpp > 1
|
|
// At least early directors were not remapping 8bpp images. But in case it is
|
|
// needed, here is the code
|
|
#if 0
|
|
|| (srcBpp == 1 &&
|
|
memcmp(g_director->_wm->getPalette(), _img->_palette, _img->_paletteSize))
|
|
#endif
|
|
) {
|
|
|
|
_ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, nullptr, 0, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize());
|
|
|
|
pal = g_director->_wm->getPalette();
|
|
} else if (srcBpp == 1) {
|
|
_ditheredImg = getDitherImg();
|
|
}
|
|
} else {
|
|
// ScummVM using 32-bit video
|
|
//if (srcBpp > 1 && srcBpp != 4) {
|
|
// non-indexed surface, convert to 32-bit
|
|
// _ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, nullptr, 0, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize());
|
|
|
|
//} else
|
|
if (srcBpp == 1) {
|
|
_ditheredImg = getDitherImg();
|
|
}
|
|
}
|
|
|
|
Movie *movie = g_director->getCurrentMovie();
|
|
Score *score = movie->getScore();
|
|
|
|
if (_ditheredImg) {
|
|
debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Dithering cast %d from source palette %s to target palette %s", _castId, _clut.asString().c_str(), score->getCurrentPalette().asString().c_str());
|
|
} else if (previouslyDithered) {
|
|
debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Removed dithered image for cast %d, score palette %s matches cast member", _castId, score->getCurrentPalette().asString().c_str());
|
|
|
|
}
|
|
}
|
|
|
|
Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false);
|
|
|
|
// scale for drawing a different size sprite
|
|
copyStretchImg(
|
|
_ditheredImg ? _ditheredImg : &_picture->_surface,
|
|
widget->getSurface()->surfacePtr(),
|
|
_initialRect,
|
|
bbox,
|
|
pal
|
|
);
|
|
|
|
return widget;
|
|
}
|
|
|
|
Graphics::Surface *BitmapCastMember::getDitherImg() {
|
|
Graphics::Surface *dither = nullptr;
|
|
|
|
// Convert indexed image to indexed palette
|
|
Movie *movie = g_director->getCurrentMovie();
|
|
Score *score = movie->getScore();
|
|
int targetBpp = g_director->_wm->_pixelformat.bytesPerPixel;
|
|
|
|
// Get the current score palette. Note that this is the ID of the palette in the list, not the cast member!
|
|
CastMemberID currentPaletteId = score->getCurrentPalette();
|
|
if (currentPaletteId.isNull())
|
|
currentPaletteId = movie->_defaultPalette;
|
|
PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
|
|
if (!currentPalette) {
|
|
currentPaletteId = CastMemberID(kClutSystemMac, -1);
|
|
currentPalette = g_director->getPalette(currentPaletteId);
|
|
}
|
|
CastMemberID castPaletteId = _clut;
|
|
// It is possible for Director to have saved an invalid ID in _clut;
|
|
// if this is the case, do no dithering.
|
|
if (castPaletteId.isNull())
|
|
castPaletteId = currentPaletteId;
|
|
|
|
// Check if the palette is in the middle of a color fade event
|
|
bool isColorCycling = score->isPaletteColorCycling();
|
|
|
|
const byte *dstPalette = targetBpp == 1 ? currentPalette->palette : nullptr;
|
|
int dstPaletteCount = targetBpp == 1 ? currentPalette->length : 0;
|
|
|
|
// First, check if the palettes are different
|
|
switch (_bitsPerPixel) {
|
|
// 1bpp - this is preconverted to 0x00 and 0xff, change nothing.
|
|
case 1:
|
|
break;
|
|
// 2bpp - convert to nearest using the standard 2-bit palette.
|
|
case 2:
|
|
{
|
|
const PaletteV4 &srcPal = g_director->getLoaded4Palette();
|
|
dither = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, dstPalette, dstPaletteCount, Graphics::kDitherNaive);
|
|
}
|
|
break;
|
|
// 4bpp - if using a builtin palette, use one of the corresponding 4-bit ones.
|
|
case 4:
|
|
{
|
|
const auto pals = g_director->getLoaded16Palettes();
|
|
// in D4 you aren't allowed to use custom palettes for 4-bit images, so uh...
|
|
// I guess default to the mac palette?
|
|
CastMemberID palIndex = pals.contains(castPaletteId) ? castPaletteId : CastMemberID(kClutSystemMac, -1);
|
|
const PaletteV4 &srcPal = pals.getVal(palIndex);
|
|
dither = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, dstPalette, dstPaletteCount, Graphics::kDitherNaive);
|
|
}
|
|
break;
|
|
// 8bpp - if using a different palette, and we're not doing a color cycling operation, convert using nearest colour matching
|
|
case 8:
|
|
// "break" means falling back to the default of rendering the image with
|
|
// the current 8-bit palette. The below is only about -redithering colours-;
|
|
// i.e. redrawing the picture to use the current palette.
|
|
// Only redither 8-bit images in 8-bit mode if we have the remap palette flag set, or it is external
|
|
if (targetBpp == 1 && !movie->_remapPalettesWhenNeeded && !_external)
|
|
break;
|
|
// If we're in 32-bit mode, and not in puppet palette mode, then "redither" as well.
|
|
if (targetBpp == 4 && score->_puppetPalette && !_external)
|
|
break;
|
|
if (_external || (castPaletteId != currentPaletteId && !isColorCycling)) {
|
|
const auto pals = g_director->getLoadedPalettes();
|
|
CastMemberID palIndex = pals.contains(castPaletteId) ? castPaletteId : CastMemberID(kClutSystemMac, -1);
|
|
const PaletteV4 &srcPal = pals.getVal(palIndex);
|
|
|
|
// If it is an external image, use the included palette.
|
|
// For BMP images especially, they'll often have the right colors
|
|
// but in the wrong palette order.
|
|
const byte *palPtr = _external ? _picture->_palette : srcPal.palette;
|
|
int palCount = _external ? _picture->getPaletteCount() : srcPal.length;
|
|
dither = _picture->_surface.convertTo(g_director->_wm->_pixelformat, palPtr, palCount, dstPalette, dstPaletteCount, Graphics::kDitherNaive);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (dither) {
|
|
// Save the palette ID so we can check if a redraw is required
|
|
_ditheredTargetClut = currentPaletteId;
|
|
|
|
if (!_external && targetBpp == 1) {
|
|
// Finally, the first and last colours in the palette are special. No matter what the palette remap
|
|
// does, we need to scrub those to be the same.
|
|
const Graphics::Surface *src = &_picture->_surface;
|
|
for (int y = 0; y < src->h; y++) {
|
|
for (int x = 0; x < src->w; x++) {
|
|
const int test = *(const byte *)src->getBasePtr(x, y);
|
|
if (test == 0 || test == (1 << _bitsPerPixel) - 1) {
|
|
*(byte *)dither->getBasePtr(x, y) = test == 0 ? 0x00 : 0xff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dither;
|
|
|
|
}
|
|
|
|
bool BitmapCastMember::isModified() {
|
|
if (CastMember::isModified()) {
|
|
// Let's us use "setChanged" when changing the picture through Lingo
|
|
return true;
|
|
}
|
|
// Check for palette changes.
|
|
// If a bitmap has a custom palette assigned to it, createWidget()
|
|
// will dither the image so that it fits within the current palette.
|
|
// When the score palette changes, we need to flag that the widget needs
|
|
// to be recreated.
|
|
if (!_clut.isNull()) {
|
|
Movie *movie = g_director->getCurrentMovie();
|
|
Score *score = movie->getScore();
|
|
CastMemberID currentPaletteId = score->getCurrentPalette();
|
|
if (currentPaletteId.isNull())
|
|
currentPaletteId = movie->_defaultPalette;
|
|
PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId);
|
|
if (!currentPalette) {
|
|
currentPaletteId = CastMemberID(kClutSystemMac, -1);
|
|
currentPalette = g_director->getPalette(currentPaletteId);
|
|
}
|
|
CastMemberID castPaletteId = _clut;
|
|
if (castPaletteId.isNull())
|
|
castPaletteId = movie->_defaultPalette;
|
|
|
|
return !_ditheredTargetClut.isNull() && _ditheredTargetClut != currentPaletteId;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BitmapCastMember::createMatte(Common::Rect &bbox) {
|
|
// Like background trans, but all white pixels NOT ENCLOSED by coloured pixels
|
|
// are transparent
|
|
Graphics::Surface tmp;
|
|
tmp.create(bbox.width(), bbox.height(), g_director->_pixelformat);
|
|
|
|
copyStretchImg(
|
|
_ditheredImg ? _ditheredImg : &_picture->_surface,
|
|
&tmp,
|
|
_initialRect,
|
|
bbox
|
|
);
|
|
|
|
_noMatte = true;
|
|
|
|
// Searching white color in the corners
|
|
uint32 whiteColor = 0;
|
|
bool colorFound = false;
|
|
|
|
if (g_director->_pixelformat.bytesPerPixel == 1) {
|
|
for (int y = 0; y < tmp.h; y++) {
|
|
for (int x = 0; x < tmp.w; x++) {
|
|
byte color = *(byte *)tmp.getBasePtr(x, y);
|
|
|
|
if (g_director->getPalette()[color * 3 + 0] == 0xff &&
|
|
g_director->getPalette()[color * 3 + 1] == 0xff &&
|
|
g_director->getPalette()[color * 3 + 2] == 0xff) {
|
|
whiteColor = color;
|
|
colorFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
whiteColor = g_director->_wm->_colorWhite;
|
|
colorFound = true;
|
|
}
|
|
|
|
if (!colorFound) {
|
|
debugC(1, kDebugImages, "BitmapCastMember::createMatte(): No white color for matte image");
|
|
} else {
|
|
if (_matte) {
|
|
_matte->free();
|
|
delete _matte;
|
|
}
|
|
|
|
Graphics::FloodFill matteFill(&tmp, whiteColor, 0, true);
|
|
|
|
for (int yy = 0; yy < tmp.h; yy++) {
|
|
matteFill.addSeed(0, yy);
|
|
matteFill.addSeed(tmp.w - 1, yy);
|
|
}
|
|
|
|
for (int xx = 0; xx < tmp.w; xx++) {
|
|
matteFill.addSeed(xx, 0);
|
|
matteFill.addSeed(xx, tmp.h - 1);
|
|
}
|
|
|
|
matteFill.fillMask();
|
|
Graphics::Surface *matteSurf = matteFill.getMask();
|
|
// convert the mask to the same surface format used for 1bpp bitmaps.
|
|
// this uses the director palette scheme, so white is 0x00 and black is 0xff.
|
|
_matte = new Graphics::Surface();
|
|
_matte->create(matteSurf->w, matteSurf->h, Graphics::PixelFormat::createFormatCLUT8());
|
|
for (int y = 0; y < matteSurf->h; y++) {
|
|
for (int x = 0; x < matteSurf->w; x++) {
|
|
_matte->setPixel(x, y, matteSurf->getPixel(x, y) ? 0x00 : 0xff);
|
|
}
|
|
}
|
|
_noMatte = false;
|
|
}
|
|
|
|
tmp.free();
|
|
}
|
|
|
|
Graphics::Surface *BitmapCastMember::getMatte(Common::Rect &bbox) {
|
|
// Lazy loading of mattes
|
|
if (!_matte && !_noMatte) {
|
|
createMatte(bbox);
|
|
}
|
|
|
|
// check for the scale matte
|
|
if (_matte && (_matte->w != bbox.width() || _matte->h != bbox.height())) {
|
|
createMatte(bbox);
|
|
}
|
|
|
|
return _matte;
|
|
}
|
|
|
|
Common::String BitmapCastMember::formatInfo() {
|
|
return Common::String::format(
|
|
"initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, regX: %d, regY: %d, pitch: %d, bitsPerPixel: %d, palette: %s",
|
|
_initialRect.width(), _initialRect.height(),
|
|
_initialRect.left, _initialRect.top,
|
|
_boundingRect.width(), _boundingRect.height(),
|
|
_boundingRect.left, _boundingRect.top,
|
|
getForeColor(), getBackColor(),
|
|
_regX, _regY, _pitch, _bitsPerPixel, _clut.asString().c_str()
|
|
);
|
|
}
|
|
|
|
void BitmapCastMember::load() {
|
|
if (_loaded && !_needsReload)
|
|
return;
|
|
|
|
_needsReload = false;
|
|
|
|
uint32 tag = _tag;
|
|
uint16 imgId = _castId;
|
|
uint16 realId = 0;
|
|
|
|
Image::ImageDecoder *img = nullptr;
|
|
Common::SeekableReadStream *pic = nullptr;
|
|
|
|
if (_cast->_version >= kFileVer400) {
|
|
for (auto &it : _children) {
|
|
if (it.tag == MKTAG('B', 'I', 'T', 'D')) {
|
|
imgId = it.index;
|
|
tag = it.tag;
|
|
|
|
pic = _cast->getResource(tag, imgId);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Common::String imageFilename = _cast->getLinkedPath(_castId);
|
|
|
|
if ((pic == nullptr || pic->size() == 0)
|
|
&& !imageFilename.empty()) {
|
|
// image file is linked, load from the filesystem
|
|
Common::Path location = findPath(imageFilename);
|
|
Common::SeekableReadStream *file = Common::MacResManager::openFileOrDataFork(location);
|
|
if (file) {
|
|
debugC(2, kDebugLoading, "****** Loading file '%s', cast id: %d", imageFilename.c_str(), imgId);
|
|
// Detect the filetype. Director will ignore file extensions, as do we.
|
|
Image::ImageDecoder *decoder = nullptr;
|
|
uint32 fileType = file->readUint32BE();
|
|
file->seek(0);
|
|
|
|
if ((fileType >> 16) == MKTAG16('B', 'M')) {
|
|
// Windows Bitmap file
|
|
decoder = new Image::BitmapDecoder();
|
|
} else if ((fileType == 0xffd8ffe0) || (fileType == 0xffd8ffe1) || (fileType == 0xffd8ffe2)) {
|
|
// JPEG file
|
|
decoder = new Image::JPEGDecoder();
|
|
} else {
|
|
// Well... Director allowed someone to add it, so it must be a PICT. No further questions!
|
|
decoder = new Image::PICTDecoder();
|
|
}
|
|
|
|
bool res = decoder->loadStream(*file);
|
|
delete file;
|
|
|
|
if (res) {
|
|
setPicture(*decoder, decoder->hasPalette());
|
|
_external = true;
|
|
|
|
const Graphics::Surface *surf = decoder->getSurface();
|
|
if (decoder->hasPalette()) {
|
|
// For BMPs this sometimes gets set to 16 in the cast record,
|
|
// we should go with what the target image has.
|
|
_bitsPerPixel = 8;
|
|
}
|
|
|
|
debugC(5, kDebugImages, "BitmapCastMember::load(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %s", imgId, surf->w, surf->h, _flags1, _flags2, _bytes, _bitsPerPixel, _clut.asString().c_str());
|
|
|
|
if (ConfMan.getBool("dump_scripts")) {
|
|
|
|
Common::String prepend = "stream";
|
|
Common::String filename = Common::String::format("./dumps/%s-%s-%d.png", encodePathForDump(prepend).c_str(), tag2str(tag), imgId);
|
|
Common::DumpFile bitmapFile;
|
|
|
|
bitmapFile.open(Common::Path(filename), true);
|
|
Image::writePNG(bitmapFile, *decoder->getSurface(), decoder->getPalette());
|
|
|
|
bitmapFile.close();
|
|
}
|
|
|
|
delete pic;
|
|
delete decoder;
|
|
_loaded = true;
|
|
return;
|
|
} else {
|
|
delete decoder;
|
|
warning("BUILDBOT: BitmapCastMember::load(): wrong format for external picture '%s'", location.toString(Common::Path::kNativeSeparator).c_str());
|
|
}
|
|
} else {
|
|
warning("BitmapCastMember::load(): cannot open external picture '%s'", location.toString(Common::Path::kNativeSeparator).c_str());
|
|
}
|
|
}
|
|
} else {
|
|
realId = imgId + _cast->_castIDoffset;
|
|
pic = _cast->getResource(tag, realId);
|
|
}
|
|
|
|
if (pic == nullptr) {
|
|
warning("BitmapCastMember::load(): Bitmap image %d not found", imgId);
|
|
return;
|
|
}
|
|
|
|
int w = _initialRect.width();
|
|
int h = _initialRect.height();
|
|
|
|
switch (tag) {
|
|
case MKTAG('D', 'I', 'B', ' '):
|
|
debugC(2, kDebugLoading, "****** Loading 'DIB ' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
|
|
img = new DIBDecoder();
|
|
break;
|
|
|
|
case MKTAG('B', 'I', 'T', 'D'):
|
|
debugC(2, kDebugLoading, "****** Loading 'BITD' id: %d (%d), %d bytes", imgId, realId, (int)pic->size());
|
|
|
|
if (w > 0 && h > 0) {
|
|
if (_cast->_version < kFileVer600) {
|
|
img = new BITDDecoder(w, h, _bitsPerPixel, _pitch, g_director->getPalette(), _cast->_version);
|
|
} else {
|
|
img = new Image::BitmapDecoder();
|
|
}
|
|
} else {
|
|
warning("BitmapCastMember::load(): Bitmap image %d not found", imgId);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
warning("BitmapCastMember::load(): Unknown Bitmap CastMember Tag: [%d] %s", tag, tag2str(tag));
|
|
break;
|
|
}
|
|
|
|
if (!img || !img->loadStream(*pic)) {
|
|
warning("BitmapCastMember::load(): Unable to load id: %d", imgId);
|
|
delete pic;
|
|
delete img;
|
|
return;
|
|
}
|
|
|
|
setPicture(*img, true);
|
|
|
|
if (ConfMan.getBool("dump_scripts")) {
|
|
|
|
Common::String prepend = "stream";
|
|
Common::String filename = Common::String::format("./dumps/%s-%s-%d.png", encodePathForDump(prepend).c_str(), tag2str(tag), imgId);
|
|
Common::DumpFile bitmapFile;
|
|
|
|
bitmapFile.open(Common::Path(filename), true);
|
|
Image::writePNG(bitmapFile, *img->getSurface(), img->getPalette());
|
|
|
|
bitmapFile.close();
|
|
}
|
|
|
|
delete img;
|
|
delete pic;
|
|
|
|
debugC(5, kDebugImages, "BitmapCastMember::load(): Bitmap: id: %d, w: %d, h: %d, flags1: %x, flags2: %x bytes: %x, bpp: %d clut: %s", imgId, w, h, _flags1, _flags2, _bytes, _bitsPerPixel, _clut.asString().c_str());
|
|
|
|
_loaded = true;
|
|
}
|
|
|
|
void BitmapCastMember::unload() {
|
|
if (!_loaded)
|
|
return;
|
|
|
|
delete _picture;
|
|
_picture = new Picture();
|
|
|
|
delete _ditheredImg;
|
|
_ditheredImg = nullptr;
|
|
|
|
_loaded = false;
|
|
}
|
|
|
|
PictureReference *BitmapCastMember::getPicture() const {
|
|
auto picture = new PictureReference;
|
|
|
|
// Not sure if we can make the assumption that the owning
|
|
// BitmapCastMember will live as long as any reference,
|
|
// so we'll make a copy of the Picture.
|
|
picture->_picture = new Picture(*_picture);
|
|
|
|
return picture;
|
|
}
|
|
|
|
void BitmapCastMember::setPicture(PictureReference &picture) {
|
|
delete _picture;
|
|
_picture = new Picture(*picture._picture);
|
|
|
|
// Force redither
|
|
delete _ditheredImg;
|
|
_ditheredImg = nullptr;
|
|
|
|
// Make sure we get redrawn
|
|
setModified(true);
|
|
// TODO: Should size be adjusted?
|
|
}
|
|
|
|
void BitmapCastMember::setPicture(Image::ImageDecoder &image, bool adjustSize) {
|
|
delete _picture;
|
|
_picture = new Picture(image);
|
|
if (adjustSize) {
|
|
auto surf = image.getSurface();
|
|
_size = surf->pitch * surf->h + _picture->getPaletteSize();
|
|
}
|
|
// Make sure we get redrawn
|
|
setModified(true);
|
|
}
|
|
|
|
Common::Point BitmapCastMember::getRegistrationOffset() {
|
|
return Common::Point(_regX - _initialRect.left, _regY - _initialRect.top);
|
|
}
|
|
|
|
Common::Point BitmapCastMember::getRegistrationOffset(int16 width, int16 height) {
|
|
Common::Point offset = getRegistrationOffset();
|
|
return Common::Point(offset.x * width / MAX((int16)1, _initialRect.width()), offset.y * height / MAX((int16)1, _initialRect.height()));
|
|
}
|
|
|
|
bool BitmapCastMember::hasField(int field) {
|
|
switch (field) {
|
|
case kTheDepth:
|
|
case kTheRegPoint:
|
|
case kThePalette:
|
|
case kThePaletteRef:
|
|
case kThePicture:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return CastMember::hasField(field);
|
|
}
|
|
|
|
Datum BitmapCastMember::getField(int field) {
|
|
Datum d;
|
|
|
|
switch (field) {
|
|
case kTheDepth:
|
|
d = _bitsPerPixel;
|
|
break;
|
|
case kTheRegPoint:
|
|
d.type = POINT;
|
|
d.u.farr = new FArray;
|
|
d.u.farr->arr.push_back(_regX);
|
|
d.u.farr->arr.push_back(_regY);
|
|
break;
|
|
case kThePalette:
|
|
// D5 and below return an integer for this field
|
|
if (_clut.castLib > 0) {
|
|
d = Datum(_clut.toMultiplex());
|
|
} else {
|
|
d = Datum(_clut.member);
|
|
}
|
|
break;
|
|
case kThePaletteRef:
|
|
if (_clut.castLib > 0) {
|
|
d = _clut;
|
|
} else if (_clut.castLib == -1) {
|
|
switch (_clut.member) {
|
|
case kClutSystemMac:
|
|
d = Datum("systemMac");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutSystemWin:
|
|
d = Datum("systemWinDir4");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutSystemWinD5:
|
|
d = Datum("systemWin");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutGrayscale:
|
|
d = Datum("grayscale");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutMetallic:
|
|
d = Datum("metallic");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutNTSC:
|
|
d = Datum("NTSC");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutPastels:
|
|
d = Datum("pastels");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutRainbow:
|
|
d = Datum("rainbow");
|
|
d.type = SYMBOL;
|
|
break;
|
|
case kClutVivid:
|
|
d = Datum("vivid");
|
|
d.type = SYMBOL;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case kThePicture:
|
|
d.type = PICTUREREF;
|
|
d.u.picture = getPicture();
|
|
break;
|
|
default:
|
|
d = CastMember::getField(field);
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
bool BitmapCastMember::setField(int field, const Datum &d) {
|
|
switch (field) {
|
|
case kTheDepth:
|
|
warning("BitmapCastMember::setField(): Attempt to set read-only field %s of cast %d", g_lingo->field2str(field), _castId);
|
|
return false;
|
|
case kTheRegPoint:
|
|
if (d.type == POINT || (d.type == ARRAY && d.u.farr->arr.size() >= 2)) {
|
|
Score *score = g_director->getCurrentMovie()->getScore();
|
|
score->invalidateRectsForMember(this);
|
|
_regX = d.u.farr->arr[0].asInt();
|
|
_regY = d.u.farr->arr[1].asInt();
|
|
_modified = true;
|
|
} else {
|
|
warning("BitmapCastMember::setField(): Wrong Datum type %d for kTheRegPoint", d.type);
|
|
return false;
|
|
}
|
|
return true;
|
|
case kThePalette:
|
|
{
|
|
CastMemberID newClut;
|
|
if (d.isCastRef()) {
|
|
newClut = *d.u.cast;
|
|
} else {
|
|
int id = d.asInt();
|
|
if (id > 0) {
|
|
// For palette IDs, D5 and above use multiples of 0x20000 to denote
|
|
// the castLib in the integer representation
|
|
newClut = CastMemberID().fromMultiplex(id);
|
|
} else if (id < 0) {
|
|
// Negative integer refers to one of the builtin palettes
|
|
newClut = CastMemberID(id, -1);
|
|
} else {
|
|
// 0 indicates a fallback to the default palette settings
|
|
newClut = CastMemberID(0, 0);
|
|
}
|
|
}
|
|
if (newClut != _clut) {
|
|
_clut = newClut;
|
|
_modified = true;
|
|
}
|
|
return true;
|
|
}
|
|
case kThePaletteRef:
|
|
{
|
|
CastMemberID newClut = _clut;
|
|
if (d.isCastRef()) {
|
|
newClut = *d.u.cast;
|
|
} else if (d.type == SYMBOL) {
|
|
Common::String name = *d.u.s;
|
|
if (name.equalsIgnoreCase("systemMac")) {
|
|
newClut = CastMemberID(kClutSystemMac, -1);
|
|
} else if (name.equalsIgnoreCase("systemWinDir4")) {
|
|
newClut = CastMemberID(kClutSystemWin, -1);
|
|
} else if (name.equalsIgnoreCase("systemWin")) {
|
|
newClut = CastMemberID(kClutSystemWinD5, -1);
|
|
} else if (name.equalsIgnoreCase("grayscale")) {
|
|
newClut = CastMemberID(kClutGrayscale, -1);
|
|
} else if (name.equalsIgnoreCase("metallic")) {
|
|
newClut = CastMemberID(kClutMetallic, -1);
|
|
} else if (name.equalsIgnoreCase("NTSC")) {
|
|
newClut = CastMemberID(kClutNTSC, -1);
|
|
} else if (name.equalsIgnoreCase("pastels")) {
|
|
newClut = CastMemberID(kClutPastels, -1);
|
|
} else if (name.equalsIgnoreCase("rainbow")) {
|
|
newClut = CastMemberID(kClutRainbow, -1);
|
|
} else if (name.equalsIgnoreCase("vivid")) {
|
|
newClut = CastMemberID(kClutVivid, -1);
|
|
}
|
|
}
|
|
if (newClut != _clut) {
|
|
_clut = newClut;
|
|
_modified = true;
|
|
}
|
|
}
|
|
return true;
|
|
case kThePicture:
|
|
if (d.type == PICTUREREF && d.u.picture != nullptr) {
|
|
setPicture(*d.u.picture);
|
|
// This is a random PICT from somewhere,
|
|
// set the external flag so we remap the palette.
|
|
_external = true;
|
|
// Remove the canvas-space transformation
|
|
_regX -= _initialRect.left;
|
|
_regY -= _initialRect.top;
|
|
_initialRect = Common::Rect(_picture->_surface.w, _picture->_surface.h);
|
|
return true;
|
|
} else {
|
|
warning("BitmapCastMember::setField(): Wrong Datum type %d for kThePicture (or nullptr)", d.type);
|
|
}
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return CastMember::setField(field, d);
|
|
}
|
|
|
|
} // End of namespace Director
|