MTROPOLIS: Improve image optimization

This commit is contained in:
elasota 2022-05-06 19:17:45 -04:00 committed by Eugene Sandulenko
parent ea7eaa4a15
commit 256b2f9ef5
8 changed files with 324 additions and 156 deletions

View File

@ -125,6 +125,51 @@ 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 {
_surface->convertToInPlace(renderFmt, nullptr);
_optimizedSurface = _surface;
}
return _optimizedSurface;
}
ImageAsset::ImageAsset() {
}
ImageAsset::~ImageAsset() {
}
bool ImageAsset::load(AssetLoaderContext &context, const Data::ImageAsset &data) {
_assetID = data.assetID;
if (!_rect.load(data.rect1))
@ -194,6 +239,158 @@ 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;
Rect16 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 + 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 + 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 + 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;
}
}
_imageCache.reset(new CachedImage());
_imageCache->resetSurface(renderDepth, imageSurface);
return _imageCache;
}
bool MToonAsset::load(AssetLoaderContext &context, const Data::MToonAsset &data) {
if (data.haveMacPart)
_imageFormat = kImageFormatMac;
@ -224,6 +421,8 @@ bool MToonAsset::load(AssetLoaderContext &context, const Data::MToonAsset &data)
}
_codecData = data.codecData;
return true;
}
AssetType MToonAsset::getAssetType() const {

View File

@ -88,8 +88,27 @@ private:
size_t _streamIndex;
};
class CachedImage {
public:
CachedImage();
const Common::SharedPtr<Graphics::Surface> &optimize(Runtime *runtime);
void resetSurface(ColorDepthMode colorDepth, const Common::SharedPtr<Graphics::Surface> &surface);
private:
Common::SharedPtr<Graphics::Surface> _surface;
Common::SharedPtr<Graphics::Surface> _optimizedSurface;
ColorDepthMode _colorDepth;
bool _isOptimized;
};
class ImageAsset : public Asset {
public:
ImageAsset();
~ImageAsset();
bool load(AssetLoaderContext &context, const Data::ImageAsset &data);
AssetType getAssetType() const override;
@ -105,7 +124,7 @@ public:
size_t getStreamIndex() const;
ImageFormat getImageFormat() const;
const Common::SharedPtr<Graphics::Surface> &loadContent();
const Common::SharedPtr<CachedImage> &loadAndCacheImage(Runtime *runtime);
private:
Rect16 _rect;
@ -115,7 +134,7 @@ private:
size_t _streamIndex;
ImageFormat _imageFormat;
Common::SharedPtr<Graphics::Surface> _surface;
Common::SharedPtr<CachedImage> _imageCache;
};

View File

@ -203,7 +203,6 @@ VThreadState MovieElement::startPlayingTask(const StartPlayingTaskData &taskData
return kVThreadReturn;
}
ImageElement::ImageElement() : _cacheBitmap(false), _runtime(nullptr) {
}
@ -243,162 +242,19 @@ void ImageElement::activate() {
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 + 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;
}
// If this is the same mode as the render target, then copy the exact mode
// so blits go faster
if (imageAsset->getColorDepth() == _runtime->getRealColorDepth()) {
pixelFmt = _runtime->getRenderPixelFormat();
}
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;
}
}
_cachedImage = static_cast<ImageAsset *>(asset.get())->loadAndCacheImage(_runtime);
}
void ImageElement::deactivate() {
_imageSurface.reset();
_cachedImage.reset();
}
void ImageElement::render(Window *window) {
if (_imageSurface) {
Common::Rect srcRect(_imageSurface->w, _imageSurface->h);
if (_cachedImage) {
Common::SharedPtr<Graphics::Surface> optimized = _cachedImage->optimize(_runtime);
Common::Rect srcRect(optimized->w, optimized->h);
Common::Rect destRect(_cachedAbsoluteOrigin.x, _cachedAbsoluteOrigin.y, _cachedAbsoluteOrigin.x + _rect.getWidth(), _cachedAbsoluteOrigin.y + _rect.getHeight());
window->getSurface()->blitFrom(*_imageSurface, srcRect, destRect);
window->getSurface()->blitFrom(*optimized, srcRect, destRect);
}
}

View File

@ -34,6 +34,7 @@ class VideoDecoder;
namespace MTropolis {
class CachedImage;
struct ElementLoaderContext;
class GraphicElement : public VisualElement {
@ -123,7 +124,7 @@ private:
bool _cacheBitmap;
uint32 _assetID;
Common::SharedPtr<Graphics::Surface> _imageSurface;
Common::SharedPtr<CachedImage> _cachedImage;
Runtime *_runtime;
};

View File

@ -46,9 +46,9 @@ struct RenderItem {
template<class TNumber, int TResolution>
void OrderedDitherGenerator<TNumber, TResolution>::generateOrderedDither(TNumber (&pattern)[TResolution][TResolution]) {
const int kHalfResolution = TResolution / 2;
byte halfRes[kHalfResolution][kHalfResolution];
TNumber halfRes[kHalfResolution][kHalfResolution];
OrderedDitherGenerator<kHalfResolution>::generateOrderedDither(halfRes);
OrderedDitherGenerator<TNumber, kHalfResolution>::generateOrderedDither(halfRes);
const int kHalfResNumSteps = kHalfResolution * kHalfResolution;
for (int y = 0; y < kHalfResolution; y++) {
@ -66,10 +66,18 @@ void OrderedDitherGenerator<TNumber, 1>::generateOrderedDither(TNumber (&pattern
pattern[0][0] = 0;
}
inline int quantize8To5(int value, byte orderedDither16x16) {
inline int quantize8To5Byte(int value, byte orderedDither16x16) {
return (value * 249 + (orderedDither16x16 << 3)) >> 11;
}
inline int quantize8To5UShort(int value, uint16 orderedDither16x16) {
return (value * 249 + orderedDither16x16) >> 11;
}
inline int expand5To8(int value) {
return (value * 33) >> 2;
}
MacFontFormatting::MacFontFormatting() : fontID(0), fontFlags(0), size(12) {
}
@ -244,6 +252,77 @@ void renderProject(Runtime *runtime, Window *mainWindow) {
renderDirectElement(*it, mainWindow);
}
void convert32To16(Graphics::Surface &destSurface, const Graphics::Surface &srcSurface) {
const Graphics::PixelFormat srcFmt = srcSurface.format;
const Graphics::PixelFormat destFmt = destSurface.format;
assert(srcFmt.bytesPerPixel == 4);
assert(destFmt.bytesPerPixel == 2);
assert(destSurface.w == srcSurface.w);
assert(srcSurface.h == destSurface.h);
uint16 ditherPattern[16][16];
OrderedDitherGenerator<uint16, 16>::generateOrderedDither(ditherPattern);
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++)
ditherPattern[y][x] <<= 3;
}
size_t w = srcSurface.w;
size_t h = srcSurface.h;
for (size_t y = 0; y < h; y++) {
const uint16 *ditherRow = ditherPattern[y % 16];
const uint32 *srcRow = static_cast<const uint32*>(srcSurface.getBasePtr(0, y));
uint16 *destRow = static_cast<uint16 *>(destSurface.getBasePtr(0, y));
for (size_t x = 0; x < w; x++) {
uint16 ditherOffset = ditherRow[x % 16];
uint32 packed32 = srcRow[x];
uint8 r = (packed32 >> srcFmt.rShift) & 0xff;
uint8 g = (packed32 >> srcFmt.gShift) & 0xff;
uint8 b = (packed32 >> srcFmt.bShift) & 0xff;
r = quantize8To5UShort(r, ditherOffset);
g = quantize8To5UShort(g, ditherOffset);
b = quantize8To5UShort(b, ditherOffset);
destRow[x] = (r << destFmt.rShift) | (g << destFmt.gShift) | (b << destFmt.bShift);
}
}
}
void convert16To32(Graphics::Surface &destSurface, const Graphics::Surface &srcSurface) {
const Graphics::PixelFormat srcFmt = srcSurface.format;
const Graphics::PixelFormat destFmt = destSurface.format;
assert(srcFmt.bytesPerPixel == 2);
assert(destFmt.bytesPerPixel == 4);
assert(destSurface.w == srcSurface.w);
assert(srcSurface.h == destSurface.h);
size_t w = srcSurface.w;
size_t h = srcSurface.h;
for (size_t y = 0; y < h; y++) {
const uint32 *srcRow = static_cast<const uint32 *>(srcSurface.getBasePtr(0, y));
uint16 *destRow = static_cast<uint16 *>(destSurface.getBasePtr(0, y));
for (size_t x = 0; x < w; x++) {
uint32 packed16 = srcRow[x];
uint8 r = (packed16 >> srcFmt.rShift) & 0x1f;
uint8 g = (packed16 >> srcFmt.gShift) & 0x1f;
uint8 b = (packed16 >> srcFmt.bShift) & 0x1f;
r = expand5To8(r);
g = expand5To8(g);
b = expand5To8(b);
destRow[x] = (r << destFmt.rShift) | (g << destFmt.gShift) | (b << destFmt.bShift);
}
}
}
} // End of namespace Render
} // End of namespace MTropolis

View File

@ -30,6 +30,7 @@
namespace Graphics {
class ManagedSurface;
struct Surface;
} // End of namespace Graphics
@ -111,6 +112,8 @@ namespace Render {
uint32 resolveRGB(uint8 r, uint8 g, uint8 b, const Graphics::PixelFormat &fmt);
void renderProject(Runtime *runtime, Window *mainWindow);
void convert32To16(Graphics::Surface &destSurface, const Graphics::Surface &srcSurface);
void convert16To32(Graphics::Surface &destSurface, const Graphics::Surface &srcSurface);
} // End of namespace Render

View File

@ -5044,6 +5044,15 @@ bool VariableModifier::isVariable() const {
return true;
}
bool VariableModifier::readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) {
if (attrib == "value") {
varGetValue(thread, result);
return true;
}
return Modifier::readAttribute(thread, result, attrib);
}
DynamicValueWriteProxy VariableModifier::createWriteProxy() {
DynamicValueWriteProxy proxy;
proxy.pod.objectRef = this;

View File

@ -2077,6 +2077,8 @@ public:
virtual bool varSetValue(MiniscriptThread *thread, const DynamicValue &value) = 0;
virtual void varGetValue(MiniscriptThread *thread, DynamicValue &dest) const = 0;
bool readAttribute(MiniscriptThread *thread, DynamicValue &result, const Common::String &attrib) override;
virtual DynamicValueWriteProxy createWriteProxy();
private: