diff --git a/es-core/src/ThemeData.cpp b/es-core/src/ThemeData.cpp index c72613168..c1acee79f 100644 --- a/es-core/src/ThemeData.cpp +++ b/es-core/src/ThemeData.cpp @@ -317,6 +317,7 @@ std::map> {"video", {{"pos", NORMALIZED_PAIR}, {"size", NORMALIZED_PAIR}, + {"cropSize", NORMALIZED_PAIR}, {"maxSize", NORMALIZED_PAIR}, {"origin", NORMALIZED_PAIR}, {"path", PATH}, diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 50e2a3b6c..38e82913c 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -28,6 +28,8 @@ VideoComponent::VideoComponent() , mTargetSize {0.0f, 0.0f} , mVideoAreaPos {0.0f, 0.0f} , mVideoAreaSize {0.0f, 0.0f} + , mTopLeftCrop {0.0f, 0.0f} + , mBottomRightCrop {1.0f, 1.0f} , mPillarboxThreshold {0.85f, 0.90f} , mStartTime {0} , mIsPlaying {false} @@ -36,6 +38,7 @@ VideoComponent::VideoComponent() , mMediaViewerMode {false} , mScreensaverMode {false} , mTargetIsMax {false} + , mTargetIsCrop {false} , mPlayAudio {true} , mDrawPillarboxes {true} , mRenderScanlines {false} @@ -154,6 +157,13 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, setMaxSize(videoMaxSize * scale); mVideoAreaSize = videoMaxSize * scale; } + else if (elem->has("cropSize")) { + glm::vec2 videoCropSize {elem->get("cropSize")}; + videoCropSize.x = glm::clamp(videoCropSize.x, 0.01f, 2.0f); + videoCropSize.y = glm::clamp(videoCropSize.y, 0.01f, 2.0f); + setCroppedSize(videoCropSize * scale); + mVideoAreaSize = videoCropSize * scale; + } } if (properties & ThemeFlags::POSITION) { @@ -302,8 +312,17 @@ void VideoComponent::applyTheme(const std::shared_ptr& theme, mPillarboxThreshold.y = glm::clamp(pillarboxThreshold.y, 0.2f, 1.0f); } - if (elem->has("scanlines")) - mRenderScanlines = elem->get("scanlines"); + if (elem->has("scanlines")) { + if (elem->has("cropSize") && elem->get("scanlines")) { + LOG(LogWarning) << "VideoComponent: Invalid theme configuration, property " + "\"cropSize\" for element \"" + << element.substr(6) + << "\" can't be combined with the \"scanlines\" property"; + } + else { + mRenderScanlines = elem->get("scanlines"); + } + } if (elem->has("scrollFadeIn") && elem->get("scrollFadeIn")) mComponentThemeFlags |= ComponentThemeFlags::SCROLL_FADE_IN; diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 4ce469b8c..65b4bf6aa 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -83,6 +83,8 @@ public: // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive. virtual void setMaxSize(float width, float height) = 0; void setMaxSize(const glm::vec2& size) { setMaxSize(size.x, size.y); } + // Resize and crop the video so it fills the entire area. + virtual void setCroppedSize(const glm::vec2& size) = 0; // Basic video controls. void startVideoPlayer(); @@ -109,6 +111,8 @@ protected: glm::vec2 mTargetSize; glm::vec2 mVideoAreaPos; glm::vec2 mVideoAreaSize; + glm::vec2 mTopLeftCrop; + glm::vec2 mBottomRightCrop; glm::vec2 mPillarboxThreshold; std::shared_ptr mTexture; std::string mStaticImagePath; @@ -126,6 +130,7 @@ protected: bool mMediaViewerMode; bool mScreensaverMode; bool mTargetIsMax; + bool mTargetIsCrop; bool mPlayAudio; bool mDrawPillarboxes; bool mRenderScanlines; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 703e210bc..45e5b4e33 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -66,6 +66,7 @@ void VideoFFmpegComponent::setResize(const float width, const float height) // This resize function is used when stretching videos to full screen in the video screensaver. mTargetSize = glm::vec2 {width, height}; mTargetIsMax = false; + mTargetIsCrop = false; mStaticImage.setResize(mTargetSize); resize(); } @@ -76,10 +77,20 @@ void VideoFFmpegComponent::setMaxSize(float width, float height) // and the gamelist videos. mTargetSize = glm::vec2 {width, height}; mTargetIsMax = true; + mTargetIsCrop = false; mStaticImage.setMaxSize(width, height); resize(); } +void VideoFFmpegComponent::setCroppedSize(const glm::vec2& size) +{ + mTargetSize = size; + mTargetIsMax = false; + mTargetIsCrop = true; + mStaticImage.setCroppedSize(size); + resize(); +} + void VideoFFmpegComponent::resize() { if (!mTexture) @@ -106,6 +117,25 @@ void VideoFFmpegComponent::resize() mSize.x = (mSize.y / textureSize.y) * textureSize.x; } + else if (mTargetIsCrop) { + // Size texture to allow for cropped video to fill the entire area. + const float cropFactor { + std::max(mTargetSize.x / textureSize.x, mTargetSize.y / textureSize.y)}; + mSize = textureSize * cropFactor; + + if (std::round(mSize.y) > std::round(mTargetSize.y)) { + const float cropSize {1.0f - (mTargetSize.y / mSize.y)}; + mTopLeftCrop.y = cropSize / 2.0f; + mBottomRightCrop.y = 1.0f - (cropSize / 2.0f); + mSize.y = mSize.y - (mSize.y * cropSize); + } + else { + const float cropSize {1.0f - (mTargetSize.x / mSize.x)}; + mTopLeftCrop.x = cropSize / 2.0f; + mBottomRightCrop.x = 1.0f - (cropSize / 2.0f); + mSize.x = mSize.x - (mSize.x * cropSize); + } + } else { // If both components are set, we just stretch. // If no components are set, we don't resize at all. @@ -159,10 +189,10 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) return; // clang-format off - vertices[0] = {{0.0f + mRectangleOffset.x, 0.0f + mRectangleOffset.y }, {0.0f, 0.0f}, 0xFFFFFFFF}; - vertices[1] = {{0.0f + mRectangleOffset.x, mSize.y + mRectangleOffset.y }, {0.0f, 1.0f}, 0xFFFFFFFF}; - vertices[2] = {{mSize.x + mRectangleOffset.x, 0.0f + + mRectangleOffset.y }, {1.0f, 0.0f}, 0xFFFFFFFF}; - vertices[3] = {{mSize.x + mRectangleOffset.x, mSize.y + + mRectangleOffset.y}, {1.0f, 1.0f}, 0xFFFFFFFF}; + vertices[0] = {{0.0f + mRectangleOffset.x, 0.0f + mRectangleOffset.y }, {mTopLeftCrop.x, 1.0f - mBottomRightCrop.y}, 0xFFFFFFFF}; + vertices[1] = {{0.0f + mRectangleOffset.x, mSize.y + mRectangleOffset.y }, {mTopLeftCrop.x, 1.0f - mTopLeftCrop.y }, 0xFFFFFFFF}; + vertices[2] = {{mSize.x + mRectangleOffset.x, 0.0f + + mRectangleOffset.y }, {mBottomRightCrop.x * 1.0f, 1.0f - mBottomRightCrop.y}, 0xFFFFFFFF}; + vertices[3] = {{mSize.x + mRectangleOffset.x, mSize.y + + mRectangleOffset.y}, {mBottomRightCrop.x * 1.0f, 1.0f - mTopLeftCrop.y }, 0xFFFFFFFF}; // clang-format on vertices[0].color = mColorShift; diff --git a/es-core/src/components/VideoFFmpegComponent.h b/es-core/src/components/VideoFFmpegComponent.h index 0be5782ee..f7da6972a 100644 --- a/es-core/src/components/VideoFFmpegComponent.h +++ b/es-core/src/components/VideoFFmpegComponent.h @@ -46,6 +46,9 @@ public: // This can be set before or after a video is loaded. // Never breaks the aspect ratio. setMaxSize() and setResize() are mutually exclusive. void setMaxSize(float width, float height) override; + // Resize and crop the video so it fills the entire area. + void setCroppedSize(const glm::vec2& size) override; + // Basic video controls. void stopVideoPlayer(bool muteAudio = true) override; void pauseVideoPlayer() override;