Skip to content

Commit

Permalink
Add support for image cropping inside ctrlImageButton
Browse files Browse the repository at this point in the history
  • Loading branch information
tehKaiN committed Oct 1, 2022
1 parent 0537322 commit 783cb2a
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 26 deletions.
3 changes: 2 additions & 1 deletion libs/common/include/helpers/mathFuncs.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ constexpr T interpolate(const T startVal, const T endVal, const U elapsedTime, c
}

/// Linear interpolation, similar to C++20's std::lerp()
constexpr float lerp(const float startVal, const float endVal, const float ratio) noexcept
template<typename T>
constexpr T lerp(const T startVal, const T endVal, const T ratio) noexcept
{
return startVal + ratio * (endVal - startVal);
}
Expand Down
41 changes: 36 additions & 5 deletions libs/s25main/controls/ctrlBaseImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,44 @@ Rect ctrlBaseImage::GetImageRect() const
return Rect(-img_->GetOrigin(), img_->GetSize());
}

void ctrlBaseImage::DrawImage(const DrawPoint& pos) const
void ctrlBaseImage::DrawImage(const Rect& dstArea) const
{
DrawImage(pos, modulationColor_);
DrawImage(dstArea, modulationColor_);
}

void ctrlBaseImage::DrawImage(const DrawPoint& pos, unsigned color) const
void ctrlBaseImage::DrawImage(const Rect& dstArea, unsigned color) const
{
if(img_)
img_->DrawFull(pos, color);
if(img_ == nullptr)
return;

auto dst = dstArea;
auto imageSize = img_->GetSize();
auto dstSize = dstArea.getSize();
Rect srcArea = Rect(DrawPoint::all(0), imageSize);

if(imageSize.x > dstSize.x)
{
auto halfDelta = (imageSize.x - dstSize.x) / 2;
srcArea.left += halfDelta;
srcArea.right -= halfDelta;
} else if(imageSize.x < dstSize.x)
{
auto halfDelta = (dstSize.x - imageSize.x) / 2;
dst.left += halfDelta;
dst.right -= halfDelta;
}

if(imageSize.y > dstSize.y)
{
auto halfDelta = (imageSize.y - dstSize.y) / 2;
srcArea.top += halfDelta;
srcArea.bottom -= halfDelta;
} else if(imageSize.y < dstSize.y)
{
auto halfDelta = (dstSize.y - imageSize.y) / 2;
dst.top += halfDelta;
dst.bottom -= halfDelta;
}

img_->Draw(dst, srcArea, color);
}
6 changes: 4 additions & 2 deletions libs/s25main/controls/ctrlBaseImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ class ctrlBaseImage
/// Swap the images of those controls
void SwapImage(ctrlBaseImage& other);
Rect GetImageRect() const;
void DrawImage(const DrawPoint& pos) const;
void DrawImage(const DrawPoint& pos, unsigned color) const;

/// Draw the image on specified rectangular area. The image is centered inside dstArea and cropped to its size.
void DrawImage(const Rect& dstArea) const;
void DrawImage(const Rect& dstArea, unsigned color) const;

private:
ITexture* img_;
Expand Down
2 changes: 1 addition & 1 deletion libs/s25main/controls/ctrlImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ ctrlImage::~ctrlImage() = default;
*/
void ctrlImage::Draw_()
{
DrawImage(GetDrawPos());
DrawImage(Rect(GetDrawPos(), GetImageRect().getSize()));
}

bool ctrlImage::Msg_MouseMove(const MouseCoords& mc)
Expand Down
23 changes: 20 additions & 3 deletions libs/s25main/controls/ctrlImageButton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include "ctrlImageButton.h"
#include <ogl/ITexture.h>

ctrlImageButton::ctrlImageButton(Window* parent, unsigned id, const DrawPoint& pos, const Extent& size,
const TextureColor tc, ITexture* const image, const std::string& tooltip)
Expand All @@ -11,11 +12,27 @@ ctrlImageButton::ctrlImageButton(Window* parent, unsigned id, const DrawPoint& p

void ctrlImageButton::DrawContent() const
{
DrawPoint pos = GetDrawPos() + DrawPoint(GetSize()) / 2;
// Adding of origin compensates for its substraction inside ITexture::Draw()
auto pos = GetDrawPos() + GetImage()->GetOrigin();
auto size = GetSize();

if(hasBorder)
{
// Ensure that 3D border is not drawn on
const unsigned borderThickness = 2;
pos += DrawPoint::all(borderThickness);
size -= Extent::all(2 * borderThickness);
}

if((state == ButtonState::Pressed || isChecked) && isEnabled)
{
pos += DrawPoint::all(2);
size -= Extent::all(2);
}

Rect drawRect(pos, size);
if(!isEnabled && GetModulationColor() == COLOR_WHITE)
DrawImage(pos, 0xFF555555);
DrawImage(drawRect, 0xFF555555);
else
DrawImage(pos);
DrawImage(drawRect);
}
5 changes: 5 additions & 0 deletions libs/s25main/ogl/ITexture.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma once

#include "Point.h"
#include "Rect.h"

class ITexture
{
Expand All @@ -15,4 +16,8 @@ class ITexture
virtual Position GetOrigin() const = 0;
virtual Extent GetSize() const = 0;
virtual void DrawFull(const Position& dstPos, unsigned color = 0xFFFFFFFFu) = 0;

/// Draws portion of image specified by srcArea on area defined by dstArea.
/// In case of srcArea and dstArea size mismatch, scaling will occur.
virtual void Draw(Rect dstArea, Rect srcArea, unsigned color = 0xFFFFFFFFu) = 0;
};
8 changes: 4 additions & 4 deletions libs/s25main/ogl/glArchivItem_Bitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ class glArchivItem_Bitmap : public virtual libsiedler2::baseArchivItem_Bitmap, p
void DrawPart(const Rect& destArea, const DrawPoint& offset, unsigned color = COLOR_WHITE);
/// Draw a rectangular part of the texture from the origin of it
void DrawPart(const Rect& destArea, unsigned color = COLOR_WHITE);
/// Draw only percent% of the height of the image
/// Draw only percent% of the height of the image, counting from the bottom of the image
void DrawPercent(const DrawPoint& dstPos, unsigned percent, unsigned color = COLOR_WHITE);

protected:
/// Draw the texture.
/// src_w/h default to the full bitmap size
/// dst_w/h default the src_w/h
void Draw(Rect dstArea, Rect srcArea, unsigned color = COLOR_WHITE);
void Draw(Rect dstArea, Rect srcArea, unsigned color = COLOR_WHITE) override;

protected:
void FillTexture() override;
Extent CalcTextureSize() const override;
};
5 changes: 5 additions & 0 deletions libs/s25main/ogl/glArchivItem_Bitmap_Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ void glArchivItem_Bitmap_Player::drawForPlayer(const DrawPoint& dst, unsigned pl
DrawFull(dst, COLOR_WHITE, playerColor);
}

void glArchivItem_Bitmap_Player::Draw(Rect dstArea, Rect srcArea, unsigned color /*= COLOR_WHITE*/)
{
Draw(dstArea, srcArea, color, COLOR_WHITE);
}

void glArchivItem_Bitmap_Player::Draw(Rect dstArea, Rect srcArea, unsigned color /*= COLOR_WHITE*/,
unsigned player_color /*= COLOR_WHITE*/)
{
Expand Down
1 change: 1 addition & 0 deletions libs/s25main/ogl/glArchivItem_Bitmap_Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class glArchivItem_Bitmap_Player : public libsiedler2::ArchivItem_Bitmap_Player,
virtual void DrawFull(const Position& dstPos, unsigned color = COLOR_WHITE) override;
/// Draw in player colors
void drawForPlayer(const DrawPoint& dst, unsigned playerColor);
void Draw(Rect dstArea, Rect srcArea, unsigned color = COLOR_WHITE) override;

protected:
void Draw(Rect dstArea, Rect srcArea, unsigned color = COLOR_WHITE, unsigned player_color = COLOR_WHITE);
Expand Down
46 changes: 36 additions & 10 deletions libs/s25main/ogl/glSmartBitmap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
#include "glSmartBitmap.h"
#include "Loader.h"
#include "drivers/VideoDriverWrapper.h"
#include "helpers/mathFuncs.h"
#include "ogl/glBitmapItem.h"
#include "libsiedler2/ArchivItem_Bitmap.h"
#include "libsiedler2/ArchivItem_Bitmap_Player.h"
#include "libsiedler2/PixelBufferBGRA.h"
#include "s25util/colors.h"
#include <glad/glad.h>
#include <cmath>
#include <limits>

namespace {
Expand Down Expand Up @@ -240,12 +242,32 @@ void glSmartBitmap::generateTexture()
}
}

void glSmartBitmap::Draw(Rect dstArea, Rect srcArea, unsigned color /*= 0xFFFFFFFF*/)
{
drawRect(dstArea, srcArea, color);
}

void glSmartBitmap::draw(DrawPoint drawPt, unsigned color, unsigned player_color)
{
drawPercent(drawPt, 100, color, player_color);
}

void glSmartBitmap::drawPercent(DrawPoint drawPt, unsigned percent, unsigned color, unsigned player_color)
{
// nothing to draw?
if(!percent)
return;
RTTR_Assert(percent <= 100);

const float partDrawn = percent / 100.f;
auto startY = int(std::floor(size_.y * (1 - partDrawn)));
auto endY = int(std::ceil(size_.y * partDrawn));
Rect dstArea(drawPt.x, drawPt.y + startY, size_.x, endY);
Rect srcArea(0, startY, size_.x, endY);
drawRect(dstArea, srcArea, color, player_color);
}

void glSmartBitmap::drawRect(Rect dstArea, Rect srcArea, unsigned color /*= 0xFFFFFFFF*/, unsigned player_color /*= 0*/)
{
if(!texture)
{
Expand All @@ -255,35 +277,39 @@ void glSmartBitmap::drawPercent(DrawPoint drawPt, unsigned percent, unsigned col
return;
}

// nothing to draw?
if(!percent)
return;
RTTR_Assert(percent <= 100);

const float partDrawn = percent / 100.f;
std::array<Point<GLfloat>, 8> vertices, curTexCoords;
std::array<GL_RGBAColor, 8> colors;

auto drawPt = dstArea.getOrigin();
drawPt -= origin_;
vertices[2] = Point<GLfloat>(drawPt) + size_;
vertices[2] = Point<GLfloat>(dstArea.getEndPt() - origin_); // destination bottom

vertices[0].x = vertices[1].x = GLfloat(drawPt.x);
vertices[3].x = vertices[2].x;

vertices[0].y = vertices[3].y = GLfloat(drawPt.y + size_.y * (1.f - partDrawn));
vertices[1].y = vertices[2].y;
vertices[0].y = vertices[3].y = GLfloat(drawPt.y); // destination top
vertices[1].y = vertices[2].y; // destination bottom

colors[0].r = GetRed(color);
colors[0].g = GetGreen(color);
colors[0].b = GetBlue(color);
colors[0].a = GetAlpha(color);
colors[3] = colors[2] = colors[1] = colors[0];

// vertical coords
curTexCoords[0] = texCoords[0];
curTexCoords[1] = texCoords[1];
curTexCoords[2] = texCoords[2];
curTexCoords[3] = texCoords[3];
curTexCoords[0].y = curTexCoords[3].y = curTexCoords[1].y - (curTexCoords[1].y - curTexCoords[0].y) * partDrawn;
curTexCoords[0].y = curTexCoords[3].y = helpers::lerp(
texCoords[0].y, texCoords[1].y, helpers::inverseLerp(.0f, float(size_.y), float(srcArea.getOrigin().y)));
curTexCoords[1].y = curTexCoords[2].y = helpers::lerp(
texCoords[0].y, texCoords[1].y, helpers::inverseLerp(.0f, float(size_.y), float(srcArea.getEndPt().y)));
// horizontal coords
curTexCoords[0].x = curTexCoords[1].x = helpers::lerp(
texCoords[0].x, texCoords[3].x, helpers::inverseLerp(.0f, float(size_.x), float(srcArea.getOrigin().x)));
curTexCoords[2].x = curTexCoords[3].x = helpers::lerp(
texCoords[0].x, texCoords[3].x, helpers::inverseLerp(.0f, float(size_.x), float(srcArea.getEndPt().x)));

int numQuads;
if(player_color && hasPlayer)
Expand Down
3 changes: 3 additions & 0 deletions libs/s25main/ogl/glSmartBitmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ class glSmartBitmap : public ITexture

void generateTexture();
void DrawFull(const Position& dstPos, unsigned color = 0xFFFFFFFF) override { draw(dstPos, color); }
void Draw(Rect dstArea, Rect srcArea, unsigned color = 0xFFFFFFFF) override;
void drawRect(Rect dstArea, Rect srcArea, unsigned color = 0xFFFFFFFF, unsigned player_color = 0);
void draw(DrawPoint drawPt, unsigned color = 0xFFFFFFFF, unsigned player_color = 0);
void drawForPlayer(DrawPoint drawPt, unsigned player_color) { draw(drawPt, 0xFFFFFFFF, player_color); }
/// Draw only percent% of the height of the image, counting from the bottom of the image
void drawPercent(DrawPoint drawPt, unsigned percent, unsigned color = 0xFFFFFFFF, unsigned player_color = 0);
/// Draw the bitmap(s) to the specified buffer at the position starting at bufOffset (must be positive)
void drawTo(libsiedler2::PixelBufferBGRA& buffer, const Extent& bufOffset = Extent(0, 0)) const;
Expand Down
79 changes: 79 additions & 0 deletions tests/s25Main/UI/testImageButton.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (C) 2005 - 2022 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#include "RectOutput.h"
#include "controls/ctrlImageButton.h"
#include "ogl/ITexture.h"
#include "uiHelper/uiHelpers.hpp"
#include <rttr/test/random.hpp>
#include <turtle/mock.hpp>
#include <boost/test/unit_test.hpp>

using namespace rttr::test;

namespace {
MOCK_BASE_CLASS(TestTexture, ITexture)
{
public:
TestTexture(Extent size, Position origin) : size_(size), origin_(origin) {}
Position GetOrigin() const override { return origin_; }
Extent GetSize() const override { return size_; }
MOCK_NON_CONST_METHOD(DrawFull, 2) // LCOV_EXCL_LINE
MOCK_NON_CONST_METHOD(Draw, 3)

private:
Extent size_;
Position origin_;
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(ImageButton, uiHelper::Fixture)

BOOST_AUTO_TEST_CASE(DrawSmallerImageThanButton)
{
Window wnd(nullptr, randomValue<unsigned>(), DrawPoint::all(0));

{
TestTexture img(Extent(20, 10), Position(0, 0));
ctrlImageButton btn(&wnd, randomValue<unsigned>(), DrawPoint(10, 5), Extent(40, 30), TextureColor::Green1, &img,
"");
MOCK_EXPECT(img.Draw).once().with(Rect(20, 15, 20, 10), Rect(0, 0, 20, 10), 0xFFFFFFFFu);
btn.Draw();
}

{
TestTexture img(Extent(20, 10), Position(13, 2));
ctrlImageButton btn(&wnd, randomValue<unsigned>(), DrawPoint(10, 5), Extent(40, 30), TextureColor::Green1, &img,
"");
MOCK_EXPECT(img.Draw).once().with(Rect(33, 17, 20, 10), Rect(0, 0, 20, 10), 0xFFFFFFFFu);
btn.Draw();
}
}

BOOST_AUTO_TEST_CASE(DrawLargerImageThanButton)
{
Window wnd(nullptr, randomValue<unsigned>(), DrawPoint::all(0));

{
TestTexture img(Extent(100, 80), Position(0, 0));
ctrlImageButton btn(&wnd, randomValue<unsigned>(), DrawPoint(10, 20), Extent(40, 30), TextureColor::Green1,
&img, "");
// Note that the image's center part is drawn, shrinked by 2px from each side to prevent dirtying 3d button's
// ridge
MOCK_EXPECT(img.Draw).once().with(Rect(12, 22, 36, 26), Rect(32, 27, 36, 26), 0xFFFFFFFFu);
btn.Draw();
}

{
TestTexture img(Extent(100, 80), Position(68, 32));
ctrlImageButton btn(&wnd, randomValue<unsigned>(), DrawPoint(10, 20), Extent(40, 30), TextureColor::Green1,
&img, "");
// Note that the image's center part is drawn, shrinked by 2px from each side to prevent dirtying 3d button's
// ridge
MOCK_EXPECT(img.Draw).once().with(Rect(80, 54, 36, 26), Rect(32, 27, 36, 26), 0xFFFFFFFFu);
btn.Draw();
}
}

BOOST_AUTO_TEST_SUITE_END()
Loading

0 comments on commit 783cb2a

Please sign in to comment.