diff --git a/src/plugins/image_display/CMakeLists.txt b/src/plugins/image_display/CMakeLists.txt index 85da0c34a..e606d0141 100644 --- a/src/plugins/image_display/CMakeLists.txt +++ b/src/plugins/image_display/CMakeLists.txt @@ -4,6 +4,5 @@ ign_gui_add_plugin(ImageDisplay QT_HEADERS ImageDisplay.hh TEST_SOURCES - # ImageDisplay_TEST.cc + ImageDisplay_TEST.cc ) - diff --git a/src/plugins/image_display/ImageDisplay.cc b/src/plugins/image_display/ImageDisplay.cc index dd5baa0de..2959d66ef 100644 --- a/src/plugins/image_display/ImageDisplay.cc +++ b/src/plugins/image_display/ImageDisplay.cc @@ -17,9 +17,6 @@ #include "ImageDisplay.hh" -#include - -#include #include #include #include @@ -39,37 +36,6 @@ namespace gui { namespace plugins { - class ImageProvider : public QQuickImageProvider - { - public: ImageProvider() - : QQuickImageProvider(QQuickImageProvider::Image) - { - } - - public: QImage requestImage(const QString &, QSize *, - const QSize &) override - { - if (!this->img.isNull()) - { - // Must return a copy - QImage copy(this->img); - return copy; - } - - // Placeholder in case we have no image yet - QImage i(400, 400, QImage::Format_RGB888); - i.fill(QColor(128, 128, 128, 100)); - return i; - } - - public: void SetImage(const QImage &_image) - { - this->img = _image; - } - - private: QImage img; - }; - class ImageDisplayPrivate { /// \brief List of topics publishing image messages. @@ -184,7 +150,11 @@ void ImageDisplay::OnTopic(const QString _topic) { auto topic = _topic.toStdString(); if (topic.empty()) + { + // LCOV_EXCL_START return; + // LCOV_EXCL_STOP + } // Unsubscribe auto subs = this->dataPtr->node.SubscribedTopics(); @@ -195,8 +165,10 @@ void ImageDisplay::OnTopic(const QString _topic) if (!this->dataPtr->node.Subscribe(topic, &ImageDisplay::OnImageMsg, this)) { + // LCOV_EXCL_START ignerr << "Unable to subscribe to topic [" << topic << "]" << std::endl; return; + // LCOV_EXCL_STOP } App()->findChild()->notifyWithDuration( QString::fromStdString("Subscribed to: " + topic + ""), 4000); diff --git a/src/plugins/image_display/ImageDisplay.hh b/src/plugins/image_display/ImageDisplay.hh index 240bce24f..8aaf62a70 100644 --- a/src/plugins/image_display/ImageDisplay.hh +++ b/src/plugins/image_display/ImageDisplay.hh @@ -18,7 +18,10 @@ #ifndef IGNITION_GUI_PLUGINS_IMAGEDISPLAY_HH_ #define IGNITION_GUI_PLUGINS_IMAGEDISPLAY_HH_ +#include #include +#include + #ifdef _MSC_VER #pragma warning(push, 0) #endif @@ -27,6 +30,16 @@ #pragma warning(pop) #endif +#ifndef _WIN32 +# define ImageDisplay_EXPORTS_API +#else +# if (defined(ImageDisplay_EXPORTS)) +# define ImageDisplay_EXPORTS_API __declspec(dllexport) +# else +# define ImageDisplay_EXPORTS_API __declspec(dllimport) +# endif +#endif + #include "ignition/gui/Plugin.hh" namespace ignition @@ -37,6 +50,37 @@ namespace plugins { class ImageDisplayPrivate; + class ImageProvider : public QQuickImageProvider + { + public: ImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image) + { + } + + public: QImage requestImage(const QString &, QSize *, + const QSize &) override + { + if (!this->img.isNull()) + { + // Must return a copy + QImage copy(this->img); + return copy; + } + + // Placeholder in case we have no image yet + QImage i(400, 400, QImage::Format_RGB888); + i.fill(QColor(128, 128, 128, 100)); + return i; + } + + public: void SetImage(const QImage &_image) + { + this->img = _image; + } + + private: QImage img; + }; + /// \brief Display images coming through an Ignition transport topic. /// /// ## Configuration @@ -44,7 +88,7 @@ namespace plugins /// \ : Set the topic to receive image messages. /// \ : Whether to show the topic picker, true by default. If /// this is false, a \ must be specified. - class ImageDisplay : public Plugin + class ImageDisplay_EXPORTS_API ImageDisplay : public Plugin { Q_OBJECT diff --git a/src/plugins/image_display/ImageDisplay.qml b/src/plugins/image_display/ImageDisplay.qml index b36bdb3e4..b11021a74 100644 --- a/src/plugins/image_display/ImageDisplay.qml +++ b/src/plugins/image_display/ImageDisplay.qml @@ -60,6 +60,7 @@ Rectangle { RowLayout { visible: showPicker RoundButton { + objectName: "refreshButton" text: "\u21bb" Material.background: Material.primary onClicked: { @@ -72,6 +73,7 @@ Rectangle { } ComboBox { id: combo + objectName: "topicsCombo" Layout.fillWidth: true model: ImageDisplay.topicList onCurrentIndexChanged: { diff --git a/src/plugins/image_display/ImageDisplay_TEST.cc b/src/plugins/image_display/ImageDisplay_TEST.cc index e20b5b53d..bbd7fd3bc 100644 --- a/src/plugins/image_display/ImageDisplay_TEST.cc +++ b/src/plugins/image_display/ImageDisplay_TEST.cc @@ -16,70 +16,125 @@ */ #include + +#ifdef _MSC_VER +#pragma warning(push, 0) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + #include #include #include +#include -#include "ignition/gui/Iface.hh" -#include "ignition/gui/Plugin.hh" +#include "ignition/gui/Application.hh" #include "ignition/gui/MainWindow.hh" +#include "ignition/gui/Plugin.hh" +#include "test_config.h" // NOLINT(build/include) +#include "ImageDisplay.hh" + +int g_argc = 1; +char* g_argv[] = +{ + reinterpret_cast(const_cast("./ImageDisplay_TEST")), +}; using namespace ignition; using namespace gui; ///////////////////////////////////////////////// -TEST(ImageDisplayTest, Load) +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(Load)) { - EXPECT_TRUE(initApp()); + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); + + // Load plugin + EXPECT_TRUE(app.LoadPlugin("ImageDisplay")); - EXPECT_TRUE(loadPlugin("ImageDisplay")); + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); - EXPECT_TRUE(stop()); + // Get plugin + auto plugins = win->findChildren(); + EXPECT_EQ(plugins.size(), 1); + + auto plugin = plugins[0]; + EXPECT_EQ(plugin->Title(), "Image display"); + + // Cleanup + plugins.clear(); } ///////////////////////////////////////////////// -TEST(ImageDisplayTest, DefaultConfig) +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(DefaultConfig)) { - setVerbosity(4); - EXPECT_TRUE(initApp()); + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); // Load plugin - EXPECT_TRUE(loadPlugin("ImageDisplay")); + EXPECT_TRUE(app.LoadPlugin("ImageDisplay")); - // Create main window - EXPECT_TRUE(createMainWindow()); - auto win = mainWindow(); - EXPECT_TRUE(win != nullptr); + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); // Get plugin - auto plugins = win->findChildren(); + auto plugins = win->findChildren(); EXPECT_EQ(plugins.size(), 1); auto plugin = plugins[0]; EXPECT_EQ(plugin->Title(), "Image display"); // Has a topic picker - auto topicsCombo = plugin->findChild("topicsCombo"); - EXPECT_TRUE(topicsCombo != nullptr); - EXPECT_EQ(topicsCombo->count(), 0); - - auto refreshButton = plugin->findChild("refreshButton"); - EXPECT_TRUE(refreshButton != nullptr); - - // No images - auto label = plugin->findChild(); - EXPECT_TRUE(label != nullptr); - EXPECT_EQ(label->text(), "No image"); + auto topicsCombo = plugin->PluginItem()->findChild("topicsCombo"); + ASSERT_NE(topicsCombo, nullptr); + auto topicProp = topicsCombo->property("model"); + EXPECT_TRUE(topicProp.isValid()); + auto topicList = topicProp.toStringList(); + EXPECT_EQ(topicList.size(), 0); + + auto refreshButton = + plugin->PluginItem()->findChild("refreshButton"); + ASSERT_NE(refreshButton, nullptr); + + auto picker = + topicsCombo->parent(); // RowLayout that holds `visible: showPicker` + ASSERT_NE(picker, nullptr); + auto pickerProp = picker->property("visible"); + EXPECT_TRUE(pickerProp.isValid()); + EXPECT_TRUE(pickerProp.toBool()); + + // No images (gray image) + auto providerBase = app.Engine()->imageProvider( + plugin->CardItem()->objectName() + "imagedisplay"); + ASSERT_NE(providerBase, nullptr); + auto imageProvider = static_cast(providerBase); + ASSERT_NE(imageProvider, nullptr); + QSize dummySize; + QImage img = imageProvider->requestImage(QString(), &dummySize, dummySize); + EXPECT_TRUE(img.allGray()); // Cleanup plugins.clear(); - EXPECT_TRUE(stop()); } ///////////////////////////////////////////////// -TEST(ImageDisplayTest, NoPickerNeedsTopic) +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(NoPickerNeedsTopic)) { - setVerbosity(4); - EXPECT_TRUE(initApp()); + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); // Load plugin const char *pluginStr = @@ -89,43 +144,60 @@ TEST(ImageDisplayTest, NoPickerNeedsTopic) tinyxml2::XMLDocument pluginDoc; pluginDoc.Parse(pluginStr); - EXPECT_TRUE(loadPlugin("ImageDisplay", + EXPECT_TRUE(app.LoadPlugin("ImageDisplay", pluginDoc.FirstChildElement("plugin"))); - // Create main window - EXPECT_TRUE(createMainWindow()); - auto win = mainWindow(); - EXPECT_TRUE(win != nullptr); + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); // Get plugin - auto plugins = win->findChildren(); + auto plugins = win->findChildren(); EXPECT_EQ(plugins.size(), 1); auto plugin = plugins[0]; EXPECT_EQ(plugin->Title(), "Image display"); // Has a topic picker anyway - auto topicsCombo = plugin->findChild("topicsCombo"); - EXPECT_TRUE(topicsCombo != nullptr); - EXPECT_EQ(topicsCombo->count(), 0); - - auto refreshButton = plugin->findChild("refreshButton"); - EXPECT_TRUE(refreshButton != nullptr); - - // No images - auto label = plugin->findChild(); - EXPECT_TRUE(label != nullptr); - EXPECT_EQ(label->text(), "No image"); + auto topicsCombo = plugin->PluginItem()->findChild("topicsCombo"); + ASSERT_NE(topicsCombo, nullptr); + auto topicProp = topicsCombo->property("model"); + EXPECT_TRUE(topicProp.isValid()); + auto topicList = topicProp.toStringList(); + EXPECT_EQ(topicList.size(), 0); + + auto refreshButton = + plugin->PluginItem()->findChild("refreshButton"); + ASSERT_NE(refreshButton, nullptr); + + auto picker = + topicsCombo->parent(); // RowLayout that holds `visible: showPicker` + ASSERT_NE(picker, nullptr); + auto pickerProp = picker->property("visible"); + EXPECT_TRUE(pickerProp.isValid()); + EXPECT_TRUE(pickerProp.toBool()); + + // No images (gray image) + auto providerBase = app.Engine()->imageProvider( + plugin->CardItem()->objectName() + "imagedisplay"); + ASSERT_NE(providerBase, nullptr); + auto imageProvider = static_cast(providerBase); + ASSERT_NE(imageProvider, nullptr); + QSize dummySize; + QImage img = imageProvider->requestImage(QString(), &dummySize, dummySize); + EXPECT_TRUE(img.allGray()); // Cleanup plugins.clear(); - EXPECT_TRUE(stop()); } ///////////////////////////////////////////////// -TEST(ImageDisplayTest, ReceiveImage) +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(ReceiveImage)) { - setVerbosity(4); - EXPECT_TRUE(initApp()); + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); // Load plugin const char *pluginStr = @@ -136,29 +208,38 @@ TEST(ImageDisplayTest, ReceiveImage) tinyxml2::XMLDocument pluginDoc; pluginDoc.Parse(pluginStr); - EXPECT_TRUE(loadPlugin("ImageDisplay", + EXPECT_TRUE(app.LoadPlugin("ImageDisplay", pluginDoc.FirstChildElement("plugin"))); - // Create main window - EXPECT_TRUE(createMainWindow()); - auto win = mainWindow(); - EXPECT_TRUE(win != nullptr); + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); // Get plugin - auto plugins = win->findChildren(); + auto plugins = win->findChildren(); EXPECT_EQ(plugins.size(), 1); auto plugin = plugins[0]; EXPECT_EQ(plugin->Title(), "Image display"); - // Doesn't have a topic picker - EXPECT_EQ(plugin->findChildren().size(), 0); - EXPECT_EQ(plugin->findChildren().size(), 0); - - // Starts with no image - auto label = plugin->findChild(); - EXPECT_TRUE(label != nullptr); - EXPECT_EQ(label->text(), "No image"); - EXPECT_TRUE(label->pixmap() == nullptr); + // Doesn't have a topic picker by checking `showPicker == false` + auto topicsCombo = plugin->PluginItem()->findChild("topicsCombo"); + ASSERT_NE(topicsCombo, nullptr); + auto picker = + topicsCombo->parent(); // RowLayout that holds `visible: showPicker` + ASSERT_NE(picker, nullptr); + auto pickerProp = picker->property("visible"); + EXPECT_TRUE(pickerProp.isValid()); + EXPECT_FALSE(pickerProp.toBool()); + + // Starts with no image (gray image) + auto providerBase = app.Engine()->imageProvider( + plugin->CardItem()->objectName() + "imagedisplay"); + ASSERT_NE(providerBase, nullptr); + auto imageProvider = static_cast(providerBase); + ASSERT_NE(imageProvider, nullptr); + QSize dummySize; + QImage img = imageProvider->requestImage(QString(), &dummySize, dummySize); + EXPECT_TRUE(img.allGray()); // Publish images transport::Node node; @@ -169,84 +250,339 @@ TEST(ImageDisplayTest, ReceiveImage) msgs::Image msg; msg.set_height(100); msg.set_width(200); - msg.set_pixel_format(common::Image::RGB_FLOAT32); + msg.set_pixel_format_type(msgs::PixelFormatType::RGB_FLOAT32); + pub.Publish(msg); + } + + // Give it time to be processed + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + QCoreApplication::processEvents(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Still no image + img = imageProvider->requestImage(QString(), &dummySize, dummySize); + EXPECT_TRUE(img.allGray()); + + // Good message + { + msgs::Image msg; + msg.set_height(100); + msg.set_width(200); + msg.set_pixel_format_type(msgs::PixelFormatType::RGB_INT8); + // bytes per pixel = channels * bytes = 3 * 1 + int bpp = 3; + msg.set_step(msg.width() * bpp); + + // red image + int bufferSize = msg.width() * msg.height() * bpp; + std::shared_ptr buffer(new unsigned char[bufferSize]); + for (int i = 0; i < bufferSize; i += bpp) + { + buffer.get()[i] = 255u; + buffer.get()[i + 1] = 0u; + buffer.get()[i + 2] = 0u; + } + msg.set_data(buffer.get(), bufferSize); pub.Publish(msg); } // Give it time to be processed int sleep = 0; - int maxSleep = 10; - while (!label->text().isEmpty() && sleep < maxSleep) + int maxSleep = 30; + while (img.allGray() && sleep < maxSleep) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); QCoreApplication::processEvents(); - sleep++; + img = imageProvider->requestImage(QString(), &dummySize, dummySize); + ++sleep; } - // Still no image - EXPECT_EQ(label->text(), "No image"); - EXPECT_TRUE(label->pixmap() == nullptr); + // Now it has an image + EXPECT_FALSE(img.allGray()); + EXPECT_EQ(img.height(), 100); + EXPECT_EQ(img.width(), 200); + + // check image is red + for (int y = 0; y < img.height(); ++y) + { + for (int x = 0; x < img.width(); ++x) + { + EXPECT_EQ(img.pixelColor(x, y).red(), 255); + EXPECT_EQ(img.pixelColor(x, y).green(), 0); + EXPECT_EQ(img.pixelColor(x, y).blue(), 0); + } + } + + // Cleanup + plugins.clear(); +} + +///////////////////////////////////////////////// +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(ReceiveImageFloat32)) +{ + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); + + // Load plugin + const char *pluginStr = + "" + "/image_test" + ""; + + tinyxml2::XMLDocument pluginDoc; + pluginDoc.Parse(pluginStr); + EXPECT_TRUE(app.LoadPlugin("ImageDisplay", + pluginDoc.FirstChildElement("plugin"))); + + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); + + // Get plugin + auto plugins = win->findChildren(); + EXPECT_EQ(plugins.size(), 1); + auto plugin = plugins[0]; + EXPECT_EQ(plugin->Title(), "Image display"); + + // Starts with no image + auto providerBase = app.Engine()->imageProvider( + plugin->CardItem()->objectName() + "imagedisplay"); + ASSERT_NE(providerBase, nullptr); + auto imageProvider = static_cast(providerBase); + ASSERT_NE(imageProvider, nullptr); + QSize dummySize; + QImage img = imageProvider->requestImage(QString(), &dummySize, dummySize); + // When there is no image yet, a placeholder image with size 400 is given + // See ImageDisplay.hh, ImageProvider for more details + int placeholderSize = 400; + EXPECT_EQ(img.width(), placeholderSize); + EXPECT_EQ(img.height(), placeholderSize); + + // Publish images + transport::Node node; + auto pub = node.Advertise("/image_test"); // Good message { msgs::Image msg; - msg.set_height(100); - msg.set_width(200); - msg.set_pixel_format(common::Image::RGB_INT8); + msg.set_height(32); + msg.set_width(32); + msg.set_pixel_format_type(msgs::PixelFormatType::R_FLOAT32); + // bytes per pixel = channels * bytes = 1 * 4 + int bpp = 4; + msg.set_step(msg.width() * bpp); + + // first half is gray, second half is black + int bufferSize = msg.width() * msg.height() * bpp; + std::shared_ptr buffer(new float[bufferSize]); + for (unsigned int y = 0; y < msg.width(); ++y) + { + float v = 0.5f * static_cast(y / (msg.height() / 2.0) + 1); + for (unsigned int x = 0; x < msg.height(); ++x) + { + buffer.get()[y * msg.width() + x] = v; + } + } + + msg.set_data(buffer.get(), bufferSize); pub.Publish(msg); } // Give it time to be processed - sleep = 0; - while (!label->text().isEmpty() && sleep < maxSleep) + int sleep = 0; + int maxSleep = 30; + while (img.width() == placeholderSize && sleep < maxSleep) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); QCoreApplication::processEvents(); - sleep++; + img = imageProvider->requestImage(QString(), &dummySize, dummySize); + ++sleep; } - // Now it has an image - EXPECT_TRUE(label->text().isEmpty()); - ASSERT_NE(nullptr, label->pixmap()); - EXPECT_EQ(label->pixmap()->height(), 100); - EXPECT_EQ(label->pixmap()->width(), 200); + EXPECT_EQ(img.width(), 32); + EXPECT_EQ(img.height(), 32); + + // check image half gray & half black + for (int y = 0; y < img.height(); ++y) + { + for (int x = 0; x < img.width(); ++x) + { + if (y < img.height() / 2) + { + // expect gray + EXPECT_EQ(img.pixelColor(x, y).red(), 127); + EXPECT_EQ(img.pixelColor(x, y).green(), 127); + EXPECT_EQ(img.pixelColor(x, y).blue(), 127); + } + else + { + // expect black + EXPECT_EQ(img.pixelColor(x, y).red(), 0); + EXPECT_EQ(img.pixelColor(x, y).green(), 0); + EXPECT_EQ(img.pixelColor(x, y).blue(), 0); + } + } + } // Cleanup plugins.clear(); - EXPECT_TRUE(stop()); } ///////////////////////////////////////////////// -TEST(ImageDisplayTest, TopicPicker) +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(ReceiveImageInt16)) { - setVerbosity(4); - EXPECT_TRUE(initApp()); + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); // Load plugin - EXPECT_TRUE(loadPlugin("ImageDisplay")); + const char *pluginStr = + "" + "/image_test" + ""; + + tinyxml2::XMLDocument pluginDoc; + pluginDoc.Parse(pluginStr); + EXPECT_TRUE(app.LoadPlugin("ImageDisplay", + pluginDoc.FirstChildElement("plugin"))); - // Create main window - EXPECT_TRUE(createMainWindow()); - auto win = mainWindow(); - EXPECT_TRUE(win != nullptr); + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); // Get plugin - auto plugins = win->findChildren(); + auto plugins = win->findChildren(); EXPECT_EQ(plugins.size(), 1); auto plugin = plugins[0]; EXPECT_EQ(plugin->Title(), "Image display"); - // Topic picker starts empty - auto topicsCombo = plugin->findChild("topicsCombo"); - EXPECT_TRUE(topicsCombo != nullptr); - EXPECT_EQ(topicsCombo->count(), 0); + // Starts with no image + auto providerBase = app.Engine()->imageProvider( + plugin->CardItem()->objectName() + "imagedisplay"); + ASSERT_NE(providerBase, nullptr); + auto imageProvider = static_cast(providerBase); + ASSERT_NE(imageProvider, nullptr); + QSize dummySize; + QImage img = imageProvider->requestImage(QString(), &dummySize, dummySize); + // When there is no image yet, a placeholder image with size 400 is given + // See ImageDisplay.hh, ImageProvider for more details + int placeholderSize = 400; + EXPECT_EQ(img.width(), placeholderSize); + EXPECT_EQ(img.height(), placeholderSize); - auto refreshButton = plugin->findChild("refreshButton"); - EXPECT_TRUE(refreshButton != nullptr); + // Publish images + transport::Node node; + auto pub = node.Advertise("/image_test"); + + // Good message + { + msgs::Image msg; + msg.set_height(32); + msg.set_width(32); + msg.set_pixel_format_type(msgs::PixelFormatType::L_INT16); + // bytes per pixel = channels * bytes = 1 * 2 + int bpp = 2; + msg.set_step(msg.width() * bpp); + + // first half is black, second half is white + int bufferSize = msg.width() * msg.height() * bpp; + std::shared_ptr buffer(new uint16_t[bufferSize]); + for (unsigned int y = 0; y < msg.width(); ++y) + { + uint16_t v = 100 * static_cast(y / (msg.height() / 2.0) + 1); + for (unsigned int x = 0; x < msg.height(); ++x) + { + buffer.get()[y * msg.width() + x] = v; + } + } + + msg.set_data(buffer.get(), bufferSize); + pub.Publish(msg); + } + + // Give it time to be processed + int sleep = 0; + int maxSleep = 30; + while (img.width() == placeholderSize && sleep < maxSleep) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + QCoreApplication::processEvents(); + img = imageProvider->requestImage(QString(), &dummySize, dummySize); + ++sleep; + } + + EXPECT_EQ(img.width(), 32); + EXPECT_EQ(img.height(), 32); + + // check image half gray & half black + for (int y = 0; y < img.height(); ++y) + { + for (int x = 0; x < img.width(); ++x) + { + if (y < img.height() / 2) + { + // expect black + EXPECT_EQ(img.pixelColor(x, y).red(), 0); + EXPECT_EQ(img.pixelColor(x, y).green(), 0); + EXPECT_EQ(img.pixelColor(x, y).blue(), 0); + } + else + { + // expect white + EXPECT_EQ(img.pixelColor(x, y).red(), 255); + EXPECT_EQ(img.pixelColor(x, y).green(), 255); + EXPECT_EQ(img.pixelColor(x, y).blue(), 255); + } + } + } + + // Cleanup + plugins.clear(); +} + +///////////////////////////////////////////////// +TEST(ImageDisplayTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(TopicPicker)) +{ + common::Console::SetVerbosity(4); + + Application app(g_argc, g_argv); + app.AddPluginPath( + common::joinPaths(std::string(PROJECT_BINARY_PATH), "lib")); + + // Load plugin + EXPECT_TRUE(app.LoadPlugin("ImageDisplay")); + + // Get main window + auto win = app.findChild(); + ASSERT_NE(win, nullptr); + + // Get plugin + auto plugins = win->findChildren(); + EXPECT_EQ(plugins.size(), 1); + auto plugin = plugins[0]; + EXPECT_EQ(plugin->Title(), "Image display"); + + // Topic picker starts empty + auto topicsCombo = plugin->PluginItem()->findChild("topicsCombo"); + ASSERT_NE(topicsCombo, nullptr); + auto topicProp = topicsCombo->property("model"); + EXPECT_TRUE(topicProp.isValid()); + auto topicList = topicProp.toStringList(); + EXPECT_EQ(topicList.size(), 0); + EXPECT_EQ(topicList.size(), plugin->TopicList().size()); // Refresh and still empty - refreshButton->click(); - EXPECT_EQ(topicsCombo->count(), 0); + plugin->OnRefresh(); + topicProp = topicsCombo->property("model"); + EXPECT_TRUE(topicProp.isValid()); + topicList = topicProp.toStringList(); + EXPECT_EQ(topicList.size(), 0); + EXPECT_EQ(topicList.size(), plugin->TopicList().size()); // Advertise topics transport::Node node; @@ -255,17 +591,30 @@ TEST(ImageDisplayTest, TopicPicker) auto pubString = node.Advertise("/string_test"); // Refresh and now we have image topics - refreshButton->click(); - EXPECT_EQ(topicsCombo->count(), 2); - EXPECT_EQ(topicsCombo->itemText(0), "/image_test"); - EXPECT_EQ(topicsCombo->itemText(1), "/image_test_2"); - - // Pick topics - topicsCombo->setCurrentIndex(1); - topicsCombo->setCurrentIndex(0); - topicsCombo->setCurrentIndex(1); + plugin->OnRefresh(); + topicProp = topicsCombo->property("model"); + EXPECT_TRUE(topicProp.isValid()); + topicList = topicProp.toStringList(); + EXPECT_EQ(topicList.size(), 2); + EXPECT_EQ(topicList.size(), plugin->TopicList().size()); + + EXPECT_EQ(topicList.at(0).toStdString(), "/image_test"); + EXPECT_EQ(topicList.at(1).toStdString(), "/image_test_2"); + EXPECT_EQ(topicList.at(0), plugin->TopicList().at(0)); + EXPECT_EQ(topicList.at(1), plugin->TopicList().at(1)); + + // Set image topics + QStringList newTopicList = {"/new_image_test"}; + plugin->SetTopicList(newTopicList); + + topicProp = topicsCombo->property("model"); + EXPECT_TRUE(topicProp.isValid()); + topicList = topicProp.toStringList(); + EXPECT_EQ(topicList.size(), 1); + EXPECT_EQ(topicList.size(), plugin->TopicList().size()); + EXPECT_EQ(topicList.at(0).toStdString(), "/new_image_test"); + EXPECT_EQ(topicList.at(0), plugin->TopicList().at(0)); // Cleanup plugins.clear(); - EXPECT_TRUE(stop()); }