[Scummvm-git-logs] scummvm master -> 272743827051002ca5a503a4efbca1a09b12ad23
elasota
noreply at scummvm.org
Tue Jul 19 03:20:37 UTC 2022
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
783d45db8d MTROPOLIS: Add anamorphic scaler for Obsidian RSG logo with widescreen mod
2727438270 MTROPOLIS: Fix uninitialized fields
Commit: 783d45db8d902f318d2b1f58618793e7d0b61c6f
https://github.com/scummvm/scummvm/commit/783d45db8d902f318d2b1f58618793e7d0b61c6f
Author: elasota (ejlasota at gmail.com)
Date: 2022-07-18T23:19:07-04:00
Commit Message:
MTROPOLIS: Add anamorphic scaler for Obsidian RSG logo with widescreen mod
Changed paths:
engines/mtropolis/elements.cpp
engines/mtropolis/elements.h
engines/mtropolis/hacks.cpp
diff --git a/engines/mtropolis/elements.cpp b/engines/mtropolis/elements.cpp
index d9cb487af99..eeec9bb08a0 100644
--- a/engines/mtropolis/elements.cpp
+++ b/engines/mtropolis/elements.cpp
@@ -419,6 +419,9 @@ void GraphicElement::render(Window *window) {
}
}
+MovieResizeFilter::~MovieResizeFilter() {
+}
+
MovieElement::MovieElement()
: _cacheBitmap(false), _reversed(false), _haveFiredAtFirstCel(false), _haveFiredAtLastCel(false)
, _alternate(false), _playEveryFrame(false), _assetID(0), _runtime(nullptr), _displayFrame(nullptr)
@@ -617,17 +620,26 @@ void MovieElement::render(Window *window) {
_videoDecoder->seek(Audio::Timestamp(0, _timeScale).addFrames(_currentTimestamp));
_videoDecoder->setEndTime(Audio::Timestamp(0, _timeScale).addFrames(_reversed ? realRange.min : realRange.max));
const Graphics::Surface *decodedFrame = _videoDecoder->decodeNextFrame();
- if (decodedFrame)
+ if (decodedFrame) {
_displayFrame = decodedFrame;
+ _scaledFrame.reset();
+ }
_needsReset = false;
}
if (_displayFrame) {
+ const Graphics::Surface *displaySurface = _displayFrame;
+ if (_resizeFilter) {
+ if (!_scaledFrame)
+ _scaledFrame = _resizeFilter->scaleFrame(*_displayFrame, _currentTimestamp);
+ displaySurface = _scaledFrame.get();
+ }
+
Graphics::ManagedSurface *target = window->getSurface().get();
- Common::Rect srcRect(0, 0, _displayFrame->w, _displayFrame->h);
+ Common::Rect srcRect(0, 0, displaySurface->w, displaySurface->h);
Common::Rect destRect(_cachedAbsoluteOrigin.x, _cachedAbsoluteOrigin.y, _cachedAbsoluteOrigin.x + _rect.width(), _cachedAbsoluteOrigin.y + _rect.height());
- target->blitFrom(*_displayFrame, srcRect, destRect);
+ target->blitFrom(*displaySurface, srcRect, destRect);
}
}
@@ -700,6 +712,7 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
_contentsDirty = true;
framesDecodedThisFrame++;
_displayFrame = decodedFrame;
+ _scaledFrame.reset();
if (_playEveryFrame)
break;
}
@@ -772,6 +785,10 @@ void MovieElement::playMedia(Runtime *runtime, Project *project) {
}
}
+void MovieElement::setResizeFilter(const Common::SharedPtr<MovieResizeFilter> &filter) {
+ _resizeFilter = filter;
+}
+
void MovieElement::onSegmentUnloaded(int segmentIndex) {
_videoDecoder.reset();
}
diff --git a/engines/mtropolis/elements.h b/engines/mtropolis/elements.h
index bbd3a59d54b..71732a18248 100644
--- a/engines/mtropolis/elements.h
+++ b/engines/mtropolis/elements.h
@@ -74,6 +74,13 @@ private:
Common::SharedPtr<Graphics::ManagedSurface> _mask;
};
+class MovieResizeFilter {
+public:
+ virtual ~MovieResizeFilter();
+
+ virtual Common::SharedPtr<Graphics::Surface> scaleFrame(const Graphics::Surface &surface, uint32 timestamp) const = 0;
+};
+
class MovieElement : public VisualElement, public ISegmentUnloadSignalReceiver, public IPlayMediaSignalReceiver {
public:
MovieElement();
@@ -95,6 +102,8 @@ public:
void render(Window *window) override;
void playMedia(Runtime *runtime, Project *project) override;
+ void setResizeFilter(const Common::SharedPtr<MovieResizeFilter> &filter);
+
#ifdef MTROPOLIS_DEBUG_ENABLE
const char *debugGetTypeName() const override { return "Movie Element"; }
SupportStatus debugGetSupportStatus() const override { return kSupportStatusDone; }
@@ -145,6 +154,8 @@ private:
IntRange _playRange;
const Graphics::Surface *_displayFrame;
+ Common::SharedPtr<Graphics::Surface> _scaledFrame;
+ Common::SharedPtr<MovieResizeFilter> _resizeFilter;
Common::SharedPtr<SegmentUnloadSignaller> _unloadSignaller;
Common::SharedPtr<PlayMediaSignaller> _playMediaSignaller;
diff --git a/engines/mtropolis/hacks.cpp b/engines/mtropolis/hacks.cpp
index 5d57e6a55ff..a0685300c25 100644
--- a/engines/mtropolis/hacks.cpp
+++ b/engines/mtropolis/hacks.cpp
@@ -22,8 +22,11 @@
#include "common/system.h"
#include "common/hashmap.h"
+#include "graphics/surface.h"
+
#include "mtropolis/assets.h"
#include "mtropolis/detection.h"
+#include "mtropolis/elements.h"
#include "mtropolis/hacks.h"
#include "mtropolis/runtime.h"
#include "mtropolis/modifiers.h"
@@ -88,7 +91,7 @@ void ObsidianInventoryWindscreenHooks::onSetPosition(Structural *structural, Com
}
}
-class ObsidianSecurityFormWindscreenHooks : public StructuralHooks {
+class ObsidianSecurityFormWidescreenHooks : public StructuralHooks {
public:
void onSetPosition(Structural *structural, Common::Point &pt) override;
@@ -96,7 +99,7 @@ private:
Common::Array<uint32> _hiddenCards;
};
-void ObsidianSecurityFormWindscreenHooks::onSetPosition(Structural *structural, Common::Point &pt) {
+void ObsidianSecurityFormWidescreenHooks::onSetPosition(Structural *structural, Common::Point &pt) {
bool cardVisibility = (pt.y > 480);
// Originally tried manipulating layer order but that's actually not a good solution because
@@ -143,6 +146,193 @@ void ObsidianSecurityFormWindscreenHooks::onSetPosition(Structural *structural,
_hiddenCards.clear();
}
+class ObsidianRSGLogoAnamorphicFilter : public MovieResizeFilter {
+public:
+ ObsidianRSGLogoAnamorphicFilter();
+
+ Common::SharedPtr<Graphics::Surface> scaleFrame(const Graphics::Surface &surface, uint timestamp) const override;
+
+private:
+ template<class TPixel>
+ void anamorphicScaleFrameTyped(const Graphics::Surface &src, Graphics::Surface &dest) const;
+
+ template<class TPixel>
+ void halveWidthTyped(const Graphics::Surface &src, Graphics::Surface &dest) const;
+
+ template<class TPixel>
+ void halveHeightTyped(const Graphics::Surface &src, Graphics::Surface &dest) const;
+
+ Common::Array<uint> _xCoordinates;
+ Common::Array<uint> _yCoordinates;
+};
+
+ObsidianRSGLogoAnamorphicFilter::ObsidianRSGLogoAnamorphicFilter() {
+ // Anamorphic rescale, keeps the RSG logo proportional but preserves the vertical spacing!
+ const uint unscaledWidth = 640;
+ const uint unscaledHeight = 480;
+
+ const uint scaledWidth = 1280;
+ const uint scaledHeight = 720;
+
+ _xCoordinates.resize(scaledWidth);
+ _yCoordinates.resize(scaledHeight);
+
+ // Margin in pixels on the side of the original image to apply filter
+ const double scalingFactor = static_cast<double>(scaledHeight) / static_cast<double>(unscaledHeight);
+ const double invScalingFactor = 1.0 / scalingFactor;
+ const double sideMarginInOriginalImage = 90.0;
+
+ const double sideMarginInScaledImage = (static_cast<double>(scaledWidth) - ((static_cast<double>(unscaledWidth) - sideMarginInOriginalImage * 2.0) * scalingFactor)) * 0.5;
+
+ const double rightMarginStart = static_cast<double>(scaledWidth) - sideMarginInScaledImage;
+
+ for (uint i = 0; i < scaledWidth; i++) {
+ double pixelCenterX = static_cast<double>(i) + 0.5;
+ double originalImagePixelCenter = 0.0;
+ if (pixelCenterX < sideMarginInScaledImage) {
+ double marginFraction = pixelCenterX / sideMarginInScaledImage;
+ originalImagePixelCenter = sqrt(marginFraction) * sideMarginInOriginalImage;
+ } else if (pixelCenterX > rightMarginStart) {
+ double marginFraction = (static_cast<double>(scaledWidth) - pixelCenterX) / sideMarginInScaledImage;
+ originalImagePixelCenter = static_cast<double>(unscaledWidth) - sqrt(marginFraction) * sideMarginInOriginalImage;
+ } else {
+ double offsetFromCenter = pixelCenterX - (static_cast<double>(scaledWidth) * 0.5);
+ double offsetFromCenterInOriginalImage = offsetFromCenter * invScalingFactor;
+ originalImagePixelCenter = static_cast<double>(unscaledWidth) * 0.5 + offsetFromCenterInOriginalImage;
+ }
+
+ double srcPixelX = floor(originalImagePixelCenter);
+ if (srcPixelX < 0.0)
+ srcPixelX = 0.0;
+ else if (srcPixelX >= static_cast<double>(unscaledWidth))
+ srcPixelX = static_cast<double>(unscaledWidth - 1);
+
+ _xCoordinates[i] = static_cast<uint>(srcPixelX);
+ }
+
+ for (uint i = 0; i < scaledHeight; i++)
+ _yCoordinates[i] = (2 * i + 1) * unscaledHeight / (scaledHeight * 2);
+}
+
+template<class TPixel>
+void ObsidianRSGLogoAnamorphicFilter::anamorphicScaleFrameTyped(const Graphics::Surface &src, Graphics::Surface &dest) const {
+ const uint width = _xCoordinates.size();
+ const uint height = _yCoordinates.size();
+
+ const uint *xCoordinates = &_xCoordinates[0];
+ const uint *yCoordinates = &_yCoordinates[0];
+
+ assert(width == static_cast<uint>(dest.w));
+ assert(height == static_cast<uint>(dest.h));
+
+ for (uint row = 0; row < height; row++) {
+ const TPixel *srcRow = static_cast<const TPixel *>(src.getBasePtr(0, yCoordinates[row]));
+ TPixel *destRow = static_cast<TPixel *>(dest.getBasePtr(0, row));
+
+ for (uint col = 0; col < width; col++)
+ destRow[col] = srcRow[xCoordinates[col]];
+ }
+}
+
+template<class TPixel>
+void ObsidianRSGLogoAnamorphicFilter::halveWidthTyped(const Graphics::Surface &src, Graphics::Surface &dest) const {
+ const uint widthHigh = src.w;
+ const uint widthLow = dest.w;
+ const uint height = src.h;
+
+ assert(widthLow * 2 == widthHigh);
+ assert(dest.h == src.h);
+
+ const Graphics::PixelFormat fmt = src.format;
+
+ for (uint row = 0; row < height; row++) {
+ const TPixel *srcRow = static_cast<const TPixel *>(src.getBasePtr(0, row));
+ TPixel *destRow = static_cast<TPixel *>(dest.getBasePtr(0, row));
+
+ for (uint col = 0; col < widthLow; col++) {
+ uint32 col1 = srcRow[col * 2];
+ uint32 col2 = srcRow[col * 2 + 1];
+
+ uint8 r1, g1, b1;
+ fmt.colorToRGB(col1, r1, g1, b1);
+
+ uint8 r2, g2, b2;
+ fmt.colorToRGB(col2, r2, g2, b2);
+
+ destRow[col] = fmt.RGBToColor((r1 + r2) >> 1, (g1 + g2) >> 1, (b1 + b2) >> 1);
+ }
+ }
+}
+
+template<class TPixel>
+void ObsidianRSGLogoAnamorphicFilter::halveHeightTyped(const Graphics::Surface &src, Graphics::Surface &dest) const {
+ const uint heightHigh = src.h;
+ const uint heightLow = dest.h;
+ const uint width = src.w;
+
+ assert(heightLow * 2 == heightHigh);
+ assert(dest.w == src.w);
+
+ const Graphics::PixelFormat fmt = src.format;
+
+ for (uint row = 0; row < heightLow; row++) {
+ const TPixel *srcRow1 = static_cast<const TPixel *>(src.getBasePtr(0, row * 2));
+ const TPixel *srcRow2 = static_cast<const TPixel *>(src.getBasePtr(0, row * 2 + 1));
+ TPixel *destRow = static_cast<TPixel *>(dest.getBasePtr(0, row));
+
+ for (uint col = 0; col < width; col++) {
+ uint32 col1 = srcRow1[col];
+ uint32 col2 = srcRow2[col];
+
+ uint8 r1, g1, b1;
+ fmt.colorToRGB(col1, r1, g1, b1);
+
+ uint8 r2, g2, b2;
+ fmt.colorToRGB(col2, r2, g2, b2);
+
+ destRow[col] = fmt.RGBToColor((r1 + r2) >> 1, (g1 + g2) >> 1, (b1 + b2) >> 1);
+ }
+ }
+}
+
+Common::SharedPtr<Graphics::Surface> ObsidianRSGLogoAnamorphicFilter::scaleFrame(const Graphics::Surface &surface, uint timestamp) const {
+ Common::SharedPtr<Graphics::Surface> result(new Graphics::Surface());
+ result->create(_xCoordinates.size() / 2, _yCoordinates.size() / 2, surface.format);
+
+ Common::SharedPtr<Graphics::Surface> temp1(new Graphics::Surface());
+ Common::SharedPtr<Graphics::Surface> temp2(new Graphics::Surface());
+
+ temp1->create(_xCoordinates.size(), _yCoordinates.size(), surface.format);
+ temp2->create(_xCoordinates.size() / 2, _yCoordinates.size(), surface.format);
+
+ if (surface.format.bytesPerPixel == 1) {
+ anamorphicScaleFrameTyped<uint8>(surface, *temp1);
+ halveWidthTyped<uint8>(*temp1, *temp2);
+ halveHeightTyped<uint8>(*temp2, *result);
+ } else if (surface.format.bytesPerPixel == 2) {
+ anamorphicScaleFrameTyped<uint16>(surface, *temp1);
+ halveWidthTyped<uint16>(*temp1, *temp2);
+ halveHeightTyped<uint16>(*temp2, *result);
+ } else if (surface.format.bytesPerPixel == 4) {
+ anamorphicScaleFrameTyped<uint32>(surface, *temp1);
+ halveWidthTyped<uint32>(*temp1, *temp2);
+ halveHeightTyped<uint32>(*temp2, *result);
+ }
+
+ return result;
+}
+
+class ObsidianRSGLogoWidescreenHooks : public StructuralHooks {
+public:
+ void onCreate(Structural *structural) override;
+};
+
+void ObsidianRSGLogoWidescreenHooks::onCreate(Structural *structural) {
+ MovieElement *movie = static_cast<MovieElement *>(structural);
+ movie->setRelativeRect(Common::Rect(0, 60, 640, 420));
+ movie->setResizeFilter(Common::SharedPtr<MovieResizeFilter>(new ObsidianRSGLogoAnamorphicFilter()));
+}
+
void addObsidianBugFixes(const MTropolisGameDescription &desc, Hacks &hacks) {
// Workaround for bug in Obsidian:
// When opening the journal in the intro, a script checks if cGSt.cfst.binjournal is false and if so,
@@ -228,13 +418,15 @@ void addObsidianImprovedWidescreen(const MTropolisGameDescription &desc, Hacks &
};
const uint32 cubeMazeSecurityFormGUID = 0x9602ec;
+ const uint32 rsgIntroMovieGUID = 0x2fc101;
Common::SharedPtr<StructuralHooks> invItemHooks(new ObsidianInventoryWindscreenHooks());
for (uint32 guid : inventoryItemGUIDs)
hacks.addStructuralHooks(guid, invItemHooks);
- hacks.addStructuralHooks(cubeMazeSecurityFormGUID, Common::SharedPtr<StructuralHooks>(new ObsidianSecurityFormWindscreenHooks()));
+ hacks.addStructuralHooks(cubeMazeSecurityFormGUID, Common::SharedPtr<StructuralHooks>(new ObsidianSecurityFormWidescreenHooks()));
+ hacks.addStructuralHooks(rsgIntroMovieGUID, Common::SharedPtr<StructuralHooks>(new ObsidianRSGLogoWidescreenHooks()));
}
if ((desc.desc.flags & ADGF_DEMO) == 0 && desc.desc.language == Common::EN_ANY && desc.desc.platform == Common::kPlatformMacintosh) {
const uint32 inventoryItemGUIDs[] = {
@@ -300,13 +492,15 @@ void addObsidianImprovedWidescreen(const MTropolisGameDescription &desc, Hacks &
};
const uint32 cubeMazeSecurityFormGUID = 0x9602ec;
+ const uint32 rsgIntroMovieGUID = 0x2fc101;
Common::SharedPtr<StructuralHooks> invItemHooks(new ObsidianInventoryWindscreenHooks());
for (uint32 guid : inventoryItemGUIDs)
hacks.addStructuralHooks(guid, invItemHooks);
- hacks.addStructuralHooks(cubeMazeSecurityFormGUID, Common::SharedPtr<StructuralHooks>(new ObsidianSecurityFormWindscreenHooks()));
+ hacks.addStructuralHooks(cubeMazeSecurityFormGUID, Common::SharedPtr<StructuralHooks>(new ObsidianSecurityFormWidescreenHooks()));
+ hacks.addStructuralHooks(rsgIntroMovieGUID, Common::SharedPtr<StructuralHooks>(new ObsidianRSGLogoWidescreenHooks()));
}
}
Commit: 272743827051002ca5a503a4efbca1a09b12ad23
https://github.com/scummvm/scummvm/commit/272743827051002ca5a503a4efbca1a09b12ad23
Author: elasota (ejlasota at gmail.com)
Date: 2022-07-18T23:20:22-04:00
Commit Message:
MTROPOLIS: Fix uninitialized fields
Changed paths:
engines/mtropolis/render.cpp
diff --git a/engines/mtropolis/render.cpp b/engines/mtropolis/render.cpp
index 40e7caa4fc4..c96001aa083 100644
--- a/engines/mtropolis/render.cpp
+++ b/engines/mtropolis/render.cpp
@@ -320,7 +320,7 @@ private:
uint16 _y;
};
-DissolveOrderedDitherPatternGenerator::DissolveOrderedDitherPatternGenerator() {
+DissolveOrderedDitherPatternGenerator::DissolveOrderedDitherPatternGenerator() : _x(0), _y(0) {
OrderedDitherGenerator<uint8, 16>::generateOrderedDither(_ditherPattern);
}
More information about the Scummvm-git-logs
mailing list