diff --git a/doc/release/master.md b/doc/release/master.md new file mode 100644 index 0000000000..35252f8f95 --- /dev/null +++ b/doc/release/master.md @@ -0,0 +1,30 @@ +YARP (UNRELEASED) {#yarp_3_11} +============================ + +[TOC] + +YARP Release Notes +============================= + + +A (partial) list of bug fixed and issues resolved in this release can be found +[here](https://github.com/robotology/yarp/issues?q=label%3A%22Fixed+in%3A+YARP+yarp-3.10%22). + +Fixes +----- + +New Features +------------ + +### GUIs + +#### `yarpopencvdisplay` + +* `yarpopencvdisplay` is now able to display a `yarp::sig::LayeredImage` + +### Libraries + +#### `libYARP_sig` + +* added new datatype `yarp::sig::LayeredImage` +* added `yarp::sig::utils::sum()` to transform `yarp::sig::LayeredImage` to `yarp::sig::Image` diff --git a/src/libYARP_sig/src/CMakeLists.txt b/src/libYARP_sig/src/CMakeLists.txt index 6048a897c1..18cfa546d6 100644 --- a/src/libYARP_sig/src/CMakeLists.txt +++ b/src/libYARP_sig/src/CMakeLists.txt @@ -16,6 +16,7 @@ set(YARP_sig_HDRS yarp/sig/ImageNetworkHeader.h yarp/sig/ImageUtils.h yarp/sig/IntrinsicParams.h + yarp/sig/LayeredImage.h yarp/sig/LaserMeasurementData.h yarp/sig/Matrix.h yarp/sig/PointCloud.h @@ -40,6 +41,7 @@ set(YARP_sig_SRCS yarp/sig/ImageUtils.cpp yarp/sig/IntrinsicParams.cpp yarp/sig/LaserMeasurementData.cpp + yarp/sig/LayeredImage.cpp yarp/sig/Matrix.cpp yarp/sig/PointCloudBase.cpp yarp/sig/PointCloudUtils.cpp diff --git a/src/libYARP_sig/src/yarp/sig/Image.cpp b/src/libYARP_sig/src/yarp/sig/Image.cpp index 2c3c26eb41..2d855d436d 100644 --- a/src/libYARP_sig/src/yarp/sig/Image.cpp +++ b/src/libYARP_sig/src/yarp/sig/Image.cpp @@ -45,12 +45,11 @@ inline bool readFromConnection(Image &dest, ImageNetworkHeader &header, Connecti //this check is redundant with assertion, I would remove it if (dest.getRawImageSize() != static_cast(header.imgSize)) { printf("There is a problem reading an image\n"); - printf("incoming: width %zu, height %zu, code %zu, quantum %zu, topIsLow %zu, size %zu\n", + printf("incoming: width %zu, height %zu, code %zu, quantum %zu, size %zu\n", static_cast(header.width), static_cast(header.height), static_cast(header.id), static_cast(header.quantum), - static_cast(header.topIsLow), static_cast(header.imgSize)); printf("my space: width %zu, height %zu, code %d, quantum %zu, size %zu\n", dest.width(), dest.height(), dest.getPixelCode(), dest.getQuantum(), allocatedBytes); diff --git a/src/libYARP_sig/src/yarp/sig/ImageNetworkHeader.h b/src/libYARP_sig/src/yarp/sig/ImageNetworkHeader.h index 00e530e9dc..83aea4eee4 100644 --- a/src/libYARP_sig/src/yarp/sig/ImageNetworkHeader.h +++ b/src/libYARP_sig/src/yarp/sig/ImageNetworkHeader.h @@ -31,17 +31,10 @@ class ImageNetworkHeader const yarp::os::NetInt32 paramIdTag{BOTTLE_TAG_VOCAB32}; yarp::os::NetInt32 id{0}; const yarp::os::NetInt32 paramListTag{BOTTLE_TAG_LIST + BOTTLE_TAG_INT32}; - // WARNING This is 5 and not 6 because quantum and topIsLow are - // transmitted in the same 32 bits for compatibility with - // YARP 3.4 and older const yarp::os::NetInt32 paramListLen{5}; yarp::os::NetInt32 depth{0}; yarp::os::NetInt32 imgSize{0}; - yarp::os::NetInt16 quantum{0}; - // WARNING The topIsLowIndex field in the ImageNetworkHeader is `0` for - // `true` and `1` for `false` for compatibility with YARP 3.4 - // and older - yarp::os::NetInt16 topIsLow{0}; + yarp::os::NetInt32 quantum{0}; yarp::os::NetInt32 width{0}; yarp::os::NetInt32 height{0}; const yarp::os::NetInt32 paramBlobTag{BOTTLE_TAG_BLOB}; @@ -52,12 +45,19 @@ class ImageNetworkHeader id = image.getPixelCode(); depth = image.getPixelSize(); imgSize = image.getRawImageSize(); - quantum = static_cast(image.getQuantum()); - topIsLow = 1; + quantum = image.getQuantum(); width = image.width(); height = image.height(); paramBlobLen = image.getRawImageSize(); } + + void setToImage(FlexImage& image) + { + image.setPixelCode(id); + //setPixelSize() is already set by setPixelCode + image.setQuantum(quantum); + image.resize(width, height); + } }; YARP_END_PACK diff --git a/src/libYARP_sig/src/yarp/sig/ImageUtils.cpp b/src/libYARP_sig/src/yarp/sig/ImageUtils.cpp index af4e87b03c..b434c33ab3 100644 --- a/src/libYARP_sig/src/yarp/sig/ImageUtils.cpp +++ b/src/libYARP_sig/src/yarp/sig/ImageUtils.cpp @@ -6,6 +6,8 @@ #include #include #include // std::math +#include +#include using namespace yarp::sig; @@ -141,3 +143,68 @@ bool utils::cropRect(const yarp::sig::Image& inImg, return true; } + +bool utils::sum(Image& OutImg, const Image& InImg, bool enable_colorkey, int colorkey, bool enable_alpha, float alpha, size_t offset_x, size_t offset_y) +{ + if (OutImg.getPixelCode() != InImg.getPixelCode()) + { + yError() << "utils::sum() Input and Output images must have the same pixelcode"; + return false; + } + + if (InImg.getPixelCode() != VOCAB_PIXEL_RGB) + { + yError() << "utils::sum() Unimplemented pixelcode"; + return false; + } + + yarp::sig::PixelRgb ColorkeyRGB; + ColorkeyRGB.r = colorkey; + ColorkeyRGB.g = colorkey; + ColorkeyRGB.b = colorkey; + + size_t yis = InImg.height(); + size_t xis = InImg.width(); + size_t yos = OutImg.height(); + size_t xos = OutImg.width(); + + for (size_t y = 0; y < yis; ++y) + { + for (size_t x = 0; x < xis; ++x) + { + size_t xo = x + offset_x; + size_t yo = y + offset_y; + if (xo > xos) { + xo = xos; + } + if (yo > yos) { + yo = yos; + } + + unsigned char* layer_pointer = InImg.getPixelAddress(x, y); + unsigned char* outimg_pointer = OutImg.getPixelAddress(xo, yo); + + yarp::sig::PixelRgb* layer_pointer_rgb = reinterpret_cast(layer_pointer); + yarp::sig::PixelRgb* outimg_pointer_rgb = reinterpret_cast(outimg_pointer); + + if (enable_colorkey && layer_pointer_rgb->r == ColorkeyRGB.r && layer_pointer_rgb->g == ColorkeyRGB.g && layer_pointer_rgb->b == ColorkeyRGB.b) + { + continue; + } + else if (enable_alpha) + { + outimg_pointer_rgb->r = layer_pointer_rgb->r * alpha + outimg_pointer_rgb->r * (1 - alpha); + outimg_pointer_rgb->g = layer_pointer_rgb->g * alpha + outimg_pointer_rgb->g * (1 - alpha); + outimg_pointer_rgb->b = layer_pointer_rgb->b * alpha + outimg_pointer_rgb->b * (1 - alpha); + } + else + { + outimg_pointer_rgb->r = layer_pointer_rgb->r; + outimg_pointer_rgb->g = layer_pointer_rgb->g; + outimg_pointer_rgb->b = layer_pointer_rgb->b; + } + } + } + + return true; +} diff --git a/src/libYARP_sig/src/yarp/sig/ImageUtils.h b/src/libYARP_sig/src/yarp/sig/ImageUtils.h index 01cdfa653c..a088a0be53 100644 --- a/src/libYARP_sig/src/yarp/sig/ImageUtils.h +++ b/src/libYARP_sig/src/yarp/sig/ImageUtils.h @@ -67,6 +67,28 @@ bool YARP_sig_API cropRect(const yarp::sig::Image& inImg, const std::pair& vertex1, const std::pair& vertex2, yarp::sig::Image& outImg); + +/** + * @brief applies an image on the top over another image. + * @param[in/out] OutImg the output image. It must be a valid image on the top of which data will be summed. It may contain a backgroud or it can be zero. + * @param[in] InImg the layer to be applied + * @param[in] colorkey colorkey for the InImg image. If a pixel is == colorkey, then it will be made transparent and the backgroud will be visible. + * @param[in] alpha to be applied to InImg. + * @param[in] off_x horizontal offset applied to InImg. Excess will be cropped. + * @param[in] off_y vertical offset applied to InImg. Excess will be cropped. + * @note The two images must have the same pixelCode. + * @return true on success, false if image cannot be summed because of incompatible format. + */ +bool YARP_sig_API sum(yarp::sig::Image& OutImg, + const yarp::sig::Image& InImg, + bool colorkey_enable, + int colorkey, + bool alpha_enable, + float alpha, + size_t off_x, + size_t off_y); + } // namespace yarp::sig::utils + #endif // YARP_SIG_IMAGEUTILS_H diff --git a/src/libYARP_sig/src/yarp/sig/LayeredImage.cpp b/src/libYARP_sig/src/yarp/sig/LayeredImage.cpp new file mode 100644 index 0000000000..f2af7c59e1 --- /dev/null +++ b/src/libYARP_sig/src/yarp/sig/LayeredImage.cpp @@ -0,0 +1,261 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 Istituto Italiano di Tecnologia (IIT) + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace yarp::sig; +using namespace yarp::os; + +inline void writeToConnection(const Image& img, ConnectionWriter& connection) +{ + ImageNetworkHeader imghdr; + + imghdr.setFromImage(img); + size_t hdrsize = sizeof(imghdr); + connection.appendInt32(BOTTLE_TAG_BLOB); + connection.appendInt32(hdrsize); + connection.appendBlock((char*)(&imghdr), hdrsize); + + size_t imgsize = img.getRawImageSize(); + connection.appendInt32(BOTTLE_TAG_BLOB); + connection.appendInt32(imgsize); + connection.appendBlock((char*)(img.getRawImage()), imgsize); + + return; +} + +inline bool readFromConnection(FlexImage& dest, ConnectionReader& connection) +{ + bool ok = true; + ImageNetworkHeader imghdr; + + connection.expectInt32(); + size_t sizeData = connection.expectInt32(); + ok &= connection.expectBlock((char*)(&imghdr), sizeData); + if (!ok) { return false; } + imghdr.setToImage(dest); + + connection.expectInt32(); + size_t sizeImg = connection.expectInt32(); + size_t psizeImg = dest.getRawImageSize(); + if (sizeImg != psizeImg) + { + return false; + } + unsigned char* pImg = dest.getRawImage(); + ok &= connection.expectBlock((char*)pImg, sizeImg); + + return ok; +} + +LayeredImage::LayeredImage() +{ +} + + +LayeredImage::~LayeredImage() +{ +} + + +void LayeredImage::clear() +{ + background.zero(); + layers.clear(); +} + +bool LayeredImage::read(yarp::os::ConnectionReader& connection) +{ + bool ok = true; + + connection.convertTextMode(); + + // LIST OF ELEMENTS + connection.expectInt32(); + size_t elems = connection.expectInt32(); + + //ELEMENT 1 + connection.expectInt32(); + size_t layersNum = connection.expectInt32(); + if (elems != 1 + (1 * 2) + (layersNum*10)) + { + return false; + } + + // ELEMENT 2-3 + ok &= readFromConnection(background, connection); + + // ELEMENT 4-... + // each layer contains 8+2 elems + layers.clear(); + for (size_t i = 0; i < layersNum; i++) + { + yarp::sig::ImageLayer::colorkey_s colorkey = yarp::sig::ImageLayer::colorkey_s {}; + yarp::sig::ImageLayer::alpha_s alpha = yarp::sig::ImageLayer::alpha_s {}; + + connection.expectInt32(); + int32_t enable_val = connection.expectInt8(); //1 + connection.expectInt32(); + colorkey.enable = connection.expectInt8(); //2 + connection.expectInt32(); + colorkey.value = connection.expectInt32(); //3 + connection.expectInt32(); + alpha.enable = connection.expectInt8(); //4 + connection.expectInt32(); + alpha.value = connection.expectFloat32(); // 5 + connection.expectInt32(); + bool can_be_compressed = connection.expectInt8(); //6 + connection.expectInt32(); + int32_t offset_x = connection.expectInt32(); //7 + connection.expectInt32(); + int32_t offset_y = connection.expectInt32(); //8 + + FlexImage fleximg; + ok &= readFromConnection(fleximg, connection); //9-10 + yarp::sig::ImageLayer oneLayer(fleximg, enable_val, colorkey, alpha, can_be_compressed, offset_x, offset_y); + layers.emplace_back(oneLayer); + } + + return true; +} + + +bool LayeredImage::write(yarp::os::ConnectionWriter& connection) const +{ + bool ok = true; + size_t layers_num = layers.size(); + + //LIST OF ELEMENTS + connection.appendInt32(BOTTLE_TAG_LIST); + connection.appendInt32(1+(1*2)+(layers_num*10)); + + //ELEMENT 1 + connection.appendInt32(BOTTLE_TAG_INT32); + connection.appendInt32(layers_num); + + // ELEMENT 2-3 + writeToConnection(background, connection); + + // ELEMENT 4-... + // each layer contains 8+2 elems + for (size_t i = 0; i < layers_num; i++) + { + connection.appendInt32(BOTTLE_TAG_INT8); //1 + connection.appendInt8 (layers[i].enable); + connection.appendInt32(BOTTLE_TAG_INT8); //2 + connection.appendInt8(layers[i].colorkey.enable); + connection.appendInt32(BOTTLE_TAG_INT32); //3 + connection.appendInt32(layers[i].colorkey.value); + connection.appendInt32(BOTTLE_TAG_INT8); //4 + connection.appendInt8(layers[i].alpha.enable); + connection.appendInt32(BOTTLE_TAG_FLOAT32); //5 + connection.appendFloat32(layers[i].alpha.value); + connection.appendInt32(BOTTLE_TAG_INT8); //6 + connection.appendInt8(layers[i].can_be_compressed); + connection.appendInt32(BOTTLE_TAG_INT32); //7 + connection.appendInt32(layers[i].offset_x); + connection.appendInt32(BOTTLE_TAG_INT32); //8 + connection.appendInt32(layers[i].offset_y); + writeToConnection(layers[i].layer, connection); // 9-10 + } + + connection.convertTextMode(); + return !connection.isError(); +} + + +LayeredImage::LayeredImage(const LayeredImage& alt) : + Portable() +{ + background = alt.background; + this->layers = alt.layers; +} + +LayeredImage::LayeredImage(LayeredImage&& other) noexcept +{ +} + +LayeredImage& LayeredImage::operator=(const LayeredImage& alt) +{ + background = alt.background; + this->layers = alt.layers; + return *this; +} + +bool LayeredImage::operator==(const LayeredImage& alt) const +{ + size_t l1 = this->layers.size(); + size_t l2 = alt.layers.size(); + + if (l1 != l2) + { + return false; + } + + if (background != alt.background) + { + return false; + } + + for (size_t i = 0; i < l1; i++) + { + if ((this->layers[i].enable != alt.layers[i].enable) || + (this->layers[i].colorkey.enable != alt.layers[i].colorkey.enable) || + (this->layers[i].colorkey.value != alt.layers[i].colorkey.value) || + (this->layers[i].alpha.enable != alt.layers[i].alpha.enable) || + (fabs(this->layers[i].alpha.value - alt.layers[i].alpha.value) > 0.001) || + (this->layers[i].layer != alt.layers[i].layer) || + (this->layers[i].can_be_compressed != alt.layers[i].can_be_compressed) || + (this->layers[i].offset_x != alt.layers[i].offset_x) || + (this->layers[i].offset_y != alt.layers[i].offset_y)) + { + return false; + } + } + + return true; +} + +yarp::sig::FlexImage LayeredImage::convert_to_flexImage() +{ + yarp::sig::FlexImage outimg = background; + + bool ret = true; + for (size_t i = 0; i < this->layers.size(); i++) + { + if (layers[i].enable == false) + { + continue; + } + + ret &= yarp::sig::utils::sum(outimg, layers[i].layer, layers[i].colorkey.enable, layers[i].colorkey.value, layers[i].alpha.enable, layers[i].alpha.value, layers[i].offset_x, layers[i].offset_y); + } + + return outimg; +} + +LayeredImage::operator yarp::sig::FlexImage() +{ + return convert_to_flexImage(); +} diff --git a/src/libYARP_sig/src/yarp/sig/LayeredImage.h b/src/libYARP_sig/src/yarp/sig/LayeredImage.h new file mode 100644 index 0000000000..aef5322c4a --- /dev/null +++ b/src/libYARP_sig/src/yarp/sig/LayeredImage.h @@ -0,0 +1,148 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 Istituto Italiano di Tecnologia (IIT) + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef YARP_LAYERED_IMAGE_H +#define YARP_LAYERED_IMAGE_H + +#include +#include +#include + +namespace yarp::sig { +class LayeredImage; +class ImageLayer; + +/** + * \ingroup sig_class + * + * A single layer of a layered image + */ +class YARP_sig_API yarp::sig::ImageLayer +{ + public: + yarp::sig::FlexImage layer; + bool enable=true; + struct colorkey_s + { + bool enable = true; + int value=0; + } colorkey; + struct alpha_s + { + bool enable = true; + float value=1.0; + } alpha; + bool can_be_compressed = true; + int offset_x=0; + int offset_y=0; + + ImageLayer(const yarp::sig::FlexImage& img, bool ena = true, colorkey_s ckey = colorkey_s {}, alpha_s alph = alpha_s {}, bool compress=true, int off_x = 0, int off_y = 0) + { + layer = img; + enable = ena; + colorkey = ckey; + alpha = alph; + can_be_compressed = compress; + offset_x = off_x; + offset_y = off_y; + } +}; + +/** + * \ingroup sig_class + * + * A Layered Image, composed by a background and multiple layers + */ +class YARP_sig_API yarp::sig::LayeredImage : public yarp::os::Portable +{ +public: + //internal data + yarp::sig::FlexImage background; + std::vector layers; + +public: + /** + * Default constructor. + * Creates an empty LayeredImage. + */ + LayeredImage(); + + /** + * Copy constructor. + * Clones the content of another LayeredImage. + * @param alt the LayeredImage to clone + */ + LayeredImage(const LayeredImage& alt); + + /** + * @brief Move constructor. + * + * @param other the LayeredImage to be moved + */ + LayeredImage(LayeredImage&& other) noexcept; + + /** + * Assignment operator. + * Clones the content of another LayeredImage. + * @param alt the LayeredImage to clone + */ + LayeredImage& operator=(const LayeredImage& alt); + + /** + * @brief Move assignment operator. + * + * @param other the LayeredImage to be moved + * @return this object + */ + LayeredImage& operator=(LayeredImage&& other) noexcept; + + /** + * Comparison operator. + * Compares two LayeredImage + * @return true if the two LayeredImages are identical + */ + bool operator==(const LayeredImage& alt) const; + + /** + * Destructor. + */ + ~LayeredImage() override; + + /** + * Conversion operator + */ + yarp::sig::FlexImage convert_to_flexImage(); + + /** + * Conversion operator + */ + operator yarp::sig::FlexImage(); + + /** + * Clear the layered Image + */ + void clear(); + + /** + * Read a LayeredImage from a connection. + * @return true iff LayeredImage was read correctly + */ + bool read(yarp::os::ConnectionReader& connection) override; + + /** + * Write a LayeredImage to a connection. + * @return true iff LayeredImage was written correctly + */ + bool write(yarp::os::ConnectionWriter& connection) const override; + + yarp::os::Type getReadType() const override { + return yarp::os::Type::byName("yarp/layeredimage"); + } +}; + + +} // namespace yarp::sig + +#endif // YARP_LAYERED_IMAGE_H diff --git a/src/libYARP_sig/tests/CMakeLists.txt b/src/libYARP_sig/tests/CMakeLists.txt index 105bb75ae3..b7ab0f7f61 100644 --- a/src/libYARP_sig/tests/CMakeLists.txt +++ b/src/libYARP_sig/tests/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(harness_sig) target_sources(harness_sig PRIVATE ImageTest.cpp + LayeredImageTest.cpp MatrixTest.cpp PointCloudTest.cpp SoundTest.cpp diff --git a/src/libYARP_sig/tests/LayeredImageTest.cpp b/src/libYARP_sig/tests/LayeredImageTest.cpp new file mode 100644 index 0000000000..61d29f1868 --- /dev/null +++ b/src/libYARP_sig/tests/LayeredImageTest.cpp @@ -0,0 +1,298 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 Istituto Italiano di Tecnologia (IIT) + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace yarp::os::impl; +using namespace yarp::sig; +using namespace yarp::os; + +TEST_CASE("sig::LayeredImageTest", "[yarp::sig]") +{ + NetworkBase::setLocalMode(true); + + SECTION("test serialization of layered images composed by a background + 2 layers") + { + FlexImage imageback; + imageback.setPixelCode(VOCAB_PIXEL_RGB); + imageback.resize(4, 8); + + FlexImage lay0; + lay0.setPixelCode(VOCAB_PIXEL_RGB); + lay0.resize(4, 8); + + FlexImage lay1; + lay1.setPixelCode(VOCAB_PIXEL_RGB); + lay1.resize(4, 8); + + LayeredImage multiLayerImageIn; + multiLayerImageIn.background = imageback; + multiLayerImageIn.layers.push_back(lay0); + multiLayerImageIn.layers.push_back(lay1); + + LayeredImage multiLayerImageOut; + + yarp::os::Bottle output_bot; + bool b1 = Property::copyPortable(multiLayerImageIn, output_bot); + CHECK(b1); + + yarp::os::Bottle input_bot = output_bot; + size_t i1 = output_bot.size(); + size_t i2 = input_bot.size(); + std::string s1 = output_bot.toString(); + std::string s2 = input_bot.toString(); + CHECK((int)i1 == 23); + CHECK((int)i2 == 23); + std::cout << "s1: " << s1 << std::endl; + std::cout << "s2: " << s2 << std::endl; + bool b2 = Property::copyPortable(input_bot, multiLayerImageOut); + CHECK(b2); + + bool b3 = (multiLayerImageIn == multiLayerImageOut); + CHECK(b3); + } + + SECTION("test serialization of layered images composed by a background image only") + { + FlexImage imageback; + imageback.setPixelCode(VOCAB_PIXEL_RGB); + imageback.resize(16, 8); + + LayeredImage multiLayerImageIn; + multiLayerImageIn.background = imageback; + + LayeredImage multiLayerImageOut; + + yarp::os::Bottle output_bot; + bool b1 = Property::copyPortable(multiLayerImageIn, output_bot); + CHECK(b1); + + yarp::os::Bottle input_bot = output_bot; + size_t i1 = output_bot.size(); + size_t i2 = input_bot.size(); + std::string s1 = output_bot.toString(); + std::string s2 = input_bot.toString(); + CHECK((int)i1 == 3); + CHECK((int)i2 == 3); + bool b2 = Property::copyPortable(input_bot, multiLayerImageOut); + CHECK(b2); + + bool b3 = (multiLayerImageIn == multiLayerImageOut); + CHECK(b3); + } + + SECTION("test alpha layered image") + { + yarp::sig::ImageOf imageback; + imageback.resize(16, 8); + for (size_t iy = 0; iy < imageback.height(); iy++) + for (size_t ix = 0; ix < imageback.width(); ix++) { + imageback.pixel(ix, iy).r = 10; + imageback.pixel(ix, iy).g = 10; + imageback.pixel(ix, iy).b = 10; + } + + yarp::sig::ImageOf lay0; + lay0.resize(16, 8); + for (size_t iy = 0; iy < lay0.height(); iy++) + for (size_t ix = 0; ix < lay0.width(); ix++) { + lay0.pixel(ix, iy).r = 8; + lay0.pixel(ix, iy).g = 10; + lay0.pixel(ix, iy).b = 12; + } + + LayeredImage multiLayerImageIn; + multiLayerImageIn.background = *(reinterpret_cast(&imageback)); + multiLayerImageIn.layers.push_back(*(reinterpret_cast(&lay0))); + multiLayerImageIn.layers[0].alpha.value = 0.5; + + FlexImage flat_img = multiLayerImageIn; + yarp::sig::ImageOf flat_rgb_img = *(reinterpret_cast*>(&flat_img)); + for (size_t iy = 0; iy < flat_rgb_img.height() / 2; iy++) + for (size_t ix = 0; ix < flat_rgb_img.width() / 2; ix++) { + CHECK(flat_rgb_img.pixel(ix, iy).r == 9); + CHECK(flat_rgb_img.pixel(ix, iy).g == 10); + CHECK(flat_rgb_img.pixel(ix, iy).b == 11); + } + } + + SECTION("test offset layered image") + { + yarp::sig::ImageOf imageback; + imageback.resize(4, 4); + for (size_t iy = 0; iy < imageback.height(); iy++) + for (size_t ix = 0; ix < imageback.width(); ix++) { + imageback.pixel(ix, iy).r = 10; + imageback.pixel(ix, iy).g = 10; + imageback.pixel(ix, iy).b = 10; + } + + yarp::sig::ImageOf lay0; + lay0.resize(2, 2); + for (size_t iy = 0; iy < lay0.height(); iy++) + for (size_t ix = 0; ix < lay0.width(); ix++) { + lay0.pixel(ix, iy).r = 20; + lay0.pixel(ix, iy).g = 20; + lay0.pixel(ix, iy).b = 20; + } + + LayeredImage multiLayerImageIn; + multiLayerImageIn.background = *(reinterpret_cast(&imageback)); + multiLayerImageIn.layers.push_back(*(reinterpret_cast(&lay0))); + multiLayerImageIn.layers[0].offset_x = 2; + multiLayerImageIn.layers[0].offset_y = 1; + + FlexImage flat_img = multiLayerImageIn; + yarp::sig::ImageOf flat_rgb_img = *(reinterpret_cast*>(&flat_img)); + CHECK(flat_rgb_img.pixel(0, 0).r == 10); + CHECK(flat_rgb_img.pixel(1, 0).r == 10); + CHECK(flat_rgb_img.pixel(2, 0).r == 10); + CHECK(flat_rgb_img.pixel(3, 0).r == 10); + CHECK(flat_rgb_img.pixel(0, 1).r == 10); + CHECK(flat_rgb_img.pixel(1, 1).r == 10); + CHECK(flat_rgb_img.pixel(2, 1).r == 20); + CHECK(flat_rgb_img.pixel(3, 1).r == 20); + CHECK(flat_rgb_img.pixel(0, 2).r == 10); + CHECK(flat_rgb_img.pixel(1, 2).r == 10); + CHECK(flat_rgb_img.pixel(2, 2).r == 20); + CHECK(flat_rgb_img.pixel(3, 2).r == 20); + CHECK(flat_rgb_img.pixel(0, 3).r == 10); + CHECK(flat_rgb_img.pixel(1, 3).r == 10); + CHECK(flat_rgb_img.pixel(2, 3).r == 10); + CHECK(flat_rgb_img.pixel(3, 3).r == 10); + } + + SECTION("test colorkey layered image") + { + yarp::sig::ImageOf imageback; + imageback.resize(4, 4); + for (size_t iy = 0; iy < imageback.height(); iy++) + for (size_t ix = 0; ix < imageback.width(); ix++) { + imageback.pixel(ix, iy).r = 10; + imageback.pixel(ix, iy).g = 10; + imageback.pixel(ix, iy).b = 10; + } + + yarp::sig::ImageOf lay0; + lay0.resize(4, 4); + lay0.zero(); + imageback.pixel(0, 0).r = 20; + imageback.pixel(1, 0).r = 20; + imageback.pixel(2, 0).r = 20; + imageback.pixel(3, 0).r = 20; + imageback.pixel(0, 1).r = 20; + imageback.pixel(1, 1).r = 50; + imageback.pixel(2, 1).r = 50; + imageback.pixel(3, 1).r = 20; + imageback.pixel(0, 2).r = 20; + imageback.pixel(1, 2).r = 50; + imageback.pixel(2, 2).r = 50; + imageback.pixel(3, 2).r = 20; + imageback.pixel(0, 3).r = 20; + imageback.pixel(1, 3).r = 50; + imageback.pixel(2, 3).r = 20; + imageback.pixel(3, 3).r = 20; + + LayeredImage multiLayerImageIn; + multiLayerImageIn.background = *(reinterpret_cast(&imageback)); + multiLayerImageIn.layers.push_back(*(reinterpret_cast(&lay0))); + + FlexImage flat_img = multiLayerImageIn; + yarp::sig::ImageOf flat_rgb_img = *(reinterpret_cast*>(&flat_img)); + + CHECK(flat_rgb_img.pixel(0, 0).r == 10); + CHECK(flat_rgb_img.pixel(1, 0).r == 10); + CHECK(flat_rgb_img.pixel(2, 0).r == 10); + CHECK(flat_rgb_img.pixel(3, 0).r == 10); + CHECK(flat_rgb_img.pixel(0, 1).r == 10); + CHECK(flat_rgb_img.pixel(1, 1).r == 50); + CHECK(flat_rgb_img.pixel(2, 1).r == 50); + CHECK(flat_rgb_img.pixel(3, 1).r == 10); + CHECK(flat_rgb_img.pixel(0, 2).r == 10); + CHECK(flat_rgb_img.pixel(1, 2).r == 50); + CHECK(flat_rgb_img.pixel(2, 2).r == 50); + CHECK(flat_rgb_img.pixel(3, 2).r == 10); + CHECK(flat_rgb_img.pixel(0, 3).r == 10); + CHECK(flat_rgb_img.pixel(1, 3).r == 50); + CHECK(flat_rgb_img.pixel(2, 3).r == 10); + CHECK(flat_rgb_img.pixel(3, 3).r == 10); + } + + SECTION("test flattening multi layered image") + { + yarp::sig::ImageOf imageback; + imageback.resize(8, 8); + for (size_t iy = 0; iy < imageback.height(); iy++) + for (size_t ix = 0; ix < imageback.width(); ix++) + { + imageback.pixel(ix, iy).r = 10; + imageback.pixel(ix, iy).g = 10; + imageback.pixel(ix, iy).b = 10; + } + + yarp::sig::ImageOf lay0; + lay0.resize(8, 8); + for (size_t iy = 0; iy < lay0.height(); iy++) + for (size_t ix = 0; ix < lay0.width(); ix++) { + lay0.pixel(ix, iy).r = 100; + lay0.pixel(ix, iy).g = 100; + lay0.pixel(ix, iy).b = 100; + } + + yarp::sig::ImageOf lay1; + lay1.resize(8, 8); + for (size_t iy = 0; iy < lay1.height(); iy++) + for (size_t ix = 0; ix < lay1.width(); ix++) { + lay1.pixel(ix, iy).r = 101; + lay1.pixel(ix, iy).g = 101; + lay1.pixel(ix, iy).b = 101; + } + + yarp::sig::ImageOf lay2; + lay2.resize(8, 8); + for (size_t iy = 0; iy < lay2.height(); iy++) + for (size_t ix = 0; ix < lay2.width(); ix++) { + lay2.pixel(ix, iy).r = 102; + lay2.pixel(ix, iy).g = 102; + lay2.pixel(ix, iy).b = 102; + } + + LayeredImage multiLayerImageIn; + multiLayerImageIn.background = *(reinterpret_cast(&imageback)); + multiLayerImageIn.layers.push_back(*(reinterpret_cast(&lay0))); + multiLayerImageIn.layers.push_back(*(reinterpret_cast(&lay1))); + multiLayerImageIn.layers[1].enable = false; + + FlexImage flat_img = multiLayerImageIn; + yarp::sig::ImageOf flat_rgb_img = *(reinterpret_cast*>(&flat_img)); + for (size_t iy = 0; iy < flat_img.height() ; iy++) + for (size_t ix = 0; ix < flat_img.width() ; ix++) + { + CHECK(flat_rgb_img.pixel(ix, iy).r == 100); + } + + multiLayerImageIn.layers.push_back(*(reinterpret_cast(&lay2))); + FlexImage flat_img2 = multiLayerImageIn; + yarp::sig::ImageOf flat_rgb_img2 = *(reinterpret_cast*>(&flat_img2)); + for (size_t iy = 0; iy < flat_img2.height() ; iy++) + for (size_t ix = 0; ix < flat_img2.width() ; ix++) { + CHECK(flat_rgb_img2.pixel(ix, iy).r == 102); + } + } + + NetworkBase::setLocalMode(false); +} diff --git a/src/portmonitors/image_rotation/imageRotation.cpp b/src/portmonitors/image_rotation/imageRotation.cpp index 0327ac3881..fa849cebcc 100644 --- a/src/portmonitors/image_rotation/imageRotation.cpp +++ b/src/portmonitors/image_rotation/imageRotation.cpp @@ -175,7 +175,7 @@ yarp::os::Things& ImageRotation::update(yarp::os::Things& thing) //just rotation cv::rotate(m_cvInImage, m_cvOutImage1, m_rot_flags); m_outImgRgb = yarp::cv::fromCvMat(m_cvOutImage1); - } + } else if (m_options_flip_str != "flip_none" && m_options_rotate_str == "rotation_none") { //just flip diff --git a/src/yarpopencvdisplay/main.cpp b/src/yarpopencvdisplay/main.cpp index 330d83b3d7..4da04f8f60 100644 --- a/src/yarpopencvdisplay/main.cpp +++ b/src/yarpopencvdisplay/main.cpp @@ -25,6 +25,7 @@ #include #include #include +#include using namespace yarp::os; using namespace yarp::sig; @@ -62,6 +63,7 @@ void display_help() yInfo() << "--record_start"; yInfo() << "--record_fps"; yInfo() << "--record_codec"; + yInfo() << "--layered"; } int main(int argc, char* argv[]) @@ -98,14 +100,38 @@ int main(int argc, char* argv[]) std::string remote = rf.check("remote", Value(""), "remote port name").asString(); std::string carrier = rf.check("carrier", Value("fast_tcp"), "carrier name").asString(); std::string local = rf.check("local", Value("/yarpopencvdisplay:i"), "local port name").asString(); + bool layered = rf.check("layered", Value(false), "set if the input port will receive a layered image or not").asBool(); + size_t visible_layers = 10; yInfo() << "yarpopencvdisplay ready. Press ESC to quit."; - BufferedPort> inputPort; - if (inputPort.open(local) == false) { + yarp::os::Contactable* inputPort = nullptr; + if (!layered) + { + inputPort = new BufferedPort>; + } + else + { + inputPort = new BufferedPort; + } + + if (inputPort->open(local) == false) + { yError() << "Failed to open port" << local; return 0; } + else + { + if (layered) + { + yInfo() << "Opened LAYERED port:" << local; + } + else + { + yInfo() << "Opened port:" << local; + } + } + if (!remote.empty()) { if (yarp::os::Network::connect(remote, local, carrier)) { yInfo() << "Successfully connected to port:" << remote; @@ -125,29 +151,69 @@ int main(int argc, char* argv[]) bool exit = false; + + FlexImage* fl = nullptr; + yarp::sig::ImageOf* imgdata = nullptr; + while(!exit) { //Receive image and draw { - auto* imgport = inputPort.read(false); - if (imgport) + if (layered) + { + BufferedPort* ip = dynamic_cast*>(inputPort); + yarp::sig::LayeredImage* ly = ip->read(false); + if (ly) + { + if (fl) + { + delete fl; + fl = nullptr; + } + for (size_t i = 0; i < ly->layers.size(); i++) + { + if (i < visible_layers) + { + ly->layers[i].enable = true; + } + else + { + ly->layers[i].enable = false; + } + } + fl = new FlexImage(*ly); + imgdata = (reinterpret_cast*>(fl)); + } + } + else + { + BufferedPort >* ip = dynamic_cast < BufferedPort < yarp::sig::ImageOf>*>(inputPort); + yarp::sig::ImageOf* ir = ip->read(false); + if (ir) + { + imgdata = ir; + } + } + + //if received a new frame, then draw/save it + if (imgdata!=nullptr) { static double old = yarp::os::Time::now(); double now = yarp::os::Time::now(); stats.interval_time = now - old; old = yarp::os::Time::now(); - //if first received frame + //if this is first received frame, allocate a buffer on the specific size if (frame.empty()) { - yDebug() << "First frame received with size" << imgport->width() << imgport->height(); + yDebug() << "First frame received with size" << imgdata->width() << imgdata->height(); //create the buffer for the frame - frame = cv::Mat(imgport->width(), imgport->height(), CV_8UC3); - //if optionally requested to create an avi file, cretate the file + frame = cv::Mat(imgdata->width(), imgdata->height(), CV_8UC3); + //if optionally requested to create an avi file, create the file if (filename.empty() == false) { int codec = cv::VideoWriter::fourcc(codec_s[0], codec_s[1], codec_s[2], codec_s[3]); - cv::Size frame_size(imgport->width(), imgport->height()); + cv::Size frame_size(imgdata->width(), imgdata->height()); bool ret = videowriter.open(filename.c_str(), codec, fps, frame_size, true); if (!ret || videowriter.isOpened() == false) { @@ -156,35 +222,39 @@ int main(int argc, char* argv[]) } } } - else if (frame.cols != (int)imgport->width() || frame.rows != (int)imgport->height()) + //you also need to realloc the buffer if the received frame changed size + else if (frame.cols != (int)imgdata->width() || frame.rows != (int)imgdata->height()) { - frame = cv::Mat(imgport->height(), imgport->width(), CV_8UC3); + yDebug() << "Steam has received a frame with different size" << imgdata->width() << imgdata->height(); + frame = cv::Mat(imgdata->height(), imgdata->width(), CV_8UC3); } + //copy the data from the yarp structure 'imgdata' to the opencv structure 'frame' double a = yarp::os::Time::now(); - for (size_t y = 0; y < imgport->height(); y++) { - for (size_t x = 0; x < imgport->width(); x++) { - PixelRgb& yarpPixel = imgport->pixel(x, y); + for (size_t y = 0; y < imgdata->height(); y++) { + for (size_t x = 0; x < imgdata->width(); x++) { + PixelRgb& yarpPixel = imgdata->pixel(x, y); frame.at(y, x) = cv::Vec3b(yarpPixel.b, yarpPixel.g, yarpPixel.r); } } + + //add stats to frame double b = yarp::os::Time::now(); stats.copy_time = b - a; - - if (!frame.empty()) + if (verbose) { - if (verbose) - { - yDebug("copytime: %5.3f frameintervale %5.3f", stats.copy_time, stats.interval_time); - drawImageExtraStuff(frame); - } - - if (recording) { - videowriter.write(frame); - } + yDebug("copytime: %5.3f frameintervale %5.3f", stats.copy_time, stats.interval_time); + drawImageExtraStuff(frame); + } - cv::imshow(window_name, frame); + //save to file + if (recording) + { + videowriter.write(frame); } + + //draw + cv::imshow(window_name, frame); } } @@ -206,8 +276,12 @@ int main(int argc, char* argv[]) yInfo("recording is now OFF"); } } - if(keypressed == 's') - { + if (keypressed == 'l') { + visible_layers++; + if (visible_layers > 10) { + visible_layers = 0; + yInfo("Changed max number of visible Layers: %d", visible_layers); + } } if(keypressed == 'v' ) { @@ -220,7 +294,8 @@ int main(int argc, char* argv[]) } } - inputPort.close(); + inputPort->close(); + delete inputPort; cv::destroyAllWindows();