Skip to content

Commit a6314ed

Browse files
committed
Add getBounds() method to IRenderedTarget
1 parent c56e995 commit a6314ed

File tree

5 files changed

+129
-0
lines changed

5 files changed

+129
-0
lines changed

src/irenderedtarget.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class IRenderedTarget : public QNanoQuickItem
6767
virtual qreal height() const = 0;
6868
virtual void setHeight(qreal width) = 0;
6969

70+
virtual libscratchcpp::Rect getBounds() const = 0;
71+
7072
virtual QPointF mapFromScene(const QPointF &point) const = 0;
7173

7274
virtual bool mirrorHorizontally() const = 0;

src/renderedtarget.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <scratchcpp/iengine.h>
44
#include <scratchcpp/costume.h>
5+
#include <scratchcpp/rect.h>
56
#include <QtSvg/QSvgRenderer>
67
#include <qnanopainter.h>
78

@@ -17,6 +18,7 @@ using namespace scratchcpprender;
1718
using namespace libscratchcpp;
1819

1920
static const double SVG_SCALE_LIMIT = 0.1; // the maximum viewport dimensions are multiplied by this
21+
static const double pi = std::acos(-1); // TODO: Use std::numbers::pi in C++20
2022

2123
RenderedTarget::RenderedTarget(QNanoQuickItem *parent) :
2224
IRenderedTarget(parent)
@@ -327,6 +329,43 @@ void RenderedTarget::setHeight(qreal height)
327329
QNanoQuickItem::setHeight(height);
328330
}
329331

332+
Rect RenderedTarget::getBounds() const
333+
{
334+
// https://github.com/scratchfoundation/scratch-render/blob/c3ede9c3d54769730c7b023021511e2aba167b1f/src/Rectangle.js#L33-L55
335+
if (!m_costume || !m_skin || !m_texture.isValid())
336+
return Rect(m_x, m_y, m_x, m_y);
337+
338+
const double width = m_texture.width() * m_size / scale() / m_costume->bitmapResolution();
339+
const double height = m_texture.height() * m_size / scale() / m_costume->bitmapResolution();
340+
const double originX = m_stageScale * m_costume->rotationCenterX() * m_size / scale() / m_costume->bitmapResolution() - width / 2;
341+
const double originY = m_stageScale * -m_costume->rotationCenterY() * m_size / scale() / m_costume->bitmapResolution() + height / 2;
342+
const double rot = -rotation() * pi / 180;
343+
double left = std::numeric_limits<double>::infinity();
344+
double top = -std::numeric_limits<double>::infinity();
345+
double right = -std::numeric_limits<double>::infinity();
346+
double bottom = std::numeric_limits<double>::infinity();
347+
348+
for (const QPointF &point : m_hullPoints) {
349+
QPointF transformed = transformPoint(point.x() - width / 2, height / 2 - point.y(), originX, originY, rot);
350+
const double x = transformed.x() * scale() / m_stageScale * (m_mirrorHorizontally ? -1 : 1);
351+
const double y = transformed.y() * scale() / m_stageScale;
352+
353+
if (x < left)
354+
left = x;
355+
356+
if (x > right)
357+
right = x;
358+
359+
if (y > top)
360+
top = y;
361+
362+
if (y < bottom)
363+
bottom = y;
364+
}
365+
366+
return Rect(left + m_x, top + m_y, right + m_x, bottom + m_y);
367+
}
368+
330369
QPointF RenderedTarget::mapFromScene(const QPointF &point) const
331370
{
332371
return QNanoQuickItem::mapFromScene(point);
@@ -537,6 +576,15 @@ void RenderedTarget::handleSceneMouseMove(qreal x, qreal y)
537576
}
538577
}
539578

579+
QPointF RenderedTarget::transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const
580+
{
581+
const double cosRot = std::cos(rot);
582+
const double sinRot = std::sin(rot);
583+
const double x = (scratchX - originX) * cosRot - (scratchY - originY) * sinRot;
584+
const double y = (scratchX - originX) * sinRot + (scratchY - originY) * cosRot;
585+
return QPointF(x, y);
586+
}
587+
540588
bool RenderedTarget::mirrorHorizontally() const
541589
{
542590
return m_mirrorHorizontally;

src/renderedtarget.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class RenderedTarget : public IRenderedTarget
7474
qreal height() const override;
7575
void setHeight(qreal height) override;
7676

77+
libscratchcpp::Rect getBounds() const override;
78+
7779
QPointF mapFromScene(const QPointF &point) const override;
7880

7981
bool mirrorHorizontally() const override;
@@ -104,6 +106,7 @@ class RenderedTarget : public IRenderedTarget
104106
void calculateRotation();
105107
void calculateSize();
106108
void handleSceneMouseMove(qreal x, qreal y);
109+
QPointF transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const;
107110

108111
libscratchcpp::IEngine *m_engine = nullptr;
109112
libscratchcpp::Costume *m_costume = nullptr;

test/mocks/renderedtargetmock.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <irenderedtarget.h>
44
#include <texture.h>
55
#include <qnanoquickitem.h>
6+
#include <scratchcpp/rect.h>
67
#include <gmock/gmock.h>
78

89
using namespace scratchcpprender;
@@ -54,6 +55,8 @@ class RenderedTargetMock : public IRenderedTarget
5455

5556
MOCK_METHOD(QPointF, mapFromScene, (const QPointF &), (const, override));
5657

58+
MOCK_METHOD(libscratchcpp::Rect, getBounds, (), (const, override));
59+
5760
MOCK_METHOD(bool, mirrorHorizontally, (), (const, override));
5861

5962
MOCK_METHOD(Texture, texture, (), (const, override));

test/renderedtarget/renderedtarget_test.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <scratchcpp/stage.h>
1010
#include <scratchcpp/sprite.h>
1111
#include <scratchcpp/costume.h>
12+
#include <scratchcpp/rect.h>
1213
#include <enginemock.h>
1314

1415
#include "../common.h"
@@ -554,3 +555,75 @@ TEST_F(RenderedTargetTest, StageScale)
554555
target.setStageScale(6.4);
555556
ASSERT_EQ(target.stageScale(), 6.4);
556557
}
558+
559+
TEST_F(RenderedTargetTest, GetBounds)
560+
{
561+
QOpenGLContext context;
562+
QOffscreenSurface surface;
563+
createContextAndSurface(&context, &surface);
564+
QOpenGLExtraFunctions glF(&context);
565+
glF.initializeOpenGLFunctions();
566+
RenderedTarget target;
567+
568+
Sprite sprite;
569+
sprite.setX(75.64);
570+
sprite.setY(-120.3);
571+
sprite.setDirection(-46.37);
572+
sprite.setSize(67.98);
573+
SpriteModel spriteModel;
574+
sprite.setInterface(&spriteModel);
575+
target.setSpriteModel(&spriteModel);
576+
EngineMock engine;
577+
target.setEngine(&engine);
578+
auto costume = std::make_shared<Costume>("", "", "png");
579+
std::string costumeData = readFileStr("image.png");
580+
costume->setData(costumeData.size(), static_cast<void *>(costumeData.data()));
581+
costume->setRotationCenterX(-15);
582+
costume->setRotationCenterY(48);
583+
costume->setBitmapResolution(3.25);
584+
sprite.addCostume(costume);
585+
586+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
587+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
588+
target.loadCostumes();
589+
target.updateCostume(costume.get());
590+
target.beforeRedraw();
591+
592+
Texture texture = target.texture();
593+
QOpenGLFramebufferObjectFormat format;
594+
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
595+
596+
QOpenGLFramebufferObject fbo(texture.size(), format);
597+
fbo.bind();
598+
glF.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle(), 0);
599+
target.updateHullPoints(&fbo);
600+
fbo.release();
601+
602+
Rect bounds = target.getBounds();
603+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 66.13);
604+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -124.52);
605+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, 66.72);
606+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -125.11);
607+
608+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
609+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
610+
target.updateRotationStyle(Sprite::RotationStyle::LeftRight);
611+
612+
bounds = target.getBounds();
613+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 71.87);
614+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -110.47);
615+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, 72.29);
616+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -110.89);
617+
618+
EXPECT_CALL(engine, stageWidth()).WillOnce(Return(480));
619+
EXPECT_CALL(engine, stageHeight()).WillOnce(Return(360));
620+
target.setStageScale(20.75);
621+
622+
bounds = target.getBounds();
623+
ASSERT_EQ(std::round(bounds.left() * 100) / 100, 71.87);
624+
ASSERT_EQ(std::round(bounds.top() * 100) / 100, -110.47);
625+
ASSERT_EQ(std::round(bounds.right() * 100) / 100, 72.29);
626+
ASSERT_EQ(std::round(bounds.bottom() * 100) / 100, -110.89);
627+
628+
context.doneCurrent();
629+
}

0 commit comments

Comments
 (0)