Skip to content

Commit de5f5c4

Browse files
committed
#34 removed image resources from qrc as they get copied so there's a local file path to load them from, extended tests for SourceImage further (now has an off-by-two on the image dimensions, but at least doesn't crash with div/0 on the unit test...)
1 parent e616377 commit de5f5c4

File tree

3 files changed

+111
-43
lines changed

3 files changed

+111
-43
lines changed

qtgui/SourceImage.cpp

+105-38
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,22 @@ namespace
1515
void scalePoint(const QPointF& source, QPoint& target, qreal scalingFactor);
1616
}
1717

18+
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
19+
#define GET_BOUNDING_RECTANGLE(name) QRectF name{ 0, 0, 400, 400 }
20+
#else
21+
#define GET_BOUNDING_RECTANGLE(name) QRectF name = boundingRect()
22+
#endif
23+
24+
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
25+
#define UPDATE_IMAGE() paint(nullptr)
26+
#else
27+
#define UPDATE_IMAGE() update()
28+
#endif
29+
1830
SourceImage::SourceImage(QQuickItem* parent)
1931
: QQuickPaintedItem()
2032
, filePath{}
21-
, resultSize{}
33+
, resultSize{1, 1}
2234
, clipTopLeft{}
2335
, clipBottomRight{}
2436
, image{}
@@ -36,14 +48,14 @@ SourceImage::SourceImage(QQuickItem* parent)
3648
void SourceImage::mousePressEvent(QMouseEvent* theEvent)
3749
{
3850
newStartingPoint = { theEvent->localPos().x(), theEvent->localPos().y() };
39-
logging::logger() << logging::Level::DEBUG << "MousePress: top left is " << newTopLeft.x() << ", " << newTopLeft.y() << logging::Level::OFF;
51+
logging::logger() << logging::Level::DEBUG << "MousePress: top left is " << newStartingPoint.x() << ", " << newStartingPoint.y() << logging::Level::OFF;
4052
}
4153

4254
void SourceImage::mouseMoveEvent(QMouseEvent* theEvent)
4355
{
4456
if (theEvent->buttons() & Qt::MouseButton::LeftButton)
4557
{
46-
QRectF bounds = boundingRect();
58+
GET_BOUNDING_RECTANGLE(bounds);
4759
logging::logger() << logging::Level::DEBUG << "MouseMove: boundingRect " << bounds.width() << ", " << bounds.height() << logging::Level::OFF;
4860
QPointF tl = { boundedMin(newStartingPoint.x(), theEvent->localPos().x(), 0), boundedMin(newStartingPoint.y(), theEvent->localPos().y(), 0) };
4961
QPointF br = { boundedMax(newStartingPoint.x(), theEvent->localPos().x(), bounds.width() - 1), boundedMax(newStartingPoint.y(), theEvent->localPos().y(), bounds.height() - 1) };
@@ -53,7 +65,7 @@ void SourceImage::mouseMoveEvent(QMouseEvent* theEvent)
5365
if (! abort)
5466
{
5567
logging::logger() << logging::Level::DEBUG << "MouseMove: top left is " << newTopLeft.x() << ", " << newTopLeft.y() << ", bottom right is " << newBottomRight.x() << ", " << newBottomRight.y() << logging::Level::OFF;
56-
update();
68+
UPDATE_IMAGE();
5769
}
5870
}
5971
}
@@ -66,7 +78,7 @@ void SourceImage::mouseReleaseEvent(QMouseEvent* theEvent)
6678
}
6779
else
6880
{
69-
QRectF bounds = boundingRect();
81+
GET_BOUNDING_RECTANGLE(bounds);
7082
QPointF tl = { boundedMin(newStartingPoint.x(), theEvent->localPos().x(), 0), boundedMin(newStartingPoint.y(), theEvent->localPos().y(), 0) };
7183
QPointF br = { boundedMax(newStartingPoint.x(), theEvent->localPos().x(), bounds.width() - 1), boundedMax(newStartingPoint.y(), theEvent->localPos().y(), bounds.height()) };
7284
topLeft = tl;
@@ -77,7 +89,7 @@ void SourceImage::mouseReleaseEvent(QMouseEvent* theEvent)
7789
newStartingPoint = { -1, -1 };
7890
newTopLeft = { -1, -1 };
7991
newBottomRight = { -1, -1 };
80-
update(); // triggers paint(...)
92+
UPDATE_IMAGE();
8193
logging::logger() << logging::Level::NOTE << "Clipping to (" << clipTopLeft.x() << ", " << clipTopLeft.y() << ")/(" << clipBottomRight.x() << ", " << clipBottomRight.y() << ")" << logging::Level::OFF;
8294
logging::logger() << logging::Level::NOTE << "Based on (" << topLeft.x() << ", " << topLeft.y() << ")/(" << bottomRight.x() << ", " << bottomRight.y() << ")" << logging::Level::OFF;
8395
}
@@ -89,25 +101,21 @@ void SourceImage::setPath(const QUrl& data)
89101
const std::string fileQuality{ image.isNull() ? "empty " : "" };
90102
logging::logger() << logging::Level::NOTE << "Loaded a new " << fileQuality << "file " << data.toString().toStdString() << logging::Level::OFF;
91103
topLeft = { 0, 0 };
92-
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
93-
QRectF bounds{ 0, 0, 400, 400 };
94-
#else
95-
QRectF bounds = boundingRect();
96-
#endif
104+
GET_BOUNDING_RECTANGLE(bounds);
97105
bottomRight = { bounds.width() - 1, bounds.height() - 1 };
98106
newTopLeft = { -1, -1 };
99107
newBottomRight = { -1, -1 };
100108
clipTopLeft = { 0, 0 };
101109
clipBottomRight = { image.width() - 1, image.height() - 1 };
102110
logging::logger() << logging::Level::NOTE << "File Size is " << image.width() << "x" << image.height() << logging::Level::OFF;
103-
update(); // triggers paint(...)
111+
UPDATE_IMAGE();
104112
}
105113

106114
void SourceImage::setResultWidth(const int& width)
107115
{
108116
logging::logger() << logging::Level::NOTE << "new width: " << width << logging::Level::OFF;
109117
resultSize.setX(width);
110-
update(); // triggers paint(...)
118+
UPDATE_IMAGE();
111119
}
112120

113121
void SourceImage::setResultHeight(const int& height)
@@ -118,14 +126,10 @@ void SourceImage::setResultHeight(const int& height)
118126
}
119127

120128
void SourceImage::paint(QPainter* painter) {
121-
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
122-
QRectF bounds{ 0, 0, 400, 400 };
123-
#else
124-
QRectF bounds = boundingRect();
125-
#endif
129+
GET_BOUNDING_RECTANGLE(bounds);
126130
if (image.isNull())
127131
{
128-
painter->fillRect(bounds, Qt::white);
132+
if (nullptr != painter) painter->fillRect(bounds, Qt::white);
129133
return;
130134
}
131135
QImage scaled = image.scaledToWidth(bounds.width());
@@ -141,38 +145,42 @@ void SourceImage::paint(QPainter* painter) {
141145
center.setY(0);
142146
setHeight(scaled.height());
143147
setWidth(scaled.width());
144-
painter->drawImage(center, scaled);
148+
if (nullptr != painter) painter->drawImage(center, scaled);
145149

146150
normalizeLocations(scaled.width(), scaled.height());
147-
painter->setPen(QColor(255, 0, 0));
148-
painter->drawRect(topLeft.x(), topLeft.y(), bottomRight.x()-topLeft.x(), bottomRight.y()-topLeft.y());
149-
painter->setPen(QColor(0, 255, 0));
150-
painter->drawRect(newTopLeft.x(), newTopLeft.y(), newBottomRight.x() - newTopLeft.x(), newBottomRight.y() - newTopLeft.y());
151-
151+
if (nullptr != painter)
152+
{
153+
painter->setPen(QColor(255, 0, 0));
154+
painter->drawRect(topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y());
155+
painter->setPen(QColor(0, 255, 0));
156+
painter->drawRect(newTopLeft.x(), newTopLeft.y(), newBottomRight.x() - newTopLeft.x(), newBottomRight.y() - newTopLeft.y());
157+
}
152158
newClipping();
153159
dataChanged();
154160
}
155161

156162
QImage SourceImage::data() const
157163
{
158-
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
159-
QRectF bounds{ 0, 0, 400, 400 };
160-
#else
161-
QRectF bounds = boundingRect();
162-
#endif
163-
QImage scaled = image.scaledToWidth(bounds.width());
164+
GET_BOUNDING_RECTANGLE(bounds);
164165
logging::logger() << logging::Level::DEBUG << "BoundingBox (" << bounds.x() << ", " << bounds.y() << ")/(" << bounds.width() << ", " << bounds.height() << ")" << logging::Level::OFF;
166+
const std::string inputIsValid{ image.isNull() ? "empty " : "" };
167+
168+
// scale overall image to bounding box
169+
QImage scaled = image.scaledToWidth(bounds.width());
165170
if (scaled.height() > bounds.height())
166171
{
167172
scaled = image.scaledToHeight(bounds.height());
168173
}
169174
const std::string scaledIsValid{ scaled.isNull() ? "empty " : "" };
170175
logging::logger() << logging::Level::DEBUG << "Rescaled to " << scaledIsValid << "file with " << scaled.width() << "/" << scaled.height() << " pixels" << logging::Level::OFF;
171-
auto returnValue{ scaled.copy(QRectF(topLeft.x(), topLeft.y(), std::min(bottomRight.x() + 1, (qreal)scaled.width()), std::min(bottomRight.y() + 1, (qreal)scaled.height())).toRect()) };
176+
177+
// clip to selected area
178+
auto returnValue{ scaled.copy(QRectF(topLeft.x(), topLeft.y(), std::min(bottomRight.x() - topLeft.x() + 1, (qreal)scaled.width()), std::min(bottomRight.y() - topLeft.y() + 1, (qreal)scaled.height())).toRect()) };
172179
const std::string outputIsValid{ returnValue.isNull() ? "empty " : "" };
173180
logging::logger() << logging::Level::DEBUG << "Actually clipped to " << outputIsValid << "file with " << returnValue.width() << "/" << returnValue.height() << " pixels" << logging::Level::OFF;
174-
logging::logger() << logging::Level::DEBUG << "Clipping to (" << clipTopLeft.x() << ", " << clipTopLeft.y() << ")/(" << clipBottomRight.x() + 1 << ", " << clipBottomRight.y() + 1 << ")" << logging::Level::OFF;
175-
const std::string inputIsValid{ image.isNull() ? "empty " : "" };
181+
182+
// validate and return
183+
logging::logger() << logging::Level::DEBUG << "Clipping to (" << clipX() << ", " << clipY() << ")/(" << clipWidth() << ", " << clipHeight() << ")" << logging::Level::OFF;
176184
logging::logger() << logging::Level::DEBUG << "Returning " << outputIsValid << "file copied from " << inputIsValid << "input" << logging::Level::OFF;
177185
return returnValue;
178186
}
@@ -208,6 +216,7 @@ QString SourceImage::clippingInfo() const
208216

209217
void SourceImage::normalizeLocations(qreal paintedWidth, qreal paintedHeight)
210218
{
219+
logging::logger() << logging::Level::DEBUG << "Normalizing..." << logging::Level::OFF;
211220
double definedAspectRatio = resultSize.y() / resultSize.x();
212221

213222
adjustToAspectRatio(topLeft, bottomRight, definedAspectRatio, paintedWidth, paintedHeight);
@@ -218,6 +227,7 @@ void SourceImage::normalizeLocations(qreal paintedWidth, qreal paintedHeight)
218227
scalePoint(bottomRight, clipBottomRight, scaling);
219228
scalePoint(newTopLeft, newClipTopLeft, scaling);
220229
scalePoint(newBottomRight, newClipBottomRight, scaling);
230+
logging::logger() << logging::Level::DEBUG << "Done." << logging::Level::OFF;
221231
}
222232

223233
namespace
@@ -306,14 +316,42 @@ namespace
306316
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
307317
#include <doctest.h>
308318

319+
class SourceImageTester
320+
{
321+
public:
322+
SourceImageTester(SourceImage& imageToTest) : m_imageToTest{ imageToTest } {}
323+
324+
inline void mouseDown(const QPointF& pos)
325+
{
326+
QMouseEvent mousePressed(QEvent::Type::MouseButtonPress, pos, Qt::MouseButton::LeftButton, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier);
327+
m_imageToTest.mousePressEvent(&mousePressed);
328+
}
329+
330+
inline void mouseMoved(const QPointF& pos)
331+
{
332+
QMouseEvent mouseMoved(QEvent::Type::MouseMove, pos, Qt::MouseButton::NoButton, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier);
333+
m_imageToTest.mouseMoveEvent(&mouseMoved);
334+
}
335+
336+
inline void mouseUp(const QPointF& pos, bool canceled = false)
337+
{
338+
QMouseEvent mouseReleased(QEvent::Type::MouseButtonRelease, pos, Qt::MouseButton::LeftButton, (canceled ? Qt::MouseButton::RightButton : Qt::MouseButton::NoButton), Qt::KeyboardModifier::NoModifier);
339+
m_imageToTest.mouseReleaseEvent(&mouseReleased);
340+
}
341+
private:
342+
SourceImage& m_imageToTest;
343+
};
344+
309345
TEST_CASE("test boundedMin") {
310346
CHECK_EQ(boundedMin(1, 2, 3), 3);
311347
CHECK_EQ(boundedMin(3, 2, 1), 2);
312348
}
349+
313350
TEST_CASE("test boundedMax") {
314351
CHECK_EQ(boundedMax(4, 3, 2), 2);
315352
CHECK_EQ(boundedMax(2, 3, 4), 3);
316353
}
354+
317355
TEST_CASE("test adjustToAspectRatio") {
318356
QPointF topLeft{0, 0};
319357
double targetAspectRatio{ 1. };
@@ -349,18 +387,22 @@ TEST_CASE("test scalePoint") {
349387
CHECK_EQ(target.x(), 20);
350388
}
351389

352-
void testFileLoad(const QUrl& in_url, SourceImage& inout_imgObject, const QString& in_expected_clipping_info, const QSize& in_expected_size)
390+
void fileSizeCheck(const SourceImage& inout_imgObject, const QString& in_expected_clipping_info, const QSize& in_expected_size)
353391
{
354-
std::cout << "As url " << in_url.url().toStdString() << std::endl;
355-
inout_imgObject.setPath(in_url);
356-
std::cout << inout_imgObject.clippingInfo().toStdString() << std::endl;
357392
REQUIRE_FALSE(inout_imgObject.data().isNull());
358393
std::cout << inout_imgObject.clippingInfo().toStdString() << std::endl;
359394
CHECK_EQ(inout_imgObject.clippingInfo(), in_expected_clipping_info);
360395
auto actualSize = inout_imgObject.data().size();
361396
CHECK_EQ(actualSize.width(), in_expected_size.width());
362397
CHECK_EQ(actualSize.height(), in_expected_size.height());
398+
}
363399

400+
void testFileLoad(const QUrl& in_url, SourceImage& inout_imgObject, const QString& in_expected_clipping_info, const QSize& in_expected_size)
401+
{
402+
std::cout << "As url " << in_url.url().toStdString() << std::endl;
403+
inout_imgObject.setPath(in_url);
404+
std::cout << inout_imgObject.clippingInfo().toStdString() << std::endl;
405+
fileSizeCheck(inout_imgObject, in_expected_clipping_info, in_expected_size);
364406
}
365407

366408
TEST_CASE("test initial image load") {
@@ -396,4 +438,29 @@ TEST_CASE("test initial image load") {
396438
testFileLoad(resourceUrl, testImage, QString("Use 0, 0 (w:400, h:400)"), expectedSize);
397439
}
398440

441+
TEST_CASE("test mouse based range selection")
442+
{
443+
SourceImage testImage;
444+
logging::logger().setLogLevel(logging::Level::DEBUG);
445+
446+
testImage.setResultWidth(10);
447+
testImage.setResultHeight(10);
448+
449+
QUrl resourceUrl;
450+
resourceUrl.setScheme("file");
451+
resourceUrl.setPath(TEST_FILE_NAME);
452+
testImage.setPath(resourceUrl);
453+
454+
SourceImageTester tester{ testImage };
455+
456+
// drag rectangle from 10, 10 to 200, 200 computationally
457+
tester.mouseDown({ 10, 10 });
458+
for (qreal pos = 20; pos < 200; pos += 10) tester.mouseMoved({ pos, pos });
459+
tester.mouseUp({ 200, 200 });
460+
461+
// this mouse action should result in a rectangle with 191x191 displayed pixels starting at 10x10 displayed
462+
// this rectangle, if resized to match the overall image size of 927x927, scales to a 442x442 rectangle starting at pixel 23x23.
463+
fileSizeCheck(testImage, QString{"Use 23, 23 (w:442, h:442)"}, QSize{191, 191});
464+
}
465+
399466
#endif

qtgui/SourceImage.h

+6
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
#include <QQuickItem>
44
#include <QPainter>
55
#include <QImage>
6+
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
7+
class SourceImageTester;
8+
#endif
69

710
class SourceImage : public QQuickPaintedItem
811
{
12+
#ifdef DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
13+
friend class SourceImageTester;
14+
#endif
915
Q_OBJECT
1016
Q_PROPERTY(QImage imageBuffer READ data NOTIFY dataChanged)
1117
Q_PROPERTY(QUrl path WRITE setPath NOTIFY pathChanged)

qtgui/resources/stixelator-resources.qrc

-5
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88
<file>qml/PixelColorSettings.qml</file>
99
<file>qml/GridLines.qml</file>
1010
<file>qml/FileDisplay.qml</file>
11-
12-
<file>image/base_stixel.png</file>
13-
<file>image/hoernchen.png</file>
14-
<file>image/schneider_wibbel.jpg</file>
15-
1611
<file>qtquickcontrols2.conf</file>
1712
</qresource>
1813
</RCC>

0 commit comments

Comments
 (0)