MTROPOLIS: Add image assets and elements

This commit is contained in:
elasota 2022-04-28 02:18:12 -04:00 committed by Eugene Sandulenko
parent a1940c352a
commit c5434a80dc
10 changed files with 395 additions and 5 deletions

View File

@ -63,6 +63,8 @@ IAssetFactory *getAssetFactoryForDataObjectType(const Data::DataObjectTypes::Dat
return AssetFactory<AudioAsset, Data::AudioAsset>::getInstance();
case Data::DataObjectTypes::kMovieAsset:
return AssetFactory<MovieAsset, Data::MovieAsset>::getInstance();
case Data::DataObjectTypes::kImageAsset:
return AssetFactory<ImageAsset, Data::ImageAsset>::getInstance();
default:
return nullptr;

View File

@ -122,5 +122,74 @@ size_t MovieAsset::getStreamIndex() const {
return _streamIndex;
}
bool ImageAsset::load(AssetLoaderContext &context, const Data::ImageAsset &data) {
_assetID = data.assetID;
if (!_rect.load(data.rect1))
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 Rect16& 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;
}
} // End of namespace MTropolis

View File

@ -87,6 +87,32 @@ private:
size_t _streamIndex;
};
class ImageAsset : public Asset {
public:
bool load(AssetLoaderContext &context, const Data::ImageAsset &data);
AssetType getAssetType() const override;
enum ImageFormat {
kImageFormatMac,
kImageFormatWindows,
};
const Rect16 &getRect() const;
ColorDepthMode getColorDepth() const;
uint32 getFilePosition() const;
uint32 getSize() const;
size_t getStreamIndex() const;
ImageFormat getImageFormat() const;
private:
Rect16 _rect;
ColorDepthMode _colorDepth;
uint32 _filePosition;
uint32 _size;
size_t _streamIndex;
ImageFormat _imageFormat;
};
} // End of namespace MTropolis
#endif

View File

@ -1544,6 +1544,36 @@ DataReadErrorCode AudioAsset::load(DataReader &reader) {
return kDataReadErrorNone;
}
DataReadErrorCode ImageAsset::load(DataReader &reader) {
if (_revision != 1)
return kDataReadErrorUnsupportedRevision;
if (!reader.readU32(persistFlags) || !reader.readU32(unknown1) || !reader.readBytes(unknown2)
|| !reader.readU32(assetID) || !reader.readU32(unknown3))
return kDataReadErrorReadFailed;
haveWinPart = false;
haveMacPart = false;
if (reader.getProjectFormat() == kProjectFormatMacintosh) {
haveMacPart = true;
if (!reader.readBytes(platform.mac.unknown7))
return kDataReadErrorReadFailed;
} else if (reader.getProjectFormat() == kProjectFormatWindows) {
haveWinPart = true;
if (!reader.readBytes(platform.win.unknown8))
return kDataReadErrorReadFailed;
} else
return kDataReadErrorUnrecognized;
if (!rect1.load(reader) || !reader.readU32(hdpiFixed) || !reader.readU32(vdpiFixed) || !reader.readU16(bitsPerPixel)
|| !reader.readBytes(unknown4) || !reader.readBytes(unknown5) || !reader.readBytes(unknown6)
|| !rect2.load(reader) || !reader.readU32(filePosition) || !reader.readU32(size))
return kDataReadErrorReadFailed;
return kDataReadErrorNone;
}
DataReadErrorCode AssetDataChunk::load(DataReader &reader) {
if (_revision != 0)
return kDataReadErrorUnsupportedRevision;
@ -1728,7 +1758,7 @@ DataReadErrorCode loadDataObject(const PlugInModifierRegistry &registry, DataRea
break;
case DataObjectTypes::kImageAsset:
//dataObject = new ImageAsset();
dataObject = new ImageAsset();
break;
case DataObjectTypes::kMToonAsset:

View File

@ -1412,6 +1412,45 @@ protected:
DataReadErrorCode load(DataReader &reader) override;
};
struct ImageAsset : public DataObject {
struct MacPart {
uint8 unknown7[44];
};
struct WinPart {
uint8 unknown8[10];
};
union PlatformPart {
WinPart win;
MacPart mac;
};
uint32 persistFlags;
uint32 unknown1;
uint8_t unknown2[4];
uint32 assetID;
uint32 unknown3;
Rect rect1;
uint32 hdpiFixed;
uint32 vdpiFixed;
uint16 bitsPerPixel;
uint8 unknown4[2];
uint8 unknown5[4];
uint8 unknown6[8];
Rect rect2;
uint32 filePosition;
uint32 size;
bool haveMacPart;
bool haveWinPart;
PlatformPart platform;
protected:
DataReadErrorCode load(DataReader &reader) override;
};
struct AssetDataChunk : public DataObject {
uint32 unknown1;
uint32 sizeIncludingTag;

View File

@ -64,6 +64,8 @@ IElementFactory *getElementFactoryForDataObjectType(const Data::DataObjectTypes:
return ElementFactory<GraphicElement, Data::GraphicElement>::getInstance();
case Data::DataObjectTypes::kMovieElement:
return ElementFactory<MovieElement, Data::MovieElement>::getInstance();
case Data::DataObjectTypes::kImageElement:
return ElementFactory<ImageElement, Data::ImageElement>::getInstance();
default:
return nullptr;

View File

@ -137,7 +137,6 @@ void MovieElement::activate() {
return;
}
Video::QuickTimeDecoder *qtDecoder = new Video::QuickTimeDecoder();
qtDecoder->setChunkBeginOffset(movieAsset->getMovieDataPos());
@ -231,4 +230,197 @@ VThreadState MovieElement::startPlayingTask(const StartPlayingTaskData &taskData
return kVThreadReturn;
}
ImageElement::ImageElement() : _cacheBitmap(false), _runtime(nullptr) {
}
ImageElement::~ImageElement() {
}
bool ImageElement::load(ElementLoaderContext &context, const Data::ImageElement &data) {
if (!VisualElement::loadCommon(data.name, data.guid, data.rect1, data.elementFlags, data.layer, data.streamLocator, data.sectionID))
return false;
_cacheBitmap = ((data.elementFlags & Data::ElementFlags::kCacheBitmap) != 0);
_runtime = context.runtime;
_assetID = data.imageAssetID;
return true;
}
bool ImageElement::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
return VisualElement::readAttribute(thread, result, attrib);
}
bool ImageElement::writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) {
return VisualElement::writeRefAttribute(thread, writeProxy, attrib);
}
void ImageElement::activate() {
Project *project = _runtime->getProject();
Common::SharedPtr<Asset> asset = project->getAssetByID(_assetID).lock();
if (!asset) {
warning("Image element references asset %i but the asset isn't loaded!", _assetID);
return;
}
if (asset->getAssetType() != kAssetTypeImage) {
warning("Image element assigned an asset that isn't an image");
return;
}
ImageAsset *imageAsset = static_cast<ImageAsset *>(asset.get());
size_t streamIndex = imageAsset->getStreamIndex();
int segmentIndex = project->getSegmentForStreamIndex(streamIndex);
project->openSegmentStream(segmentIndex);
Common::SeekableReadStream *stream = project->getStreamForSegment(segmentIndex);
if (!stream->seek(imageAsset->getFilePosition())) {
warning("Image element failed to load");
return;
}
size_t bytesPerRow = 0;
Rect16 imageRect = imageAsset->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;
}
Graphics::PixelFormat pixelFmt;
switch (imageAsset->getColorDepth()) {
case kColorDepthMode1Bit:
bytesPerRow = (width + 7) / 8;
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
break;
case kColorDepthMode2Bit:
bytesPerRow = (width + 3) / 4;
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
break;
case kColorDepthMode4Bit:
bytesPerRow = (width + 1) / 2;
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
break;
case kColorDepthMode8Bit:
bytesPerRow = width;
pixelFmt = Graphics::PixelFormat::createFormatCLUT8();
break;
case kColorDepthMode16Bit:
bytesPerRow = width * 2;
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;
}
Common::Array<uint8> rowBuffer;
rowBuffer.resize(bytesPerRow);
ImageAsset::ImageFormat imageFormat = imageAsset->getImageFormat();
bool bottomUp = (imageFormat == ImageAsset::kImageFormatWindows);
bool isBigEndian = (imageFormat == ImageAsset::kImageFormatMac);
_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 (imageAsset->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 + 0];
uint8 g = inRowBytes[x * 4 + 1];
uint8 b = inRowBytes[x * 4 + 2];
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;
}
}
}
void ImageElement::deactivate() {
_imageSurface.reset();
}
void ImageElement::render(Window *window) {
if (_imageSurface) {
Common::Rect srcRect(_imageSurface->w, _imageSurface->h);
Common::Rect destRect(_rect.left, _rect.top, _rect.right, _rect.bottom);
window->getSurface()->blitFrom(*_imageSurface, srcRect, destRect);
}
}
} // End of namespace MTropolis

View File

@ -60,8 +60,8 @@ public:
bool load(ElementLoaderContext &context, const Data::MovieElement &data);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
bool writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
bool writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib) override;
VThreadState consumeCommand(Runtime *runtime, const Common::SharedPtr<MessageProperties> &msg) override;
void activate() override;
@ -103,6 +103,35 @@ private:
Runtime *_runtime;
};
class ImageElement : public VisualElement {
public:
ImageElement();
~ImageElement();
bool load(ElementLoaderContext &context, const Data::ImageElement &data);
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib);
bool writeRefAttribute(MiniscriptThread *thread, DynamicValueWriteProxy &writeProxy, const Common::String &attrib);
void activate() override;
void deactivate() override;
void render(Window *window) override;
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Image Element"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusPartial; }
#endif
private:
bool _cacheBitmap;
uint32 _assetID;
Common::SharedPtr<Graphics::Surface> _imageSurface;
Runtime *_runtime;
};
} // End of namespace MTropolis
#endif

View File

@ -2787,7 +2787,7 @@ void Runtime::removeWindow(Window *window) {
}
}
void Runtime::setupDisplayMode(ColorDepthMode displayMode, const Graphics::PixelFormat& pixelFormat) {
void Runtime::setupDisplayMode(ColorDepthMode displayMode, const Graphics::PixelFormat &pixelFormat) {
_displayModeSupported[displayMode] = true;
_displayModePixelFormats[displayMode] = pixelFormat;
}

View File

@ -1713,6 +1713,7 @@ enum AssetType {
kAssetTypeMovie,
kAssetTypeAudio,
kAssetTypeColorTable,
kAssetTypeImage,
};
class Asset {