I'm trying to create a chroma key for a qwebview in Qt5. This means I need to make a specific color be transparent (other widgets should be visible through webview's pixels with that color). I've found that it can be done using QPainter::CompositionMode operations, but can't make it work.
For example, I need to make all black pixels of a webview be transparent (the source color should be changed in runtime).
I've reimplemented QWebView::paintEvent in my class (get a part of a code from Qt sources), but don't know what to do next
WebView::paintEvent(QPaintEvent *event) {
if (!page()) return;
QWebFrame *frame = page()->mainFrame();
QPainter painter(this);
painter.setRenderHints(renderHints());
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
frame->render(&painter, event->region());
}
I found a way how to make any source color be white with the following code:
QWebFrame *frame = page()->mainFrame();
QImage source_image(size(), QImage::Format_ARGB32_Premultiplied);
QImage result_image(size(), QImage::Format_ARGB32_Premultiplied);
QPainter imagePainter(&source_image);
imagePainter.setRenderHints(renderHints());
frame->render(&imagePainter, event->region());
imagePainter.end();
QImage mask = source_image.createMaskFromColor(qRgb(0x00,0x00,0x00)); // Source color
QPainter resultPainter(&result_image);
resultPainter.drawImage(source_image.rect(), source_image);
resultPainter.setCompositionMode(QPainter::CompositionMode_Screen);
resultPainter.drawImage(source_image.rect(), mask);
QPainter painter(this);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawImage(0, 0, result_image);
But I don't know how to convert a white color to transparent.
I found a solution, but it consumes a lot of CPU.
First it's required to set
setStyleSheet("QWebView { background: transparent }");
setAttribute(Qt::WA_OpaquePaintEvent, true);
somewhere in WebView's constructor (I just forgot to mention that in the first message). Then reimplement paintEvent:
void WebView::paintEvent(QPaintEvent *event)
{
if (!page())
return;
QWebFrame *frame = page()->mainFrame();
QPainter painter(this);
QColor chroma_color(0, 0, 0); // A color that should be transparent
float opacity_level = 0.9; // WebView opacity
m_render_pixmap.fill(Qt::transparent);
QPainter pixmapPainter(&m_render_pixmap);
pixmapPainter.setRenderHints(renderHints());
frame->render(&pixmapPainter, event->region());
pixmapPainter.end();
m_render_pixmap.setMask(m_render_pixmap.createMaskFromColor(
chroma_color, Qt::MaskInColor));
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setOpacity(opacity_level);
painter.drawPixmap(QPoint(event->rect().left(), event->rect().top()), m_render_pixmap, event->rect());
painter.end();
}
m_render_pixmap is an instance of QPixmap. I don't want to recreate it every time paintEvent is called. I just recreate it on resizeEvent
void WebView::resizeEvent(QResizeEvent *event)
{
QWebView::resizeEvent(event);
m_render_pixmap = QPixmap(size());
}
The code above work great but in my case I want to render a video widget below a webview. So WebView::paintEvent calls about 25 times per second and each call takes about 20-25 ms in windowed mode on my PC. And it takes about 100% of one of CPU cores in a fullscreen mode.
Related
While i'm trying to draw text using QPainter::drawText() the text is not antialiased (comparing to MS word)
void TextLabel::paintEvent(QPaintEvent*) {
QPainter p(this);
p.setRenderHint(QPainter::TextAntialiasing);
QFont font;
font.setFamily("Roboto medium");
font.setPointSize(32);
font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias);
p.setPen(_brush);
p.setFont(font);
p.drawText(rect(), Qt::AlignLeft , _text);
}
Qt Doc says:
QPainter::TextAntialiasing -> Indicates that the engine should antialias text if possible
Is this impossible ?
What should i do ?
The word one:
The Qt one :
Seems it's an issue Qt has on Window OS (font rendering) and work with some fonts >=48pt and doesn't work with some other.
Issue : https://bugreports.qt.io/browse/QTBUG-40052
We hope they will fix it in the near future.
You can draw with QPainterPath it's more expensive but still helps :
void TextLabel::paintEvent(QPaintEvent*) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(Qt::black);
QFont font;
font.setPointSize(38);
font.setFamily("Roboto");
painter.setFont(font);
painter.drawText(0, 60, "Google");
QPainterPath textPath;
textPath.addText(0, 140, font, "Google");
painter.drawPath(textPath);
}
Roboto # 38pt :
Try painting via a QImage -- the QPainter::TextAntialiasing is more likely to be honoured that way.
QImage image(size(), QImage::Format_ARGB32_Premultiplied);
{
QPainter p(&image);
p.setRenderHint(QPainter::TextAntialiasing);
QFont font;
font.setFamily("Roboto medium");
font.setPointSize(16);
font.setStyleHint(QFont::Helvetica, QFont::PreferAntialias);
p.setPen(_brush);
p.setFont(_font);
p.drawText(rect(), Qt::AlignLeft , _text);
}
QPainter p(this);
p.drawImage(rect(), image);
Note: if this works then the QImage used should probably be a private class member rather than recreating it every time paintEvent is invoked.
Is it possible to use clipping in an widgets painEvent, if the widget is using stylesheets?
The background and reason for my question is that I want to make the widget animating when it appears and disappears. (Something like a resizing circle or square, that gets bigger starting as a small area from the center).
My first (and only) thought on how to solve this, was to use the clipping of a QPainter, so that only the required area is drawn.
If I make the Background of the widget transparent and use the primitive drawing functions from QPainter it works fine. But how can I solve this, if the widget has a stylesheet applied? Is it even possible?
The used Qt version is Qt 4.8.6
My questions are:
Is it possible to achieve what I want with the mentioned strategy?
Is it possible in any way to clip all the children, too?
Is my strategy appropriate or is it a bad Idea to solve it that way?
Are there any other ideas, best practices, Qt Classes, ... that can give me what I want?
Additional Information
I haven't much code to show, because I stuck with this clipping things. But here is something to get an idea of what I have tried:
This works.
/* Shows a small red circle inside the widget as expected */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry()
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setPen(QColor(255, 0, 0));
painter.setBrush(QColor(255, 0, 0));
painter.setOpacity(1);
painter.drawRect(rect);
}
But the following doesn't change anything:
/* This shows the widget as usual */
void MyAnimatingWidget::paintEvent(QPaintEvent *ev) {
QPainter painter(this);
QRect rect = this->geometry();
QStyleOption opt;
painter.setClipRegion(QRegion(rect.width()/2,
rect.height()/2,
150, 150,
QRegion::Ellipse));
painter.setRenderHint(QPainter::Antialiasing);
painter.setOpacity(1);
opt.init(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
Moreover I have noticed, that the stylesheet is also drawn, even if I remove the style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); line at all.
The stylesheet you apply to your widget overrides the OS-specific style(s) widgets are equipped with by default. This can even cause problems, if you want to have a, say, Windows look, but still want to use a stylesheet. Anyway, you can check what each style does in the Qt source directory: src/gui/styles. For style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);, the code reads:
case PE_Widget:
if (w && !rule.hasDrawable()) {
QWidget *container = containerWidget(w);
if (styleSheetCaches->autoFillDisabledWidgets.contains(container)
&& (container == w || !renderRule(container, opt).hasBackground())) {
//we do not have a background, but we disabled the autofillbackground anyway. so fill the background now.
// (this may happen if we have rules like :focus)
p->fillRect(opt->rect, opt->palette.brush(w->backgroundRole()));
}
break;
}
As you can see clipping is not meddled with in any way, so your idea of setting a clip region should work. Now for the painting mystery. The painting of the background happens in void QWidgetPrivate::paintBackground(QPainter *painter, const QRegion &rgn, int flags) const, which is called from void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter, QWidgetBackingStore *backingStore). You can find the code in: /src/gui/kernel/qwidget.cpp. The relevant code reads:
if (q->testAttribute(Qt::WA_StyledBackground)) {
painter->setClipRegion(rgn);
QStyleOption opt;
opt.initFrom(q);
q->style()->drawPrimitive(QStyle::PE_Widget, &opt, painter, q);
}
Maybe turning the attribute off would help? The basic lesson you should draw from my answer is to get accustomed to source diving. The idea behind Qt is nice (instantiating controls, without bothering about implementation details), but it rarely works in practice, i.e. you often need to source dive.
To clip widget's children to arbitrary clip regions, you can capture them into a pixmap, example:
QPixmap pixmap(widget->size());
widget->render(&pixmap);
And then draw the pixmap manually. You might also be able to prevent them repainting automatically (via setUpdatesEnabled() or by hiding them) and then calling their render in you paintEvent handler manually.
I need to change the alpha of a QImage I have so it blends with the other QImages behind and infront of it. This needs to be toggled quickly on and off.
Previously I've had to recreate every single image and give them new colors with just a different alpha value. But now I want to retain that same original image instead of redrawing and painting it.
I'm trying to do it like this now:
QImage image;
unsigned int rgb;
for(int y=0;y<image.height();y++){
for(int x=0;x<image.width();x++){
rgb=image.pixel(x,y);
image.setPixel(x,y,qRgba(qRed(rgb),qGreen(rgb),qRed(rgb),120));
}
}
I'm getting some fairly unpredictable behavior. When I toggle the image sometimes I lose colors or the alpha isn't changed. And if the alpha did get changed when I toggle back (I hardcode the alpha 255 elsewhere instead of 120) it doesn't go back to normal.
This doesn't seem like the right way to do this anyways, it shouldn't be this difficult. It seems like there should be a single function call on an image to change the alpha but I haven't found one yet.
If you are using the QImage in QGraphicsView or in some other QWidget, you should look into this QGraphicsEffect:
http://qt-project.org/doc/qt-4.8/qgraphicsopacityeffect.html
http://doc-snapshot.qt-project.org/4.8/qwidget.html#setGraphicsEffect
http://doc-snapshot.qt-project.org/4.8/qgraphicsitem.html#setGraphicsEffect
If you are using a QLabel, I would try this out:
#include <QLabel>
#include <QPainter>
class TransparentQLabel : public QLabel
{
Q_OBJECT
public:
explicit TransparentQLabel() : QLabel() {}
~TransparentQLabel(){}
void setOpacity(const qreal & val)
{
if (this->pixmap() == null || this->pixmap().isNull())
return;
QPixmap result(this->pixmap()->size());
result.fill(Qt::transparent);
QPainter painter;
painter.begin(&result);
painter.setOpacity(val);
painter.drawPixmap(0, 0, *(this->pixmap()));
painter.end();
QLabel::setPixmap(result);
}
};
This next bit is only slightly related to your question, but it is nice to know. If you are layering outside of your QApplication onto the operating system, you need some things like this:
this->setWindowFlags( Qt::WindowStaysOnTopHint |
Qt::FramelessWindowHint | Qt::Tool);
this->setAttribute(Qt::WA_TranslucentBackground, true);
this->setAttribute (Qt::WA_TransparentForMouseEvents, true);
Here is an example of this stuff in action:
http://qt-project.org/wiki/QSplashScreen-Replacement-for-Semitransparent-Images
Hope that helps.
Maybe someone can help me with the following problem:
I want to draw the content of a QImage in a QGLWidget, but the widget is painted in black.
class QGLCanvas {
public:
QGLCanvas(QWidget* parent) : QGLWidget(parent) {
}
void setImage(const QImage* image) {
img = image;
}
void paintEvent(QPaintEvent*) {
// From Painter Documentation Qt
QPainter p(this);
p.setRenderHint(QPainter::SmoothPixmapTransform, 1);
p.drawImage(this->rect(), *img);
p.end();
}
public slots:
void rgb_data(const void *data) {
memcpy((void *)img->bits(), data, img->byteCount()); // data will be copied (sizes are known)
// img.save("text.png"); // saves right image
this->update(); // calls repaint, but does not draw the image.
}
private:
QImage *img;
}
The Bug:
When the slot is called, the memory is copied to the image. If the image is saved, the content is correct. But the repaint method just draws black content to the widget.
The Fix:
If the memcpy line is implemented outside the slot, the image content is drawn to the widget. This fix increased code complexity a lot. Thus, i've got the following question:
The Question:
Why does the memcpy not work within the slot? Is this a general problem with Qt?
There's nothing special about a slot which would stop your code from working.
What's probably the issue is that when you call update(), a repaint is scheduled but happens asynchronously. From the code you've provided the most likely cause is img being modified between the calls to rbg_data and paintEvent
You want to be sure about the format of the QImage. When you call bits and are expecting it to be RGB, you need to check the format.
if( img->format() != QImage::Format_RGB888 )
{
// convert the image format to RGB888
*img = img->convertToFormat(QImage::Format_RGB888);
}
This way, Qt will know the image format when trying to paint it. If you fill it with RGB data but the QImage is "formatted" as ARGB, you will get some painting errors.
I want to draw an image, pixel by pixel at run time. I use QPainter and paintEvent to draw. But when paintEvent is called each time, the previously drawn image is cleared and the new point has been drawn.
How to avoid clearing the previously drawn parts? I just want to append the new pixel point to the previously drawn points.
Lines::Lines(QWidget *parent)
: QWidget(parent)
{
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_timer->start();
m_x = 0;
m_y = 0;
}
void Lines::paintEvent(QPaintEvent *event)
{
QPen pen(Qt::black, 2, Qt::SolidLine);
QPainter painter(this);
painter.setPen(pen);
painter.drawPoint(m_x, m_y);
}
void Lines::updateStatus()
{
m_x++;
m_y++;
update();
}
paintEvent is supposed to do a complete redraw of the widget region specified in the event.
So you are responsible for buffering previous results.
It doesn't really make sense to change the desired output in paintEvent, as it may be randomly called and when it is called is out of your control.
If you want to avoid that you can use a QGraphicsView.
Buffering could be done using a QPixmap, which would be part of the Lines class. You draw the pixel in the pixmap (not in the paint event, in updateStatus), and draw the pixmap in the paint event.
QWidget::setAttribute( WA_OpaquePaintEvent, true );
prevents clearing the widget. However, this is just for optimization in case the widget does a complete repaint anyway.
You should follow Dr. Hirsch's advice.