Chapter 18. The Legend of King Arthur and Drawing Context

Ever faced a situation where “you drew everything” but the screen is empty? Or when the window flickers, lines look “jagged”, and transparency behaves like it has its own rules? In Qt graphics, one small error turns into hours of debugging — and every developer who’s touched paintEvent() knows this.

This chapter will reveal why the “sword” (QPainter) makes no sense without the “sheath” (QPaintDevice) — and what exactly professional developers keep in mind to make drawing always predictable. You’ll discover non-obvious causes of flickering, “missing” shapes, and strange alpha channel behavior — and learn the secret of achieving clean output without unnecessary performance costs.

We’ll cover 3 cornerstone classes (QPainter / QPaintDevice / QPaintEngine), 3 types of gradients, Anti-aliasing modes, QPicture for recording commands, transformations (translate/scale/rotate/shear), clipping (ClipRegion/ClipPath), and the “magic” of composition mode that often replaces manual pixel processing.

Skip this chapter and you’ll keep drawing “by luck”. Or you can make Qt graphics finally work as a system.

This chapter includes ready-to-use code examples.

Chapter Self-Check

Why must drawing in Qt be performed exclusively in the paintEvent() method, not in the widget constructor or regular slots?Answer
Correct answer: This is a fundamental principle of working with graphics in Qt, related to the event processing loop and repaint mechanism. Attempts to draw outside paintEvent() won’t work because the drawing context isn’t properly initialized.
Why does Qt use double buffering if it requires additional memory?Answer
Correct answer: Double buffering suppresses flickering during repaints by forming the image in an invisible buffer and transferring it to the visible area all at once. This happens automatically without requiring code implementation.
What does “cosmetic pen width” mean and in which cases should it be used?Answer
Correct answer: This is a pen with width equal to zero, meaning drawing the line as thin as possible depending on the output device. Used for maximally thin lines during scaling or printing.
Why does a black pixel at coordinates (50; 50) display as four gray pixels when antialiasing is enabled?Answer
Correct answer: The pixel center is at its middle (50.5; 50.5), and antialiasing adds intermediate colors to smooth edges, distributing color across four neighboring pixels at coordinates (49.5; 49.5), (49.5; 50.5), (50.5; 49.5), and (50.5; 50.5).
What’s the main advantage of using QPainterPath compared to directly drawing shapes?Answer
Correct answer: A once-created path can be displayed multiple times with a single drawPath() call, which is more efficient when repeatedly drawing complex geometric compositions.
Why does the order of transformations (translate, scale, rotate) affect the final result?Answer
Correct answer: Transformations are applied sequentially to the coordinate system, and each subsequent transformation works in an already transformed coordinate system. For example, rotation after translation will give a different result than rotation before translation.
Why are the save() and restore() methods of QPainter needed?Answer
Correct answer: They save and restore QPainter state (pen, brush, transformations, fonts), allowing temporary changes to settings for drawing individual elements, then returning to original settings.
In what case can a pen with NoPen style be useful?Answer
Correct answer: When you need to output a closed shape (rectangle, ellipse) of a specific color without an outline, using brush fill without drawing the contour.
How does a linear gradient differ from a radial one in terms of color distribution?Answer
Correct answer: Linear gradient interpolates colors along a straight line between two control points, while radial — from a center point (or focus point) to a circle of specified radius.
What will happen if you try to draw an ellipse with a clip region set that doesn’t fully cover it?Answer
Correct answer: Only the part of the ellipse that falls within the clip region will be visible; the rest of the drawing will be invisible, as going beyond the clip region is blocked.
Why was the RHI (Rendering Hardware Interface) architecture introduced in Qt6?Answer
Correct answer: RHI is an abstract layer over various graphics APIs (Vulkan, Metal, Direct3D, OpenGL), allowing Qt to automatically choose the optimal backend for each platform, improving performance and compatibility.
How can using QPicture help when needing to output the same graphics to screen and printer?Answer
Correct answer: QPicture records drawing commands in a device-independent format, which can then be played back on any drawing context (screen, printer, file) without code changes.
Why is Format_ARGB32_Premultiplied format recommended for working with transparency?Answer
Correct answer: This format uses premultiplied color values (premultiplied alpha), which speeds up blending and compositing operations, especially when working with gradients and composition modes.
What’s the difference between united() and xored() methods when combining QRegion areas?Answer
Correct answer: united() returns the union of two regions (everything that’s in at least one), while xored() returns the symmetric difference (points from each region but not from both simultaneously).
Why check composition mode before manipulating alpha channel pixels of a raster image?Answer
Correct answer: Often the desired effect can be achieved simply by setting an appropriate composition mode (CompositionMode), which is much more efficient than manually traversing all image pixels.

Practical Exercises

Easy Level

Gradient Color Kaleidoscope
Create a widget that displays four squares, each with a different gradient type: linear (red→blue), conical (green→yellow→green), radial (white→black), and regular solid fill. Arrange squares in a 2×2 grid. Enable antialiasing for smooth transitions.
Hints: Use QLinearGradient, QConicalGradient, and QRadialGradient. The setColorAt() method sets colors at points from 0 to 1. For drawing squares, use drawRect(). Don’t forget to call setRenderHint(QPainter::Antialiasing, true).

Medium Level

Animated Transformations
Create an application that displays a rectangle with text inside. Add three buttons: “Rotate”, “Scale”, and “Reset”. When buttons are pressed, apply corresponding transformations: rotate by 15°, scale by ×1.2, or return to initial state. Use save()/restore() for proper QPainter state management.
Hints: Store current transformation matrix QTransform as a widget class member. In paintEvent(), apply it through setTransform(). Methods rotate(), scale(), and reset() modify the matrix. Call update() after changes for repainting.

Hard Level

Graphics Editor with Composition Modes
Create a mini-editor allowing mouse drawing on canvas. Add tool selection (brush, eraser), pen thickness adjustment, and a dropdown list of composition modes (SourceOver, Clear, Xor, Multiply). Implement undo for the last action. Use QPicture or QPixmap to store drawing to avoid data loss during repaints. Add ability to save result to file.
Hints: Use mousePressEvent, mouseMoveEvent, mouseReleaseEvent events for tracking drawing. Store QPainterPath for current stroke. Save operation history in QList<QPicture> for undo functionality. setCompositionMode() method switches composition modes. For saving, use QPixmap::save().

🎨 Join the Discussion!

Figured out the “Arthur” rendering architecture? Applied gradients or mastered coordinate transformations?

Maybe you have questions about composition modes or optimizing graphics operations? Or perhaps you’ve created an interesting visual effect and want to share your experience?

Discuss your findings, ask questions about QPainter, or help other readers understand the subtleties of working with graphics in Qt. Your experience is invaluable to the community!

Leave a Reply

Your email address will not be published. Required fields are marked *