[Scummvm-git-logs] scummvm master -> 2af98018a97a80d4503fd92a18a06e7b97229efc
sev-
noreply at scummvm.org
Sat Apr 26 11:36:20 UTC 2025
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
b6df3118b9 VIDEO: QTVR: Implement the warping functionality
28257c7ae5 VIDEO: QTVR: Implement the higher quality modes
a6f3c04218 VIDEO: QTVR: Fix distortion bug while zooming out
770feb4a23 VIDEO: QTVR: Implement the Swing Transition functionality
8615695c7c VIDEO: QTVR: Minor Fixes and Formatting
2af98018a9 VIDEO: QTVR: Memory Optimization for Upscaled Panorama
Commit: b6df3118b94404b5d5c36cb4ac268fb231c7de5b
https://github.com/scummvm/scummvm/commit/b6df3118b94404b5d5c36cb4ac268fb231c7de5b
Author: Malhar (themalharbdv2046 at gmail.com)
Date: 2025-04-26T19:36:15+08:00
Commit Message:
VIDEO: QTVR: Implement the warping functionality
Add the warping functionality using the warpMode variable
as described in the original QTVR Xtra documentation
provided by Apple.
Set three modes of warping in the qtvr decoder
- Mode 2: Already there, applies both planar projection as
well as perspective correction for perspectively correct
image
- Mode 1: Only apply planar projection, image becomes more
distorted as we move up or down from the midpoint
- Mode 0: No projection, blit the image as it is, exact
copy of the original QTVR movie
Changed paths:
video/qt_decoder.h
video/qtvr_decoder.cpp
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 2b2b7570829..755d7710dcb 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -96,6 +96,7 @@ public:
float getTiltAngle() const { return _tiltAngle; }
void setTiltAngle(float tiltAngle);
float getFOV() const { return _fov; }
+ float getHFOV() const { return _hfov; }
bool setFOV(float fov);
int getCurrentNodeID() { return _currentSample == -1 ? 0 : _panoTrack->panoSamples[_currentSample].hdr.nodeID; }
Common::String getCurrentNodeName();
@@ -421,7 +422,6 @@ private:
private:
bool _isPanoConstructed;
-
bool _dirty;
};
};
diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index 13e7cba69b9..907e3172629 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -162,9 +162,7 @@ void QuickTimeDecoder::setQuality(float quality) {
}
void QuickTimeDecoder::setWarpMode(int warpMode) {
- _warpMode = warpMode;
-
- warning("STUB: Warp mode set to %d", warpMode);
+ _warpMode = CLIP(warpMode, 0, 2);
// 2 Two-dimensional warping. This produces perspectively correct
// images from a panoramic source picture.
@@ -174,7 +172,8 @@ void QuickTimeDecoder::setWarpMode(int warpMode) {
// 0 No warping. This reproduces the source panorama directly,
// without warping it at all.
- ((PanoTrackHandler *)getTrack(_panoTrack->targetTrack))->setDirty();
+ PanoTrackHandler *track = ((PanoTrackHandler *)getTrack(_panoTrack->targetTrack));
+ track->setDirty();
}
void QuickTimeDecoder::setTransitionMode(Common::String mode) {
@@ -291,6 +290,9 @@ void QuickTimeDecoder::setTargetSize(uint16 w, uint16 h) {
_width = w;
_height = h;
}
+ // Set up the _hfov properly for the very first frame of the pano
+ // After our setFOV will handle the _hfov
+ _hfov = _fov * (float)_width / (float)_height;
}
void QuickTimeDecoder::setPanAngle(float angle) {
@@ -648,6 +650,7 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
return Common::Point(-1, -1);
uint16 w = _decoder->getWidth(), h = _decoder->getHeight();
+ uint16 hotWidth = _constructedHotspots->w, hotHeight = _constructedHotspots->h;
PanoSampleDesc *desc = (PanoSampleDesc *)_parent->sampleDescs[0];
@@ -683,43 +686,72 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
float minTiltY = tan(desc->_vPanBottom * M_PI / 180.0f);
float maxTiltY = tan(desc->_vPanTop * M_PI / 180.0f);
+ int warpMode = _decoder->_warpMode;
+ int hotX, hotY;
+
+ // Compute the largest projected X value, which determines the horizontal angle range
+ float maxProjectedX = 0.0f;
+
+ // X coords are the same here so whichever has the lower Z coord will have the maximum projected X
+ if (topRightVector[2] < bottomRightVector[2])
+ maxProjectedX = topRightVector[0] / topRightVector[2];
+ else
+ maxProjectedX = bottomRightVector[0] / bottomRightVector[2];
+
// Compute the side edge vector by interpolating between topRightVector and
// bottomRightVector based on the mouse Y position
float yRatio = (float)my / (float)h;
mousePixelVector[0] = topRightVector[0] + yRatio * (bottomRightVector[0] - topRightVector[0]);
mousePixelVector[1] = topRightVector[1] + yRatio * (bottomRightVector[1] - topRightVector[1]);
mousePixelVector[2] = topRightVector[2] + yRatio * (bottomRightVector[2] - topRightVector[2]);
+
+ float t, xCoord = 0, yawRatio = 0;
+ float horizontalFovRadians = _decoder->_hfov * M_PI / 180.0f;
+ float verticalFovRadians = _decoder->_fov * M_PI / 180.0f;
+ float tiltAngleRadians = -_decoder->_tiltAngle * M_PI / 180.0f;
+
+ if (warpMode == 0) {
+ t = (float)mx / (float)(w) - 0.5f;
+ xCoord = t * horizontalFovRadians / M_PI / 2.0f;
+ yawRatio = xCoord;
+ } else {
+ t = ((float)(mx - w / 2) / (float)w * 2.0);
+ if (warpMode == 1) {
+ xCoord = t * maxProjectedX;
+ } else if (warpMode == 2) {
+ xCoord = t * mousePixelVector[0] / mousePixelVector[2];
+ }
+ yawRatio = atan(xCoord) / (2.0 * M_PI);
+ }
- // Multiply the X value ([0]) of the result of that on a value ranging from -1 to 1
- // based on the mouse X position to get the mouse pixel vector
- mousePixelVector[0] = mousePixelVector[0] * ((float)(mx - w / 2) / (float)w * 2.0);
-
- // The yaw angle is atan2(mousePixelVector[0], mousePixelVector[2]), multiply that
- // by the panorama width and divide by 2*pi to get the horizontal coordinate
- float yawAngle = atan2(mousePixelVector[0], mousePixelVector[2]);
+ float angleT = (360.0f - _decoder->_panAngle) / 360.0f;
// panorama is turned 90 degrees, width is height
- int hotX = (1.0f - (yawAngle / (2.0 * M_PI) + (360.0f - _decoder->_panAngle) / 360.0f)) * (float)_constructedHotspots->h;
+ hotX = (1.0f - (angleT + yawRatio)) * (float)hotHeight;
- hotX = hotX % _constructedHotspots->h;
+ if (warpMode == 0) {
+ float tiltFactor = tan(verticalFovRadians / 2.0f);
+ float normalizedY = ((float)my / (float)(h - 1)) * 2.0f - 1.0f; // Range [-1, 1]
+ float projectedY = (normalizedY * tiltFactor) + tan(tiltAngleRadians);
+ hotY = static_cast<int32>((projectedY + 1.0f) / 2.0f * hotWidth);
+ } else {
+ // To get the vertical coordinate, need to project the vector on to a unit cylinder.
+ // To do that, compute the length of the XZ vector,
+ float xzVectorLen = sqrt(xCoord * xCoord + 1.0f);
+
+ float projectedY = xzVectorLen * mousePixelVector[1] / mousePixelVector[2];
+ float normalizedYCoordinate = (projectedY - minTiltY) / (maxTiltY - minTiltY);
+ hotY = (int)(normalizedYCoordinate * (float)hotWidth);
+ }
+
+ hotX = hotX % hotHeight;
if (hotX < 0)
- hotX += _constructedHotspots->h;
-
- // To get the vertical coordinate, need to project the vector on to a unit cylinder.
- // To do that, compute the length of the XZ vector,
- float xzVectorLen = sqrt(mousePixelVector[0] * mousePixelVector[0] + mousePixelVector[2] * mousePixelVector[2]);
-
- // then compute projectedY = mousePixelVector[1] / xzVectorLen
- float projectedY = mousePixelVector[1] / xzVectorLen;
-
- float normalizedYCoordinate = (projectedY - minTiltY) / (maxTiltY - minTiltY);
-
- int hotY = (int)(normalizedYCoordinate * (float)_constructedHotspots->w);
-
+ hotX += hotHeight;
+
if (hotY < 0)
hotY = 0;
- else if (hotY > _constructedHotspots->w)
- hotY = _constructedHotspots->w;
+ else if (hotY > hotWidth)
+ hotY = hotWidth;
return Common::Point(hotX, hotY);
}
@@ -760,6 +792,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
float cosTilt = cos(-_decoder->_tiltAngle * M_PI / 180.0);
float sinTilt = sin(-_decoder->_tiltAngle * M_PI / 180.0);
+ // Using trigonometry to account for the tilt
for (int v = 0; v < 2; v++) {
float y = cornerVectors[v][1];
float z = cornerVectors[v][2];
@@ -775,6 +808,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
float maxTiltY = tan(desc->_vPanTop * M_PI / 180.0f);
// Compute the largest projected X value, which determines the horizontal angle range
+ // Same with max projected y as well
float maxProjectedX = 0.0f;
// X coords are the same here so whichever has the lower Z coord will have the maximum projected X
@@ -786,126 +820,138 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
float minProjectedY = topRightVector[1] / topRightVector[2];
float maxProjectedY = bottomRightVector[1] / bottomRightVector[2];
- Common::Array<float> sideEdgeXYInterpolators;
-
- // The X interpolators are from 0 to maxProjectedX
- // The Y interpolators are the interpolator from minProjectedY to maxProjectedY
- sideEdgeXYInterpolators.resize(h * 2);
-
- for (uint16 y = 0; y < h; y++) {
- float t = ((float)y + 0.5f) / (float)h;
-
- float vector[3];
- for (int v = 0; v < 3; v++)
- vector[v] = cornerVectors[0][v] * (1.0f - t) + cornerVectors[1][v] * t;
+ float angleT = fmod((360.0f - _decoder->_panAngle) / 360.0f, 1.0f);
+ if (angleT < 0.0f) {
+ angleT += 1.0f;
+ }
- float projectedX = vector[0] / vector[2];
- float projectedY = vector[1] / vector[2];
+ Graphics::Surface *sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
- sideEdgeXYInterpolators[y * 2 + 0] = projectedX / maxProjectedX;
- sideEdgeXYInterpolators[y * 2 + 1] = (projectedY - minProjectedY) / (maxProjectedY - minProjectedY);
- }
+ int32 panoWidth = sourceSurface->h;
+ int32 panoHeight = sourceSurface->w;
+ // This angle offset will tell you exactly which portion of Cylindrical panorama
+ // (when you construct a rectangular mosaic out of it) you're projecting on the plane surface
+ uint16 angleOffset = static_cast<uint32>(angleT * panoWidth);
+
const bool isWidthOdd = ((w % 2) == 1);
uint16 halfWidthRoundedUp = (w + 1) / 2;
-
float halfWidthFloat = (float)w * 0.5f;
+ float verticalFovRadians = _decoder->_fov * M_PI / 180.0f;
+ float horizontalFovRadians = _decoder->_hfov * M_PI / 180.0f;
+ float tiltAngleRadians = _decoder->_tiltAngle * M_PI / 180.0f;
+
+ float warpMode = _decoder->_warpMode;
+
Common::Array<float> cylinderProjectionRanges;
Common::Array<float> cylinderAngleOffsets;
cylinderProjectionRanges.resize(halfWidthRoundedUp * 2);
cylinderAngleOffsets.resize(halfWidthRoundedUp);
- for (uint16 x = 0; x < halfWidthRoundedUp; x++) {
- float xFloat = (float)x;
+ if (warpMode == 0.0f) {
+ for (uint16 x = 0; x < halfWidthRoundedUp; x++) {
+ float xFloat = (float) x;
- // If width is odd, then the first column is on the pixel center
- // If width is even, then the first column is on the pixel boundary
- if (!isWidthOdd)
- xFloat += 0.5f;
+ if (!isWidthOdd) {
+ xFloat += 0.5f;
+ }
+
+ float normalizedX = (float)xFloat / (float)(w - 1);
+ cylinderAngleOffsets[x] = normalizedX * horizontalFovRadians / M_PI / 2.0f; // Scale to FOV
+ }
+ } else {
+ for (uint16 x = 0; x < halfWidthRoundedUp; x++) {
+ float xFloat = (float)x;
- float t = xFloat / halfWidthFloat;
- float xCoord = t * maxProjectedX;
+ // If width is odd, then the first column is on the pixel center
+ // If width is even, then the first column is on the pixel boundary
+ if (!isWidthOdd)
+ xFloat += 0.5f;
- float yCoords[2] = {minProjectedY, maxProjectedY};
+ float t = xFloat / halfWidthFloat;
+ float xCoord = t * maxProjectedX;
- // Compute projection ranges
- for (int v = 0; v < 2; v++) {
- // Intersect (xCoord, yCoord[v], 1) with a 1-radius cylinder
+ float yCoords[2] = {minProjectedY, maxProjectedY};
float length = sqrt(xCoord * xCoord + 1.0f);
- float newY = yCoords[v] / length;
+ // Compute projection ranges
+ for (int v = 0; v < 2; v++) {
+ // Intersect (xCoord, yCoord[v], 1) with a 1-radius cylinder
+ float newY = yCoords[v] / length;
+ cylinderProjectionRanges[x * 2 + v] = (newY - minTiltY) / (maxTiltY - minTiltY);
+ }
- cylinderProjectionRanges[x * 2 + v] = (newY - minTiltY) / (maxTiltY - minTiltY);
+ cylinderAngleOffsets[x] = atan(xCoord) * 0.5f / M_PI;
}
-
- cylinderAngleOffsets[x] = atan(xCoord) * 0.5f / M_PI;
}
- float angleT = fmod((360.0f - _decoder->_panAngle) / 360.0f, 1.0f);
- if (angleT < 0.0f)
- angleT += 1.0f;
-
- Graphics::Surface *srcSurf = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
-
- int32 panoWidth = srcSurf->h;
- int32 panoHeight = srcSurf->w;
-
- uint16 angleOffset = static_cast<uint32>(angleT * panoWidth);
-
- // Convert cylinder projection into planar projection
- for (uint16 projectionCol = 0; projectionCol < halfWidthRoundedUp; projectionCol++) {
+ for (uint16 x = 0; x < halfWidthRoundedUp; x++) {
int32 centerXImageCoord = static_cast<int32>(angleOffset);
+ int32 edgeCoordOffset = static_cast<int32>(cylinderAngleOffsets[x] * panoWidth);
- int32 edgeCoordOffset = static_cast<int32>(cylinderAngleOffsets[projectionCol] * panoWidth);
+ int32 leftSourceXCoord = centerXImageCoord - edgeCoordOffset;
+ int32 rightSourceXCoord = centerXImageCoord + edgeCoordOffset;
- int32 leftSrcCoord = centerXImageCoord - edgeCoordOffset;
- int32 rightSrcCoord = centerXImageCoord + edgeCoordOffset;
+ int32 topSrcCoord = 0, bottomSrcCoord = 0;
+ if (warpMode != 0) {
+ topSrcCoord = static_cast<int32>(cylinderProjectionRanges[x * 2 + 0] * panoHeight);
+ bottomSrcCoord = static_cast<int32>(cylinderProjectionRanges[x * 2 + 1] * panoHeight);
- int32 topSrcCoord = static_cast<int32>(cylinderProjectionRanges[projectionCol * 2 + 0] * panoHeight);
- int32 bottomSrcCoord = static_cast<int32>(cylinderProjectionRanges[projectionCol * 2 + 1] * panoHeight);
+ if (topSrcCoord < 0)
+ topSrcCoord = 0;
- if (topSrcCoord < 0)
- topSrcCoord = 0;
+ // Should never happen
+ else if (topSrcCoord >= panoHeight)
+ topSrcCoord = panoHeight - 1;
- // Should never happen
- if (topSrcCoord >= panoHeight)
- topSrcCoord = panoHeight - 1;
-
- if (bottomSrcCoord >= panoHeight)
- bottomSrcCoord = panoHeight - 1;
+ if (bottomSrcCoord >= panoHeight)
+ bottomSrcCoord = panoHeight - 1;
+ }
- // Should never happen
- if (bottomSrcCoord < 0)
- bottomSrcCoord = 0;
+ // Wrap around if out of range
+ leftSourceXCoord = leftSourceXCoord % static_cast<int32>(panoWidth);
+ if (leftSourceXCoord < 0)
+ leftSourceXCoord += panoWidth;
- leftSrcCoord = leftSrcCoord % static_cast<int32>(panoWidth);
- if (leftSrcCoord < 0)
- leftSrcCoord += panoWidth;
+ leftSourceXCoord = desc->_sceneSizeY - 1 - leftSourceXCoord;
- leftSrcCoord = desc->_sceneSizeY - 1 - leftSrcCoord;
+ rightSourceXCoord = rightSourceXCoord % static_cast<int32>(panoWidth);
+ if (rightSourceXCoord < 0)
+ rightSourceXCoord += panoWidth;
- rightSrcCoord = rightSrcCoord % static_cast<int32>(panoWidth);
- if (rightSrcCoord < 0)
- rightSrcCoord += w;
+ rightSourceXCoord = desc->_sceneSizeY - 1 - rightSourceXCoord;
+
+ uint16 x1 = halfWidthRoundedUp - 1 - x;
+ uint16 x2 = w - halfWidthRoundedUp + x;
- rightSrcCoord = desc->_sceneSizeY - 1 - rightSrcCoord;
+ for (uint16 y = 0; y < h; y++) {
+ int32 sourceYCoord;
- uint16 x1 = halfWidthRoundedUp - 1 - projectionCol;
- uint16 x2 = w - halfWidthRoundedUp + projectionCol;
+ if (warpMode == 0) {
+ float tiltFactor = tan(verticalFovRadians / 2.0f);
+ float normalizedY = ((float)y / (float)(h - 1)) * 2.0f - 1.0f;
+ float projectedY = (normalizedY * tiltFactor) - tan(tiltAngleRadians);
+ sourceYCoord = static_cast<int32>((projectedY + 1.0f) / 2.0f * panoHeight);
+ } else {
+ sourceYCoord = (2 * y + 1) * (bottomSrcCoord - topSrcCoord) / (2 * h) + topSrcCoord;
+ }
- for (uint16 y = 0; y < h; y++) {
- int32 sourceYCoord = (2 * y + 1) * (bottomSrcCoord - topSrcCoord) / (2 * h) + topSrcCoord;
+ if (sourceYCoord < 0)
+ sourceYCoord = 0;
+ if (sourceYCoord >= panoHeight)
+ sourceYCoord = panoHeight - 1;
- uint32 pixel1 = srcSurf->getPixel(sourceYCoord, leftSrcCoord);
- uint32 pixel2 = srcSurf->getPixel(sourceYCoord, rightSrcCoord);
+ // Our panorma is apparently vertical, that's why we're passing Y co-ord before X co-ord
+ uint32 pixel1 = sourceSurface->getPixel(sourceYCoord, leftSourceXCoord);
+ uint32 pixel2 = sourceSurface->getPixel(sourceYCoord, rightSourceXCoord);
if (_decoder->_renderHotspots) {
- const byte *col = &quickTimeDefaultPalette256[pixel1 * 3];
- pixel1 = _planarProjection->format.RGBToColor(col[0], col[1], col[2]);
- col = &quickTimeDefaultPalette256[pixel2 * 3];
- pixel2 = _planarProjection->format.RGBToColor(col[0], col[1], col[2]);
+ const byte *col1 = &quickTimeDefaultPalette256[pixel1 * 3];
+ pixel1 = _planarProjection->format.RGBToColor(col1[0], col1[1], col1[2]);
+ const byte *col2 = &quickTimeDefaultPalette256[pixel2 * 3];
+ pixel2 = _planarProjection->format.RGBToColor(col2[0], col2[1], col2[2]);
}
_planarProjection->setPixel(x1, y, pixel1);
@@ -913,20 +959,48 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
}
}
+ if (warpMode != 2) {
+ _projectedPano->copyFrom(*_planarProjection);
+ _dirty = false;
+ return;
+ }
+
+ // If warp mode is set to 2, also apply the perspective projection
+ Common::Array<float> sideEdgeXYInterpolators;
+
+ // The X interpolators are from 0 to maxProjectedX
+ // The Y interpolators are the interpolator from minProjectedY to maxProjectedY
+ sideEdgeXYInterpolators.resize(h * 2);
+
+ for (uint16 y = 0; y < h; y++) {
+ float t = ((float)y + 0.5f) / (float)h;
+
+ float vector[3];
+ for (int v = 0; v < 3; v++) {
+ vector[v] = cornerVectors[0][v] * (1.0f - t) + cornerVectors[1][v] * t;
+ }
+
+ float projectedX = vector[0] / vector[2];
+ float projectedY = vector[1] / vector[2];
+
+ sideEdgeXYInterpolators[y * 2 + 0] = projectedX / maxProjectedX;
+ sideEdgeXYInterpolators[y * 2 + 1] = (projectedY - minProjectedY) / (maxProjectedY - minProjectedY);
+ }
+
// Convert planar projection into perspective projection
for (uint16 y = 0; y < h; y++) {
float xInterpolator = sideEdgeXYInterpolators[y * 2 + 0];
float yInterpolator = sideEdgeXYInterpolators[y * 2 + 1];
int32 srcY = static_cast<int32>(yInterpolator * (float)h);
- int32 scanlineWidth = static_cast<int32>(xInterpolator * w) / 2 * 2;
+ int32 scanlineWidth = static_cast<int32>(xInterpolator * w);
int32 startX = (w - scanlineWidth) / 2;
//int32 endX = startX + scanlineWidth;
// It would be better to compute a reciprocal and do this as a multiply instead,
// doing divides per pixel is SLOW!
for (uint16 x = 0; x < w; x++) {
- int32 srcX = (2 * x + 1) * scanlineWidth / (2 * w) + startX;
+ int32 srcX = (x + 0.5f) * xInterpolator + startX;
uint32 pixel = _planarProjection->getPixel(srcX, srcY);
_projectedPano->setPixel(x, y, pixel);
}
Commit: 28257c7ae520cc364c3b10c16c49f6c39abb752f
https://github.com/scummvm/scummvm/commit/28257c7ae520cc364c3b10c16c49f6c39abb752f
Author: Malhar (themalharbdv2046 at gmail.com)
Date: 2025-04-26T19:36:15+08:00
Commit Message:
VIDEO: QTVR: Implement the higher quality modes
Add the quality mode stubbed functionality using the
as described in the original QTVR Xtra documentation
provided by Apple.
Add two new functions:
upscalePanorama(): Upscales the original panorama movie
to 2x width or 2x width and 2x height, these upscaled
panorama are named (_1D/_2D)UpscaledConstructedPano and
used for projecting in quality mode 2.0f and 4.0f
boxAverage(): The panorama is rendered with 2x or 3x the
target size and then averaged to target size by averaging
in 2x2 or 3x3 box to apply anti-aliasing
Set three quality modes in the QTVR decoder
- Mode 1.0f: No quality upscaling, already present,
low quality
- Mode 2.0f: 1 dimensional upscaling and 2 times scaled
rendering followed by 2x2 box average, medium quality
- Mode 4.0f: 2 dimensinoal upscaling and 3 times scaled
rendering followed by 3x3 box average, high quality
- Mode 0: Dynamic quality mode, change between low
quality and high quality based on whether the user is
actively interacting with the panorama
Changed paths:
video/qt_decoder.h
video/qtvr_decoder.cpp
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 755d7710dcb..23a08fdb572 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -410,12 +410,17 @@ private:
QuickTimeDecoder *_decoder;
Common::QuickTimeParser::Track *_parent;
- void projectPanorama();
+ void projectPanorama(int scaleFactor, float fov, float hfov, float panAngle, float tiltAngle);
+ void swingTransitionHandler();
+ void boxAverage(Graphics::Surface *sourceSurface, int scaleFactor);
+ Graphics::Surface* upscalePanorama(Graphics::Surface *sourceSurface, int8 level);
const Graphics::Surface *bufferNextFrame();
public:
Graphics::Surface *_constructedPano;
+ Graphics::Surface *_1DUpscaledConstructedPano;
+ Graphics::Surface *_2DUpscaledConstructedPano;
Graphics::Surface *_constructedHotspots;
Graphics::Surface *_projectedPano;
Graphics::Surface *_planarProjection;
diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index 907e3172629..3f05fe91cd4 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -143,9 +143,7 @@ void QuickTimeDecoder::renderHotspots(bool mode) {
}
void QuickTimeDecoder::setQuality(float quality) {
- _quality = quality;
-
- warning("STUB: Quality mode set to %f", quality);
+ _quality = CLIP<float>(quality, 0.0f, 4.0f);
// 4.0 Highest quality rendering. The rendered image is fully anti-aliased.
//
@@ -158,7 +156,8 @@ void QuickTimeDecoder::setQuality(float quality) {
// 4.0 during idle time when the user stops panning or zooming.
// All other updates are rendered at quality 4.0.
- ((PanoTrackHandler *)getTrack(_panoTrack->targetTrack))->setDirty();
+ PanoTrackHandler *track = ((PanoTrackHandler *)getTrack(_panoTrack->targetTrack));
+ track->setDirty();
}
void QuickTimeDecoder::setWarpMode(int warpMode) {
@@ -484,6 +483,8 @@ QuickTimeDecoder::PanoTrackHandler::PanoTrackHandler(QuickTimeDecoder *decoder,
_constructedPano = nullptr;
_constructedHotspots = nullptr;
+ _1DUpscaledConstructedPano = nullptr;
+ _2DUpscaledConstructedPano = nullptr;
_projectedPano = nullptr;
_planarProjection = nullptr;
@@ -497,6 +498,12 @@ QuickTimeDecoder::PanoTrackHandler::~PanoTrackHandler() {
_constructedPano->free();
delete _constructedPano;
+ _1DUpscaledConstructedPano->free();
+ delete _1DUpscaledConstructedPano;
+
+ _2DUpscaledConstructedPano->free();
+ delete _2DUpscaledConstructedPano;
+
_constructedHotspots->free();
delete _constructedHotspots;
}
@@ -537,8 +544,39 @@ const Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::decodeNextFrame() {
if (!_isPanoConstructed)
return nullptr;
- if (_dirty)
- projectPanorama();
+ if (_dirty) {
+ float quality = _decoder->getQuality();
+ float fov = _decoder->_fov;
+ float hfov = _decoder->_hfov;
+ float tiltAngle = _decoder->_tiltAngle;
+ float panAngle = _decoder->_panAngle;
+
+ switch ((int) quality) {
+ case 1:
+ projectPanorama(1, fov, hfov, tiltAngle, panAngle);
+ break;
+
+ case 2:
+ projectPanorama(2, fov, hfov, tiltAngle, panAngle);
+ break;
+
+ case 4:
+ projectPanorama(3, fov, hfov, tiltAngle, panAngle);
+ break;
+
+ case 0:
+ if (_decoder->_isMouseButtonDown || _decoder->_isKeyDown) {
+ projectPanorama(1, fov, hfov, tiltAngle, panAngle);
+ } else {
+ projectPanorama(3, fov, hfov, tiltAngle, panAngle);
+ }
+ break;
+
+ default:
+ projectPanorama(3, fov, hfov, tiltAngle, panAngle);
+ break;
+ }
+ }
return _projectedPano;
}
@@ -595,6 +633,145 @@ void QuickTimeDecoder::PanoTrackHandler::initPanorama() {
_decoder->goToNode(_decoder->_panoTrack->panoInfo.defNodeID);
}
+Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::upscalePanorama(Graphics::Surface *sourceSurface, int8 level) {
+ // This algorithm (bilinear upscaling) takes quite some time to complete
+ // PanoTrackHandler is larger by two very large images
+ // This may have consequences on the memory usage
+
+ // The original director has three quality levels (plus one dynamic quality mode)
+ // In quality mode = 1.0f, the _constructedPano is left as it is, no upscaling
+ // In quality mode = 2.0f, the _constructedPano is upscaled in one dimension (twice the width; same height)
+ // In quality mode = 4.0f, the _constructedPano is upscaled in two dimensions (twice the width; twice the height)
+
+ // upscaling method
+ //
+ // for level 1 upscaling
+ // a | b a | avg(a, b) | b
+ // ----- => -----------------
+ // c | d c | avg(c, d) | d
+ //
+ // for level 2 upscaling
+ // a | avg(a, b) | b
+ // a | b -----------------------------------------
+ // ----- => avg(a, c) [e] | avg(e, f) | avg(b, d) [f]
+ // c | d -----------------------------------------
+ // c | avg(c, d) | d
+
+ uint w = sourceSurface->w;
+ uint h = sourceSurface->h;
+
+ Graphics::Surface *target = new Graphics::Surface();
+
+ if (level == 2) {
+ target->create(w * 2, h * 2, sourceSurface->format);
+
+ for (uint y = 0; y < h; ++y) {
+ for (uint x = 0; x < w; ++x) {
+ // For the row x==w and column y==h, wrap around the panorama
+ // and average these pixels with row x==0 and column y==0 respectively
+ uint x1 = (x + 1) % w;
+ uint y1 = (y + 1) % h;
+
+ // Couldn't find a better way to do this
+ // Should I write a function in the Surface or PixelFormat class?
+ int32 p00 = sourceSurface->getPixel(x, y);
+ int32 p01 = sourceSurface->getPixel(x, y1);
+ int32 p10 = sourceSurface->getPixel(x1, y);
+ int32 p11 = sourceSurface->getPixel(x1, y1);
+
+ uint8 a00, r00, g00, b00;
+ uint8 a01, r01, g01, b01;
+ uint8 a10, r10, g10, b10;
+ uint8 a11, r11, g11, b11;
+
+ sourceSurface->format.colorToARGB(p00, a00, r00, g00, b00);
+ sourceSurface->format.colorToARGB(p01, a01, r01, g01, b01);
+ sourceSurface->format.colorToARGB(p10, a10, r10, g10, b10);
+ sourceSurface->format.colorToARGB(p11, a11, r11, g11, b11);
+
+ int32 avgPixel00 = p00;
+ int32 avgPixel01 = sourceSurface->format.ARGBToColor((a00 + a01) >> 1, (r00 + r01) >> 1, (g00 + g01) >> 1, (b00 + b01) >> 1);
+ int32 avgPixel10 = sourceSurface->format.ARGBToColor((a00 + a10) >> 1, (r00 + r10) >> 1, (g00 + g10) >> 1, (b00 + b10) >> 1);
+ int32 avgPixel11 = sourceSurface->format.ARGBToColor(
+ (a00 + a01 + a10 + a11) >> 2,
+ (r00 + r01 + r10 + r11) >> 2,
+ (g00 + g01 + g10 + g11) >> 2,
+ (b00 + b01 + b10 + b11) >> 2);
+
+ target->setPixel(x * 2, y * 2, avgPixel00);
+ target->setPixel(x * 2, y * 2 + 1, avgPixel01);
+ target->setPixel(x * 2 + 1, y * 2, avgPixel10);
+ target->setPixel(x * 2 + 1, y * 2 + 1,avgPixel11);
+ }
+ }
+ } else {
+ target->create(w * 2, h, sourceSurface->format);
+
+ for (uint y = 0; y < h; ++y) {
+ for (uint x = 0; x < w; ++x) {
+ uint x1 = (x + 1) % w;
+
+ int32 p00 = sourceSurface->getPixel(x, y);
+ int32 p01 = sourceSurface->getPixel(x1, y);
+
+ uint8 a00, r00, g00, b00;
+ uint8 a01, r01, g01, b01;
+ sourceSurface->format.colorToARGB(p00, a00, r00, g00, b00);
+ sourceSurface->format.colorToARGB(p01, a01, r01, g01, b01);
+
+ int32 avgPixel00 = p00;
+ int32 avgPixel01 = sourceSurface->format.ARGBToColor((a00 + a01) >> 1, (r00 + r01) >> 1, (g00 + g01) >> 1, (b00 + b01) >> 1);
+
+ target->setPixel(x * 2, y, avgPixel00);
+ target->setPixel(x * 2 + 1, y, avgPixel01);
+ }
+ }
+ }
+
+ return target;
+}
+
+void QuickTimeDecoder::PanoTrackHandler::boxAverage(Graphics::Surface *sourceSurface, int scaleFactor) {
+ uint16 h = sourceSurface->h;
+ uint16 w = sourceSurface->w;
+
+ // Apply anti-aliasing if quality is higher than 1
+ // Otherwise it'll be quicker to just copy the _planarProjection onto _projectedPano
+ if (scaleFactor == 1) {
+ _projectedPano->copyFrom(*sourceSurface);
+ return;
+ }
+
+ // Average out the pixels from the larger image
+ for (uint16 y = 0; y < h / scaleFactor; y++) {
+ for (uint16 x = 0; x < w / scaleFactor; x++) {
+ uint16 avgA = 0, avgR = 0, avgG = 0, avgB = 0;
+
+ for (uint8 row = 0; row < scaleFactor; row++) {
+ for (uint8 column = 0; column < scaleFactor; column++) {
+ uint8 a00, r00, g00, b00;
+ uint32 pixel00 = sourceSurface->getPixel(MIN((x * scaleFactor + row), w - 1), MIN((y * scaleFactor + column), h - 1));
+ _projectedPano->format.colorToARGB(pixel00, a00, r00, g00, b00);
+
+ avgA += a00;
+ avgR += r00;
+ avgG += g00;
+ avgB += b00;
+ }
+ }
+
+ uint8 scaleSquare = scaleFactor * scaleFactor;
+ avgA /= scaleSquare;
+ avgR /= scaleSquare;
+ avgG /= scaleSquare;
+ avgB /= scaleSquare;
+
+ uint32 avgPixel = _projectedPano->format.ARGBToColor(avgA, avgR, avgG, avgB);
+ _projectedPano->setPixel(x, y, avgPixel);
+ }
+ }
+}
+
void QuickTimeDecoder::PanoTrackHandler::constructPanorama() {
PanoSampleDesc *desc = (PanoSampleDesc *)_parent->sampleDescs[0];
PanoTrackSample *sample = &_parent->panoSamples[_decoder->_currentSample];
@@ -635,6 +812,8 @@ void QuickTimeDecoder::PanoTrackHandler::constructPanorama() {
track->seek(Audio::Timestamp(0, timestamp, _decoder->_timeScale));
_constructedPano = constructMosaic(track, desc->_sceneNumFramesX, desc->_sceneNumFramesY, "dumps/pano-full.png");
+ _1DUpscaledConstructedPano = upscalePanorama(_constructedPano, 1);
+ _2DUpscaledConstructedPano = upscalePanorama(_constructedPano, 2);
track = (VideoTrackHandler *)(_decoder->getTrack(_decoder->Common::QuickTimeParser::_tracks[desc->_hotSpotTrackID - 1]->targetTrack));
@@ -756,19 +935,28 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
return Common::Point(hotX, hotY);
}
-void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
+void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
+ float fov,
+ float hfov,
+ float tiltAngle,
+ float panAngle) {
if (!_isPanoConstructed)
return;
- uint16 w = _decoder->getWidth(), h = _decoder->getHeight();
+ uint16 w = _decoder->getWidth() * scaleFactor, h = _decoder->getHeight() * scaleFactor;
if (!_projectedPano) {
if (w == 0 || h == 0)
error("QuickTimeDecoder::PanoTrackHandler::projectPanorama(): setTargetSize() was not called");
_projectedPano = new Graphics::Surface();
- _projectedPano->create(w, h, _constructedPano->format);
+ _projectedPano->create(w / scaleFactor, h / scaleFactor, _constructedPano->format);
+ }
+ // The size of _planarProjection will keep changing in quality mode 0
+ // It might (will definitely) cause some out of bound access if left as it is
+ if (!_planarProjection || _planarProjection->w != w || _planarProjection->h != h) {
+ delete _planarProjection;
_planarProjection = new Graphics::Surface();
_planarProjection->create(w, h, _constructedPano->format);
}
@@ -780,7 +968,9 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
float *topRightVector = cornerVectors[0];
float *bottomRightVector = cornerVectors[1];
- bottomRightVector[1] = tan(_decoder->_fov * M_PI / 360.0);
+ // tangent of half of fov angle, is the bottom edge point
+ // the negative of that is the top edge point
+ bottomRightVector[1] = tan(fov * M_PI / 360.0);
bottomRightVector[0] = bottomRightVector[1] * (float)w / (float)h;
bottomRightVector[2] = 1.0f;
@@ -789,8 +979,8 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
topRightVector[2] = bottomRightVector[2];
// Apply pitch (tilt) rotation
- float cosTilt = cos(-_decoder->_tiltAngle * M_PI / 180.0);
- float sinTilt = sin(-_decoder->_tiltAngle * M_PI / 180.0);
+ float cosTilt = cos(-tiltAngle * M_PI / 180.0);
+ float sinTilt = sin(-tiltAngle * M_PI / 180.0);
// Using trigonometry to account for the tilt
for (int v = 0; v < 2; v++) {
@@ -825,7 +1015,21 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
angleT += 1.0f;
}
- Graphics::Surface *sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
+ Graphics::Surface *sourceSurface;
+ switch(scaleFactor) {
+ case 1:
+ sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
+ break;
+ case 2:
+ sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _1DUpscaledConstructedPano;
+ break;
+ case 3:
+ sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _2DUpscaledConstructedPano;
+ break;
+ default:
+ sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
+ break;
+ }
int32 panoWidth = sourceSurface->h;
int32 panoHeight = sourceSurface->w;
@@ -910,18 +1114,24 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
bottomSrcCoord = panoHeight - 1;
}
+ // The descriptor has the original sceneSizeX and sceneSizeY of the movie
+ // But in quality mode 3 we're increasing them to twice the original
+ // This factor just make sure we're wrapping leftSourceXCoord and rightSourceXCoord
+ // According to the sourceSurface's dimensions rather than the original's
+ int factor = scaleFactor == 3 && !_decoder->_renderHotspots ? 2 : 1;
+
// Wrap around if out of range
leftSourceXCoord = leftSourceXCoord % static_cast<int32>(panoWidth);
if (leftSourceXCoord < 0)
leftSourceXCoord += panoWidth;
- leftSourceXCoord = desc->_sceneSizeY - 1 - leftSourceXCoord;
+ leftSourceXCoord = desc->_sceneSizeY * factor - 1 - leftSourceXCoord;
rightSourceXCoord = rightSourceXCoord % static_cast<int32>(panoWidth);
if (rightSourceXCoord < 0)
rightSourceXCoord += panoWidth;
- rightSourceXCoord = desc->_sceneSizeY - 1 - rightSourceXCoord;
+ rightSourceXCoord = desc->_sceneSizeY * factor - 1 - rightSourceXCoord;
uint16 x1 = halfWidthRoundedUp - 1 - x;
uint16 x2 = w - halfWidthRoundedUp + x;
@@ -960,7 +1170,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
}
if (warpMode != 2) {
- _projectedPano->copyFrom(*_planarProjection);
+ boxAverage(_planarProjection, scaleFactor);
_dirty = false;
return;
}
@@ -987,6 +1197,9 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
sideEdgeXYInterpolators[y * 2 + 1] = (projectedY - minProjectedY) / (maxProjectedY - minProjectedY);
}
+ Graphics::Surface *aliasedProjectedPano = new Graphics::Surface();
+ aliasedProjectedPano->create(w, h, _planarProjection->format);
+
// Convert planar projection into perspective projection
for (uint16 y = 0; y < h; y++) {
float xInterpolator = sideEdgeXYInterpolators[y * 2 + 0];
@@ -1002,10 +1215,17 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
for (uint16 x = 0; x < w; x++) {
int32 srcX = (x + 0.5f) * xInterpolator + startX;
uint32 pixel = _planarProjection->getPixel(srcX, srcY);
- _projectedPano->setPixel(x, y, pixel);
+
+ // I increased the size of the surface (made it twice as wide and twice as tall)
+ // Now I'm just getting the perspective projected pixel and creating a 2x2 box
+ aliasedProjectedPano->setPixel(x, y, pixel);
}
}
+ boxAverage(aliasedProjectedPano, scaleFactor);
+ // Memory leakage avoided; Should I use a stack allocated object?
+ delete aliasedProjectedPano;
+
_dirty = false;
}
@@ -1204,6 +1424,8 @@ void QuickTimeDecoder::handleObjectMouseButton(bool isDown, int16 x, int16 y, bo
}
_isMouseButtonDown = isDown;
+ PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
+ track->setDirty();
}
void QuickTimeDecoder::handlePanoMouseButton(bool isDown, int16 x, int16 y, bool repeat) {
@@ -1238,6 +1460,13 @@ void QuickTimeDecoder::handlePanoMouseButton(bool isDown, int16 x, int16 y, bool
}
}
+ // This line is necessary to let the decodeNextFrame() function know that the _isMouseButtonDown
+ // variable has been updated, otherwise, it won't call projectPanorama() function
+ if (!isDown) {
+ PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
+ track->setDirty();
+ }
+
// Further we have simulated mouse button which are generated by timer, e.g. those
// are used for dragging
if (!repeat)
@@ -1306,6 +1535,9 @@ void QuickTimeDecoder::handleObjectKey(Common::KeyState &state, bool down, bool
}
void QuickTimeDecoder::handlePanoKey(Common::KeyState &state, bool down, bool repeat) {
+ PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
+ track->setDirty();
+
if ((state.flags & Common::KBD_SHIFT) && (state.flags & Common::KBD_CTRL)) {
_zoomState = kZoomQuestion;
} else if (state.flags & Common::KBD_SHIFT) {
Commit: a6f3c04218e3b09f0060b01f0436e7bfc595c485
https://github.com/scummvm/scummvm/commit/a6f3c04218e3b09f0060b01f0436e7bfc595c485
Author: Malhar (themalharbdv2046 at gmail.com)
Date: 2025-04-26T19:36:15+08:00
Commit Message:
VIDEO: QTVR: Fix distortion bug while zooming out
There is a minor bug in QTVR decoder when zooming out when
Tilt Angle != 0, in warp mode 2, it causes distorion of the
panorama, in warp mode 1 and 0, it causes panorama image to
go out of bounds and the upper/lower (depending on whether
tilt angle is positive or negative) part of the image is
invalid
Check for out of bound values of tilt angle and pan angle
when setFOV() function is called during zooming in or out
which fixes the bug
Changed paths:
video/qtvr_decoder.cpp
diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index 3f05fe91cd4..e4249bfbb64 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -348,12 +348,18 @@ bool QuickTimeDecoder::setFOV(float fov) {
}
if (_fov != fov) {
- _fov = fov;
-
- _hfov = _fov * (float)_width / (float)_height;
-
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
- track->setDirty();
+
+ _fov = fov;
+
+ _hfov = _fov * (float)_width / (float)_height;
+
+ // We need to recalculate the pan angle and tilt angle to see if it has went
+ // out of bound for the current value of FOV
+ // This solves the distortion that we got sometimes when we zoom out at Tilt Angle != 0
+ setPanAngle(_panAngle);
+ setTiltAngle(_tiltAngle);
+ track->setDirty();
}
return success;
Commit: 770feb4a230c064ccab3e504b93f488024b42100
https://github.com/scummvm/scummvm/commit/770feb4a230c064ccab3e504b93f488024b42100
Author: Malhar (themalharbdv2046 at gmail.com)
Date: 2025-04-26T19:36:15+08:00
Commit Message:
VIDEO: QTVR: Implement the Swing Transition functionality
Add the swing Transition functionality using the transitionMode
variable as described in the original QTVR Xtra documentation
provided by Apple.
In the swing transition mode, make the video decoder internally
handle the animation of "swinging" panorama video from the
current position (described by _fov, _tiltAngle, _panAngle)
to the new position while taking the shortest path
Changed paths:
video/qt_decoder.h
video/qtvr_decoder.cpp
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 23a08fdb572..9928af02618 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -83,6 +83,7 @@ public:
// QTVR stuff
////////////////
void setTargetSize(uint16 w, uint16 h);
+ void setOrigin(int left, int top) { _origin = Common::Point(left, top); }
void handleMouseMove(int16 x, int16 y);
void handleMouseButton(bool isDown, int16 x = -1, int16 y = -1, bool repeat = false);
@@ -183,6 +184,11 @@ private:
void computeInteractivityZones();
uint16 _width, _height;
+ // _origin is the top left corner point of the panorama video being played
+ // by director engine or whichever engine is using QTVR decoder currently
+ // decoder handles swing transitions (in QTVR xtra) internally
+ // Hence, it needs to know where to blit the projected panorama during transition
+ Common::Point _origin;
public:
int _currentSample = -1;
@@ -425,6 +431,14 @@ private:
Graphics::Surface *_projectedPano;
Graphics::Surface *_planarProjection;
+ // Defining these to make the swing transition happen
+ // which requires storing the previous point during every change in FOV, Pan Angle and Tilt Angle
+ // If swing transition is called, this will be the start point of the transition
+ float _currentFOV = 0;
+ float _currentHFOV = 0;
+ float _currentPanAngle = 0;
+ float _currentTiltAngle = 0;
+
private:
bool _isPanoConstructed;
bool _dirty;
diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index e4249bfbb64..03d86c04a40 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -35,6 +35,7 @@
#include "common/archive.h"
#include "common/config-manager.h"
#include "common/debug.h"
+#include "common/events.h"
#include "common/file.h"
#include "common/keyboard.h"
#include "common/memstream.h"
@@ -181,8 +182,6 @@ void QuickTimeDecoder::setTransitionMode(Common::String mode) {
else
_transitionMode = kTransitionModeNormal;
- warning("STUB: Transition mode set to '%s'", getTransitionMode().c_str());
-
// normal The new view is imaged and displayed. No transition effect is
// used. The user sees a "cut" from the current view to the new
// view.
@@ -199,8 +198,6 @@ void QuickTimeDecoder::setTransitionMode(Common::String mode) {
void QuickTimeDecoder::setTransitionSpeed(float speed) {
_transitionSpeed = speed;
- warning("STUB: Transition Speed set to %f", speed);
-
// The TransitionSpeed is a floating point quantity that provides the slowest swing transition
// at 1.0 and faster transitions at higher values. On mid-range computers, a rate of 4.0
// performs well off CD-ROM.
@@ -306,10 +303,11 @@ void QuickTimeDecoder::setPanAngle(float angle) {
}
if (_panAngle != angle) {
- _panAngle = angle;
-
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
- track->setDirty();
+
+ track->_currentPanAngle = _panAngle;
+ _panAngle = angle;
+ track->setDirty();
}
}
@@ -323,10 +321,11 @@ void QuickTimeDecoder::setTiltAngle(float angle) {
angle = desc->_vPanTop - _fov / 2;
if (_tiltAngle != angle) {
- _tiltAngle = angle;
-
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
- track->setDirty();
+
+ track->_currentTiltAngle = _tiltAngle;
+ _tiltAngle = angle;
+ track->setDirty();
}
}
@@ -334,9 +333,6 @@ bool QuickTimeDecoder::setFOV(float fov) {
PanoSampleDesc *desc = (PanoSampleDesc *)_panoTrack->sampleDescs[0];
bool success = true;
- if (fov == 0.0f) // No change
- return true;
-
if (fov <= desc->_minimumZoom) {
fov = desc->_minimumZoom;
success = false;
@@ -350,10 +346,12 @@ bool QuickTimeDecoder::setFOV(float fov) {
if (_fov != fov) {
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
+ track->_currentFOV = _fov;
_fov = fov;
+ track->_currentHFOV = _hfov;
_hfov = _fov * (float)_width / (float)_height;
-
+
// We need to recalculate the pan angle and tilt angle to see if it has went
// out of bound for the current value of FOV
// This solves the distortion that we got sometimes when we zoom out at Tilt Angle != 0
@@ -546,11 +544,104 @@ Common::Rational QuickTimeDecoder::PanoTrackHandler::getScaledHeight() const {
return Common::Rational(_parent->height) / _parent->scaleFactorY;
}
+void QuickTimeDecoder::PanoTrackHandler::swingTransitionHandler() {
+ // The number of steps (the speed) of the transition is decided by the _transitionSpeed
+ const int NUM_STEPS = 300 / _decoder->_transitionSpeed;
+
+ // Calculate the step
+ float stepFOV = (_decoder->_fov - _currentFOV) / NUM_STEPS;
+ float stepHFOV = (_decoder->_hfov - _currentHFOV) / NUM_STEPS;
+
+ float stepTiltAngle = (_decoder->_tiltAngle - _currentTiltAngle) / NUM_STEPS;
+
+ float targetPanAngle = _decoder->_panAngle;
+ float difference = ABS(_currentPanAngle - targetPanAngle);
+ float stepPanAngle;
+
+ // All of this is just because the panorama is supposed to take the smallest path
+ if (targetPanAngle > _currentPanAngle) {
+ if (difference <= 180) {
+ stepPanAngle = difference / NUM_STEPS;
+ } else {
+ stepPanAngle = (- (360 - difference)) / NUM_STEPS;
+ }
+ } else {
+ if (difference <= 180) {
+ stepPanAngle = (- difference) / NUM_STEPS;
+ } else {
+ stepPanAngle = (360 - difference) / NUM_STEPS;
+ }
+ }
+
+ int16 rectLeft = _decoder->_origin.x;
+ int16 rectRight = _decoder->_origin.y;
+ Common::Point mouse;
+ int mw = _decoder->_width, mh = _decoder->_height;
+
+ for (int i = 0; i < NUM_STEPS; i++) {
+ projectPanorama(1,
+ _currentFOV + i * stepFOV,
+ _currentHFOV + i * stepHFOV,
+ _currentTiltAngle + i * stepTiltAngle,
+ _currentPanAngle + i * stepPanAngle);
+
+ g_system->copyRectToScreen(_projectedPano->getPixels(),
+ _projectedPano->pitch,
+ rectLeft,
+ rectRight,
+ _projectedPano->w,
+ _projectedPano->h);
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT) {
+ debugC(1, kDebugLevelGVideo, "EVENT_QUIT passed during the Swing Transition.");
+ g_system->quit();
+ return;
+ }
+
+ if (Common::isMouseEvent(event)) {
+ mouse = event.mouse;
+ }
+
+ // Referenced from video.cpp in testbed engine
+ if (mouse.x >= _decoder->_prevMouse.x &&
+ mouse.x < _decoder->_prevMouse.x + mw &&
+ mouse.y >= _decoder->_prevMouse.y &&
+ mouse.y < _decoder->_prevMouse.y + mh) {
+
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
+ _decoder->handleMouseMove(event.mouse.x - _decoder->_prevMouse.x, event.mouse.y - _decoder->_prevMouse.y);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ g_system->updateScreen();
+ g_system->delayMillis(10);
+ }
+
+ // This is so if we call swingTransitionHandler() twice
+ // The second time there will be no transition
+ _currentFOV = _decoder->_fov;
+ _currentPanAngle = _decoder->_panAngle;
+ _currentHFOV = _decoder->_hfov;
+ _currentTiltAngle = _decoder->_tiltAngle;
+
+ // Due to floating point errors, we may end a few degrees here and there
+ // Make sure we reach the destination at the end of this loop
+ // Also we have to go back to our original quality
+ projectPanorama(3, _decoder->_fov, _decoder->_hfov, _decoder->_panAngle, _decoder->_panAngle);
+}
+
const Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::decodeNextFrame() {
if (!_isPanoConstructed)
return nullptr;
- if (_dirty) {
+ if (_dirty && _decoder->_transitionMode == kTransitionModeNormal) {
float quality = _decoder->getQuality();
float fov = _decoder->_fov;
float hfov = _decoder->_hfov;
@@ -582,6 +673,8 @@ const Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::decodeNextFrame() {
projectPanorama(3, fov, hfov, tiltAngle, panAngle);
break;
}
+ } else if (_decoder->_transitionMode == kTransitionModeSwing) {
+ swingTransitionHandler();
}
return _projectedPano;
Commit: 8615695c7c4b4721bb13dcbd87fcfe9d3b95a141
https://github.com/scummvm/scummvm/commit/8615695c7c4b4721bb13dcbd87fcfe9d3b95a141
Author: Malhar (themalharbdv2046 at gmail.com)
Date: 2025-04-26T19:36:15+08:00
Commit Message:
VIDEO: QTVR: Minor Fixes and Formatting
Fix a minor logical error in swing transition function
Remove Unnecessary whitespace and formatting
Changed paths:
video/qt_decoder.h
video/qtvr_decoder.cpp
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index 9928af02618..bfd078b9533 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -438,7 +438,7 @@ private:
float _currentHFOV = 0;
float _currentPanAngle = 0;
float _currentTiltAngle = 0;
-
+
private:
bool _isPanoConstructed;
bool _dirty;
diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index 03d86c04a40..1af75f18643 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -177,10 +177,11 @@ void QuickTimeDecoder::setWarpMode(int warpMode) {
}
void QuickTimeDecoder::setTransitionMode(Common::String mode) {
- if (mode.equalsIgnoreCase("swing"))
+ if (mode.equalsIgnoreCase("swing")) {
_transitionMode = kTransitionModeSwing;
- else
+ } else {
_transitionMode = kTransitionModeNormal;
+ }
// normal The new view is imaged and displayed. No transition effect is
// used. The user sees a "cut" from the current view to the new
@@ -295,37 +296,37 @@ void QuickTimeDecoder::setPanAngle(float angle) {
PanoSampleDesc *desc = (PanoSampleDesc *)_panoTrack->sampleDescs[0];
if (desc->_hPanStart != desc->_hPanStart && (desc->_hPanStart != 0.0 || desc->_hPanStart != 360.0)) {
- if (angle < desc->_hPanStart)
+ if (angle < desc->_hPanStart) {
angle = desc->_hPanStart + _hfov / 2;
-
- if (angle > desc->_hPanEnd - _hfov / 2)
+ } else if (angle > desc->_hPanEnd - _hfov / 2) {
angle = desc->_hPanStart - _hfov / 2;
+ }
}
if (_panAngle != angle) {
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
-
+
track->_currentPanAngle = _panAngle;
_panAngle = angle;
- track->setDirty();
+ track->setDirty();
}
}
void QuickTimeDecoder::setTiltAngle(float angle) {
PanoSampleDesc *desc = (PanoSampleDesc *)_panoTrack->sampleDescs[0];
- if (angle < desc->_vPanBottom + _fov / 2)
+ if (angle < desc->_vPanBottom + _fov / 2) {
angle = desc->_vPanBottom + _fov / 2;
-
- if (angle > desc->_vPanTop - _fov / 2)
+ } else if (angle > desc->_vPanTop - _fov / 2) {
angle = desc->_vPanTop - _fov / 2;
+ }
if (_tiltAngle != angle) {
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
-
+
track->_currentTiltAngle = _tiltAngle;
_tiltAngle = angle;
- track->setDirty();
+ track->setDirty();
}
}
@@ -336,9 +337,7 @@ bool QuickTimeDecoder::setFOV(float fov) {
if (fov <= desc->_minimumZoom) {
fov = desc->_minimumZoom;
success = false;
- }
-
- if (fov >= desc->_maximumZoom) {
+ } else if (fov >= desc->_maximumZoom) {
fov = desc->_maximumZoom;
success = false;
}
@@ -347,17 +346,17 @@ bool QuickTimeDecoder::setFOV(float fov) {
PanoTrackHandler *track = (PanoTrackHandler *)getTrack(_panoTrack->targetTrack);
track->_currentFOV = _fov;
- _fov = fov;
-
+ _fov = fov;
+
track->_currentHFOV = _hfov;
- _hfov = _fov * (float)_width / (float)_height;
+ _hfov = _fov * (float)_width / (float)_height;
// We need to recalculate the pan angle and tilt angle to see if it has went
// out of bound for the current value of FOV
// This solves the distortion that we got sometimes when we zoom out at Tilt Angle != 0
setPanAngle(_panAngle);
setTiltAngle(_tiltAngle);
- track->setDirty();
+ track->setDirty();
}
return success;
@@ -548,7 +547,7 @@ void QuickTimeDecoder::PanoTrackHandler::swingTransitionHandler() {
// The number of steps (the speed) of the transition is decided by the _transitionSpeed
const int NUM_STEPS = 300 / _decoder->_transitionSpeed;
- // Calculate the step
+ // Calculate the step
float stepFOV = (_decoder->_fov - _currentFOV) / NUM_STEPS;
float stepHFOV = (_decoder->_hfov - _currentHFOV) / NUM_STEPS;
@@ -563,7 +562,7 @@ void QuickTimeDecoder::PanoTrackHandler::swingTransitionHandler() {
if (difference <= 180) {
stepPanAngle = difference / NUM_STEPS;
} else {
- stepPanAngle = (- (360 - difference)) / NUM_STEPS;
+ stepPanAngle = (- (360 - difference)) / NUM_STEPS;
}
} else {
if (difference <= 180) {
@@ -580,19 +579,19 @@ void QuickTimeDecoder::PanoTrackHandler::swingTransitionHandler() {
for (int i = 0; i < NUM_STEPS; i++) {
projectPanorama(1,
- _currentFOV + i * stepFOV,
- _currentHFOV + i * stepHFOV,
- _currentTiltAngle + i * stepTiltAngle,
+ _currentFOV + i * stepFOV,
+ _currentHFOV + i * stepHFOV,
+ _currentTiltAngle + i * stepTiltAngle,
_currentPanAngle + i * stepPanAngle);
- g_system->copyRectToScreen(_projectedPano->getPixels(),
+ g_system->copyRectToScreen(_projectedPano->getPixels(),
_projectedPano->pitch,
- rectLeft,
+ rectLeft,
rectRight,
_projectedPano->w,
_projectedPano->h);
- Common::Event event;
+ Common::Event event;
while (g_system->getEventManager()->pollEvent(event)) {
if (event.type == Common::EVENT_QUIT) {
debugC(1, kDebugLevelGVideo, "EVENT_QUIT passed during the Swing Transition.");
@@ -605,13 +604,13 @@ void QuickTimeDecoder::PanoTrackHandler::swingTransitionHandler() {
}
// Referenced from video.cpp in testbed engine
- if (mouse.x >= _decoder->_prevMouse.x &&
+ if (mouse.x >= _decoder->_prevMouse.x &&
mouse.x < _decoder->_prevMouse.x + mw &&
- mouse.y >= _decoder->_prevMouse.y &&
+ mouse.y >= _decoder->_prevMouse.y &&
mouse.y < _decoder->_prevMouse.y + mh) {
- switch (event.type) {
- case Common::EVENT_MOUSEMOVE:
+ switch (event.type) {
+ case Common::EVENT_MOUSEMOVE:
_decoder->handleMouseMove(event.mouse.x - _decoder->_prevMouse.x, event.mouse.y - _decoder->_prevMouse.y);
break;
@@ -626,7 +625,7 @@ void QuickTimeDecoder::PanoTrackHandler::swingTransitionHandler() {
// This is so if we call swingTransitionHandler() twice
// The second time there will be no transition
- _currentFOV = _decoder->_fov;
+ _currentFOV = _decoder->_fov;
_currentPanAngle = _decoder->_panAngle;
_currentHFOV = _decoder->_hfov;
_currentTiltAngle = _decoder->_tiltAngle;
@@ -668,7 +667,7 @@ const Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::decodeNextFrame() {
projectPanorama(3, fov, hfov, tiltAngle, panAngle);
}
break;
-
+
default:
projectPanorama(3, fov, hfov, tiltAngle, panAngle);
break;
@@ -858,7 +857,7 @@ void QuickTimeDecoder::PanoTrackHandler::boxAverage(Graphics::Surface *sourceSur
avgB += b00;
}
}
-
+
uint8 scaleSquare = scaleFactor * scaleFactor;
avgA /= scaleSquare;
avgR /= scaleSquare;
@@ -969,7 +968,7 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
// Compute the largest projected X value, which determines the horizontal angle range
float maxProjectedX = 0.0f;
-
+
// X coords are the same here so whichever has the lower Z coord will have the maximum projected X
if (topRightVector[2] < bottomRightVector[2])
maxProjectedX = topRightVector[0] / topRightVector[2];
@@ -982,7 +981,7 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
mousePixelVector[0] = topRightVector[0] + yRatio * (bottomRightVector[0] - topRightVector[0]);
mousePixelVector[1] = topRightVector[1] + yRatio * (bottomRightVector[1] - topRightVector[1]);
mousePixelVector[2] = topRightVector[2] + yRatio * (bottomRightVector[2] - topRightVector[2]);
-
+
float t, xCoord = 0, yawRatio = 0;
float horizontalFovRadians = _decoder->_hfov * M_PI / 180.0f;
float verticalFovRadians = _decoder->_fov * M_PI / 180.0f;
@@ -1016,16 +1015,16 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
// To get the vertical coordinate, need to project the vector on to a unit cylinder.
// To do that, compute the length of the XZ vector,
float xzVectorLen = sqrt(xCoord * xCoord + 1.0f);
-
+
float projectedY = xzVectorLen * mousePixelVector[1] / mousePixelVector[2];
float normalizedYCoordinate = (projectedY - minTiltY) / (maxTiltY - minTiltY);
hotY = (int)(normalizedYCoordinate * (float)hotWidth);
}
-
+
hotX = hotX % hotHeight;
if (hotX < 0)
hotX += hotHeight;
-
+
if (hotY < 0)
hotY = 0;
else if (hotY > hotWidth)
@@ -1034,11 +1033,11 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
return Common::Point(hotX, hotY);
}
-void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
- float fov,
- float hfov,
- float tiltAngle,
- float panAngle) {
+void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
+ float fov,
+ float hfov,
+ float tiltAngle,
+ float panAngle) {
if (!_isPanoConstructed)
return;
@@ -1109,7 +1108,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
float minProjectedY = topRightVector[1] / topRightVector[2];
float maxProjectedY = bottomRightVector[1] / bottomRightVector[2];
- float angleT = fmod((360.0f - _decoder->_panAngle) / 360.0f, 1.0f);
+ float angleT = fmod((360.0f - panAngle) / 360.0f, 1.0f);
if (angleT < 0.0f) {
angleT += 1.0f;
}
@@ -1136,17 +1135,17 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
// This angle offset will tell you exactly which portion of Cylindrical panorama
// (when you construct a rectangular mosaic out of it) you're projecting on the plane surface
uint16 angleOffset = static_cast<uint32>(angleT * panoWidth);
-
+
const bool isWidthOdd = ((w % 2) == 1);
uint16 halfWidthRoundedUp = (w + 1) / 2;
float halfWidthFloat = (float)w * 0.5f;
- float verticalFovRadians = _decoder->_fov * M_PI / 180.0f;
- float horizontalFovRadians = _decoder->_hfov * M_PI / 180.0f;
- float tiltAngleRadians = _decoder->_tiltAngle * M_PI / 180.0f;
-
+ float verticalFovRadians = fov * M_PI / 180.0f;
+ float horizontalFovRadians = hfov * M_PI / 180.0f;
+ float tiltAngleRadians = tiltAngle * M_PI / 180.0f;
+
float warpMode = _decoder->_warpMode;
-
+
Common::Array<float> cylinderProjectionRanges;
Common::Array<float> cylinderAngleOffsets;
@@ -1170,8 +1169,9 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
// If width is odd, then the first column is on the pixel center
// If width is even, then the first column is on the pixel boundary
- if (!isWidthOdd)
+ if (!isWidthOdd) {
xFloat += 0.5f;
+ }
float t = xFloat / halfWidthFloat;
float xCoord = t * maxProjectedX;
@@ -1202,15 +1202,15 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
topSrcCoord = static_cast<int32>(cylinderProjectionRanges[x * 2 + 0] * panoHeight);
bottomSrcCoord = static_cast<int32>(cylinderProjectionRanges[x * 2 + 1] * panoHeight);
- if (topSrcCoord < 0)
+ if (topSrcCoord < 0) {
topSrcCoord = 0;
-
- // Should never happen
- else if (topSrcCoord >= panoHeight)
+ } else if (topSrcCoord >= panoHeight) {
topSrcCoord = panoHeight - 1;
+ }
- if (bottomSrcCoord >= panoHeight)
+ if (bottomSrcCoord >= panoHeight) {
bottomSrcCoord = panoHeight - 1;
+ }
}
// The descriptor has the original sceneSizeX and sceneSizeY of the movie
@@ -1221,17 +1221,19 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
// Wrap around if out of range
leftSourceXCoord = leftSourceXCoord % static_cast<int32>(panoWidth);
- if (leftSourceXCoord < 0)
+ if (leftSourceXCoord < 0) {
leftSourceXCoord += panoWidth;
+ }
leftSourceXCoord = desc->_sceneSizeY * factor - 1 - leftSourceXCoord;
rightSourceXCoord = rightSourceXCoord % static_cast<int32>(panoWidth);
- if (rightSourceXCoord < 0)
+ if (rightSourceXCoord < 0) {
rightSourceXCoord += panoWidth;
+ }
rightSourceXCoord = desc->_sceneSizeY * factor - 1 - rightSourceXCoord;
-
+
uint16 x1 = halfWidthRoundedUp - 1 - x;
uint16 x2 = w - halfWidthRoundedUp + x;
@@ -1268,7 +1270,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
}
}
- if (warpMode != 2) {
+ if (warpMode != 2) {
boxAverage(_planarProjection, scaleFactor);
_dirty = false;
return;
@@ -1314,7 +1316,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
for (uint16 x = 0; x < w; x++) {
int32 srcX = (x + 0.5f) * xInterpolator + startX;
uint32 pixel = _planarProjection->getPixel(srcX, srcY);
-
+
// I increased the size of the surface (made it twice as wide and twice as tall)
// Now I'm just getting the perspective projected pixel and creating a 2x2 box
aliasedProjectedPano->setPixel(x, y, pixel);
@@ -1589,10 +1591,11 @@ void QuickTimeDecoder::handlePanoMouseButton(bool isDown, int16 x, int16 y, bool
}
void QuickTimeDecoder::handleKey(Common::KeyState &state, bool down, bool repeat) {
- if (_qtvrType == QTVRType::OBJECT)
+ if (_qtvrType == QTVRType::OBJECT) {
handleObjectKey(state, down, repeat);
- else if (_qtvrType == QTVRType::PANORAMA)
+ } else if (_qtvrType == QTVRType::PANORAMA) {
handlePanoKey(state, down, repeat);
+ }
if (down) {
_lastKey = state;
Commit: 2af98018a97a80d4503fd92a18a06e7b97229efc
https://github.com/scummvm/scummvm/commit/2af98018a97a80d4503fd92a18a06e7b97229efc
Author: Malhar (themalharbdv2046 at gmail.com)
Date: 2025-04-26T19:36:15+08:00
Commit Message:
VIDEO: QTVR: Memory Optimization for Upscaled Panorama
Previous approach used two Surface Objects to store the 1D and 2D
upscaled panorama used while projecting the panorama in higher
quailty respectively causing higher memory usage
Instead use only one Surface object to store the upscaled panorama
which stores both 1D and 2D upscaled panorama as and if needed.
Minor fixes and formatting
Changed paths:
video/qt_decoder.h
video/qtvr_decoder.cpp
diff --git a/video/qt_decoder.h b/video/qt_decoder.h
index bfd078b9533..02b4a595450 100644
--- a/video/qt_decoder.h
+++ b/video/qt_decoder.h
@@ -416,21 +416,24 @@ private:
QuickTimeDecoder *_decoder;
Common::QuickTimeParser::Track *_parent;
- void projectPanorama(int scaleFactor, float fov, float hfov, float panAngle, float tiltAngle);
+ void projectPanorama(uint8 scaleFactor, float fov, float hfov, float panAngle, float tiltAngle);
void swingTransitionHandler();
- void boxAverage(Graphics::Surface *sourceSurface, int scaleFactor);
+ void boxAverage(Graphics::Surface *sourceSurface, uint8 scaleFactor);
Graphics::Surface* upscalePanorama(Graphics::Surface *sourceSurface, int8 level);
const Graphics::Surface *bufferNextFrame();
public:
Graphics::Surface *_constructedPano;
- Graphics::Surface *_1DUpscaledConstructedPano;
- Graphics::Surface *_2DUpscaledConstructedPano;
+ Graphics::Surface *_upscaledConstructedPano;
Graphics::Surface *_constructedHotspots;
Graphics::Surface *_projectedPano;
Graphics::Surface *_planarProjection;
+ // Current upscale level (1 or 2) of _upscaledConstructedPanorama compared to _constructedPano
+ // Level 1 means only upscaled height (2x pixels), level 2 means upscaled height and width (4x pixels)
+ uint8 _upscaleLevel = 0;
+
// Defining these to make the swing transition happen
// which requires storing the previous point during every change in FOV, Pan Angle and Tilt Angle
// If swing transition is called, this will be the start point of the transition
diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index 1af75f18643..5dfcd950572 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -351,7 +351,7 @@ bool QuickTimeDecoder::setFOV(float fov) {
track->_currentHFOV = _hfov;
_hfov = _fov * (float)_width / (float)_height;
- // We need to recalculate the pan angle and tilt angle to see if it has went
+ // We need to recalculate the pan angle and tilt angle to see if it has went
// out of bound for the current value of FOV
// This solves the distortion that we got sometimes when we zoom out at Tilt Angle != 0
setPanAngle(_panAngle);
@@ -486,8 +486,7 @@ QuickTimeDecoder::PanoTrackHandler::PanoTrackHandler(QuickTimeDecoder *decoder,
_constructedPano = nullptr;
_constructedHotspots = nullptr;
- _1DUpscaledConstructedPano = nullptr;
- _2DUpscaledConstructedPano = nullptr;
+ _upscaledConstructedPano = nullptr;
_projectedPano = nullptr;
_planarProjection = nullptr;
@@ -501,11 +500,8 @@ QuickTimeDecoder::PanoTrackHandler::~PanoTrackHandler() {
_constructedPano->free();
delete _constructedPano;
- _1DUpscaledConstructedPano->free();
- delete _1DUpscaledConstructedPano;
-
- _2DUpscaledConstructedPano->free();
- delete _2DUpscaledConstructedPano;
+ _upscaledConstructedPano->free();
+ delete _upscaledConstructedPano;
_constructedHotspots->free();
delete _constructedHotspots;
@@ -647,7 +643,7 @@ const Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::decodeNextFrame() {
float tiltAngle = _decoder->_tiltAngle;
float panAngle = _decoder->_panAngle;
- switch ((int) quality) {
+ switch ((int)quality) {
case 1:
projectPanorama(1, fov, hfov, tiltAngle, panAngle);
break;
@@ -733,14 +729,7 @@ void QuickTimeDecoder::PanoTrackHandler::initPanorama() {
Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::upscalePanorama(Graphics::Surface *sourceSurface, int8 level) {
// This algorithm (bilinear upscaling) takes quite some time to complete
- // PanoTrackHandler is larger by two very large images
- // This may have consequences on the memory usage
-
- // The original director has three quality levels (plus one dynamic quality mode)
- // In quality mode = 1.0f, the _constructedPano is left as it is, no upscaling
- // In quality mode = 2.0f, the _constructedPano is upscaled in one dimension (twice the width; same height)
- // In quality mode = 4.0f, the _constructedPano is upscaled in two dimensions (twice the width; twice the height)
-
+ //
// upscaling method
//
// for level 1 upscaling
@@ -766,7 +755,7 @@ Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::upscalePanorama(Graphics:
for (uint y = 0; y < h; ++y) {
for (uint x = 0; x < w; ++x) {
// For the row x==w and column y==h, wrap around the panorama
- // and average these pixels with row x==0 and column y==0 respectively
+ // and average these pixels with row x==0 and column y==0 respectively
uint x1 = (x + 1) % w;
uint y1 = (y + 1) % h;
@@ -829,17 +818,19 @@ Graphics::Surface *QuickTimeDecoder::PanoTrackHandler::upscalePanorama(Graphics:
return target;
}
-void QuickTimeDecoder::PanoTrackHandler::boxAverage(Graphics::Surface *sourceSurface, int scaleFactor) {
+void QuickTimeDecoder::PanoTrackHandler::boxAverage(Graphics::Surface *sourceSurface, uint8 scaleFactor) {
uint16 h = sourceSurface->h;
uint16 w = sourceSurface->w;
- // Apply anti-aliasing if quality is higher than 1
+ // Apply box average if quality is higher than 1
// Otherwise it'll be quicker to just copy the _planarProjection onto _projectedPano
if (scaleFactor == 1) {
_projectedPano->copyFrom(*sourceSurface);
return;
}
+ uint8 scaleSquare = scaleFactor * scaleFactor;
+
// Average out the pixels from the larger image
for (uint16 y = 0; y < h / scaleFactor; y++) {
for (uint16 x = 0; x < w / scaleFactor; x++) {
@@ -858,7 +849,6 @@ void QuickTimeDecoder::PanoTrackHandler::boxAverage(Graphics::Surface *sourceSur
}
}
- uint8 scaleSquare = scaleFactor * scaleFactor;
avgA /= scaleSquare;
avgR /= scaleSquare;
avgG /= scaleSquare;
@@ -910,8 +900,6 @@ void QuickTimeDecoder::PanoTrackHandler::constructPanorama() {
track->seek(Audio::Timestamp(0, timestamp, _decoder->_timeScale));
_constructedPano = constructMosaic(track, desc->_sceneNumFramesX, desc->_sceneNumFramesY, "dumps/pano-full.png");
- _1DUpscaledConstructedPano = upscalePanorama(_constructedPano, 1);
- _2DUpscaledConstructedPano = upscalePanorama(_constructedPano, 2);
track = (VideoTrackHandler *)(_decoder->getTrack(_decoder->Common::QuickTimeParser::_tracks[desc->_hotSpotTrackID - 1]->targetTrack));
@@ -1033,7 +1021,40 @@ Common::Point QuickTimeDecoder::PanoTrackHandler::projectPoint(int16 mx, int16 m
return Common::Point(hotX, hotY);
}
-void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
+// It may get confusing what the three terms _quality, scaleVector and _upscaleLevel mean
+// They are all related to the quality of the _projectedPanorama
+// These are relevant for the functions upscalePanorama(), boxAverage() and projectPanorama()
+//
+// _quality is the attribute of the panorama that dictates how good the panorama looks
+// This is taken directly from the original QTVR Xtra documentation by Apple
+// (float) _quality can take three values:
+// 1.0f; (higher
+// 2.0f; means
+// 4.0f; better)
+// Excluding _quality = 0.0f (which is a combination of _quality = 1.0f and _quality = 4.0f)
+// See setQuality() function to get a better idea
+//
+// This _quality determines what the scaleFactor value will be passed to projectPanorama()
+// scaleFactor is the parameter to projectPanorama used for anti-aliasing the panorama
+// We project a Surface object that has [scaleFactor] times the target height and width from _constructedPano
+// Then we take a boxAverage() in a [scaleFactor * scaleFactor] box to make the panorama target sized
+// This effectively removes aliasing from the image
+// (uint8) scaleFactor can take three values:
+// 1; (no-anti-aliasing)
+// 2; (some anti-aliasing)
+// 3; (better anti-aliasing) (For better Performance, we may remove this option)
+//
+// _quality also controls another factor that is the scale of original panorama (_constructedPano)
+// Basically we upscalePanorama() at higher _quality values, exactly by how much is given by _upscaleLevel
+// (uint8) _upscaleLevel can take two values:
+// 1 (upscale to twice the width);
+// 2 (upscale to twice the width and height);
+//
+// _quality = 1.0f => scaleFactor = 1; No upscaling;
+// _quality = 2.0f => scaleFactor = 2; _upscaleLevel = 1;
+// _quality = 4.0f => scaleFactor = 3; _upscaleLevel = 2;
+
+void QuickTimeDecoder::PanoTrackHandler::projectPanorama(uint8 scaleFactor,
float fov,
float hfov,
float tiltAngle,
@@ -1054,7 +1075,12 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
// The size of _planarProjection will keep changing in quality mode 0
// It might (will definitely) cause some out of bound access if left as it is
if (!_planarProjection || _planarProjection->w != w || _planarProjection->h != h) {
+ // If _planarProjection holds some pixels, first need to free all the pixel data
+ if (_planarProjection) {
+ _planarProjection->free();
+ }
delete _planarProjection;
+
_planarProjection = new Graphics::Surface();
_planarProjection->create(w, h, _constructedPano->format);
}
@@ -1114,16 +1140,38 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
}
Graphics::Surface *sourceSurface;
- switch(scaleFactor) {
+
+ switch (scaleFactor) {
case 1:
sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
break;
+
case 2:
- sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _1DUpscaledConstructedPano;
+ if (!_upscaledConstructedPano || _upscaleLevel != 1) {
+ // Avoid memory leakage if _upscaledConstructedPano already points to a Panorama
+ if (_upscaledConstructedPano) {
+ _upscaledConstructedPano->free();
+ delete _upscaledConstructedPano;
+ }
+ _upscaleLevel = 1;
+ _upscaledConstructedPano = upscalePanorama(_constructedPano, _upscaleLevel);
+ }
+ sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _upscaledConstructedPano;
break;
+
case 3:
- sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _2DUpscaledConstructedPano;
+ if (!_upscaledConstructedPano || _upscaleLevel != 2) {
+ // Avoid memory leakage if _upscaledConstructedPano already points to a Panorama
+ if (_upscaledConstructedPano) {
+ _upscaledConstructedPano->free();
+ delete _upscaledConstructedPano;
+ }
+ _upscaleLevel = 2;
+ _upscaledConstructedPano = upscalePanorama(_constructedPano, _upscaleLevel);
+ }
+ sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _upscaledConstructedPano;
break;
+
default:
sourceSurface = _decoder->_renderHotspots ? _constructedHotspots : _constructedPano;
break;
@@ -1132,7 +1180,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
int32 panoWidth = sourceSurface->h;
int32 panoHeight = sourceSurface->w;
- // This angle offset will tell you exactly which portion of Cylindrical panorama
+ // This angle offset will tell you exactly which portion of Cylindrical panorama
// (when you construct a rectangular mosaic out of it) you're projecting on the plane surface
uint16 angleOffset = static_cast<uint32>(angleT * panoWidth);
@@ -1215,11 +1263,11 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
// The descriptor has the original sceneSizeX and sceneSizeY of the movie
// But in quality mode 3 we're increasing them to twice the original
- // This factor just make sure we're wrapping leftSourceXCoord and rightSourceXCoord
+ // This factor just make sure we're wrapping leftSourceXCoord and rightSourceXCoord
// According to the sourceSurface's dimensions rather than the original's
int factor = scaleFactor == 3 && !_decoder->_renderHotspots ? 2 : 1;
- // Wrap around if out of range
+ // Wrap around if out of range
leftSourceXCoord = leftSourceXCoord % static_cast<int32>(panoWidth);
if (leftSourceXCoord < 0) {
leftSourceXCoord += panoWidth;
@@ -1325,6 +1373,7 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama(int scaleFactor,
boxAverage(aliasedProjectedPano, scaleFactor);
// Memory leakage avoided; Should I use a stack allocated object?
+ aliasedProjectedPano->free();
delete aliasedProjectedPano;
_dirty = false;
More information about the Scummvm-git-logs
mailing list