Search code examples
qtsvgqgraphicsitem

Apply QGraphicsColorizeEffect to a part of a QGraphicsSvgItem (only to the svg itself)


I have a QGraphicsSvgItem subclass where I would like to modify the svg color. I want to use for that QGraphicsColorizeEffect, and it works great.

My items also have a custom selection rectangle, highlighted - similar to other item types.

When I apply the colorize effect, the highlight also turns to the same color...

I have tried to setEnabled(false); in paint but it seems to have no effect.

sample code:

file mysvg.h

#ifndef MYSVG_H
#define MYSVG_H

#include <QGraphicsSvgItem>
#include <QGraphicsColorizeEffect>

class MySvg : public QGraphicsSvgItem
{
public:
    MySvg();
    ~MySvg();
    virtual void paint(QPainter* painter,
                       const QStyleOptionGraphicsItem* option,
                       QWidget* widget = NULL);
private:
    QGraphicsColorizeEffect* m_effect;
    void drawSelectionRectangle(QPainter* painter, const QStyleOptionGraphicsItem* option, const QRectF& rectangle);
};
#endif // MYSVG_H

file mysvg.cpp

#include <QStyleOptionGraphicsItem>
#include <QStyle>
#include <QPainterPath>
#include <QPainter>
#include <QFileDialog>
#include <QSvgRenderer>

MySvg::MySvg()
{
    m_effect = new QGraphicsColorizeEffect();
    m_effect->setColor(Qt::red);
    setGraphicsEffect(m_effect);
    setFlags(QGraphicsItem::ItemIsMovable    |
             QGraphicsItem::ItemIsFocusable  |
             QGraphicsItem::ItemIsSelectable);

    QString filename = QFileDialog::getOpenFileName(0, tr("Open Svg File"),
                     QString(), tr("Svg files (*.svg *.svgz)"));
    setSharedRenderer(new QSvgRenderer(filename));
}

MySvg::~MySvg()
{
    delete renderer();
    delete m_effect;
}

void MySvg::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QStyleOptionGraphicsItem opt(*option);
    opt.state = QStyle::State_None;

    QGraphicsSvgItem::paint(painter, &opt, widget);

    //m_effect->setEnabled(false);  // no effect though seemed logical
    QRectF rectangle = boundingRect();
    if (option->state & (QStyle::State_Selected))
        drawSelectionRectangle(painter, option, rectangle);
    //m_effect->setEnabled(true);
}

void MySvg::drawSelectionRectangle(QPainter *painter, const QStyleOptionGraphicsItem *option, const QRectF &rectangle)
{
    painter->setPen(QPen(option->palette.windowText(), 0, Qt::DotLine));
    painter->setBrush(QColor(255, 188, 0, 50));
    painter->drawRect(rectangle);
}

file main.cpp

#include <QApplication>
#include <QGraphicsView>
#include "mysvg.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QGraphicsScene s;
    QGraphicsView view;
    view.setScene(&s);
    s.setSceneRect(-50, -50, 500, 650);
    view.show();
    MySvg* svg = new MySvg();
    s.addItem(svg);
    return app.exec();
}

file mysvg.pro

QT       += core gui svg
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = mysvg
TEMPLATE = app
SOURCES += main.cpp \
           mysvg.cpp
HEADERS +=  mysvg.h

I have considered making a QGraphicsSvgItem a private member of the MySvg item - but the MySvg item has to do a lot of other things, and I don't know what to do with the renderer (who would own it...) - if I can figure out how to make a QGraphicsSvgItem subclass a member of the MySvg class, I can apply the colorize to the member and perform all other operations on the MySvg item...

Please help me figure a way to apply color to the svg, but not other drawing portion of the item.

Edit - I have tried to add a member item to the class and apply the colorize effect to the member - but it doesn't apply the colorize effect at all... The svg loads with all original colors.

Here is the code containing a member item:

new mysvg.h

class SvgMember : public QGraphicsSvgItem
{
public:
    SvgMember (const QByteArray &content, const QColor& c);
    ~SvgMember ();
private:
    QGraphicsColorizeEffect* m_effect;
};

class MySvg : public QGraphicsItem
{
public:
    MySvg();
    ~MySvg();
    virtual void paint(QPainter* painter,
                       const QStyleOptionGraphicsItem* option,
                       QWidget* widget = NULL);
    virtual QRectF boundingRect() const;
    virtual QPainterPath shape() const;

private:
    void drawSelectionRectangle(QPainter* painter, const QStyleOptionGraphicsItem* option, const QRectF& rectangle);
    SvgMember * m_member;
};

new mysvg.cpp

MySvg::MySvg()
{
    setFlags(QGraphicsItem::ItemIsMovable    |
             QGraphicsItem::ItemIsFocusable  |
             QGraphicsItem::ItemIsSelectable);

    QString filename = QFileDialog::getOpenFileName(0, QObject::tr("Open Svg File"),
                     QString(), QObject::tr("Svg files (*.svg *.svgz)"));
    QFile f(filename);
    f.open(QFile::ReadOnly | QFile::Text);
    QByteArray svgContents = f.readAll();
    f.close();
    m_member = new SvgMember (svgContents, Qt::red);
}

MySvg::~MySvg()
{ 
    delete m_member;
}

void MySvg::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QStyleOptionGraphicsItem opt(*option);
    opt.state = QStyle::State_None;

    m_member->paint(painter, &opt, widget);

    QRectF rectangle = boundingRect();
    if (option->state & (QStyle::State_Selected))
        drawSelectionRectangle(painter, option, rectangle);
}

/*! \brief reimplemented to use member rectangle */
QRectF MySvg::boundingRect() const
{
    return m_member->boundingRect();
}

/*! \brief reimplemented to use member shape */
QPainterPath MySvg::shape() const
{
    return m_member->shape();
}

void MySvg::drawSelectionRectangle(QPainter *painter, const QStyleOptionGraphicsItem *option, const QRectF &rectangle)
{
    painter->setPen(QPen(option->palette.windowText(), 0, Qt::DotLine));
    painter->setBrush(QColor(255, 188, 0, 50));
    painter->drawRect(rectangle);
}

SvgMember ::SvgMember (const QByteArray &content, const QColor &c)
{
    m_effect = new QGraphicsColorizeEffect();
    setGraphicsEffect(m_effect);
    m_effect->setColor(c);
    setSharedRenderer(new QSvgRenderer(content));
}

SvgMember ::~SvgMember ()
{
    delete renderer();
    delete m_effect;
}

What can I do to apply the colorize effect to the svg - but not to the selection rectangle ?


Solution

  • The effect is a feature of the base class QGraphicsItem. It is applied to the entire graphics item and all its children. So, everything that is painted inside the item is affected by its effect.

    The selection rectangle should be painted outside of the SVG item object. It can be achieved by enclosing QGraphicsSvgItem by the composite class QGraphicsItemGroup.

    When a QGraphicsItem is added to QGraphicsItemGroup it becomes reparented. Thus the item is destroyed when the group object is destroyed. So, it is not needed to delete it manually. QGraphicsItem takes ownership of effect, so it is not needed to delete the effect object.

    The following class MyGraphicsItemGroup works as you expect.

    Implementation "mygraphicsitemgroup.cpp"

    #include "mygraphicsitemgroup.h"
    
    #include <QGraphicsColorizeEffect>
    #include <QGraphicsSvgItem>
    #include <QStyleOptionGraphicsItem>
    #include <QPainter>
    #include <QFileDialog>
    
    MyGraphicsItemGroup::MyGraphicsItemGroup()
    {
        setFlags(QGraphicsItem::ItemIsMovable    |
                 QGraphicsItem::ItemIsFocusable  |
                 QGraphicsItem::ItemIsSelectable);
    
        QString filename = QFileDialog::getOpenFileName(0,
            QObject::tr("Open Svg File"), QString(),
            QObject::tr("Svg files (*.svg *.svgz)"));
    
        QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect();
        effect->setColor(Qt::red);
    
        QGraphicsSvgItem *svg = new QGraphicsSvgItem(filename);
        svg->setGraphicsEffect(effect);
        addToGroup(svg);
    }
    
    void MyGraphicsItemGroup::paint(QPainter* painter,
        const QStyleOptionGraphicsItem* option, QWidget* widget)
    {
        QStyleOptionGraphicsItem opt(*option);
        opt.state = QStyle::State_None;
        QGraphicsItemGroup::paint(painter, &opt, widget);
    
        QRectF rectangle = boundingRect();
        if (option->state & QStyle::State_Selected)
            drawSelectionRectangle(painter, option, rectangle);
    }
    
    void MyGraphicsItemGroup::drawSelectionRectangle(QPainter *painter,
        const QStyleOptionGraphicsItem *option, const QRectF &rectangle)
    {
        painter->setPen(QPen(option->palette.windowText(), 0, Qt::DotLine));
        painter->setBrush(QColor(255, 188, 0, 50));
        painter->drawRect(rectangle);
    }
    

    Header "mygraphicsitemgroup.h"

    #ifndef MYGRAPHICSITEMGROUP_H
    #define MYGRAPHICSITEMGROUP_H
    
    #include <QGraphicsItemGroup>
    
    class MyGraphicsItemGroup : public QGraphicsItemGroup
    {
    public:
        MyGraphicsItemGroup();
    
        virtual void paint(QPainter* painter,
            const QStyleOptionGraphicsItem* option, QWidget* widget);
    
        void drawSelectionRectangle(QPainter *painter,
            const QStyleOptionGraphicsItem *option, const QRectF &rectangle);
    };
    
    #endif // MYGRAPHICSITEMGROUP_H