ES-DE/es-core/src/components/ImageComponent.cpp

393 lines
10 KiB
C++
Raw Normal View History

#include "components/ImageComponent.h"
2017-11-01 22:21:10 +00:00
#include "resources/TextureResource.h"
#include "Log.h"
#include "Renderer.h"
#include "Settings.h"
#include "ThemeData.h"
Vector2i ImageComponent::getTextureSize() const
{
if(mTexture)
return mTexture->getSize();
else
return Vector2i::Zero();
2013-08-06 13:15:20 +00:00
}
ImageComponent::ImageComponent(Window* window, bool forceLoad, bool dynamic) : GuiComponent(window),
mTargetIsMax(false), mFlipX(false), mFlipY(false), mTargetSize(0, 0), mColorShift(0xFFFFFFFF),
mForceLoad(forceLoad), mDynamic(dynamic), mFadeOpacity(0), mFading(false), mRotateByTargetSize(false)
{
updateColors();
}
2013-06-02 15:08:32 +00:00
ImageComponent::~ImageComponent()
{
}
void ImageComponent::resize()
{
if(!mTexture)
return;
const Vector2f textureSize = mTexture->getSourceImageSize();
if(textureSize == Vector2f::Zero())
return;
if(mTexture->isTiled())
{
mSize = mTargetSize;
}else{
// SVG rasterization is determined by height (see SVGResource.cpp), and rasterization is done in terms of pixels
// if rounding is off enough in the rasterization step (for images with extreme aspect ratios), it can cause cutoff when the aspect ratio breaks
// so, we always make sure the resultant height is an integer to make sure cutoff doesn't happen, and scale width from that
// (you'll see this scattered throughout the function)
// this is probably not the best way, so if you're familiar with this problem and have a better solution, please make a pull request!
if(mTargetIsMax)
{
mSize = textureSize;
Vector2f resizeScale((mTargetSize.x() / mSize.x()), (mTargetSize.y() / mSize.y()));
if(resizeScale.x() < resizeScale.y())
{
mSize[0] *= resizeScale.x();
mSize[1] *= resizeScale.x();
}else{
mSize[0] *= resizeScale.y();
mSize[1] *= resizeScale.y();
}
// for SVG rasterization, always calculate width from rounded height (see comment above)
2017-11-13 22:16:38 +00:00
mSize[1] = Math::round(mSize[1]);
mSize[0] = (mSize[1] / textureSize.y()) * textureSize.x();
}else{
// if both components are set, we just stretch
// if no components are set, we don't resize at all
mSize = mTargetSize == Vector2f::Zero() ? textureSize : mTargetSize;
// if only one component is set, we resize in a way that maintains aspect ratio
// for SVG rasterization, we always calculate width from rounded height (see comment above)
if(!mTargetSize.x() && mTargetSize.y())
{
2017-11-13 22:16:38 +00:00
mSize[1] = Math::round(mTargetSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}else if(mTargetSize.x() && !mTargetSize.y())
{
2017-11-13 22:16:38 +00:00
mSize[1] = Math::round((mTargetSize.x() / textureSize.x()) * textureSize.y());
mSize[0] = (mSize.y() / textureSize.y()) * textureSize.x();
}
}
}
// mSize.y() should already be rounded
2017-11-17 14:58:52 +00:00
mTexture->rasterizeAt((size_t)Math::round(mSize.x()), (size_t)Math::round(mSize.y()));
onSizeChanged();
}
void ImageComponent::onSizeChanged()
{
updateVertices();
}
void ImageComponent::setDefaultImage(std::string path)
{
mDefaultPath = path;
}
void ImageComponent::setImage(std::string path, bool tile)
{
if(path.empty() || !ResourceManager::getInstance()->fileExists(path))
{
if(mDefaultPath.empty() || !ResourceManager::getInstance()->fileExists(mDefaultPath))
mTexture.reset();
else
mTexture = TextureResource::get(mDefaultPath, tile, mForceLoad, mDynamic);
} else {
mTexture = TextureResource::get(path, tile, mForceLoad, mDynamic);
}
resize();
}
void ImageComponent::setImage(const char* path, size_t length, bool tile)
{
mTexture.reset();
mTexture = TextureResource::get("", tile);
mTexture->initFromMemory(path, length);
resize();
}
void ImageComponent::setImage(const std::shared_ptr<TextureResource>& texture)
{
mTexture = texture;
resize();
}
void ImageComponent::setResize(float width, float height)
{
mTargetSize = Vector2f(width, height);
mTargetIsMax = false;
resize();
}
void ImageComponent::setMaxSize(float width, float height)
{
mTargetSize = Vector2f(width, height);
mTargetIsMax = true;
resize();
}
Vector2f ImageComponent::getRotationSize() const
{
return mRotateByTargetSize ? mTargetSize : mSize;
}
void ImageComponent::setRotateByTargetSize(bool rotate)
{
mRotateByTargetSize = rotate;
}
2013-06-02 15:08:32 +00:00
void ImageComponent::setFlipX(bool flip)
2012-10-07 22:25:51 +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
{
mFlipY = flip;
updateVertices();
2012-10-07 22:25:51 +00:00
}
void ImageComponent::setColorShift(unsigned int color)
{
mColorShift = color;
// Grab the opacity from the color shift because we may need to apply it if
// fading textures in
mOpacity = color & 0xff;
updateColors();
}
void ImageComponent::setOpacity(unsigned char opacity)
{
mOpacity = opacity;
mColorShift = (mColorShift >> 8 << 8) | mOpacity;
updateColors();
}
void ImageComponent::updateVertices()
{
if(!mTexture || !mTexture->isInitialized())
return;
// 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
Vector2f topLeft(0.0, 0.0);
2017-11-13 22:16:38 +00:00
Vector2f bottomRight(Math::round(mSize.x()), Math::round(mSize.y()));
mVertices[0].pos = Vector2f(topLeft.x(), topLeft.y());
mVertices[1].pos = Vector2f(topLeft.x(), bottomRight.y());
mVertices[2].pos = Vector2f(bottomRight.x(), topLeft.y());
mVertices[3].pos = Vector2f(bottomRight.x(), topLeft.y());
mVertices[4].pos = Vector2f(topLeft.x(), bottomRight.y());
mVertices[5].pos = Vector2f(bottomRight.x(), bottomRight.y());
float px, py;
if(mTexture->isTiled())
{
px = mSize.x() / getTextureSize().x();
py = mSize.y() / getTextureSize().y();
}else{
px = 1;
py = 1;
}
mVertices[0].tex = Vector2f(0, py);
mVertices[1].tex = Vector2f(0, 0);
mVertices[2].tex = Vector2f(px, py);
mVertices[3].tex = Vector2f(px, py);
mVertices[4].tex = Vector2f(0, 0);
mVertices[5].tex = Vector2f(px, 0);
2012-10-07 22:25:51 +00:00
if(mFlipX)
{
for(int i = 0; i < 6; i++)
mVertices[i].tex[0] = mVertices[i].tex[0] == px ? 0 : px;
2012-10-07 22:25:51 +00:00
}
if(mFlipY)
{
2018-04-01 15:18:12 +00:00
for(int i = 0; i < 6; i++)
mVertices[i].tex[1] = mVertices[i].tex[1] == py ? 0 : py;
2012-10-07 22:25:51 +00:00
}
}
void ImageComponent::updateColors()
{
Renderer::buildGLColorArray(mColors, mColorShift, 6);
}
void ImageComponent::render(const Transform4x4f& parentTrans)
{
Transform4x4f trans = parentTrans * getTransform();
Renderer::setMatrix(trans);
if(mTexture && mOpacity > 0)
{
if(Settings::getInstance()->getBool("DebugImage")) {
Vector2f targetSizePos = (mTargetSize - mSize) * mOrigin * -1;
Renderer::drawRect(targetSizePos.x(), targetSizePos.y(), mTargetSize.x(), mTargetSize.y(), 0xFF000033);
Renderer::drawRect(0.0f, 0.0f, mSize.x(), mSize.y(), 0x00000033);
}
if(mTexture->isInitialized())
{
// 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 'jump' in
// when it finally loads
fadeIn(mTexture->bind());
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
2012-10-17 17:15:58 +00:00
glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &mVertices[0].pos);
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &mVertices[0].tex);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, mColors);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
}else{
LOG(LogError) << "Image texture is not initialized!";
mTexture.reset();
}
}
2012-10-17 17:15:58 +00:00
GuiComponent::renderChildren(trans);
}
void ImageComponent::fadeIn(bool textureLoaded)
{
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
mFadeOpacity = 0;
mFading = true;
// Set the colours to be translucent
mColorShift = (mColorShift >> 8 << 8) | 0;
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
int opacity = mFadeOpacity + 255 / 15;
// See if we've finished fading
if (opacity >= 255)
{
mFadeOpacity = 255;
mFading = false;
}
else
{
mFadeOpacity = (unsigned char)opacity;
}
// Apply the combination of the target opacity and current fade
float newOpacity = (float)mOpacity * ((float)mFadeOpacity / 255.0f);
mColorShift = (mColorShift >> 8 << 8) | (unsigned char)newOpacity;
updateColors();
}
}
}
2013-06-02 15:08:32 +00:00
bool ImageComponent::hasImage()
{
return (bool)mTexture;
}
2013-04-08 16:52:40 +00:00
void ImageComponent::applyTheme(const std::shared_ptr<ThemeData>& theme, const std::string& view, const std::string& element, unsigned int properties)
{
using namespace ThemeFlags;
const ThemeData::ThemeElement* elem = theme->getElement(view, element, "image");
if(!elem)
{
return;
}
Vector2f scale = getParent() ? getParent()->getSize() : Vector2f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
if(properties & POSITION && elem->has("pos"))
{
Vector2f denormalized = elem->get<Vector2f>("pos") * scale;
setPosition(Vector3f(denormalized.x(), denormalized.y(), 0));
}
if(properties & ThemeFlags::SIZE)
{
if(elem->has("size"))
setResize(elem->get<Vector2f>("size") * scale);
else if(elem->has("maxSize"))
setMaxSize(elem->get<Vector2f>("maxSize") * scale);
}
// position + size also implies origin
if((properties & ORIGIN || (properties & POSITION && properties & ThemeFlags::SIZE)) && elem->has("origin"))
setOrigin(elem->get<Vector2f>("origin"));
if(elem->has("default")) {
setDefaultImage(elem->get<std::string>("default"));
}
if(properties & PATH && elem->has("path"))
{
bool tile = (elem->has("tile") && elem->get<bool>("tile"));
setImage(elem->get<std::string>("path"), tile);
}
if(properties & COLOR && elem->has("color"))
setColorShift(elem->get<unsigned int>("color"));
if(properties & ThemeFlags::ROTATION) {
if(elem->has("rotation"))
setRotationDegrees(elem->get<float>("rotation"));
if(elem->has("rotationOrigin"))
setRotationOrigin(elem->get<Vector2f>("rotationOrigin"));
}
if(properties & ThemeFlags::Z_INDEX && elem->has("zIndex"))
setZIndex(elem->get<float>("zIndex"));
else
setZIndex(getDefaultZIndex());
}
std::vector<HelpPrompt> ImageComponent::getHelpPrompts()
{
std::vector<HelpPrompt> ret;
ret.push_back(HelpPrompt("a", "select"));
return ret;
}