I expanded the Qt Imageviewer example by some functionality. I basically want to add a save function. In this example there are two functions of the same class handling the picture open process:
void ImageViewer::open()
{
QStringList mimeTypeFilters;
foreach (const QByteArray &mimeTypeName, QImageReader::supportedMimeTypes())
mimeTypeFilters.append(mimeTypeName);
mimeTypeFilters.sort();
const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
QFileDialog dialog(this, tr("Open File"),
picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last());
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setMimeTypeFilters(mimeTypeFilters);
dialog.selectMimeTypeFilter("image/jpeg");
while (dialog.exec() == QDialog::Accepted && !loadFile(dialog.selectedFiles().first())) {}
}
and
bool ImageViewer::loadFile(const QString &fileName)
{
QImageReader reader(fileName);
reader.setAutoTransform(true);
const QImage image = reader.read();
if (image.isNull()) {
QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
tr("Cannot load %1.").arg(QDir::toNativeSeparators(fileName)));
setWindowFilePath(QString());
imageLabel->setPixmap(QPixmap());
imageLabel->adjustSize();
return false;
}
imageLabel->setPixmap(QPixmap::fromImage(image));
scaleFactor = 1.0;
printAct->setEnabled(true);
fitToWindowAct->setEnabled(true);
convAct->setEnabled(true); // so the image can be converted if it was loaded ...
updateActions();
if (!fitToWindowAct->isChecked()) {
imageLabel->adjustSize();
}
setWindowFilePath(fileName);
return true;
}
So I added a save button in the menus, and in the ImageViewer.h class:
class ImageViewer : public QMainWindow
{
Q_OBJECT
public:
ImageViewer();
bool loadFile(const QString &);
private slots:
void open();
void print();
void save(); // <---
Everything is fine, but I don't know how to get my Image in the new function, besides the fact, that I obviously make a wrong conversion from QPixmap to QImage - but I also tried replacing it with QPixmap test = imageLabel->pixmap() without any success.
void ImageViewer::save()
{
QImage test = imageLabel->pixmap();
qWarning()<< test;
QByteArray bytes;
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "BMP");
QString monobitmap = QString::fromLatin1(bytes.toBase64().data());
}
In the end, I want to save it as a monochrome bitmap (no matter what it was before). Sorry for posting a lot of code.
It sounds like your problem is that you have a QPixmap object and you need a QImage object. If that's the case, then you can convert a QPixmap into a QImage by calling the toImage() method on the QPixmap; it will return the resulting QImage object.
As for you converting the QImage to a monochrome bitmap, you should be able to do that by calling convertToFormat(QImage::Format_Mono) on your QImage. That call will return the new (1-bit) version of the QImage.
Related
I'm using QGraphicsView and QGraphicsScene to display an uploaded image and then show some drawing on it. I'm uploading and image like so:
void MeasuresWidget::on_openAction_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Measures Application"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
The problem i'm facing is that if i upload an image that is smaller than the one that was uploaded before, there appears to be empty space, i.e. scene maintains the size of previous image(the bigger one) and is bigger than current one. I tried maintaining original size of scene in individual variable and using setSceneRect() in each upload action:
//in constructor
originalRect = ui->graphicsView->rect();
//in upload action
scene->setSceneRect(originalRect);
but result is that size of scene always stays the same and, if it's bigger than the actual image, cuts it. I used QLabel to display an image before and i used QLabel::setScaledContents() function and it worked fine for me. So, my question is can i achieve the same behaviour with QGraphicsScene?
Update 1:
Application behaves the way i want if i create new scene every upload action. The code now looks like:
void MeasuresWidget::on_openAction_triggered()
{
scene = new QGraphicsScene(this);
ui->graphicsView->setScene(scene);
QString fileName = QFileDialog::getOpenFileName(this,tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
{
QImage image(fileName);
if (image.isNull())
{
QMessageBox::information(this, tr("Image Viewer"), tr("Cannot load %1.").arg(fileName));
return;
}
scene->clear();
scene->addPixmap(QPixmap::fromImage(image).scaledToWidth(w, Qt::SmoothTransformation));
}
}
Is this ok? Can i achieve the behaviour i want without a need to create new scene every upload action?
You just have to resize the scene when you insert your pixmap based on its size.
If you define a new class inheriting from QGraphicsScene, you can handle it easily:
class GraphicsScene: public QGraphicsScene
{
public:
GraphicsScene(QRect const& rect, QObject* parent=nullptr): QGraphicsScene(rect, parent),
background(nullptr)
{}
QGraphicsPixmapItem *addPixmap(const QPixmap &pixmap)
{
// We already have a background. Remove it
if (background)
{
removeItem(background);
delete background;
}
background = QGraphicsScene::addPixmap(pixmap);
// Move the pixmap
background->setPos(0, 0);
// Change the scene rect based on the size of the pixmap
setSceneRect(background->boundingRect());
return background;
}
private:
QGraphicsPixmapItem* background;
};
GraphicsScene* scene = new GraphicsScene(QRect());
QGraphicsView* view = new QGraphicsView();
view->setScene(scene);
view->show();
QPixmap pix1(QSize(2000, 2000));
pix1.fill(Qt::red);
QPixmap pix2(QSize(100, 300));
pix2.fill(Qt::green);
// The scene will be 2000x2000
QTimer::singleShot(1000, [=]() { scene->addPixmap(pix1); });
// The scene will be 100x300
QTimer::singleShot(10000, [=]() { scene->addPixmap(pix2); });
I have a project where i want to draw a point into a image inside a QPixmap. The point would be draw with the mouse click on the QLabel. I created a eventFilter() which corresponds to mouse click. When I click with the mouse, these eventFilter is called and draw a point in the image, but my code doesn't works. I tried many others options like subclassing the QLabel, but didn't work either.
And sometimes my compiler shows these error messages:
QPainter::begin: Paint device returned engine == 0, type: 2
QPainter::setPen: Painter not active
QPainter::drawPoints: Painter not active
QPainter::end: Painter not active, aborted
but I don't understand, because the Qt documentation says that is allowed use the QPainter outside of paintEvent just using with QPixmap.
Below is my code with the method that starts the QPainter.
bool mainwindow::eventFilter(QObject* watched, QEvent* event) {
if ( watched != ui->labelScreen )
return false;
if ( event->type() != QEvent::MouseButtonPress )
return false;
const QMouseEvent* const me = static_cast<const QMouseEvent*>( event );
//might want to check the buttons here
const QPoint p = me->pos(); //...or ->globalPos();
ui->label_Xget->setNum(this->xReal);
ui->label_Yget->setNum(this->yReal);
///////////////////////////////////
QPixmap pix;
pix.fromImage(QImage::fromData("C:/Users/Syn/Pictures/imagem137.jpg"));
QPainter *painter = new QPainter(&pix);
painter->setPen(Qt::red);
painter->drawPoint(p.x(), p.y());
ui->labelScreen->setPixmap(pix);
painter->end();
///////////////////////////////////
return false;
}
Someone would can help me solve this problem? Thanks.
The error messages are not from your compiler, they happen at runtime, and you can't be sure whether the code you quote above is their cause.
There are several problem:
QPixmap::fromImage is a static method that returns a pixmap. You're ignoring its return value. The correct way to use it would be:
// C++11
auto pix = QPixmap::fromImage(QImage{"filename"});
// C++98
QPixmap pix(QPixmap::fromImage(QImage("filename")));
You can pass the filename directly to QPixmap constructor:
// C++11
QPixmap pix{"filename"};
// C++98
QPixmap pix("filename");
You're leaking the painter instance. You should avoid any dynamic memory allocation unless you truly need it. Also, store ui by value - it's cheaper that way.
It is a very bad idea to do any blocking I/O, such as reading a file, in event handlers. Preload and store the pixmap as a member of the MainWindow class.
Thus (in C++11):
Interface
template <class F>
class MainWindow : public QMainWindow {
QPixmap m_clickPixmap;
Ui::MainWindow ui;
bool eventFilter(QObject*, QEvent*) override;
public:
MainWindow(QWidget * parent = nullptr);
};
Implementation
void loadImage(const QString & fileName, QObject * context, F && functor) {
QtConcurrent::run([=]{
QImage img{fileName};
if (img.isNull()) return;
QTimer::singleShot(0, context, functor, img);
});
}
MainWindow::MainWindow(QWidget * parent) :
QMainWindow{parent}
{
loadImage("C:/Users/Syn/Pictures/imagem137.jpg", this, [this](const QImage & img){
m_clickPixmap = QPixmap::fromImage(img);
});
ui.setupUi(this);
}
bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
if ( watched != ui.labelScreen )
return false;
if ( event->type() != QEvent::MouseButtonPress )
return false;
auto const me = static_cast<const QMouseEvent*>(event);
auto const p = me->pos(); //...or ->globalPos();
ui.label_Xget->setNum(this->xReal);
ui.label_Yget->setNum(this->yReal);
auto pix{m_clickPixmap};
QPainter painter(&pix);
painter.setPen(Qt::red);
painter.drawPoint(p.x(), p.y());
painter.end(); // probably not needed
ui.labelScreen->setPixmap(pix);
return false;
}
I have to connect a QQuickImageProvider with a class to pass an image that the image provider must return, but I am not finding a way to do that.
I have a class called provedorImagem.cpp with a virtual requestImage function implemented and I have also a class called processaImagem.cpp that is the class to perform modification at the image.
The provedorImagem class is passed to engine as a provider: engine.addImageProvider("provedor", provedorImg) in main.cpp
What I need is a way to connect a slot in the provider in main.cpp with a signal in processaImagem.cpp. Doing that the processaImagem.cpp can send the image I must return to Qml to provedorImagem.cpp and send it back to Qml.
Could someone help me?
Code below
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include "processaimagem.h"
#include "provedorimagem.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");
QQmlApplicationEngine engine;
provedorImagem *provedorImg = new provedorImagem;
//------------ I have to create a connection here between the provider slot and a signal in processaImagem with the image to provide -----------------
engine.addImageProvider("provedor", provedorImg);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
processaImagem.h
#ifndef PROCESSAIMAGEM_H
#define PROCESSAIMAGEM_H
#include <QObject>
#include <QImage>
#include <QQmlEngine>
#include <QQmlContext>
#include <QQuickImageProvider>
#include "provedorimagem.h"
class processaImagem : public QObject
{
Q_OBJECT
public slots:
QString recebeImagem(const QString &caminho);
public:
processaImagem(QObject *parent = 0);
QImage carregaImagem(const QString &caminho);
signals:
void enviaImagem(QImage);
};
#endif // PROCESSAIMAGEM_H
processaImagem.cpp
#include "processaimagem.h"
#include <QDebug>
processaImagem::processaImagem(QObject *parent)
{
}
QString processaImagem::recebeImagem(const QString &caminho)
{
QImage imagem = this->carregaImagem(caminho);
QString caminhoRetorno;
if(imagem.isNull())
{
qDebug() << "Erro ao receber a imagem";
}
else
{
qDebug() << "Imagem recebida";
caminhoRetorno = "image://provedor/imagemEditada";
}
return caminhoRetorno;
}
QImage processaImagem::carregaImagem(const QString &caminho)
{
QUrl caminhoImagem(caminho);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageSize;
QString imageId = caminhoImagem.path().remove(0, 1);
QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);
if(imagem.isNull())
{
qDebug() << "Erro ao carregar a imagem";
imagem = QImage();
}
else
{
qDebug() << "Imagem carregada";
}
return imagem;
}
provedorimagem.h
#ifndef PROVEDORIMAGEM_H
#define PROVEDORIMAGEM_H
#include <QImage>
#include <QQuickImageProvider>
class provedorImagem : public QQuickImageProvider
{
public:
provedorImagem();
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
void carregaImagem();
public slots:
void carregaImagem(QImage imagemRecebida);
private:
QImage imagem;
};
#endif // PROVEDORIMAGEM_H
provedorimagem.cpp
#include "provedorimagem.h"
#include <QDebug>
provedorImagem::provedorImagem() : QQuickImageProvider(QQuickImageProvider::Image)
{
}
QImage provedorImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
if(imagem.isNull())
{
qDebug() << "Erro ao prover a imagem";
}
else
{
qDebug() << "Imagem provida";
}
return imagem;
}
void provedorImagem::carregaImagem(QImage imagemRecebida)
{
imagem = imagemRecebida;
}
Answering my own question
Problem solved. Here is the solution step by step:
1 - Create a class that inherits from QQuickImageProvider and QObject and inside it create a Image member (QImage) that is the image to be provided.
class provedorImagem : public QObject, public QQuickImageProvider
Implement the virtual requestImage method. This is the method that will return the image to Qml
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize)
Create a method to load the provider’s image to return
void provedorImagem::carregaImagem(QImage imagemRecebida)
{
imagem = imagemRecebida;
}
Now set it as the engine image provider in the main.cpp file
provedorImagem *provedorImg = new provedorImagem;
engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);
2 - Create another class that inherits from QObject.
class processaImagem : public QObject
Inside this class you must implement a method that will get the image from camera provider, perform the image modifications and return the modified image.
PS: The p_caminhoImagem is a property that I created inside the processaImagem class that receives the camera preview path.
QImage processaImagem::carregaImagem()
{
QUrl caminhoImagem(p_caminhoImagem);
QQmlEngine *engine = QQmlEngine::contextForObject(this)->engine();
QQmlImageProviderBase *imageProviderBase = engine->imageProvider(caminhoImagem.host());
QQuickImageProvider *imageProvider = static_cast<QQuickImageProvider*>(imageProviderBase);
QSize imageSize;
QString imageId = caminhoImagem.path().remove(0, 1);
QImage imagem = imageProvider->requestImage(imageId, &imageSize, imageSize);
if(imagem.isNull())
{
imagem = QImage();
}
else
{
//Perform the modifications
}
return imagem;
}
3 - Now is the main part. The image requestImage provider method must receive the modified image from the processaImagem class to provide it to QML. To do it the provider class pointer must be accessible to the QML file, so, in the main.cpp file just make the pointer available to QML as a property
engine.rootContext()->setContextProperty("ProvedorImagem", provedorImg);
and register the processaImagem class as a QML type
qmlRegisterType<processaImagem>("ProcessaImagemQml", 1, 0, "ProcessaImagem");
Now we link it inside the QML file
ProvedorImagem.carregaImagem(processaImagem.carregaImagem());
4 - It is done. Now just request the image from the provider:
imagemPreview.source = "image://provedor/imagemEditada_" + camera.numeroImagem.toString();
I hope you are trying to send a image from qt(c++) code to display in qml.
Please look the below camera application source. Passing the camera image to qml using QQuickPaintedItem.
https://github.com/econsysqtcam/qtcam.git
Have a look at videostreaming.cpp/ videostreaming.h and their connection to qml. Hope that helps
For image provider to work one of the below methods needs to be implemented:
virtual QImage requestImage(const QString &id, QSize *size, const QSize& requestedSize);
virtual QPixmap requestPixmap(const QString &id, QSize *size, const QSize& requestedSize);
virtual QQuickTextureFactory *requestTexture(const QString &id, QSize *size, const QSize &requestedSize);
Derived from the example above.
QImage provedorImagem::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
// the id has value like "imagemEditada" or the last word in URL
if(imagem.isNull())
{
qDebug() << "Erro ao prover a imagem";
}
else if (caminhoRetorno.endsWith(id, Qt::CaseInsensitive)) // that will be a check for id after "provedor"
{
qDebug() << "Imagem provida";
return imagem; // provide an image
}
return QImage(); // no match with id from url
}
And your QML code should have something like:
Image {
// // //
source: "image://provedor/imagemEditada"
}
There is a nice complete example in Qt docs.
#GuiDupas, from your corrected question it seems that you would like to recognize the image provided. The method to check the last word in URL with QString::endsWith is for the demo of course.
I have an image which I need to display as the background of a QLabel. This is my code (culled from Qt documentation here):
#include <QtWidgets>
#include "imageviewer.h"
ImageViewer::ImageViewer()
{
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Base);
imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
imageLabel->setScaledContents(true);
setCentralWidget(imageLabel);
createActions();
createMenus();
resize(570,357);
}
bool ImageViewer::loadFile(const QString &fileName)
{
QImageReader reader(fileName);
const QImage image = reader.read();
if (image.isNull()) {
QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
tr("Cannot load %1.").arg(QDir::toNativeSeparators(fileName)));
setWindowFilePath(QString());
imageLabel->setPixmap(QPixmap());
imageLabel->adjustSize();
return false;
}
imageLabel->setPixmap(QPixmap::fromImage(image).scaled(size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
return true;
}
void ImageViewer::open()
{
QStringList mimeTypeFilters;
foreach (const QByteArray &mimeTypeName, QImageReader::supportedMimeTypes())
mimeTypeFilters.append(mimeTypeName);
mimeTypeFilters.sort();
const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
QFileDialog dialog(this, tr("Open File"),
picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last());
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setMimeTypeFilters(mimeTypeFilters);
dialog.selectMimeTypeFilter("image/jpeg");
while (dialog.exec() == QDialog::Accepted && !loadFile(dialog.selectedFiles().first())) {}
}
void ImageViewer::createActions()
{
openAct = new QAction(tr("&Open..."), this);
openAct->setShortcut(tr("Ctrl+O"));
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
}
void ImageViewer::createMenus()
{
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
menuBar()->addMenu(fileMenu);
}
Header file:
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include <QMainWindow>
class QAction;
class QLabel;
class QMenu;
class QScrollArea;
class QScrollBar;
class ImageViewer : public QMainWindow
{
Q_OBJECT
public:
ImageViewer();
bool loadFile(const QString &);
private slots:
void open();
private:
void createActions();
void createMenus();
QLabel *imageLabel;
QAction *openAct;
QMenu *fileMenu;
};
#endif
Main:
#include <QApplication>
#include <QCommandLineParser>
#include "imageviewer.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGuiApplication::setApplicationDisplayName(ImageViewer::tr("Image Viewer"));
ImageViewer imageViewer;
imageViewer.show();
return app.exec();
}
As you can see, the viewport size is 570 by 357. The image I am using is this (size is 2560 by 1600, both have an aspect ratio of almost 1.6, too big to upload here, so will upload a screenshot of the pic):
But when I open the app and add the image there, the image I get in the QLabel is pretty blurred:
How do I make the image in the label as well defined as the actual image (it is in bmp format, so you have to change the mime type to bmp in the file open dialog in Mac)?
When I do this task through QPainter, i.e making a QImage out of the image file, then passing it to the painter and calling update it comes up fine. But I need to be able to zoom the image inline when clicked (which I am doing by calling resize() on the QLabel), and the QPainter way does not let me zoom the image.
Platform - OS X 10.10 (Retina Display), Qt 5.3.1, 32 bit.
You are running into a known bug in QLabel on a Retina display Mac in Qt 5.3:
https://bugreports.qt.io/browse/QTBUG-42503
From your image, it looks like you are simply getting a non-retina version of your image when you use QLabel, but if you code it manually in the QPainter you're getting the full resolution of your source QImage. If thats the case, then this is indeed caused by this bug.
You have two solutions:
Roll your own solution, don't use QLabel. There is no reason you can't easily "zoom the image inline" without using QLabel (just use a custom QWidget class with an overrided PaintEvent).
Upgrade to a version of Qt where this bug has been fixed. This is probably the right solution anyway, unless there are any regressions in the latest version that cause a problem in your application. According to the bug report, this issues was fixed in v5.5.0, so the latest v5.5.1 should work fine.
An example of the first approach (I'll leave the header out the header file for brevity, its pretty simple):
void ImageWidget::setImage(QImage image)
{
//Set the image and invalidate our cached pixmap
m_image = image;
m_cachedPixmap = QPixmap();
update();
}
void ImageWidget::paintEvent(QPaintEvent *)
{
if ( !m_image.isNull() )
{
QSize scaledSize = size() * devicePixelRatio();
if (m_cachedPixmap.size() != scaledSize)
{
QImage scaledImage = m_image.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
m_cachedPixmap = QPixmap::fromImage(scaledImage);
m_cachedPixmap.setDevicePixelRatio(devicePixelRatio());
}
QPainter p(this);
p.drawPixmap(0, 0, m_cachedPixmap);
}
}
This is simply a very basic widget that just draws a QImage to its full size, respecting the DevicePixelRatio, and hence taking advantage of Retina resolution. Note: coded this on a non-retina machine, so I can't guarantee that it works on Retina, but I got the basic implementation from the Qt 5.5.1 fix for QLabel. Like QLabel, it also caches the Pixmap so it doesn't have to re-scale every time paintEvent is called, unless the widget has actually been resized.
You compress the image more than 4-fold (2560 -> 570) and it seems there are small details in the image impossible to retain after shriking this much.
Actually, you get more or less what you can expect after shrinking an image so much.
I have a svg loaded in the resources, but it is black. How do I change the color to white?
This is how you can do it in Qt, don´t forget to add the xml and svg modules to your qt project (*.pro file). This code snippet changes the color by modifying the "fill" attribute of any "path" element but you could use it to modify any attribute of any element.
void SetAttrRecur(QDomElement &elem, QString strtagname, QString strattr, QString strattrval);
void ChangeSVGColor()
{
// open svg resource load contents to qbytearray
QFile file("myfile.svg");
file.open(QIODevice::ReadOnly);
QByteArray baData = file.readAll();
// load svg contents to xml document and edit contents
QDomDocument doc;
doc.setContent(baData);
// recurivelly change color
SetAttrRecur(doc.documentElement(), "path", "fill", "white");
// create svg renderer with edited contents
QSvgRenderer svgRenderer(doc.toByteArray());
// create pixmap target (could be a QImage)
QPixmap pix(svgRenderer.defaultSize());
pix.fill(Qt::transparent);
// create painter to act over pixmap
QPainter pixPainter(&pix);
// use renderer to render over painter which paints on pixmap
svgRenderer.render(&pixPainter);
QIcon myicon(pix);
// Use icon ....
}
void SetAttrRecur(QDomElement &elem, QString strtagname, QString strattr, QString strattrval)
{
// if it has the tagname then overwritte desired attribute
if (elem.tagName().compare(strtagname) == 0)
{
elem.setAttribute(strattr, strattrval);
}
// loop all children
for (int i = 0; i < elem.childNodes().count(); i++)
{
if (!elem.childNodes().at(i).isElement())
{
continue;
}
SetAttrRecur(elem.childNodes().at(i).toElement(), strtagname, strattr, strattrval);
}
}
Since the SVG format is XML based, and XML is just ASCII text... you could load the SVG resource in to a QString, call QString::replace("\"#000000\"", "\"#ffffff\""), and then pass the modified QString in to your QSVGRenderer.
If your SVG is black, there is an extremely easy way to do it: QGraphicsEffect
#include <QGraphicsItem>
#include <QGraphicsColorizeEffect>
QGraphicsItem *item;
QGraphicsColorizeEffect *effect;
item = new QGraphicsItem;
effect = new QGraphicsColorizeEffect;
effect->setColor(Qt::white);
effect->setStrength(1);
item->setGraphicsEffect(effect)
This doesn't work with white SVGs, but given that almost any website that provides icons does so in black, this is pretty neat.
As long as you don't need it on Mac, this should work:
http://doc-snapshot.qt-project.org/4.8/qwidget.html#setGraphicsEffect
http://doc-snapshot.qt-project.org/4.8/qgraphicscolorizeeffect.html
EDIT: Or if you need to support Mac, do the svg rendering and effects inside a QGraphicsView.
http://doc-snapshot.qt-project.org/4.8/qgraphicsitem.html#setGraphicsEffect
Setup your colorize effect to color it white, and set it to the svgWidget.
Hope that helps.
solution for pyqt5. You can easily convert it to c++
def QIcon_from_svg(svg_filepath, color='black'):
img = QPixmap(svg_filepath)
qp = QPainter(img)
qp.setCompositionMode(QPainter.CompositionMode_SourceIn)
qp.fillRect( img.rect(), QColor(color) )
qp.end()
return QIcon(img)
You can use a ColorOverlay, as described in the end of this question:
Qt QML LevelAdjust shows strange edge effects when applied to svg source
It doesn't change the SVG per say, but it creates a colored layer which will have the same shape as your drawing (assuming your drawing's background is transparent).
#ifndef SVG_ITEM_H
#define SVG_ITEM_H
#include <QObject>
#include <QPen>
#include <QQuickItem>
#include <QQuickPaintedItem>
#include <QSvgRenderer>
class SVG_Item : public QQuickPaintedItem
{
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
Q_PROPERTY(QPen stroke READ stroke WRITE setStroke NOTIFY strokeChanged)
Q_PROPERTY(bool debuging READ debuging WRITE setdebuging)
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged)
Q_OBJECT
public:
explicit SVG_Item(QQuickItem *parent = nullptr);
void paint(QPainter *painter) Q_DECL_OVERRIDE;
QString source() const;
QColor color() const;
QPen stroke() const;
bool debuging() const;
QColor backgroundColor() const;
signals:
void sourceChanged(QString source);
void colorChanged(QColor color);
void strokeChanged(QPen stroke);
void backgroundColorChanged(QColor backgroundColor);
public slots:
void setSource(QString source);
void setColor(QColor color);
void setStroke(QPen stroke);
void setdebuging(bool debuging);
void setBackgroundColor(QColor backgroundColor);
private:
QString m_source;
QColor m_color;
QPen m_stroke;
QString svgContent;
QSvgRenderer *renderer;
bool changed;
bool m_debuging;
QColor m_backgroundColor;
QColor m_test;
};
#endif // SVG_ITEM_H
#include "svg_item.h"
#include <QSvgRenderer>
#include <QDebug>
#include <QPainter>
#include <QSvgGenerator>
SVG_Item::SVG_Item(QQuickItem *parent) : QQuickPaintedItem(parent)
{
changed = false;
renderer = NULL;
m_debuging = false;
m_backgroundColor = Qt::transparent;
}
void SVG_Item::paint(QPainter *painter)
{
painter->fillRect(0,0,this->width(),this->height(),m_backgroundColor);
if(m_source != "")
{
if(changed)
{
if(renderer != NULL)
{
renderer->deleteLater();
}
renderer = new QSvgRenderer(svgContent.toLocal8Bit());
}
if(renderer != NULL)
renderer->render(painter);
}
}
void SVG_Item::setSource(QString source)
{
if(source.startsWith("qrc"))
source = source.remove(0,3);
if (m_source == source)
return;
QFile readFile(source);
if(!readFile.exists())
{
qWarning("file not found");
}
readFile.open(QFile::ReadOnly);
svgContent = readFile.readAll();
setColor(color());
//readData.replace()
m_source = source;
emit sourceChanged(m_source);
}
void SVG_Item::setColor(QColor color)
{
changed = true;
QString fillStr = "fill:%1";
fillStr = fillStr.arg(color.name());
svgContent = svgContent.replace(QRegExp("fill:[# 0-9 a b c d e f A B C D E F]+"), fillStr);
if(!svgContent.contains(QRegExp("fill:[# 0-9 a b c d e f A B C D E F]+")))
{
QString style = "<path \n style=\"fill:%1;fill-opacity:1\"";
style = style.arg(color.name());
svgContent = svgContent.replace("<path",style);
style = "<rect \n style=\"fill:%1;fill-opacity:1\"";
style = style.arg(color.name());
svgContent = svgContent.replace("<rect",style);
style = "<circle \n style=\"fill:%1;fill-opacity:1\"";
style = style.arg(color.name());
svgContent = svgContent.replace("<circle",style);
style = "<ellipse \n style=\"fill:%1;fill-opacity:1\"";
style = style.arg(color.name());
svgContent = svgContent.replace("<ellipse",style);
style = "<polygon \n style=\"fill:%1;fill-opacity:1\"";
style = style.arg(color.name());
svgContent = svgContent.replace("<polygon",style);
}
//
this->update();
if (m_color == color)
return;
m_color = color;
emit colorChanged(m_color);
}
void SVG_Item::setStroke(QPen stroke)
{
changed = true;
if (m_stroke == stroke)
return;
m_stroke = stroke;
emit strokeChanged(m_stroke);
}
void SVG_Item::setdebuging(bool debuging)
{
m_debuging = debuging;
}
void SVG_Item::setBackgroundColor(QColor backgroundColor)
{
if (m_backgroundColor == backgroundColor)
return;
m_backgroundColor = backgroundColor;
emit backgroundColorChanged(m_backgroundColor);
}
QString SVG_Item::source() const
{
return m_source;
}
QColor SVG_Item::color() const
{
return m_color;
}
QPen SVG_Item::stroke() const
{
return m_stroke;
}
bool SVG_Item::debuging() const
{
return m_debuging;
}
QColor SVG_Item::backgroundColor() const
{
return m_backgroundColor;
}
Pretty old thread but also so viewed so I have the need to answer for those like me that still are using qt5 and the answer is not working because in qt5 doc.documentElement() returns a const element. There is the simple changes you need to make to make it work:
void SetAttrRecur(QDomElement &elem, QString strtagname, QString strattr,
QString strattrval)
{
// if it has the tagname then overwritte desired attribute
if (elem.tagName().compare(strtagname) == 0)
{
elem.setAttribute(strattr, strattrval);
}
// loop all children
for (int i = 0; i < elem.childNodes().count(); i++)
{
if (!elem.childNodes().at(i).isElement())
{
continue;
}
QDomElement docElem = elem.childNodes().at(i).toElement(); //<-- make const "variable"
SetAttrRecur(docElem, strtagname, strattr, strattrval);
}
}
QIcon ChangeSVGColor(QString iconPath, QString color)
{
// open svg resource load contents to qbytearray
QFile file(iconPath);
if(!file.open(QIODevice::ReadOnly))
return {};
QByteArray baData = file.readAll();
// load svg contents to xml document and edit contents
QDomDocument doc;
doc.setContent(baData);
// recurivelly change color
QDomElement docElem = doc.documentElement(); //<-- make const "variable"
SetAttrRecur(docElem, "path", "fill", color);
// create svg renderer with edited contents
QSvgRenderer svgRenderer(doc.toByteArray());
// create pixmap target (could be a QImage)
QPixmap pix(svgRenderer.defaultSize());
pix.fill(Qt::transparent);
// create painter to act over pixmap
QPainter pixPainter(&pix);
// use renderer to render over painter which paints on pixmap
svgRenderer.render(&pixPainter);
QIcon myicon(pix);
return myicon;
}
If white is the only color you need, then a simple solution would be to change the color of the original SVG image with an image editor (e.g. Inkscape). Of course, if you need use the image with many different colors, that wont be a reasonable solution.