Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for image cropping inside ctrlImageButton #1536

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion libs/common/include/helpers/mathFuncs.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,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;
tehKaiN marked this conversation as resolved.
Show resolved Hide resolved
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;
tehKaiN marked this conversation as resolved.
Show resolved Hide resolved
};
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 @@ -238,12 +240,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be tested too. Especially coming up with a few corner cases. I.e. at least percent = 0 or 100 and some values where you notice the (correct) usage of floor/ceil below. I.e. choose appropriate image size and percent values.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some unit test here, but I'm not quite sure on how I should approach the float/ceil thingy - I can't really subclass glSmartBitmap and mock drawRect unless I make it virtual, which I think shouldn't be done just for unit tests. The texture coords don't follow image size in pixels, for example I tried to create 50x100 bitmap and coord span for the texture was something like 0 .. 0.7825. Checking for magic float values seems not to be the best approach either.


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 @@ -253,35 +275,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)));
tehKaiN marked this conversation as resolved.
Show resolved Hide resolved
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));
tehKaiN marked this conversation as resolved.
Show resolved Hide resolved

{
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
Loading