I have been using Qt in development for over 6 years, of which the last 3 years have been building applications for Android and iOS on Qt Quick. My commitment to this framework is due to two reasons:
For example, in Qt Quick, there is an Image component that places an image in an interface. The component has many parameters: location, scaling method, anti-aliasing, etc., but there is no radius parameter for rounding the image at the corners. At the same time, round images can now be found in almost any modern interface and because of this there was a need to write your Image. With support for all parameters Image and radius. In this article I will describe several ways to make rounded images.
Qt Quick has a library for working with graphic effects QtGraphicalEffects . In essence, each component is a wrapper over shaders and OpenGL. So I suggested that this should work quickly and did something like this:
import QtQuick 2.0 import QtGraphicalEffects 1.0 Item { property alias source: imageOriginal.source property alias radius: mask.radius Image { id: imageOriginal anchors.fill: parent visible: false } Rectangle { id: rectangleMask anchors.fill: parent radius: 0.5*height visible: false } OpacityMask { id: opacityMask anchors.fill: imageOriginal source: imageOriginal maskSource: rectangleMask } }
Let's look at how this works: opacityMask
superimposes the rectangleMask
mask on the imageOriginal
image and displays what happened. Please note that the original image and the rectangle are invisible visible: false
. This is necessary to avoid overlap, because opacityMask
is a separate component and does not directly affect the display of other elements of the scene.
This is the simplest and slowest implementation possible. Display lags will be immediately visible if you create a long list of images and scroll through it (for example, the list of contacts as in Telegram). Even greater discomfort will deliver the image resizing brakes. The problem is that all components of the QtGraphicalEffects
library heavily load the graphics subsystem, even if the original image and the size of the element do not change. The problem can be slightly reduced by using the grubToImage (...) function to create a static round image, but it’s better to use another implementation of the image rounding.
The next method that came to mind was to draw the corners over the image with the background color using Canvas . In this case, with the same size and radius of the image, the Canvas can not be redrawn, but copied for each new element. Due to this optimization, the advantage in rendering speed is achieved in comparison with the first implementation.
This approach has two minuses. Firstly, any change in size and radius requires a redrawing of the Canvas, which in some cases will reduce performance even lower than in the solution with OpacityMask. And second, the background under the image must be uniform, otherwise our illusion will be revealed.
import QtQuick 2.0 import QtGraphicalEffects 1.0 Item { property alias source: imageOriginal.source property real radius: 20 property color backgroundColor: "white" Image { id: imageOriginal anchors.fill: parent visible: false } Canvas { id: roundedCorners anchors.fill: parent onPaint: { var ctx = getContext("2d"); ctx.reset(); ctx.fillStyle = backgroundColor; ctx.beginPath(); ctx.moveTo(0, radius) ctx.lineTo(0, 0); ctx.lineTo(radius, 0); ctx.arc(radius, radius, radius, 3/2*Math.PI, Math.PI, true); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(width, radius) ctx.lineTo(width, 0); ctx.lineTo(width-radius, 0); ctx.arc(width-radius, radius, radius, 3/2*Math.PI, 2*Math.PI, false); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(0, height-radius) ctx.lineTo(0, height); ctx.lineTo(radius, height); ctx.arc(radius, height-radius, radius, 0.5*Math.PI, Math.PI, false); ctx.closePath(); ctx.fill(); ctx.beginPath(); ctx.moveTo(width-radius, height) ctx.lineTo(width, height); ctx.lineTo(width, height-radius); ctx.arc(width-radius, height-radius, radius, 0, 0.5*Math.PI, false); ctx.closePath(); ctx.fill(); } } }
To increase performance and get rid of the dependence on a uniform background, I created a QML component based on the C ++ class QQuickPaintedItem . This class provides a component drawing mechanism through QPainter . To do this, override the void paint(QPainter *painter)
method of the parent class. From the name it is clear that the method is called to draw the component.
void ImageRounded::paint(QPainter *painter) { QPen pen; pen.setStyle(Qt::NoPen); painter->setPen(pen); QImage *image = new QImage("image.png"); // QBrush brush(image); // qreal wi = static_cast<qreal>(image.width()); qreal hi = static_cast<qreal>(image.height()); qreal sw = wi / width(); qreal sh = hi / height(); brush.setTransform(QTransform().scale(1/sw, 1/sh)); painter->setBrush(brush); // qreal radius = 10 painter->drawRoundedRect(QRectF(0, 0, width(), height()), radius, radius); }
In the example above, the original image is stretched to the size of the element and is used as a pattern when drawing a rectangle with rounded edges. To simplify the code, hereinafter, image scaling options are not considered: PreserveAspectFit
and PreserveAspectFit
, but only Stretch
.
By default, QPainter
draws on the image, and then copies it into the OpenGL buffer. If you draw directly in FBO , then component rendering will be accelerated several times. To do this, call the following two functions in the class constructor:
setRenderTarget(QQuickPaintedItem::FramebufferObject); setPerformanceHint(QQuickPaintedItem::FastFBOResizing, true);
The implementation on QQuickPaintedItem
works much faster than the first and second. But even in this case, on smartphones, there is a noticeable delay in rendering when the image is resized. The fact is that any function of the scaling image is made at the processor's capacity and takes at least 150 ms (measured on i7 and on HTC One M8). You can take out the scaling in a separate stream and draw the picture by readiness - this will improve responsiveness (the application will always respond to user actions), but it will not solve the problem in essence - you will see the image twitching when scaling.
Once the bottleneck is the processor, it comes to mind to use the power of the video accelerator. Qt Quick provides the QQuickItem class for this. When inheriting from it, you must override the updatePaintNode
method. The method is called every time the component needs to be drawn.
QSGNode* ImageRounded::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) { if (_status != Ready) { return nullptr; } QSGGeometryNode *node; if (!oldNode) { node = new QSGGeometryNode(); // QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), _segmentCount); geometry->setDrawingMode(QSGGeometry::DrawTriangleFan); setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry); node->setFlag(QSGNode::OwnsOpaqueMaterial); // auto image = new QImage("image.png"); auto texture = qApp->view()->createTextureFromImage(image); auto material = new QSGOpaqueTextureMaterial; material->setTexture(texture); material->setFiltering(QSGTexture::Linear); material->setMipmapFiltering(QSGTexture::Linear); setMaterial(material); node->markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial); } else { node = oldNode; node->markDirty(QSGNode::DirtyGeometry); } // QSGGeometry::TexturedPoint2D *vertices = node->geometry()->vertexDataAsTexturedPoint2D(); const int count = 20; // const int segmentCount = 4*count + 3; // Coefficients cf = {0, 0, width(), height() ,0, 0, 1/width(), 1/height()}; const float ox = 0.5f*cf.w + cf.x; const float oy = 0.5f*cf.h + cf.y; const float lx = 0.5f*cf.w + cf.x; const float ly = cf.y; const float ax = 0 + cf.x; const float ay = 0 + cf.y; const float bx = 0 + cf.x; const float by = cf.h + cf.y; const float cx = cf.w + cf.x; const float cy = cf.h + cf.y; const float dx = cf.w + cf.x; const float dy = 0 + cf.y; const float r = 2*_radius <= cf.w && 2*_radius <= cf.h ? _radius : 2*_radius <= cf.w ? 0.5f*cf.w : 0.5f*cf.h; vertices[0].set(ox, oy, ox*cf.tw+cf.tx, oy*cf.th+cf.ty); vertices[1].set(lx, ly, lx*cf.tw+cf.tx, ly*cf.th+cf.ty); // int start = 2; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = ax + r*(1 - qFastSin(angle)); float y = ay + r*(1 - qFastCos(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } // start += count; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = bx + r*(1 - qFastCos(angle)); float y = by + r*(-1 + qFastSin(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } // start += count; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = cx + r*(-1 + qFastSin(angle)); float y = cy + r*(-1 + qFastCos(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } // start += count; for (int i=0; i < count; ++i) { double angle = M_PI_2 * static_cast<double>(i) / static_cast<double>(count-1); float x = dx + r*(-1 + qFastCos(angle)); float y = dy + r*(1 - qFastSin(angle)); vertices[start+i].set (x, y, x*cf.tw+cf.tx, y*cf.th+cf.ty); } vertices[segmentCount-1].set(lx, ly, lx*cf.tw+cf.tx, ly*cf.th+cf.ty); return node; }
In the example under the spoiler, we first create an object of the QSGGeometryNode class - we return this object to the Qt Quick Scene Graph engine for rendering. Then we specify the geometry of the object - a rectangle with rounded corners, create a texture from the original image and transfer the texture coordinates (they indicate how the texture is stretched onto the geometry). Note: the geometry in the example is given by the triangle fan method. Here is an example of the component:
In this article, I tried to collect various methods of drawing a rounded image in Qt Quick: from the simplest to the most productive. I deliberately missed the methods of loading images and specifics in creating QML components, because the topic of a separate article with its pitfalls. However, you can always see the source code of our library, which my friend and I use to create mobile applications: here .
Source: https://habr.com/ru/post/320212/
All Articles