Program Listing for File overlay_text_display.cpp

Return to documentation for file (src/overlay_text_display.cpp)

// -*- mode: c++; -*-
/*********************************************************************
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2022, Team Spatzenhirn
 *  Copyright (c) 2014, JSK Lab
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/o2r other materials provided
 *     with the distribution.
 *   * Neither the name of the JSK Lab nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************/

#include "overlay_text_display.hpp"

#include <OgreHardwarePixelBuffer.h>
#include <OgreMaterialManager.h>
#include <OgreTexture.h>
#include <QFontDatabase>
#include <QPainter>
#include <QStaticText>
#include <QTextDocument>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <regex>
#include <rviz_common/logging.hpp>
#include <rviz_rendering/render_system.hpp>
#include <sstream>

namespace rviz_2d_overlay_plugins {
    OverlayTextDisplay::OverlayTextDisplay() :
        texture_width_(0),
        texture_height_(0),
        bg_color_(0, 0, 0, 0),
        fg_color_(255, 255, 255, 255.0),
        text_size_(14),
        line_width_(2),
        text_(""),
        font_(""),
        require_update_texture_(false) {
        overtake_position_properties_property_ = new rviz_common::properties::BoolProperty(
                "Overtake Position Properties", false,
                "overtake position properties specified by message such as left, top and font", this,
                SLOT(updateOvertakePositionProperties()));
        overtake_fg_color_properties_property_ = new rviz_common::properties::BoolProperty(
                "Overtake FG Color Properties", false,
                "overtake color properties specified by message such as foreground color and alpha", this,
                SLOT(updateOvertakeFGColorProperties()));
        overtake_bg_color_properties_property_ = new rviz_common::properties::BoolProperty(
                "Overtake BG Color Properties", false,
                "overtake color properties specified by message such as background color and alpha", this,
                SLOT(updateOvertakeBGColorProperties()));
        align_bottom_property_ = new rviz_common::properties::BoolProperty(
                "Align Bottom", false, "align text with the bottom of the overlay region", this,
                SLOT(updateAlignBottom()));
        invert_shadow_property_ = new rviz_common::properties::BoolProperty(
                "Invert Shadow", false, "make shadow lighter than original text", this, SLOT(updateInvertShadow()));
        hor_dist_property_ = new rviz_common::properties::IntProperty("hor_dist", 0, "horizontal distance to anchor",
                                                                      this, SLOT(updateHorizontalDistance()));
        ver_dist_property_ = new rviz_common::properties::IntProperty("ver_dist", 0, "vertical distance to anchor",
                                                                      this, SLOT(updateVerticalDistance()));
        hor_alignment_property_ =
                new rviz_common::properties::EnumProperty("hor_alignment", "left", "horizontal alignment of the overlay",
                                                          this, SLOT(updateHorizontalAlignment()));
        hor_alignment_property_->addOption("left", rviz_2d_overlay_msgs::msg::OverlayText::LEFT);
        hor_alignment_property_->addOption("center", rviz_2d_overlay_msgs::msg::OverlayText::CENTER);
        hor_alignment_property_->addOption("right", rviz_2d_overlay_msgs::msg::OverlayText::RIGHT);
        ver_alignment_property_ =
                new rviz_common::properties::EnumProperty("ver_alignment", "top", "vertical alignment of the overlay",
                                                          this, SLOT(updateVerticalAlignment()));
        ver_alignment_property_->addOption("top", rviz_2d_overlay_msgs::msg::OverlayText::TOP);
        ver_alignment_property_->addOption("center", rviz_2d_overlay_msgs::msg::OverlayText::CENTER);
        ver_alignment_property_->addOption("bottom", rviz_2d_overlay_msgs::msg::OverlayText::BOTTOM);
        width_property_ =
                new rviz_common::properties::IntProperty("width", 128, "width position", this, SLOT(updateWidth()));
        width_property_->setMin(0);
        height_property_ =
                new rviz_common::properties::IntProperty("height", 128, "height position", this, SLOT(updateHeight()));
        height_property_->setMin(0);
        text_size_property_ =
                new rviz_common::properties::IntProperty("text size", 12, "text size", this, SLOT(updateTextSize()));
        text_size_property_->setMin(0);
        line_width_property_ =
                new rviz_common::properties::IntProperty("line width", 2, "line width", this, SLOT(updateLineWidth()));
        line_width_property_->setMin(0);
        fg_color_property_ = new rviz_common::properties::ColorProperty(
                "Foreground Color", QColor(25, 255, 240), "Foreground Color", this, SLOT(updateFGColor()));
        fg_alpha_property_ = new rviz_common::properties::FloatProperty("Foreground Alpha", 0.8, "Foreground Alpha",
                                                                        this, SLOT(updateFGAlpha()));
        fg_alpha_property_->setMin(0.0);
        fg_alpha_property_->setMax(1.0);
        bg_color_property_ = new rviz_common::properties::ColorProperty(
                "Background Color", QColor(0, 0, 0), "Background Color", this, SLOT(updateBGColor()));
        bg_alpha_property_ = new rviz_common::properties::FloatProperty("Background Alpha", 0.8, "Background Alpha",
                                                                        this, SLOT(updateBGAlpha()));
        bg_alpha_property_->setMin(0.0);
        bg_alpha_property_->setMax(1.0);

        QFontDatabase database;
        font_families_ = database.families();
        font_property_ =
                new rviz_common::properties::EnumProperty("font", "DejaVu Sans Mono", "font", this, SLOT(updateFont()));
        for (ssize_t i = 0; i < font_families_.size(); i++) {
            font_property_->addOption(font_families_[i], (int) i);
        }
    }

    OverlayTextDisplay::~OverlayTextDisplay() {
        onDisable();
    }

    void OverlayTextDisplay::onEnable() {
        if (overlay_) {
            overlay_->show();
        }
        subscribe();
    }

    void OverlayTextDisplay::onDisable() {
        if (overlay_) {
            overlay_->hide();
        }
        unsubscribe();
    }

    // only the first time
    void OverlayTextDisplay::onInitialize() {
        RTDClass::onInitialize();
        rviz_rendering::RenderSystem::get()->prepareOverlays(scene_manager_);

        onEnable();
        updateTopic();
        updateOvertakePositionProperties();
        updateOvertakeFGColorProperties();
        updateOvertakeBGColorProperties();
        updateAlignBottom();
        updateInvertShadow();
        updateHorizontalDistance();
        updateVerticalDistance();
        updateHorizontalAlignment();
        updateVerticalAlignment();
        updateWidth();
        updateHeight();
        updateTextSize();
        updateFGColor();
        updateFGAlpha();
        updateBGColor();
        updateBGAlpha();
        updateFont();
        updateLineWidth();
        require_update_texture_ = true;
    }

    void OverlayTextDisplay::update(float /*wall_dt*/, float /*ros_dt*/) {
        if (!require_update_texture_) {
            return;
        }
        if (!isEnabled()) {
            return;
        }
        if (!overlay_) {
            return;
        }

        overlay_->updateTextureSize(texture_width_, texture_height_);
        {
            rviz_2d_overlay_plugins::ScopedPixelBuffer buffer = overlay_->getBuffer();
            QImage Hud = buffer.getQImage(*overlay_, bg_color_);
            QPainter painter(&Hud);
            painter.setRenderHint(QPainter::Antialiasing, true);
            painter.setPen(QPen(fg_color_, std::max(line_width_, 1), Qt::SolidLine));
            uint16_t w = overlay_->getTextureWidth();
            uint16_t h = overlay_->getTextureHeight();

            // font
            if (text_size_ != 0) {
                // QFont font = painter.font();
                QFont font(font_.length() > 0 ? font_.c_str() : "Liberation Sans");
                font.setPointSize(text_size_);
                font.setBold(true);
                painter.setFont(font);
            }
            if (text_.length() > 0) {

                QColor shadow_color;
                if (invert_shadow_)
                    shadow_color = Qt::white; // fg_color_.lighter();
                else
                    shadow_color = Qt::black; // fg_color_.darker();
                shadow_color.setAlpha(fg_color_.alpha());

                std::string color_wrapped_text =
                        (boost::format("<span style=\"color: rgba(%2%, %3%, %4%, %5%)\">%1%</span>") % text_ %
                         fg_color_.red() % fg_color_.green() % fg_color_.blue() % fg_color_.alpha())
                                .str();

                // find a remove "color: XXX;" regex match to generate a proper shadow
                std::regex color_tag_re("color:.+?;");
                std::string null_char("");
                std::string formatted_text_ = std::regex_replace(text_, color_tag_re, null_char);
                std::string color_wrapped_shadow =
                        (boost::format("<span style=\"color: rgba(%2%, %3%, %4%, %5%)\">%1%</span>") % formatted_text_ %
                         shadow_color.red() % shadow_color.green() % shadow_color.blue() % shadow_color.alpha())
                                .str();

                QStaticText static_text(boost::algorithm::replace_all_copy(color_wrapped_text, "\n", "<br >").c_str());
                static_text.setTextWidth(w);

                painter.setPen(QPen(shadow_color, std::max(line_width_, 1), Qt::SolidLine));
                QStaticText static_shadow(
                        boost::algorithm::replace_all_copy(color_wrapped_shadow, "\n", "<br >").c_str());
                static_shadow.setTextWidth(w);

                if (!align_bottom_) {
                    painter.drawStaticText(1, 1, static_shadow);
                    painter.drawStaticText(0, 0, static_text);
                } else {
                    QStaticText only_wrapped_text(color_wrapped_text.c_str());
                    QFontMetrics fm(painter.fontMetrics());
                    QRect text_rect = fm.boundingRect(0, 0, w, h, Qt::TextWordWrap | Qt::AlignLeft | Qt::AlignTop,
                                                      only_wrapped_text.text().remove(QRegExp("<[^>]*>")));
                    painter.drawStaticText(1, h - text_rect.height() + 1, static_shadow);
                    painter.drawStaticText(0, h - text_rect.height(), static_text);
                }
            }
            painter.end();
        }
        overlay_->setDimensions(overlay_->getTextureWidth(), overlay_->getTextureHeight());
        require_update_texture_ = false;
    }

    void OverlayTextDisplay::reset() {
        RTDClass::reset();

        if (overlay_) {
            overlay_->hide();
        }
    }

    void OverlayTextDisplay::processMessage(rviz_2d_overlay_msgs::msg::OverlayText::ConstSharedPtr msg) {
        if (!isEnabled()) {
            return;
        }
        if (!overlay_) {
            static int count = 0;
            std::stringstream ss;
            ss << "OverlayTextDisplayObject" << count++;
            overlay_.reset(new rviz_2d_overlay_plugins::OverlayObject(ss.str()));
            overlay_->show();
        }
        if (overlay_) {
            if (msg->action == rviz_2d_overlay_msgs::msg::OverlayText::DELETE) {
                overlay_->hide();
            } else if (msg->action == rviz_2d_overlay_msgs::msg::OverlayText::ADD) {
                overlay_->show();
            }
        }

        // store message for update method
        text_ = msg->text;

        if (!overtake_position_properties_) {
            texture_width_ = msg->width;
            texture_height_ = msg->height;
            text_size_ = msg->text_size;
            horizontal_dist_ = msg->horizontal_distance;
            vertical_dist_ = msg->vertical_distance;

            horizontal_alignment_ = HorizontalAlignment{msg->horizontal_alignment};
            vertical_alignment_ = VerticalAlignment{msg->vertical_alignment};
        }
        if (!overtake_bg_color_properties_)
            bg_color_ = QColor(msg->bg_color.r * 255.0, msg->bg_color.g * 255.0, msg->bg_color.b * 255.0,
                               msg->bg_color.a * 255.0);
        if (!overtake_fg_color_properties_) {
            fg_color_ = QColor(msg->fg_color.r * 255.0, msg->fg_color.g * 255.0, msg->fg_color.b * 255.0,
                               msg->fg_color.a * 255.0);
            font_ = msg->font;
            line_width_ = msg->line_width;
        }
        if (overlay_) {
            overlay_->setPosition(horizontal_dist_, vertical_dist_, horizontal_alignment_, vertical_alignment_);
        }
        require_update_texture_ = true;
    }

    void OverlayTextDisplay::updateOvertakePositionProperties() {

        if (!overtake_position_properties_ && overtake_position_properties_property_->getBool()) {
            updateVerticalDistance();
            updateHorizontalDistance();
            updateVerticalAlignment();
            updateHorizontalAlignment();
            updateWidth();
            updateHeight();
            updateTextSize();
            require_update_texture_ = true;
        }

        overtake_position_properties_ = overtake_position_properties_property_->getBool();
        if (overtake_position_properties_) {
            hor_dist_property_->show();
            ver_dist_property_->show();
            hor_alignment_property_->show();
            ver_alignment_property_->show();
            width_property_->show();
            height_property_->show();
            text_size_property_->show();
        } else {
            hor_dist_property_->hide();
            ver_dist_property_->hide();
            hor_alignment_property_->hide();
            ver_alignment_property_->hide();
            width_property_->hide();
            height_property_->hide();
            text_size_property_->hide();
        }
    }

    void OverlayTextDisplay::updateOvertakeFGColorProperties() {
        if (!overtake_fg_color_properties_ && overtake_fg_color_properties_property_->getBool()) {
            // read all the parameters from properties
            updateFGColor();
            updateFGAlpha();
            updateFont();
            updateLineWidth();
            require_update_texture_ = true;
        }
        overtake_fg_color_properties_ = overtake_fg_color_properties_property_->getBool();
        if (overtake_fg_color_properties_) {
            fg_color_property_->show();
            fg_alpha_property_->show();
            line_width_property_->show();
            font_property_->show();
        } else {
            fg_color_property_->hide();
            fg_alpha_property_->hide();
            line_width_property_->hide();
            font_property_->hide();
        }
    }

    void OverlayTextDisplay::updateOvertakeBGColorProperties() {
        if (!overtake_bg_color_properties_ && overtake_bg_color_properties_property_->getBool()) {
            // read all the parameters from properties
            updateBGColor();
            updateBGAlpha();
            require_update_texture_ = true;
        }
        overtake_bg_color_properties_ = overtake_bg_color_properties_property_->getBool();
        if (overtake_bg_color_properties_) {
            bg_color_property_->show();
            bg_alpha_property_->show();
        } else {
            bg_color_property_->hide();
            bg_alpha_property_->hide();
        }
    }

    void OverlayTextDisplay::updateAlignBottom() {
        if (align_bottom_ != align_bottom_property_->getBool()) {
            require_update_texture_ = true;
        }
        align_bottom_ = align_bottom_property_->getBool();
    }

    void OverlayTextDisplay::updateInvertShadow() {
        if (invert_shadow_ != invert_shadow_property_->getBool()) {
            require_update_texture_ = true;
        }
        invert_shadow_ = invert_shadow_property_->getBool();
    }

    void OverlayTextDisplay::updateVerticalDistance() {
        vertical_dist_ = ver_dist_property_->getInt();
        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateHorizontalDistance() {
        horizontal_dist_ = hor_dist_property_->getInt();
        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateVerticalAlignment() {
        vertical_alignment_ = VerticalAlignment{static_cast<uint8_t>(ver_alignment_property_->getOptionInt())};

        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateHorizontalAlignment() {
        horizontal_alignment_ = HorizontalAlignment{static_cast<uint8_t>(hor_alignment_property_->getOptionInt())};

        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateWidth() {
        texture_width_ = width_property_->getInt();
        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateHeight() {
        texture_height_ = height_property_->getInt();
        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateTextSize() {
        text_size_ = text_size_property_->getInt();
        if (overtake_position_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateBGColor() {
        QColor c = bg_color_property_->getColor();
        bg_color_.setRed(c.red());
        bg_color_.setGreen(c.green());
        bg_color_.setBlue(c.blue());
        if (overtake_bg_color_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateBGAlpha() {
        bg_color_.setAlpha(bg_alpha_property_->getFloat() * 255.0);
        if (overtake_bg_color_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateFGColor() {
        QColor c = fg_color_property_->getColor();
        fg_color_.setRed(c.red());
        fg_color_.setGreen(c.green());
        fg_color_.setBlue(c.blue());
        if (overtake_fg_color_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateFGAlpha() {
        fg_color_.setAlpha(fg_alpha_property_->getFloat() * 255.0);
        if (overtake_fg_color_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateFont() {
        int font_index = font_property_->getOptionInt();
        if (font_index < font_families_.size()) {
            font_ = font_families_[font_index].toStdString();
        } else {
            RVIZ_COMMON_LOG_ERROR_STREAM("Unexpected error at selecting font index " << font_index);
            return;
        }
        if (overtake_fg_color_properties_) {
            require_update_texture_ = true;
        }
    }

    void OverlayTextDisplay::updateLineWidth() {
        line_width_ = line_width_property_->getInt();
        if (overtake_fg_color_properties_) {
            require_update_texture_ = true;
        }
    }

} // namespace rviz_2d_overlay_plugins

#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(rviz_2d_overlay_plugins::OverlayTextDisplay, rviz_common::Display)