mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
1195 lines
36 KiB
C++
1195 lines
36 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/managed_surface.h"
|
|
#include "graphics/surface.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
|
|
#include "common/endian.h"
|
|
#include "common/memstream.h"
|
|
|
|
#include "image/codecs/codec.h"
|
|
|
|
#include "mtropolis/assets.h"
|
|
#include "mtropolis/asset_factory.h"
|
|
|
|
namespace MTropolis {
|
|
|
|
Asset::Asset() : _assetID(0) {
|
|
}
|
|
|
|
Asset::~Asset() {
|
|
}
|
|
|
|
uint32 Asset::getAssetID() const {
|
|
return _assetID;
|
|
}
|
|
|
|
bool ColorTableAsset::load(AssetLoaderContext &context, const Data::ColorTableAsset &data) {
|
|
_assetID = data.assetID;
|
|
for (int i = 0; i < 256; i++) {
|
|
if (!_colors[i].load(data.colors[i]))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AssetType ColorTableAsset::getAssetType() const {
|
|
return kAssetTypeColorTable;
|
|
}
|
|
|
|
|
|
CachedAudio::CachedAudio() {
|
|
}
|
|
|
|
bool CachedAudio::loadFromStream(const AudioMetadata &metadata, Common::ReadStream *stream, size_t size) {
|
|
_data.resize(size);
|
|
if (size > 0) {
|
|
stream->read(&_data[0], size);
|
|
if (stream->err())
|
|
return false;
|
|
|
|
if (metadata.encoding == AudioMetadata::kEncodingUncompressed && metadata.bitsPerSample == 16) {
|
|
int16 *samples = reinterpret_cast<int16 *>(&_data[0]);
|
|
size_t numSamples = _data.size() / 2;
|
|
|
|
if (metadata.isBigEndian) {
|
|
for (size_t i = 0; i < numSamples; i++)
|
|
samples[i] = FROM_BE_16(samples[i]);
|
|
} else {
|
|
for (size_t i = 0; i < numSamples; i++)
|
|
samples[i] = FROM_LE_16(samples[i]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const void *CachedAudio::getData() const {
|
|
if (_data.size() == 0)
|
|
return nullptr;
|
|
return &_data[0];
|
|
}
|
|
|
|
size_t CachedAudio::getSize() const {
|
|
return _data.size();
|
|
}
|
|
|
|
size_t CachedAudio::getNumSamples(const AudioMetadata &metadata) const {
|
|
switch (metadata.encoding) {
|
|
case AudioMetadata::kEncodingMace6:
|
|
return _data.size() * 6 / metadata.channels;
|
|
case AudioMetadata::kEncodingMace3:
|
|
return _data.size() * 3 / metadata.channels;
|
|
case AudioMetadata::kEncodingUncompressed:
|
|
return _data.size() / (metadata.channels * metadata.bitsPerSample / 8);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
CachedMToon::CachedMToon() : _isRLETemporalCompressed(false) {
|
|
}
|
|
|
|
bool CachedMToon::loadFromStream(const Common::SharedPtr<MToonMetadata> &metadata, Common::ReadStream *stream, size_t size) {
|
|
_metadata = metadata;
|
|
|
|
Common::Array<uint8> data;
|
|
data.resize(size);
|
|
if (size > 0) {
|
|
stream->read(&data[0], size);
|
|
if (stream->err())
|
|
return false;
|
|
}
|
|
|
|
if (metadata->codecID == kMToonRLECodecID) {
|
|
loadRLEFrames(data);
|
|
|
|
uint16 fullWidth = metadata->rect.width();
|
|
uint16 fullHeight = metadata->rect.height();
|
|
|
|
bool haveAnyTemporalFrames = false;
|
|
bool haveDifferentDimensions = false;
|
|
_isRLETemporalCompressed = false;
|
|
|
|
for (size_t i = 0; i < metadata->frames.size(); i++) {
|
|
if (!_rleData[i].isKeyframe)
|
|
haveAnyTemporalFrames = true;
|
|
|
|
if (_rleData[i].width != fullWidth || _rleData[i].height != fullHeight) {
|
|
haveDifferentDimensions = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (haveAnyTemporalFrames && !haveDifferentDimensions)
|
|
_isRLETemporalCompressed = true;
|
|
}
|
|
|
|
if (!_isRLETemporalCompressed)
|
|
decompressFrames(data);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CachedMToon::decompressFrames(const Common::Array<uint8> &data) {
|
|
size_t numFrames = _metadata->frames.size();
|
|
|
|
_decompressedFrames.resize(numFrames);
|
|
_optimizedFrames.resize(numFrames);
|
|
|
|
for (size_t i = 0; i < numFrames; i++) {
|
|
if (_metadata->codecID == kMToonRLECodecID) {
|
|
decompressRLEFrame(i);
|
|
} else if (_metadata->codecID == 0) {
|
|
loadUncompressedFrame(data, i);
|
|
} else {
|
|
decompressQuickTimeFrame(data, i);
|
|
}
|
|
}
|
|
|
|
_rleData.clear();
|
|
}
|
|
|
|
template<class TNumber, uint32 TLiteralMask, uint32 TTransparentRowSkipMask>
|
|
bool CachedMToon::decompressMToonRLE(const RleFrame &frame, const Common::Array<TNumber> &coefsArray, Graphics::Surface &surface, bool isBottomUp) {
|
|
assert(sizeof(TNumber) == surface.format.bytesPerPixel);
|
|
|
|
size_t size = coefsArray.size();
|
|
if (size == 0)
|
|
return false;
|
|
|
|
const TNumber *coefs = &coefsArray[0];
|
|
|
|
size_t x = 0;
|
|
size_t y = 0;
|
|
size_t w = surface.w;
|
|
size_t h = surface.h;
|
|
|
|
if (w != frame.width || h != frame.height)
|
|
return false;
|
|
|
|
TNumber *rowData = static_cast<TNumber *>(surface.getBasePtr(0, isBottomUp ? (h - 1) : 0));
|
|
|
|
for (;;) {
|
|
if (size == 0)
|
|
return false;
|
|
const TNumber rleCode = coefs[0];
|
|
coefs++;
|
|
size--;
|
|
|
|
size_t remainingInRow = w - x;
|
|
|
|
if (rleCode == 0) {
|
|
if (size == 0)
|
|
return false;
|
|
const TNumber transparentCountCode = coefs[0];
|
|
coefs++;
|
|
size--;
|
|
|
|
if (transparentCountCode & TTransparentRowSkipMask) {
|
|
// Vertical skip
|
|
y += (transparentCountCode - TTransparentRowSkipMask);
|
|
x = 0;
|
|
if (y < h) {
|
|
rowData = static_cast<TNumber *>(surface.getBasePtr(0, isBottomUp ? (h - 1 - y) : y));
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
// Horizontal skip
|
|
const size_t horizontalSkip = transparentCountCode;
|
|
if (horizontalSkip > remainingInRow)
|
|
return false;
|
|
x += horizontalSkip;
|
|
}
|
|
} else if (rleCode & TLiteralMask) {
|
|
// Literals
|
|
const size_t numLiterals = (rleCode - TLiteralMask);
|
|
if (numLiterals > size || numLiterals > remainingInRow)
|
|
return false;
|
|
memcpy(rowData + x, coefs, sizeof(TNumber) * numLiterals);
|
|
coefs += numLiterals;
|
|
size -= numLiterals;
|
|
x += numLiterals;
|
|
} else {
|
|
// Literals
|
|
const size_t numCopies = rleCode;
|
|
if (numCopies > remainingInRow || size == 0)
|
|
return false;
|
|
const TNumber repeatedValue = coefs[0];
|
|
for (size_t i = 0; i < numCopies; i++)
|
|
rowData[x + i] = repeatedValue;
|
|
coefs++;
|
|
size--;
|
|
x += numCopies;
|
|
}
|
|
|
|
if (x == w) {
|
|
y++;
|
|
x = 0;
|
|
if (y < h) {
|
|
rowData = static_cast<TNumber *>(surface.getBasePtr(0, isBottomUp ? (h - 1 - y) : y));
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CachedMToon::decompressRLEFrameToImage(size_t frameIndex, Graphics::Surface &surface) {
|
|
assert(surface.format == _rleOptimizedFormat);
|
|
|
|
bool isBottomUp = (_metadata->imageFormat == MToonMetadata::kImageFormatWindows);
|
|
|
|
bool decompressedOK = false;
|
|
if (_rleOptimizedFormat.bytesPerPixel == 4) {
|
|
decompressedOK = decompressMToonRLE<uint32, 0x80000000u, 0x80000000u>(_rleData[frameIndex], _rleData[frameIndex].data32, surface, isBottomUp);
|
|
} else if (_rleOptimizedFormat.bytesPerPixel == 2) {
|
|
decompressedOK = decompressMToonRLE<uint16, 0x8000u, 0x8000u>(_rleData[frameIndex], _rleData[frameIndex].data16, surface, isBottomUp);
|
|
} else if (_rleOptimizedFormat.bytesPerPixel == 1) {
|
|
decompressedOK = decompressMToonRLE<uint8, 0x80u, 0u>(_rleData[frameIndex], _rleData[frameIndex].data8, surface, isBottomUp);
|
|
} else
|
|
error("Unknown mToon encoding");
|
|
|
|
if (!decompressedOK)
|
|
warning("mToon RLE frame decompression failed");
|
|
}
|
|
|
|
void CachedMToon::loadRLEFrames(const Common::Array<uint8> &data) {
|
|
size_t numFrames = _metadata->frames.size();
|
|
uint16 bpp = _metadata->bitsPerPixel;
|
|
|
|
_rleData.resize(numFrames);
|
|
|
|
for (size_t i = 0; i < numFrames; i++) {
|
|
const MToonMetadata::FrameDef &frameDef = _metadata->frames[i];
|
|
|
|
RleFrame &rleFrame = _rleData[i];
|
|
|
|
size_t baseOffset = frameDef.dataOffset;
|
|
|
|
uint32 headerInts[5];
|
|
for (size_t hi = 0; hi < 5; hi++) {
|
|
uint32 unpacked = 0;
|
|
for (size_t b = 0; b < 4; b++)
|
|
unpacked = (unpacked << 8) + data[baseOffset + hi * 4 + b];
|
|
headerInts[hi] = unpacked;
|
|
}
|
|
|
|
rleFrame.isKeyframe = (headerInts[0] == kMToonRLEKeyframePrefix);
|
|
if (headerInts[1] == 0x01000001) {
|
|
if (bpp != 8)
|
|
error("Unknown mToon encoding");
|
|
} else if (headerInts[1] == 0x01000002) {
|
|
if (bpp != 16)
|
|
error("Unknown mToon encoding");
|
|
} else
|
|
error("Unknown mToon encoding");
|
|
|
|
rleFrame.version = headerInts[1];
|
|
rleFrame.width = headerInts[2];
|
|
rleFrame.height = headerInts[3];
|
|
|
|
uint32 frameDataSize = headerInts[4];
|
|
|
|
if (frameDataSize > 0) {
|
|
if (bpp == 8) {
|
|
rleFrame.data8.resize(frameDataSize);
|
|
memcpy(&rleFrame.data8[0], &data[baseOffset + 20], frameDataSize);
|
|
} else if (bpp == 16) {
|
|
// In RLE16, frameDataSize is sometimes set to frameDef.compressedSize but sometimes contains garbage,
|
|
// so we need to ignore it and derive size from the frameDef instead.
|
|
uint32 numDWords = (frameDef.compressedSize - 20) / 2;
|
|
rleFrame.data16.resize(numDWords);
|
|
memcpy(&rleFrame.data16[0], &data[baseOffset + 20], numDWords * 2);
|
|
|
|
uint16 *i16 = &rleFrame.data16[0];
|
|
if (_metadata->imageFormat == MToonMetadata::kImageFormatWindows) {
|
|
for (size_t swapIndex = 0; swapIndex < numDWords; swapIndex++)
|
|
i16[swapIndex] = FROM_LE_16(i16[swapIndex]);
|
|
} else if (_metadata->imageFormat == MToonMetadata::kImageFormatMac) {
|
|
for (size_t swapIndex = 0; swapIndex < numDWords; swapIndex++)
|
|
i16[swapIndex] = FROM_BE_16(i16[swapIndex]);
|
|
}
|
|
} else
|
|
error("Unknown mToon encoding");
|
|
}
|
|
}
|
|
|
|
if (bpp == 8)
|
|
_rleInternalFormat = Graphics::PixelFormat::createFormatCLUT8();
|
|
else if (bpp == 16)
|
|
_rleInternalFormat = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 15);
|
|
else
|
|
error("Unknown mToon encoding");
|
|
|
|
_rleOptimizedFormat = _rleInternalFormat;
|
|
}
|
|
|
|
void CachedMToon::decompressRLEFrame(size_t frameIndex) {
|
|
Common::SharedPtr<Graphics::Surface> surface(new Graphics::Surface());
|
|
|
|
RleFrame &frame = _rleData[frameIndex];
|
|
|
|
surface->create(frame.width, frame.height, _rleInternalFormat);
|
|
|
|
decompressRLEFrameToImage(frameIndex, *surface);
|
|
|
|
this->_decompressedFrames[frameIndex] = surface;
|
|
}
|
|
|
|
void CachedMToon::loadUncompressedFrame(const Common::Array<uint8> &data, size_t frameIndex) {
|
|
const MToonMetadata::FrameDef &frameDef = _metadata->frames[frameIndex];
|
|
uint16 stride = frameDef.decompressedBytesPerRow;
|
|
|
|
uint16 bpp = _metadata->bitsPerPixel;
|
|
|
|
Common::SharedPtr<Graphics::Surface> surface(new Graphics::Surface());
|
|
Graphics::PixelFormat pixFmt;
|
|
|
|
if (bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8)
|
|
pixFmt = Graphics::PixelFormat::createFormatCLUT8();
|
|
else if (bpp == 16)
|
|
pixFmt = Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 15);
|
|
else if (bpp == 32)
|
|
pixFmt = Graphics::PixelFormat(4, 8, 8, 8, 0, 0, 8, 16, 24);
|
|
else
|
|
error("Unknown mToon encoding");
|
|
|
|
size_t w = frameDef.rect.width();
|
|
size_t h = frameDef.rect.height();
|
|
|
|
surface->create(w, h, pixFmt);
|
|
|
|
for (size_t row = 0; row < h; row++) {
|
|
const uint8 *inData = &data[frameDef.dataOffset + row * stride];
|
|
void *outDataUntyped = nullptr;
|
|
|
|
if (_metadata->imageFormat == MToonMetadata::kImageFormatWindows)
|
|
outDataUntyped = surface->getBasePtr(0, h - 1 - row);
|
|
else if (_metadata->imageFormat == MToonMetadata::kImageFormatMac)
|
|
outDataUntyped = surface->getBasePtr(0, row);
|
|
else
|
|
error("Unimplemented mToon uncompressed image layout");
|
|
|
|
if (bpp == 1) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint8 *>(outDataUntyped)[col] = (inData[col / 8] >> (7 - (col % 8))) & 1;
|
|
} else if (bpp == 2) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint8 *>(outDataUntyped)[col] = (inData[col / 4] >> (6 - (col % 4) * 2)) & 3;
|
|
} else if (bpp == 4) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint8 *>(outDataUntyped)[col] = (inData[col / 2] >> (4 - (col % 2) * 4)) & 15;
|
|
} else if (bpp == 8) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint8 *>(outDataUntyped)[col] = inData[col];
|
|
} else if (bpp == 16) {
|
|
if (_metadata->imageFormat == MToonMetadata::kImageFormatMac) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint16 *>(outDataUntyped)[col] = (inData[col * 2] << 8) | (inData[col * 2 + 1]);
|
|
} else if (_metadata->imageFormat == MToonMetadata::kImageFormatWindows) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint16 *>(outDataUntyped)[col] = (inData[col * 2 + 1] << 8) | (inData[col * 2]);
|
|
}
|
|
} else if (bpp == 32) {
|
|
if (_metadata->imageFormat == MToonMetadata::kImageFormatMac) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint32 *>(outDataUntyped)[col] = (0xff000000 | (inData[col * 4 + 1]) | (inData[col * 4 + 2] << 8) | (inData[col * 4 + 3] << 16));
|
|
} else if (_metadata->imageFormat == MToonMetadata::kImageFormatWindows) {
|
|
for (size_t col = 0; col < w; col++)
|
|
static_cast<uint32 *>(outDataUntyped)[col] = (0xff000000 | (inData[col * 4 + 2]) | (inData[col * 4 + 1] << 8) | (inData[col * 4 + 0] << 16));
|
|
}
|
|
}
|
|
}
|
|
|
|
_decompressedFrames[frameIndex] = surface;
|
|
}
|
|
|
|
void CachedMToon::decompressQuickTimeFrame(const Common::Array<uint8> &data, size_t frameIndex) {
|
|
const MToonMetadata::FrameDef &frameDef = _metadata->frames[frameIndex];
|
|
|
|
// We used to validate that these match the sample desc, but that actually breaks in Obsidian
|
|
// on the Bureau Immediate Action clock puzzle, because the frames have different sizes and
|
|
// the codec data encodes the size of the last frame.
|
|
uint16 w = frameDef.rect.width();
|
|
uint16 h = frameDef.rect.height();
|
|
uint16 bpp = READ_BE_UINT16(&_metadata->codecData[82]);
|
|
|
|
Image::Codec *codec = Image::createQuickTimeCodec(_metadata->codecID, w, h, bpp);
|
|
if (!codec) {
|
|
error("Unknown QuickTime codec for mToon frame");
|
|
}
|
|
|
|
if (frameDef.dataOffset > data.size())
|
|
error("Invalid framedef offset");
|
|
|
|
if (frameDef.compressedSize > data.size())
|
|
error("Invalid compressed size");
|
|
|
|
if (frameDef.compressedSize - data.size() < frameDef.dataOffset)
|
|
error("Not enough available bytes for compressed data");
|
|
|
|
Common::MemoryReadStream stream(&data[frameDef.dataOffset], frameDef.compressedSize);
|
|
|
|
const Graphics::Surface *surface = codec->decodeFrame(stream);
|
|
if (!surface) {
|
|
error("mToon QuickTime frame failed to decompress");
|
|
}
|
|
|
|
// Clone the decompressed frame
|
|
_decompressedFrames[frameIndex] = Common::SharedPtr<Graphics::Surface>(new Graphics::Surface(*surface));
|
|
}
|
|
|
|
template<class TSrcNumber, uint32 TSrcLiteralMask, uint32 TSrcTransparentSkipMask, class TDestNumber, uint32 TDestLiteralMask, uint32 TDestTransparentSkipMask>
|
|
void CachedMToon::rleReformat(RleFrame &frame, const Common::Array<TSrcNumber> &srcData, const Graphics::PixelFormat &srcFormatRef, Common::Array<TDestNumber> &destData, const Graphics::PixelFormat &destFormatRef) {
|
|
const Graphics::PixelFormat srcFormat = srcFormatRef;
|
|
const Graphics::PixelFormat destFormat = destFormatRef;
|
|
|
|
size_t offset = 0;
|
|
destData.resize(srcData.size());
|
|
|
|
while (offset < srcData.size()) {
|
|
uint32 rleCodeOffset = offset;
|
|
const uint32 rleCode = srcData[rleCodeOffset];
|
|
if (rleCode == 0) {
|
|
destData[offset] = 0;
|
|
offset++;
|
|
|
|
uint32 numTransparentCode = srcData[offset];
|
|
if (numTransparentCode & TSrcTransparentSkipMask)
|
|
destData[offset] = (numTransparentCode - TSrcTransparentSkipMask) + TDestTransparentSkipMask;
|
|
else
|
|
destData[offset] = numTransparentCode;
|
|
offset++;
|
|
} else if (rleCode & TSrcLiteralMask) {
|
|
uint32 numLiterals = rleCode - TSrcLiteralMask;
|
|
|
|
destData[offset] = numLiterals + TDestLiteralMask;
|
|
offset++;
|
|
|
|
while (numLiterals) {
|
|
uint8 a, r, g, b;
|
|
srcFormat.colorToARGB(srcData[offset], a, r, g, b);
|
|
destData[offset] = destFormat.ARGBToColor(a, r, g, b);
|
|
offset++;
|
|
numLiterals--;
|
|
}
|
|
} else {
|
|
destData[offset] = rleCode;
|
|
offset++;
|
|
|
|
uint8 a, r, g, b;
|
|
srcFormat.colorToARGB(srcData[offset], a, r, g, b);
|
|
destData[offset] = destFormat.ARGBToColor(a, r, g, b);
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CachedMToon::optimize(Runtime *runtime) {
|
|
Graphics::PixelFormat renderFmt = runtime->getRenderPixelFormat();
|
|
if (_isRLETemporalCompressed)
|
|
optimizeRLE(renderFmt);
|
|
else
|
|
optimizeNonTemporal(renderFmt);
|
|
}
|
|
|
|
void CachedMToon::optimizeNonTemporal(const Graphics::PixelFormat &targetFormatRef) {
|
|
const Graphics::PixelFormat targetFormat = targetFormatRef;
|
|
|
|
_optimizedFrames.resize(_decompressedFrames.size());
|
|
|
|
for (size_t i = 0; i < _decompressedFrames.size(); i++) {
|
|
Common::SharedPtr<Graphics::Surface> srcSurface = _decompressedFrames[i];
|
|
Common::SharedPtr<Graphics::Surface> &optimizedSurfRef = _optimizedFrames[i];
|
|
|
|
// FIXME: Aggregate these checks and merge into a single format field
|
|
if (optimizedSurfRef == nullptr || optimizedSurfRef->format != targetFormat) {
|
|
if (targetFormat.bytesPerPixel > 1 && srcSurface->format.bytesPerPixel > 1) {
|
|
if (targetFormat.bytesPerPixel == srcSurface->format.bytesPerPixel) {
|
|
srcSurface->convertToInPlace(targetFormat);
|
|
optimizedSurfRef = srcSurface;
|
|
} else {
|
|
optimizedSurfRef.reset();
|
|
optimizedSurfRef.reset(srcSurface->convertTo(targetFormat));
|
|
}
|
|
} else {
|
|
optimizedSurfRef = srcSurface;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CachedMToon::optimizeRLE(const Graphics::PixelFormat &targetFormatRef) {
|
|
const Graphics::PixelFormat targetFormat = targetFormatRef;
|
|
|
|
if (targetFormat == _rleOptimizedFormat)
|
|
return;
|
|
|
|
if (_rleInternalFormat.bytesPerPixel != 2 && _rleInternalFormat.bytesPerPixel != 4)
|
|
return; // Can't optimize
|
|
|
|
size_t numFrames = _metadata->frames.size();
|
|
for (size_t i = 0; i < numFrames; i++) {
|
|
if (_rleInternalFormat.bytesPerPixel == 2) {
|
|
if (targetFormat.bytesPerPixel == 4)
|
|
rleReformat<uint16, 0x8000u, 0x8000u, uint32, 0x80000000u, 0x80000000u>(_rleData[i], _rleData[i].data16, _rleInternalFormat, _rleData[i].data32, targetFormat);
|
|
else if (targetFormat.bytesPerPixel == 2)
|
|
rleReformat<uint16, 0x8000u, 0x8000u, uint16, 0x8000u, 0x8000u>(_rleData[i], _rleData[i].data16, _rleInternalFormat, _rleData[i].data16, targetFormat);
|
|
} else if (_rleInternalFormat.bytesPerPixel == 4) {
|
|
if (targetFormat.bytesPerPixel == 4)
|
|
rleReformat<uint32, 0x80000000u, 0x80000000u, uint32, 0x80000000u, 0x80000000u>(_rleData[i], _rleData[i].data32, _rleInternalFormat, _rleData[i].data32, targetFormat);
|
|
else if (targetFormat.bytesPerPixel == 2)
|
|
rleReformat<uint32, 0x80000000u, 0x80000000u, uint16, 0x8000u, 0x8000u>(_rleData[i], _rleData[i].data32, _rleInternalFormat, _rleData[i].data16, targetFormat);
|
|
}
|
|
}
|
|
|
|
if (_rleInternalFormat.bytesPerPixel == targetFormat.bytesPerPixel)
|
|
_rleInternalFormat = targetFormat;
|
|
|
|
_rleOptimizedFormat = targetFormat;
|
|
}
|
|
|
|
void CachedMToon::getOrRenderFrame(uint32 prevFrame, uint32 targetFrame, Common::SharedPtr<Graphics::Surface>& surface) const {
|
|
if (!_isRLETemporalCompressed) {
|
|
surface = _optimizedFrames[targetFrame];
|
|
} else if (_metadata->codecID == kMToonRLECodecID) {
|
|
uint32 firstFrameToRender = 0;
|
|
uint32 backStopFrame = 0;
|
|
|
|
if (surface && surface->format != _rleOptimizedFormat)
|
|
surface.reset();
|
|
|
|
if (surface != nullptr) {
|
|
if (prevFrame == targetFrame)
|
|
return;
|
|
if (prevFrame < targetFrame)
|
|
backStopFrame = prevFrame;
|
|
}
|
|
|
|
firstFrameToRender = targetFrame;
|
|
while (firstFrameToRender > backStopFrame) {
|
|
if (_metadata->frames[firstFrameToRender].isKeyFrame)
|
|
break;
|
|
firstFrameToRender--;
|
|
}
|
|
|
|
if (!surface || surface->format != _rleOptimizedFormat) {
|
|
surface.reset(new Graphics::Surface());
|
|
surface->create(_metadata->rect.width(), _metadata->rect.height(), _rleOptimizedFormat);
|
|
}
|
|
|
|
bool isBottomUp = (_metadata->imageFormat == MToonMetadata::kImageFormatWindows);
|
|
|
|
for (size_t i = firstFrameToRender; i <= targetFrame; i++) {
|
|
if (_rleOptimizedFormat.bytesPerPixel == 1)
|
|
decompressMToonRLE<uint8, 0x80u, 0>(_rleData[i], _rleData[i].data8, *surface, isBottomUp);
|
|
else if (_rleOptimizedFormat.bytesPerPixel == 2)
|
|
decompressMToonRLE<uint16, 0x8000u, 0x8000u>(_rleData[i], _rleData[i].data16, *surface, isBottomUp);
|
|
else if (_rleOptimizedFormat.bytesPerPixel == 4)
|
|
decompressMToonRLE<uint32, 0x80000000u, 0x80000000u>(_rleData[i], _rleData[i].data32, *surface, isBottomUp);
|
|
}
|
|
}
|
|
}
|
|
|
|
const Common::SharedPtr<MToonMetadata>& CachedMToon::getMetadata() const {
|
|
return _metadata;
|
|
}
|
|
|
|
bool AudioAsset::load(AssetLoaderContext &context, const Data::AudioAsset &data) {
|
|
_assetID = data.assetID;
|
|
|
|
_metadata.reset(new AudioMetadata());
|
|
_metadata->sampleRate = data.sampleRate1;
|
|
_metadata->bitsPerSample = data.bitsPerSample;
|
|
|
|
_streamIndex = context.streamIndex;
|
|
|
|
switch (data.encoding1) {
|
|
case 0:
|
|
_metadata->encoding = AudioMetadata::kEncodingUncompressed;
|
|
break;
|
|
case 3:
|
|
_metadata->encoding = AudioMetadata::kEncodingMace3;
|
|
break;
|
|
case 4:
|
|
_metadata->encoding = AudioMetadata::kEncodingMace6;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
_metadata->channels = data.channels;
|
|
// Hours Minutes Seconds Hundredths -> msec
|
|
// Maximum is 0x37a4f52e so this fits in 30 bits
|
|
_metadata->durationMSec = ((((data.codedDuration[0] * 60u) + data.codedDuration[1]) * 60u + data.codedDuration[2]) * 100u + data.codedDuration[3]) * 10u;
|
|
_filePosition = data.filePosition;
|
|
_size = data.size;
|
|
_metadata->cuePoints.resize(data.cuePoints.size());
|
|
_metadata->isBigEndian = data.isBigEndian;
|
|
|
|
for (size_t i = 0; i < data.cuePoints.size(); i++) {
|
|
_metadata->cuePoints[i].cuePointID = data.cuePoints[i].cuePointID;
|
|
_metadata->cuePoints[i].position = data.cuePoints[i].position;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AssetType AudioAsset::getAssetType() const {
|
|
return kAssetTypeAudio;
|
|
}
|
|
|
|
size_t AudioAsset::getStreamIndex() const {
|
|
return _streamIndex;
|
|
}
|
|
|
|
const Common::SharedPtr<AudioMetadata> &AudioAsset::getMetadata() const {
|
|
return _metadata;
|
|
}
|
|
|
|
|
|
const Common::SharedPtr<CachedAudio> &AudioAsset::loadAndCacheAudio(Runtime *runtime) {
|
|
if (_audioCache)
|
|
return _audioCache;
|
|
|
|
size_t streamIndex = getStreamIndex();
|
|
int segmentIndex = runtime->getProject()->getSegmentForStreamIndex(streamIndex);
|
|
runtime->getProject()->openSegmentStream(segmentIndex);
|
|
Common::SeekableReadStream *stream = runtime->getProject()->getStreamForSegment(segmentIndex);
|
|
|
|
if (!stream || !stream->seek(_filePosition)) {
|
|
warning("Audio asset failed to load, couldn't seek to position");
|
|
return _audioCache;
|
|
}
|
|
|
|
Common::SharedPtr<CachedAudio> audio(new CachedAudio());
|
|
if (!audio->loadFromStream(*_metadata, stream, _size)) {
|
|
warning("Audio asset failed to load, couldn't read data");
|
|
return _audioCache;
|
|
}
|
|
|
|
_audioCache.reset();
|
|
_audioCache = audio;
|
|
|
|
return _audioCache;
|
|
}
|
|
|
|
bool MovieAsset::load(AssetLoaderContext &context, const Data::MovieAsset &data) {
|
|
_assetID = data.assetID;
|
|
_moovAtomPos = data.moovAtomPos;
|
|
_movieDataPos = data.movieDataPos;
|
|
_movieDataSize = data.movieDataSize;
|
|
_extFileName = data.extFileName;
|
|
_streamIndex = context.streamIndex;
|
|
|
|
return true;
|
|
}
|
|
|
|
AssetType MovieAsset::getAssetType() const {
|
|
return kAssetTypeMovie;
|
|
}
|
|
|
|
uint32 MovieAsset::getMovieDataPos() const {
|
|
return _movieDataPos;
|
|
}
|
|
|
|
uint32 MovieAsset::getMoovAtomPos() const {
|
|
return _moovAtomPos;
|
|
}
|
|
|
|
uint32 MovieAsset::getMovieDataSize() const {
|
|
return _movieDataSize;
|
|
}
|
|
|
|
|
|
const Common::String &MovieAsset::getExtFileName() const {
|
|
return _extFileName;
|
|
}
|
|
|
|
size_t MovieAsset::getStreamIndex() const {
|
|
return _streamIndex;
|
|
}
|
|
|
|
|
|
CachedImage::CachedImage() : _colorDepth(kColorDepthModeInvalid), _isOptimized(false) {
|
|
}
|
|
|
|
void CachedImage::resetSurface(ColorDepthMode colorDepth, const Common::SharedPtr<Graphics::Surface> &surface) {
|
|
_optimizedSurface.reset();
|
|
_isOptimized = false;
|
|
|
|
_colorDepth = colorDepth;
|
|
_surface = surface;
|
|
}
|
|
|
|
const Common::SharedPtr<Graphics::Surface> &CachedImage::optimize(Runtime *runtime) {
|
|
ColorDepthMode renderDepth = runtime->getRealColorDepth();
|
|
const Graphics::PixelFormat &renderFmt = runtime->getRenderPixelFormat();
|
|
|
|
if (renderDepth != _colorDepth) {
|
|
size_t w = _surface->w;
|
|
size_t h = _surface->h;
|
|
|
|
if (renderDepth == kColorDepthMode16Bit && _colorDepth == kColorDepthMode32Bit) {
|
|
_optimizedSurface.reset(new Graphics::Surface());
|
|
_optimizedSurface->create(w, h, renderFmt);
|
|
Render::convert32To16(*_optimizedSurface, *_surface);
|
|
} else if (renderDepth == kColorDepthMode32Bit && _colorDepth == kColorDepthMode16Bit) {
|
|
_optimizedSurface.reset(new Graphics::Surface());
|
|
_optimizedSurface->create(w, h, renderFmt);
|
|
Render::convert16To32(*_optimizedSurface, *_surface);
|
|
} else {
|
|
_optimizedSurface = _surface; // Can't optimize
|
|
}
|
|
} else {
|
|
static const byte bwPalette[6] = {255, 255, 255, 0, 0, 0};
|
|
|
|
const byte *palette = nullptr;
|
|
|
|
if (_colorDepth == kColorDepthMode16Bit || _colorDepth == kColorDepthMode32Bit)
|
|
palette = bwPalette;
|
|
|
|
_surface->convertToInPlace(renderFmt, palette);
|
|
_optimizedSurface = _surface;
|
|
}
|
|
|
|
return _optimizedSurface;
|
|
}
|
|
|
|
ImageAsset::ImageAsset() {
|
|
}
|
|
|
|
ImageAsset::~ImageAsset() {
|
|
}
|
|
|
|
bool ImageAsset::load(AssetLoaderContext &context, const Data::ImageAsset &data) {
|
|
_assetID = data.assetID;
|
|
if (!data.rect1.toScummVMRect(_rect))
|
|
return false;
|
|
_filePosition = data.filePosition;
|
|
_size = data.size;
|
|
_streamIndex = context.streamIndex;
|
|
|
|
switch (data.bitsPerPixel) {
|
|
case 1:
|
|
_colorDepth = kColorDepthMode1Bit;
|
|
break;
|
|
case 2:
|
|
_colorDepth = kColorDepthMode2Bit;
|
|
break;
|
|
case 4:
|
|
_colorDepth = kColorDepthMode4Bit;
|
|
break;
|
|
case 8:
|
|
_colorDepth = kColorDepthMode8Bit;
|
|
break;
|
|
case 16:
|
|
_colorDepth = kColorDepthMode16Bit;
|
|
break;
|
|
case 32:
|
|
_colorDepth = kColorDepthMode32Bit;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (data.haveMacPart)
|
|
_imageFormat = kImageFormatMac;
|
|
else if (data.haveWinPart)
|
|
_imageFormat = kImageFormatWindows;
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
AssetType ImageAsset::getAssetType() const {
|
|
return kAssetTypeImage;
|
|
}
|
|
|
|
const Common::Rect &ImageAsset::getRect() const {
|
|
return _rect;
|
|
}
|
|
|
|
ColorDepthMode ImageAsset::getColorDepth() const {
|
|
return _colorDepth;
|
|
}
|
|
|
|
uint32 ImageAsset::getFilePosition() const {
|
|
return _filePosition;
|
|
}
|
|
|
|
uint32 ImageAsset::getSize() const {
|
|
return _size;
|
|
}
|
|
|
|
size_t ImageAsset::getStreamIndex() const {
|
|
return _streamIndex;
|
|
}
|
|
|
|
ImageAsset::ImageFormat ImageAsset::getImageFormat() const {
|
|
return _imageFormat;
|
|
}
|
|
|
|
const Common::SharedPtr<CachedImage> &ImageAsset::loadAndCacheImage(Runtime *runtime) {
|
|
if (_imageCache)
|
|
return _imageCache;
|
|
|
|
ColorDepthMode renderDepth = runtime->getRealColorDepth();
|
|
|
|
size_t streamIndex = getStreamIndex();
|
|
int segmentIndex = runtime->getProject()->getSegmentForStreamIndex(streamIndex);
|
|
runtime->getProject()->openSegmentStream(segmentIndex);
|
|
Common::SeekableReadStream *stream = runtime->getProject()->getStreamForSegment(segmentIndex);
|
|
|
|
if (!stream || !stream->seek(getFilePosition())) {
|
|
warning("Image element failed to load");
|
|
return _imageCache;
|
|
}
|
|
|
|
size_t bytesPerRow = 0;
|
|
|
|
Common::Rect imageRect = getRect();
|
|
int width = imageRect.right - imageRect.left;
|
|
int height = imageRect.bottom - imageRect.top;
|
|
|
|
if (width <= 0 || height < 0) {
|
|
warning("Image asset has invalid size");
|
|
return _imageCache;
|
|
}
|
|
|
|
Graphics::PixelFormat pixelFmt;
|
|
switch (getColorDepth()) {
|
|
case kColorDepthMode1Bit:
|
|
bytesPerRow = (width + 31) / 32 * 4;
|
|
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
|
|
break;
|
|
case kColorDepthMode2Bit:
|
|
bytesPerRow = (width + 15) / 16 * 4;
|
|
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
|
|
break;
|
|
case kColorDepthMode4Bit:
|
|
bytesPerRow = (width + 7) / 8 * 4;
|
|
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
|
|
break;
|
|
case kColorDepthMode8Bit:
|
|
bytesPerRow = (width + 3) / 4 * 4;
|
|
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
|
|
break;
|
|
case kColorDepthMode16Bit:
|
|
bytesPerRow = (width * 2 + 3) / 4 * 4;
|
|
pixelFmt = Graphics::createPixelFormat<1555>();
|
|
break;
|
|
case kColorDepthMode32Bit:
|
|
bytesPerRow = width * 4;
|
|
pixelFmt = Graphics::createPixelFormat<8888>();
|
|
break;
|
|
default:
|
|
warning("Image asset has an unrecognizable pixel format");
|
|
return _imageCache;
|
|
}
|
|
|
|
Common::Array<uint8> rowBuffer;
|
|
rowBuffer.resize(bytesPerRow);
|
|
|
|
ImageAsset::ImageFormat imageFormat = getImageFormat();
|
|
bool bottomUp = (imageFormat == ImageAsset::kImageFormatWindows);
|
|
bool isBigEndian = (imageFormat == ImageAsset::kImageFormatMac);
|
|
|
|
Common::SharedPtr<Graphics::Surface> imageSurface;
|
|
imageSurface.reset(new Graphics::Surface());
|
|
imageSurface->create(width, height, pixelFmt);
|
|
|
|
for (int inRow = 0; inRow < height; inRow++) {
|
|
int outRow = bottomUp ? (height - 1 - inRow) : inRow;
|
|
|
|
stream->read(&rowBuffer[0], bytesPerRow);
|
|
const uint8 *inRowBytes = &rowBuffer[0];
|
|
|
|
void *outBase = imageSurface->getBasePtr(0, outRow);
|
|
|
|
switch (getColorDepth()) {
|
|
case kColorDepthMode1Bit: {
|
|
for (int x = 0; x < width; x++) {
|
|
int bit = (inRowBytes[x / 8] >> (7 - (x % 8))) & 1;
|
|
static_cast<uint8 *>(outBase)[x] = bit;
|
|
}
|
|
} break;
|
|
case kColorDepthMode2Bit: {
|
|
for (int x = 0; x < width; x++) {
|
|
int bit = (inRowBytes[x / 4] >> (3 - (x % 4))) & 3;
|
|
static_cast<uint8 *>(outBase)[x] = bit;
|
|
}
|
|
} break;
|
|
case kColorDepthMode4Bit: {
|
|
for (int x = 0; x < width; x++) {
|
|
int bit = (inRowBytes[x / 2] >> (1 - (x % 2))) & 15;
|
|
static_cast<uint8 *>(outBase)[x] = bit;
|
|
}
|
|
} break;
|
|
case kColorDepthMode8Bit:
|
|
memcpy(outBase, inRowBytes, width);
|
|
break;
|
|
case kColorDepthMode16Bit: {
|
|
if (isBigEndian) {
|
|
for (int x = 0; x < width; x++) {
|
|
uint16 packedPixel = inRowBytes[x * 2 + 1] + (inRowBytes[x * 2 + 0] << 8);
|
|
int r = ((packedPixel >> 10) & 0x1f);
|
|
int g = ((packedPixel >> 5) & 0x1f);
|
|
int b = (packedPixel & 0x1f);
|
|
|
|
uint16 repacked = (1 << pixelFmt.aShift) | (r << pixelFmt.rShift) | (g << pixelFmt.gShift) | (b << pixelFmt.bShift);
|
|
static_cast<uint16 *>(outBase)[x] = repacked;
|
|
}
|
|
} else {
|
|
for (int x = 0; x < width; x++) {
|
|
uint16 packedPixel = inRowBytes[x * 2 + 0] + (inRowBytes[x * 2 + 1] << 8);
|
|
int r = ((packedPixel >> 10) & 0x1f);
|
|
int g = ((packedPixel >> 5) & 0x1f);
|
|
int b = (packedPixel & 0x1f);
|
|
|
|
uint16 repacked = (1 << pixelFmt.aShift) | (r << pixelFmt.rShift) | (g << pixelFmt.gShift) | (b << pixelFmt.bShift);
|
|
static_cast<uint16 *>(outBase)[x] = repacked;
|
|
}
|
|
}
|
|
} break;
|
|
case kColorDepthMode32Bit: {
|
|
if (imageFormat == ImageAsset::kImageFormatMac) {
|
|
for (int x = 0; x < width; x++) {
|
|
uint8 r = inRowBytes[x * 4 + 1];
|
|
uint8 g = inRowBytes[x * 4 + 2];
|
|
uint8 b = inRowBytes[x * 4 + 3];
|
|
uint32 repacked = (255 << pixelFmt.aShift) | (r << pixelFmt.rShift) | (g << pixelFmt.gShift) | (b << pixelFmt.bShift);
|
|
static_cast<uint32 *>(outBase)[x] = repacked;
|
|
}
|
|
} else if (imageFormat == ImageAsset::kImageFormatWindows) {
|
|
for (int x = 0; x < width; x++) {
|
|
uint8 r = inRowBytes[x * 4 + 2];
|
|
uint8 g = inRowBytes[x * 4 + 1];
|
|
uint8 b = inRowBytes[x * 4 + 0];
|
|
uint32 repacked = (255 << pixelFmt.aShift) | (r << pixelFmt.rShift) | (g << pixelFmt.gShift) | (b << pixelFmt.bShift);
|
|
static_cast<uint32 *>(outBase)[x] = repacked;
|
|
}
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
_imageCache.reset(new CachedImage());
|
|
_imageCache->resetSurface(renderDepth, imageSurface);
|
|
|
|
return _imageCache;
|
|
}
|
|
|
|
bool MToonAsset::load(AssetLoaderContext &context, const Data::MToonAsset &data) {
|
|
_streamIndex = context.streamIndex;
|
|
_assetID = data.assetID;
|
|
|
|
_metadata.reset(new MToonMetadata());
|
|
|
|
if (data.haveMacPart)
|
|
_metadata->imageFormat = MToonMetadata::kImageFormatMac;
|
|
else if (data.haveWinPart)
|
|
_metadata->imageFormat = MToonMetadata::kImageFormatWindows;
|
|
else
|
|
return false;
|
|
|
|
_frameDataPosition = data.frameDataPosition;
|
|
_sizeOfFrameData = data.sizeOfFrameData;
|
|
|
|
if (!data.rect.toScummVMRect(_metadata->rect))
|
|
return false;
|
|
|
|
_metadata->bitsPerPixel = data.bitsPerPixel;
|
|
_metadata->codecID = data.codecID;
|
|
_metadata->encodingFlags = data.encodingFlags;
|
|
|
|
_metadata->frames.resize(data.frames.size());
|
|
for (size_t i = 0; i < data.frames.size(); i++) {
|
|
if (!_metadata->frames[i].load(context, data.frames[i]))
|
|
return false;
|
|
}
|
|
|
|
_metadata->frameRanges.resize(data.frameRangesPart.frameRanges.size());
|
|
for (size_t i = 0; i < data.frameRangesPart.frameRanges.size(); i++) {
|
|
if (!_metadata->frameRanges[i].load(context, data.frameRangesPart.frameRanges[i]))
|
|
return false;
|
|
}
|
|
|
|
_metadata->codecData = data.codecData;
|
|
|
|
return true;
|
|
}
|
|
|
|
AssetType MToonAsset::getAssetType() const {
|
|
return kAssetTypeMToon;
|
|
}
|
|
|
|
const Common::SharedPtr<CachedMToon> &MToonAsset::loadAndCacheMToon(Runtime *runtime) {
|
|
if (_cachedMToon)
|
|
return _cachedMToon;
|
|
|
|
Common::SharedPtr<CachedMToon> cachedMToon(new CachedMToon());
|
|
|
|
size_t streamIndex = _streamIndex;
|
|
int segmentIndex = runtime->getProject()->getSegmentForStreamIndex(streamIndex);
|
|
runtime->getProject()->openSegmentStream(segmentIndex);
|
|
Common::SeekableReadStream *stream = runtime->getProject()->getStreamForSegment(segmentIndex);
|
|
|
|
if (!stream || !stream->seek(_frameDataPosition)) {
|
|
warning("Couldn't seek stream to mToon data");
|
|
return _cachedMToon;
|
|
}
|
|
|
|
if (!cachedMToon->loadFromStream(_metadata, stream, _sizeOfFrameData)) {
|
|
warning("mToon data failed to load");
|
|
return _cachedMToon;
|
|
}
|
|
|
|
_cachedMToon = cachedMToon;
|
|
|
|
return _cachedMToon;
|
|
}
|
|
|
|
bool MToonMetadata::FrameDef::load(AssetLoaderContext &context, const Data::MToonAsset::FrameDef &data) {
|
|
compressedSize = data.compressedSize;
|
|
dataOffset = data.dataOffset;
|
|
decompressedBytesPerRow = data.decompressedBytesPerRow;
|
|
decompressedSize = data.decompressedSize;
|
|
isKeyFrame = (data.keyframeFlag != 0);
|
|
if (!data.rect1.toScummVMRect(rect))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MToonMetadata::FrameRangeDef::load(AssetLoaderContext &context, const Data::MToonAsset::FrameRangeDef &data) {
|
|
name = data.name;
|
|
startFrame = data.startFrame;
|
|
endFrame = data.endFrame;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TextAsset::load(AssetLoaderContext &context, const Data::TextAsset &data) {
|
|
_assetID = data.assetID;
|
|
|
|
switch (data.alignment) {
|
|
case Data::kTextAlignmentCodeLeft:
|
|
_alignment = kTextAlignmentLeft;
|
|
break;
|
|
case Data::kTextAlignmentCodeRight:
|
|
_alignment = kTextAlignmentRight;
|
|
break;
|
|
case Data::kTextAlignmentCodeCenter:
|
|
_alignment = kTextAlignmentCenter;
|
|
break;
|
|
default:
|
|
return false;
|
|
};
|
|
|
|
_isBitmap = ((data.isBitmap & 1) != 0);
|
|
|
|
if (_isBitmap) {
|
|
if (!data.bitmapRect.toScummVMRect(_bitmapRect))
|
|
return false;
|
|
|
|
_bitmapData.reset(new Graphics::ManagedSurface());
|
|
|
|
uint16 width = _bitmapRect.width();
|
|
uint16 height = _bitmapRect.height();
|
|
|
|
uint16 pitch = (data.pitchBigEndian[0] << 8) + data.pitchBigEndian[1];
|
|
|
|
if (static_cast<uint32>(pitch * height) != data.bitmapSize) {
|
|
// Pitch is normally aligned to 4 bytes, so if this fails, maybe compute it that way?
|
|
warning("Pre-rendered text bitmap pitch didn't compute to bitmap size correctly, maybe it's wrong?");
|
|
return false;
|
|
}
|
|
|
|
if (pitch * 8 < width) {
|
|
warning("Pre-rendered text pitch is too small");
|
|
return false;
|
|
}
|
|
|
|
_bitmapData->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
|
|
|
for (int row = 0; row < height; row++) {
|
|
uint8 *outRow = static_cast<uint8 *>(_bitmapData->getBasePtr(0, row));
|
|
const uint8 *inRow = &data.bitmapData[row * pitch];
|
|
for (int col = 0; col < width; col++) {
|
|
int bit = ((inRow[col / 8] >> (7 - (col & 7))) & 1);
|
|
outRow[col] = bit;
|
|
}
|
|
}
|
|
} else {
|
|
_bitmapRect = Common::Rect(0, 0, 0, 0);
|
|
|
|
_stringData = data.text;
|
|
|
|
for (size_t i = 0; i < data.macFormattingSpans.size(); i++) {
|
|
const Data::TextAsset::MacFormattingSpan &inSpan = data.macFormattingSpans[i];
|
|
MacFormattingSpan fmtSpan;
|
|
fmtSpan.formatting = MacFontFormatting(inSpan.fontID, inSpan.fontFlags, inSpan.size);
|
|
fmtSpan.spanStart = inSpan.spanStart;
|
|
|
|
_macFormattingSpans.push_back(fmtSpan);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
AssetType TextAsset::getAssetType() const {
|
|
return kAssetTypeText;
|
|
}
|
|
|
|
bool TextAsset::isBitmap() const {
|
|
return _isBitmap;
|
|
}
|
|
|
|
const Common::SharedPtr<Graphics::ManagedSurface>& TextAsset::getBitmapSurface() const {
|
|
return _bitmapData;
|
|
}
|
|
|
|
const Common::String& TextAsset::getString() const {
|
|
return _stringData;
|
|
}
|
|
|
|
const Common::Array<MacFormattingSpan> &TextAsset::getMacFormattingSpans() const {
|
|
return _macFormattingSpans;
|
|
}
|
|
|
|
} // End of namespace MTropolis
|