2020-09-16 20:14:35 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-06-28 16:39:18 +00:00
|
|
|
//
|
2020-09-16 20:14:35 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-28 16:39:18 +00:00
|
|
|
// ImageComponent.cpp
|
|
|
|
//
|
|
|
|
// Handles images: loading, resizing, cropping, color shifting etc.
|
|
|
|
//
|
|
|
|
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "components/ImageComponent.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "Log.h"
|
2018-01-23 00:31:18 +00:00
|
|
|
#include "Settings.h"
|
2014-06-20 01:30:09 +00:00
|
|
|
#include "ThemeData.h"
|
2022-01-09 18:50:11 +00:00
|
|
|
#include "Window.h"
|
2021-07-07 18:31:46 +00:00
|
|
|
#include "resources/TextureResource.h"
|
|
|
|
#include "utils/CImgUtil.h"
|
2022-02-14 18:32:07 +00:00
|
|
|
#include "utils/StringUtil.h"
|
2012-08-02 01:43:55 +00:00
|
|
|
|
2021-08-17 16:41:45 +00:00
|
|
|
glm::ivec2 ImageComponent::getTextureSize() const
|
2013-06-21 16:49:29 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mTexture)
|
|
|
|
return mTexture->getSize();
|
|
|
|
else
|
2022-01-16 11:09:55 +00:00
|
|
|
return glm::ivec2 {};
|
2013-08-06 13:15:20 +00:00
|
|
|
}
|
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
glm::vec2 ImageComponent::getSize() const
|
2018-04-12 09:00:47 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
return GuiComponent::getSize() * (mBottomRightCrop - mTopLeftCrop);
|
|
|
|
}
|
|
|
|
|
2022-01-19 17:01:54 +00:00
|
|
|
ImageComponent::ImageComponent(bool forceLoad, bool dynamic)
|
2022-03-14 18:51:48 +00:00
|
|
|
: mRenderer {Renderer::getInstance()}
|
|
|
|
, mTargetSize {0, 0}
|
2022-01-16 17:18:28 +00:00
|
|
|
, mFlipX {false}
|
|
|
|
, mFlipY {false}
|
|
|
|
, mTargetIsMax {false}
|
|
|
|
, mTargetIsMin {false}
|
|
|
|
, mColorShift {0xFFFFFFFF}
|
|
|
|
, mColorShiftEnd {0xFFFFFFFF}
|
|
|
|
, mColorGradientHorizontal {true}
|
2022-02-11 21:10:25 +00:00
|
|
|
, mFadeOpacity {0.0f}
|
2022-04-18 19:37:58 +00:00
|
|
|
, mReflectionsFalloff {0.0f}
|
2022-01-16 17:18:28 +00:00
|
|
|
, mFading {false}
|
|
|
|
, mForceLoad {forceLoad}
|
|
|
|
, mDynamic {dynamic}
|
|
|
|
, mRotateByTargetSize {false}
|
2022-02-13 10:45:06 +00:00
|
|
|
, mLinearInterpolation {false}
|
2022-01-16 17:18:28 +00:00
|
|
|
, mTopLeftCrop {0.0f, 0.0f}
|
|
|
|
, mBottomRightCrop {1.0f, 1.0f}
|
2020-06-28 16:39:18 +00:00
|
|
|
{
|
|
|
|
updateColors();
|
2012-08-02 01:43:55 +00:00
|
|
|
}
|
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
void ImageComponent::resize(bool rasterize)
|
2012-08-09 21:19:07 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (!mTexture)
|
|
|
|
return;
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
const glm::vec2 textureSize {mTexture->getSourceImageSize()};
|
2022-08-23 20:24:24 +00:00
|
|
|
if (textureSize == glm::vec2 {0.0f, 0.0f})
|
2020-06-28 16:39:18 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (mTexture->isTiled()) {
|
|
|
|
mSize = mTargetSize;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// SVG rasterization is determined by height and rasterization is done in terms of pixels.
|
|
|
|
// If rounding is off enough in the rasterization step (for images with extreme aspect
|
2021-10-23 13:45:44 +00:00
|
|
|
// ratios), it can cause cutoff when the aspect ratio breaks. So we always make sure to
|
|
|
|
// round accordingly to avoid such issues.
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mTargetIsMax) {
|
|
|
|
mSize = textureSize;
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::vec2 resizeScale {(mTargetSize.x / mSize.x), (mTargetSize.y / mSize.y)};
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (resizeScale.x < resizeScale.y) {
|
|
|
|
// This will be mTargetSize.x. We can't exceed it, nor be lower than it.
|
|
|
|
mSize.x *= resizeScale.x;
|
2020-06-28 16:39:18 +00:00
|
|
|
// We need to make sure we're not creating an image larger than max size.
|
2021-10-23 13:45:44 +00:00
|
|
|
mSize.y = floorf(std::min(mSize.y * resizeScale.x, mTargetSize.y));
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// This will be mTargetSize.y(). We can't exceed it.
|
2021-10-23 13:45:44 +00:00
|
|
|
mSize.y *= resizeScale.y;
|
2021-08-16 16:25:01 +00:00
|
|
|
mSize.x = std::min((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (mTargetIsMin) {
|
|
|
|
mSize = textureSize;
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::vec2 resizeScale {(mTargetSize.x / mSize.x), (mTargetSize.y / mSize.y)};
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (resizeScale.x > resizeScale.y) {
|
|
|
|
mSize.x *= resizeScale.x;
|
|
|
|
mSize.y *= resizeScale.x;
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
float cropPercent {(mSize.y - mTargetSize.y) / (mSize.y * 2.0f)};
|
2021-08-16 16:25:01 +00:00
|
|
|
crop(0.0f, cropPercent, 0.0f, cropPercent);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-08-16 16:25:01 +00:00
|
|
|
mSize.x *= resizeScale.y;
|
|
|
|
mSize.y *= resizeScale.y;
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
float cropPercent {(mSize.x - mTargetSize.x) / (mSize.x * 2.0f)};
|
2021-08-16 16:25:01 +00:00
|
|
|
crop(cropPercent, 0.0f, cropPercent, 0.0f);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2021-10-23 13:45:44 +00:00
|
|
|
mSize.y = std::max(mSize.y, mTargetSize.y);
|
2021-08-16 16:25:01 +00:00
|
|
|
mSize.x = std::max((mSize.y / textureSize.y) * textureSize.x, mTargetSize.x);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// If both components are set, we just stretch.
|
|
|
|
// If no components are set, we don't resize at all.
|
2022-08-23 20:24:24 +00:00
|
|
|
mSize = mTargetSize == glm::vec2 {0.0f, 0.0f} ? textureSize : mTargetSize;
|
2020-06-28 16:39:18 +00:00
|
|
|
|
|
|
|
// If only one component is set, we resize in a way that maintains aspect ratio.
|
2021-08-16 16:25:01 +00:00
|
|
|
if (!mTargetSize.x && mTargetSize.y) {
|
2021-10-23 13:45:44 +00:00
|
|
|
mSize.y = mTargetSize.y;
|
2021-08-16 16:25:01 +00:00
|
|
|
mSize.x = (mSize.y / textureSize.y) * textureSize.x;
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2021-08-16 16:25:01 +00:00
|
|
|
else if (mTargetSize.x && !mTargetSize.y) {
|
2021-10-23 13:45:44 +00:00
|
|
|
mSize.y = (mTargetSize.x / textureSize.x) * textureSize.y;
|
2021-08-16 16:25:01 +00:00
|
|
|
mSize.x = (mSize.y / textureSize.y) * textureSize.x;
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-10 09:53:44 +00:00
|
|
|
// Make sure sub-pixel values are not rounded to zero and that the size is not unreasonably
|
|
|
|
// large (which may be caused by a mistake in the theme configuration).
|
|
|
|
mSize.x = glm::clamp(mSize.x, 1.0f, mRenderer->getScreenWidth() * 2.0f);
|
|
|
|
mSize.y = glm::clamp(mSize.y, 1.0f, mRenderer->getScreenHeight() * 2.0f);
|
2021-10-17 19:51:21 +00:00
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
if (rasterize) {
|
|
|
|
mTexture->rasterizeAt(mSize.x, mSize.y);
|
|
|
|
onSizeChanged();
|
|
|
|
}
|
2014-04-12 01:48:13 +00:00
|
|
|
}
|
|
|
|
|
2022-02-13 10:45:06 +00:00
|
|
|
void ImageComponent::setImage(const std::string& path, bool tile)
|
2012-08-09 21:19:07 +00:00
|
|
|
{
|
2021-03-16 20:43:14 +00:00
|
|
|
// Always load bundled graphic resources statically, unless mForceLoad has been set.
|
|
|
|
// This eliminates annoying texture pop-in problems that would otherwise occur.
|
|
|
|
if (!mForceLoad && (path[0] == ':') && (path[1] == '/')) {
|
|
|
|
mDynamic = false;
|
|
|
|
}
|
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
const bool isScalable {path != "" ? Utils::String::toLower(path.substr(
|
|
|
|
path.size() - 4, std::string::npos)) == ".svg" :
|
|
|
|
false};
|
|
|
|
|
|
|
|
// Create an initial blank texture if needed.
|
2022-01-04 20:21:26 +00:00
|
|
|
if (path.empty() || !ResourceManager::getInstance().fileExists(path)) {
|
|
|
|
if (mDefaultPath.empty() || !ResourceManager::getInstance().fileExists(mDefaultPath))
|
2020-06-28 16:39:18 +00:00
|
|
|
mTexture.reset();
|
|
|
|
else
|
2022-02-13 10:45:06 +00:00
|
|
|
mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic,
|
|
|
|
mLinearInterpolation);
|
2022-08-23 20:24:24 +00:00
|
|
|
resize(true);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
else {
|
2022-08-23 20:24:24 +00:00
|
|
|
// For raster images we just load and resize but for SVG images we first need to resize
|
|
|
|
// without rasterizing in order to calculate the correct image size. Then we delete and
|
|
|
|
// reload the texture at the requested size in order to add a valid cache entry. Finally
|
|
|
|
// we perform the actual rasterization to have the cache entry updated with the proper
|
|
|
|
// texture. For SVG images this requires that every call to setImage is made only after
|
|
|
|
// a call to setResize or setMaxSize (so the requested size is known upfront).
|
2022-02-13 10:45:06 +00:00
|
|
|
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation);
|
2012-08-02 01:43:55 +00:00
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
if (isScalable) {
|
|
|
|
resize(false);
|
|
|
|
mTexture.reset();
|
2022-08-23 20:50:14 +00:00
|
|
|
mTexture =
|
|
|
|
TextureResource::get(path, tile, mForceLoad, mDynamic, mLinearInterpolation, false,
|
|
|
|
static_cast<size_t>(mSize.x), static_cast<size_t>(mSize.y));
|
2022-08-23 20:24:24 +00:00
|
|
|
mTexture->rasterizeAt(mSize.x, mSize.y);
|
|
|
|
onSizeChanged();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
resize(true);
|
|
|
|
}
|
|
|
|
}
|
2013-11-12 23:28:15 +00:00
|
|
|
}
|
|
|
|
|
2020-09-04 17:05:48 +00:00
|
|
|
void ImageComponent::setImage(const char* data, size_t length, bool tile)
|
2013-09-20 23:55:05 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mTexture.reset();
|
2013-09-20 23:55:05 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
mTexture = TextureResource::get("", tile);
|
2020-09-04 17:05:48 +00:00
|
|
|
mTexture->initFromMemory(data, length);
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
resize();
|
2013-09-20 23:55:05 +00:00
|
|
|
}
|
|
|
|
|
2021-09-27 19:03:53 +00:00
|
|
|
void ImageComponent::setImage(const std::shared_ptr<TextureResource>& texture, bool resizeTexture)
|
2012-08-13 18:32:53 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mTexture = texture;
|
2021-09-27 19:03:53 +00:00
|
|
|
if (resizeTexture)
|
|
|
|
resize();
|
2012-08-13 18:32:53 +00:00
|
|
|
}
|
|
|
|
|
2014-01-10 23:45:47 +00:00
|
|
|
void ImageComponent::setResize(float width, float height)
|
|
|
|
{
|
2022-01-16 11:09:55 +00:00
|
|
|
mTargetSize = glm::vec2 {width, height};
|
2020-06-28 16:39:18 +00:00
|
|
|
mTargetIsMax = false;
|
|
|
|
mTargetIsMin = false;
|
|
|
|
resize();
|
2012-08-13 18:32:53 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::setMaxSize(const float width, const float height)
|
2012-10-05 20:04:12 +00:00
|
|
|
{
|
2022-01-16 11:09:55 +00:00
|
|
|
mTargetSize = glm::vec2 {width, height};
|
2020-06-28 16:39:18 +00:00
|
|
|
mTargetIsMax = true;
|
|
|
|
mTargetIsMin = false;
|
|
|
|
resize();
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::cropLeft(const float percent)
|
2018-04-12 09:00:47 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
assert(percent >= 0.0f && percent <= 1.0f);
|
2021-08-16 16:25:01 +00:00
|
|
|
mTopLeftCrop.x = percent;
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::cropTop(const float percent)
|
2018-04-12 09:00:47 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
assert(percent >= 0.0f && percent <= 1.0f);
|
2021-08-16 16:25:01 +00:00
|
|
|
mTopLeftCrop.y = percent;
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::cropRight(const float percent)
|
2018-04-12 09:00:47 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
assert(percent >= 0.0f && percent <= 1.0f);
|
2021-08-16 16:25:01 +00:00
|
|
|
mBottomRightCrop.x = 1.0f - percent;
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::cropBot(const float percent)
|
2018-04-12 09:00:47 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
assert(percent >= 0.0f && percent <= 1.0f);
|
2021-08-16 16:25:01 +00:00
|
|
|
mBottomRightCrop.y = 1.0f - percent;
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::crop(const float left, const float top, const float right, const float bot)
|
2018-04-12 09:00:47 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
cropLeft(left);
|
|
|
|
cropTop(top);
|
|
|
|
cropRight(right);
|
|
|
|
cropBot(bot);
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ImageComponent::uncrop()
|
|
|
|
{
|
2021-07-07 18:31:46 +00:00
|
|
|
// Remove any applied crop.
|
2021-08-16 16:25:01 +00:00
|
|
|
crop(0.0f, 0.0f, 0.0f, 0.0f);
|
2018-04-12 09:00:47 +00:00
|
|
|
}
|
|
|
|
|
2021-11-09 21:40:08 +00:00
|
|
|
void ImageComponent::cropTransparentPadding(const float maxSizeX, const float maxSizeY)
|
2021-06-12 19:08:35 +00:00
|
|
|
{
|
2022-08-23 20:24:24 +00:00
|
|
|
if (mSize == glm::vec2 {0.0f, 0.0f})
|
2021-06-12 19:08:35 +00:00
|
|
|
return;
|
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
std::vector<unsigned char> imageRGBA {mTexture.get()->getRawRGBAData()};
|
2021-06-12 19:08:35 +00:00
|
|
|
|
|
|
|
if (imageRGBA.size() == 0)
|
|
|
|
return;
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::ivec2 imageSize {mTexture.get()->getSize()};
|
2021-08-17 16:41:45 +00:00
|
|
|
cimg_library::CImg<unsigned char> imageCImg(imageSize.x, imageSize.y, 1, 4, 0);
|
2021-06-12 19:08:35 +00:00
|
|
|
|
2022-04-18 19:37:58 +00:00
|
|
|
int paddingCoords[4] {0, 0, 0, 0};
|
2021-06-12 19:08:35 +00:00
|
|
|
|
|
|
|
// We need to convert our RGBA data to the CImg internal format as CImg does not interleave
|
|
|
|
// the pixels (as in RGBARGBARGBA).
|
|
|
|
Utils::CImg::convertRGBAToCImg(imageRGBA, imageCImg);
|
|
|
|
|
|
|
|
// This will give us the coordinates for the fully transparent areas.
|
|
|
|
Utils::CImg::getTransparentPaddingCoords(imageCImg, paddingCoords);
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::vec2 originalSize {mSize};
|
2021-06-12 19:08:35 +00:00
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
float cropLeft {static_cast<float>(paddingCoords[0]) / static_cast<float>(imageSize.x)};
|
|
|
|
float cropTop {static_cast<float>(paddingCoords[1]) / static_cast<float>(imageSize.y)};
|
|
|
|
float cropRight {static_cast<float>(paddingCoords[2]) / static_cast<float>(imageSize.x)};
|
|
|
|
float cropBottom {static_cast<float>(paddingCoords[3]) / static_cast<float>(imageSize.y)};
|
2021-06-12 19:08:35 +00:00
|
|
|
|
|
|
|
crop(cropLeft, cropTop, cropRight, cropBottom);
|
|
|
|
|
|
|
|
// Cropping the image obviously leads to a reduction in size, so we need to determine
|
|
|
|
// how much to scale up after cropping to keep within the max size restrictions that
|
|
|
|
// were passed as arguments.
|
2021-08-16 16:25:01 +00:00
|
|
|
mSize.x -= mSize.x * (cropLeft + cropRight);
|
|
|
|
mSize.y -= mSize.y * (cropTop + cropBottom);
|
2021-06-12 19:08:35 +00:00
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
float scaleFactor {originalSize.y / mSize.y};
|
2021-06-12 19:08:35 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (scaleFactor * mSize.x < maxSizeX)
|
|
|
|
scaleFactor = maxSizeX / mSize.x;
|
2021-06-14 17:56:21 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (scaleFactor * mSize.y < maxSizeY)
|
|
|
|
scaleFactor = maxSizeY / mSize.y;
|
2021-06-14 17:56:21 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (scaleFactor * mSize.x > maxSizeX)
|
|
|
|
scaleFactor = maxSizeX / mSize.x;
|
2021-06-12 19:08:35 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
if (scaleFactor * mSize.y > maxSizeY)
|
|
|
|
scaleFactor = maxSizeY / mSize.y;
|
2021-06-12 19:08:35 +00:00
|
|
|
|
2021-08-16 16:25:01 +00:00
|
|
|
setResize(mSize.x * scaleFactor, mSize.y * scaleFactor);
|
2021-06-12 19:08:35 +00:00
|
|
|
updateVertices();
|
|
|
|
}
|
|
|
|
|
2013-06-02 15:08:32 +00:00
|
|
|
void ImageComponent::setFlipX(bool flip)
|
2012-10-07 22:25:51 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mFlipX = flip;
|
|
|
|
updateVertices();
|
2012-10-07 22:25:51 +00:00
|
|
|
}
|
|
|
|
|
2013-06-02 15:08:32 +00:00
|
|
|
void ImageComponent::setFlipY(bool flip)
|
2012-10-07 22:25:51 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mFlipY = flip;
|
|
|
|
updateVertices();
|
2012-10-07 22:25:51 +00:00
|
|
|
}
|
|
|
|
|
2013-08-07 22:40:27 +00:00
|
|
|
void ImageComponent::setColorShift(unsigned int color)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mColorShift = color;
|
|
|
|
mColorShiftEnd = color;
|
|
|
|
updateColors();
|
2013-08-07 22:40:27 +00:00
|
|
|
}
|
|
|
|
|
2019-07-04 01:10:17 +00:00
|
|
|
void ImageComponent::setColorShiftEnd(unsigned int color)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mColorShiftEnd = color;
|
|
|
|
updateColors();
|
2019-07-04 01:10:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ImageComponent::setColorGradientHorizontal(bool horizontal)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mColorGradientHorizontal = horizontal;
|
|
|
|
updateColors();
|
2019-07-04 01:10:17 +00:00
|
|
|
}
|
|
|
|
|
2022-02-11 21:10:25 +00:00
|
|
|
void ImageComponent::setOpacity(float opacity)
|
2012-08-02 01:43:55 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
mOpacity = opacity;
|
|
|
|
updateColors();
|
2012-08-29 21:52:25 +00:00
|
|
|
}
|
2012-08-29 18:53:53 +00:00
|
|
|
|
2020-08-30 20:19:37 +00:00
|
|
|
void ImageComponent::setSaturation(float saturation)
|
|
|
|
{
|
|
|
|
mSaturation = saturation;
|
|
|
|
updateColors();
|
|
|
|
}
|
|
|
|
|
2022-03-11 22:51:41 +00:00
|
|
|
void ImageComponent::setDimming(float dimming)
|
2022-03-11 22:17:04 +00:00
|
|
|
{
|
2022-03-11 22:51:41 +00:00
|
|
|
// Set dimming value.
|
|
|
|
mDimming = dimming;
|
2022-03-11 22:17:04 +00:00
|
|
|
}
|
|
|
|
|
2014-04-12 01:48:13 +00:00
|
|
|
void ImageComponent::updateVertices()
|
2012-08-29 21:52:25 +00:00
|
|
|
{
|
2021-10-26 16:22:41 +00:00
|
|
|
if (!mTexture)
|
2020-06-28 16:39:18 +00:00
|
|
|
return;
|
2014-04-12 01:48:13 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
// We go through this mess to make sure everything is properly rounded.
|
|
|
|
// If we just round vertices at the end, edge cases occur near sizes of 0.5.
|
2022-08-23 20:24:24 +00:00
|
|
|
const glm::vec2 topLeft {0.0f, 0.0f};
|
2022-01-16 11:09:55 +00:00
|
|
|
const glm::vec2 bottomRight {mSize};
|
|
|
|
const float px {mTexture->isTiled() ? mSize.x / getTextureSize().x : 1.0f};
|
|
|
|
const float py {mTexture->isTiled() ? mSize.y / getTextureSize().y : 1.0f};
|
2019-08-31 13:06:23 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format off
|
2021-08-17 16:41:45 +00:00
|
|
|
mVertices[0] = {{topLeft.x, topLeft.y }, {mTopLeftCrop.x, py - mTopLeftCrop.y }, 0};
|
|
|
|
mVertices[1] = {{topLeft.x, bottomRight.y}, {mTopLeftCrop.x, 1.0f - mBottomRightCrop.y}, 0};
|
|
|
|
mVertices[2] = {{bottomRight.x, topLeft.y }, {mBottomRightCrop.x * px, py - mTopLeftCrop.y }, 0};
|
|
|
|
mVertices[3] = {{bottomRight.x, bottomRight.y}, {mBottomRightCrop.x * px, 1.0f - mBottomRightCrop.y}, 0};
|
2021-07-07 18:31:46 +00:00
|
|
|
// clang-format on
|
2019-08-31 13:06:23 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
updateColors();
|
2012-10-07 22:25:51 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
// Round vertices.
|
2021-11-17 16:48:49 +00:00
|
|
|
for (int i = 0; i < 4; ++i)
|
2022-03-11 22:51:41 +00:00
|
|
|
mVertices[i].position = glm::round(mVertices[i].position);
|
2019-08-31 16:19:43 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mFlipX) {
|
2021-11-17 16:48:49 +00:00
|
|
|
for (int i = 0; i < 4; ++i)
|
2022-03-14 23:14:06 +00:00
|
|
|
mVertices[i].texcoord[0] = px - mVertices[i].texcoord[0];
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2019-08-31 16:19:43 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mFlipY) {
|
2021-11-17 16:48:49 +00:00
|
|
|
for (int i = 0; i < 4; ++i)
|
2022-03-14 23:14:06 +00:00
|
|
|
mVertices[i].texcoord[1] = py - mVertices[i].texcoord[1];
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2012-09-07 21:44:07 +00:00
|
|
|
}
|
|
|
|
|
2014-04-12 01:48:13 +00:00
|
|
|
void ImageComponent::updateColors()
|
2012-09-07 21:44:07 +00:00
|
|
|
{
|
2022-04-18 19:37:58 +00:00
|
|
|
const float opacity {mOpacity * (mFading ? mFadeOpacity : 1.0f)};
|
2022-03-11 22:17:04 +00:00
|
|
|
const unsigned int color {(mColorShift & 0xFFFFFF00) |
|
|
|
|
static_cast<unsigned char>((mColorShift & 0xFF) * opacity)};
|
|
|
|
const unsigned int colorEnd {(mColorShiftEnd & 0xFFFFFF00) |
|
|
|
|
static_cast<unsigned char>((mColorShiftEnd & 0xFF) * opacity)};
|
2019-08-08 20:16:11 +00:00
|
|
|
|
2022-03-11 22:51:41 +00:00
|
|
|
mVertices[0].color = color;
|
|
|
|
mVertices[1].color = mColorGradientHorizontal ? color : colorEnd;
|
|
|
|
mVertices[2].color = mColorGradientHorizontal ? colorEnd : color;
|
|
|
|
mVertices[3].color = colorEnd;
|
2014-04-12 01:48:13 +00:00
|
|
|
}
|
2013-06-21 16:49:29 +00:00
|
|
|
|
2021-08-15 17:30:31 +00:00
|
|
|
void ImageComponent::render(const glm::mat4& parentTrans)
|
2014-04-12 01:48:13 +00:00
|
|
|
{
|
2022-02-12 16:38:55 +00:00
|
|
|
if (!isVisible() || mThemeOpacity == 0.0f || mTexture == nullptr ||
|
|
|
|
mTargetSize == glm::vec2 {0.0f, 0.0f} || mSize == glm::vec2 {0.0f, 0.0f})
|
2020-06-28 16:39:18 +00:00
|
|
|
return;
|
|
|
|
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::mat4 trans {parentTrans * getTransform()};
|
2022-04-27 16:40:28 +00:00
|
|
|
|
|
|
|
// Don't round vertices if scaled as it may lead to single-pixel alignment issues.
|
|
|
|
if (mScale == 1.0f)
|
|
|
|
mRenderer->setMatrix(trans, true);
|
|
|
|
else
|
|
|
|
mRenderer->setMatrix(trans, false);
|
2020-06-28 16:39:18 +00:00
|
|
|
|
2022-02-11 21:10:25 +00:00
|
|
|
if (mTexture && mOpacity > 0.0f) {
|
2020-06-28 16:39:18 +00:00
|
|
|
if (Settings::getInstance()->getBool("DebugImage")) {
|
2022-01-16 11:09:55 +00:00
|
|
|
glm::vec2 targetSizePos {(mTargetSize - mSize) * mOrigin * glm::vec2 {-1.0f}};
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->drawRect(targetSizePos.x, targetSizePos.y, mTargetSize.x, mTargetSize.y,
|
|
|
|
0xFF000033, 0xFF000033);
|
|
|
|
mRenderer->drawRect(0.0f, 0.0f, mSize.x, mSize.y, 0xFF000033, 0xFF000033);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2021-07-02 18:33:50 +00:00
|
|
|
// An image with zero size would normally indicate a corrupt image file.
|
2022-01-16 11:09:55 +00:00
|
|
|
if (mTexture->getSize() != glm::ivec2 {}) {
|
2020-06-28 16:39:18 +00:00
|
|
|
// Actually draw the image.
|
|
|
|
// The bind() function returns false if the texture is not currently loaded. A blank
|
|
|
|
// texture is bound in this case but we want to handle a fade so it doesn't just
|
2022-01-09 18:50:11 +00:00
|
|
|
// 'jump' in when it finally loads. The exception is if the cached background is
|
|
|
|
// getting invalidated, in which case we want to make sure to not get a partially
|
|
|
|
// faded texture rendered onto the new background.
|
|
|
|
if (mWindow->isInvalidatingCachedBackground())
|
|
|
|
mTexture->bind();
|
|
|
|
else
|
|
|
|
fadeIn(mTexture->bind());
|
|
|
|
|
2022-03-17 18:33:09 +00:00
|
|
|
mVertices->saturation = mSaturation * mThemeSaturation;
|
2022-03-11 22:17:04 +00:00
|
|
|
mVertices->opacity = mThemeOpacity;
|
2022-03-11 22:51:41 +00:00
|
|
|
mVertices->dimming = mDimming;
|
2022-04-18 19:37:58 +00:00
|
|
|
mVertices->reflectionsFalloff = mReflectionsFalloff;
|
2022-03-11 22:17:04 +00:00
|
|
|
|
2022-03-14 18:51:48 +00:00
|
|
|
mRenderer->drawTriangleStrips(&mVertices[0], 4);
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-07-02 18:33:50 +00:00
|
|
|
if (!mTexture) {
|
|
|
|
LOG(LogError) << "Image texture is not initialized";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
std::string textureFilePath = mTexture->getTextureFilePath();
|
|
|
|
if (textureFilePath != "") {
|
2021-07-07 18:31:46 +00:00
|
|
|
LOG(LogError) << "Image texture for file \"" << textureFilePath
|
|
|
|
<< "\" has zero size";
|
2021-07-02 18:33:50 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG(LogError) << "Image texture has zero size";
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 16:39:18 +00:00
|
|
|
mTexture.reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
GuiComponent::renderChildren(trans);
|
2012-08-02 01:43:55 +00:00
|
|
|
}
|
2012-09-04 16:45:16 +00:00
|
|
|
|
2017-01-22 23:28:06 +00:00
|
|
|
void ImageComponent::fadeIn(bool textureLoaded)
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
if (!mForceLoad) {
|
|
|
|
if (!textureLoaded) {
|
|
|
|
// Start the fade if this is the first time we've encountered the unloaded texture.
|
|
|
|
if (!mFading) {
|
|
|
|
// Start with a zero opacity and flag it as fading.
|
2022-02-11 21:10:25 +00:00
|
|
|
mFadeOpacity = 0.0f;
|
2020-06-28 16:39:18 +00:00
|
|
|
mFading = true;
|
|
|
|
updateColors();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (mFading) {
|
|
|
|
// The texture is loaded and we need to fade it in. The fade is based on the frame
|
|
|
|
// rate and is 1/4 second if running at 60 frames per second although the actual
|
|
|
|
// value is not that important.
|
2022-02-11 21:10:25 +00:00
|
|
|
float opacity {mFadeOpacity + 1.0f / 15.0f};
|
2020-06-28 16:39:18 +00:00
|
|
|
// See if we've finished fading.
|
2022-02-11 21:10:25 +00:00
|
|
|
if (opacity >= 1.0f) {
|
|
|
|
mFadeOpacity = 1.0f;
|
2020-06-28 16:39:18 +00:00
|
|
|
mFading = false;
|
|
|
|
}
|
|
|
|
else {
|
2022-02-11 21:10:25 +00:00
|
|
|
mFadeOpacity = opacity;
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
|
|
|
updateColors();
|
|
|
|
}
|
|
|
|
}
|
2017-01-22 23:28:06 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme,
|
|
|
|
const std::string& view,
|
|
|
|
const std::string& element,
|
|
|
|
unsigned int properties)
|
2014-01-01 05:39:22 +00:00
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
using namespace ThemeFlags;
|
2021-07-07 18:31:46 +00:00
|
|
|
GuiComponent::applyTheme(theme, view, element,
|
|
|
|
(properties ^ ThemeFlags::SIZE) |
|
|
|
|
((properties & (ThemeFlags::SIZE | POSITION)) ? ORIGIN : 0));
|
2019-08-25 15:23:02 +00:00
|
|
|
|
2022-08-23 20:24:24 +00:00
|
|
|
const ThemeData::ThemeElement* elem {theme->getElement(view, element, "image")};
|
2020-06-28 16:39:18 +00:00
|
|
|
if (!elem)
|
|
|
|
return;
|
2014-01-01 05:39:22 +00:00
|
|
|
|
2022-02-11 22:38:23 +00:00
|
|
|
glm::vec2 scale {getParent() ?
|
|
|
|
getParent()->getSize() :
|
|
|
|
glm::vec2(Renderer::getScreenWidth(), Renderer::getScreenHeight())};
|
2017-10-01 03:17:58 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (properties & ThemeFlags::SIZE) {
|
2022-06-04 15:25:36 +00:00
|
|
|
if (elem->has("size")) {
|
|
|
|
glm::vec2 imageSize {elem->get<glm::vec2>("size")};
|
|
|
|
if (imageSize == glm::vec2 {0.0f, 0.0f}) {
|
|
|
|
LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property <size> "
|
|
|
|
"for element \""
|
|
|
|
<< element.substr(6) << "\" is set to zero";
|
|
|
|
imageSize = {0.001f, 0.001f};
|
|
|
|
}
|
|
|
|
if (imageSize.x > 0.0f)
|
|
|
|
imageSize.x = glm::clamp(imageSize.x, 0.001f, 2.0f);
|
|
|
|
if (imageSize.y > 0.0f)
|
|
|
|
imageSize.y = glm::clamp(imageSize.y, 0.001f, 2.0f);
|
|
|
|
setResize(imageSize * scale);
|
|
|
|
}
|
|
|
|
else if (elem->has("maxSize")) {
|
|
|
|
glm::vec2 imageMaxSize {elem->get<glm::vec2>("maxSize")};
|
|
|
|
imageMaxSize.x = glm::clamp(imageMaxSize.x, 0.001f, 2.0f);
|
|
|
|
imageMaxSize.y = glm::clamp(imageMaxSize.y, 0.001f, 2.0f);
|
|
|
|
setMaxSize(imageMaxSize * scale);
|
|
|
|
}
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2014-01-23 21:30:32 +00:00
|
|
|
|
2022-02-13 10:45:06 +00:00
|
|
|
if (elem->has("interpolation")) {
|
|
|
|
const std::string interpolation {elem->get<std::string>("interpolation")};
|
|
|
|
if (interpolation == "linear") {
|
|
|
|
mLinearInterpolation = true;
|
|
|
|
}
|
|
|
|
else if (interpolation == "nearest") {
|
|
|
|
mLinearInterpolation = false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mLinearInterpolation = false;
|
|
|
|
LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<interpolation> defined as \""
|
2022-02-13 10:45:06 +00:00
|
|
|
<< interpolation << "\"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (elem->has("default"))
|
|
|
|
setDefaultImage(elem->get<std::string>("default"));
|
2019-07-04 01:10:17 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (properties & PATH && elem->has("path")) {
|
2022-08-23 20:24:24 +00:00
|
|
|
bool tile {elem->has("tile") && elem->get<bool>("tile")};
|
2020-06-28 16:39:18 +00:00
|
|
|
setImage(elem->get<std::string>("path"), tile);
|
|
|
|
}
|
2019-07-04 01:10:17 +00:00
|
|
|
|
2022-02-14 18:32:07 +00:00
|
|
|
if (properties && elem->has("imageType")) {
|
|
|
|
std::string imageTypes {elem->get<std::string>("imageType")};
|
|
|
|
for (auto& character : imageTypes) {
|
|
|
|
if (std::isspace(character))
|
|
|
|
character = ',';
|
|
|
|
}
|
|
|
|
imageTypes = Utils::String::replace(imageTypes, ",,", ",");
|
|
|
|
mThemeImageTypes = Utils::String::delimitedStringToVector(imageTypes, ",");
|
2022-08-19 15:07:45 +00:00
|
|
|
|
|
|
|
if (mThemeImageTypes.empty()) {
|
|
|
|
LOG(LogError) << "ImageComponent: Invalid theme configuration, property <imageType> "
|
|
|
|
"contains no values";
|
|
|
|
}
|
|
|
|
|
|
|
|
for (std::string& type : mThemeImageTypes) {
|
|
|
|
if (std::find(supportedImageTypes.cbegin(), supportedImageTypes.cend(), type) ==
|
|
|
|
supportedImageTypes.cend()) {
|
|
|
|
LOG(LogError)
|
|
|
|
<< "ImageComponent: Invalid theme configuration, property <imageType> "
|
|
|
|
"defined as \""
|
|
|
|
<< type << "\"";
|
|
|
|
mThemeImageTypes.clear();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<std::string> sortedTypes {mThemeImageTypes};
|
|
|
|
std::stable_sort(sortedTypes.begin(), sortedTypes.end());
|
|
|
|
|
|
|
|
if (std::adjacent_find(sortedTypes.begin(), sortedTypes.end()) != sortedTypes.end()) {
|
|
|
|
LOG(LogError) << "ImageComponent: Invalid theme configuration, property <imageType> "
|
|
|
|
"contains duplicate values";
|
|
|
|
mThemeImageTypes.clear();
|
|
|
|
}
|
2022-02-14 18:32:07 +00:00
|
|
|
}
|
2022-01-29 17:41:22 +00:00
|
|
|
|
2022-08-18 20:51:21 +00:00
|
|
|
if (elem->has("metadataElement") && elem->get<bool>("metadataElement"))
|
|
|
|
mComponentThemeFlags |= ComponentThemeFlags::METADATA_ELEMENT;
|
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (properties & COLOR) {
|
|
|
|
if (elem->has("color"))
|
|
|
|
setColorShift(elem->get<unsigned int>("color"));
|
|
|
|
if (elem->has("colorEnd"))
|
|
|
|
setColorShiftEnd(elem->get<unsigned int>("colorEnd"));
|
2022-02-11 17:44:24 +00:00
|
|
|
if (elem->has("gradientType")) {
|
|
|
|
const std::string gradientType {elem->get<std::string>("gradientType")};
|
|
|
|
if (gradientType == "horizontal") {
|
|
|
|
setColorGradientHorizontal(true);
|
|
|
|
}
|
|
|
|
else if (gradientType == "vertical") {
|
|
|
|
setColorGradientHorizontal(false);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
setColorGradientHorizontal(true);
|
|
|
|
LOG(LogWarning) << "ImageComponent: Invalid theme configuration, property "
|
2022-03-18 19:31:04 +00:00
|
|
|
"<gradientType> defined as \""
|
2022-02-11 17:44:24 +00:00
|
|
|
<< gradientType << "\"";
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 16:39:18 +00:00
|
|
|
}
|
2022-01-30 18:30:38 +00:00
|
|
|
|
|
|
|
if (elem->has("scrollFadeIn") && elem->get<bool>("scrollFadeIn"))
|
|
|
|
mComponentThemeFlags |= ComponentThemeFlags::SCROLL_FADE_IN;
|
2014-01-01 05:39:22 +00:00
|
|
|
}
|
2014-05-16 21:21:33 +00:00
|
|
|
|
|
|
|
std::vector<HelpPrompt> ImageComponent::getHelpPrompts()
|
|
|
|
{
|
2020-06-28 16:39:18 +00:00
|
|
|
std::vector<HelpPrompt> ret;
|
|
|
|
ret.push_back(HelpPrompt("a", "select"));
|
|
|
|
return ret;
|
2014-05-16 21:21:33 +00:00
|
|
|
}
|