Turn the "concentriccircles" example into snippets, update screenshot

The example has a lot of code and documentation, but in essence shows
how to use float-based QPainter APIs and how to set a render hint. That
is two lines of code, which we can show as snippets.

Update the screenshot of the example with a higher-resolution version.

Fixes: QTBUG-119983
Change-Id: Iafcb813dff6ab8c269176f7994c95947ebf5e559
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
(cherry picked from commit 33254fb41f29b510d3d74dbaab60f0a67ef56d46)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
(cherry picked from commit 6c726979f744e85dc1284374ef1663a85647365e)
This commit is contained in:
Volker Hilsheimer 2023-12-12 14:54:50 +01:00 committed by Qt Cherry-pick Bot
parent db1710d418
commit f35eae4b79
15 changed files with 30 additions and 499 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -1,210 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
\example painting/concentriccircles
\title Concentric Circles Example
\examplecategory {Graphics & Multimedia}
\ingroup examples-painting
\brief Demonstrates the improved quality that antialiasing and floating point precision gives.
\brief The Concentric Circles example shows the improved rendering
quality that can be obtained using floating point precision and
anti-aliasing when drawing custom widgets. The example also shows
how to do simple animations.
The application's main window displays several widgets which are
drawn using various combinations of precision and
anti-aliasing.
\image concentriccircles-example.png
Anti-aliasing is one of QPainter's render hints. The
QPainter::RenderHints are used to specify flags to QPainter that
may, or may not, be respected by any given
engine. QPainter::Antialiasing indicates that the engine should
anti-alias the edges of primitives if possible, i.e. put
additional pixels around the original ones to smooth the edges.
The difference between floating point precision and integer
precision is a matter of accuracy, and is visible in the
application's main window: Even though the logic that is
calculating the circles' geometry is the same, floating points
ensure that the white spaces between each circle are of the same
size, while integers make two and two circles appear as if they
belong together. The reason is that the integer based precision
rely on rounding off non-integer calculations.
The example consists of two classes:
\list
\li \c CircleWidget is a custom widget which renders several animated
concentric circles.
\li \c Window is the application's main window displaying four \c
{CircleWidget}s drawn using different combinations of precision
and aliasing.
\endlist
First we will review the CircleWidget class, then we will take a
look at the Window class.
\section1 CircleWidget Class Definition
The CircleWidget class inherits QWidget, and is a custom widget
which renders several animated concentric circles.
\snippet painting/concentriccircles/circlewidget.h 0
We declare the \c floatBased and \c antialiased variables to hold
whether an instance of the class should be rendered with integer
or float based precision, and whether the rendering should be
anti-aliased or not. We also declare functions setting each of
these variables.
In addition we reimplement the QWidget::paintEvent() function to
apply the various combinations of precision and anti-aliasing when
rendering, and to support the animation. We reimplement the
QWidget::minimumSizeHint() and QWidget::sizeHint() functions to
give the widget a reasonable size within our application.
We declare the private \c nextAnimationFrame() slot, and the
associated \c frameNo variable holding the number of "animation
frames" for the widget, to facilitate the animation.
\section1 CircleWidget Class Implementation
In the constructor we make the widget's rendering integer based
and aliased by default:
\snippet painting/concentriccircles/circlewidget.cpp 0
We initialize the widget's \c frameNo variable, and set the
widget's background color using the QWidget::setBackgroundColor()
function which takes a \l {QPalette::ColorRole}{color role} as
argument; the QPalette::Base color role is typically white.
Then we set the widgets size policy using the
QWidget::setSizePolicy() function. QSizePolicy::Expanding means
that the widget's \l {QWidget::sizeHint()}{sizeHint()} is a
sensible size, but that the widget can be shrunk and still be
useful. The widget can also make use of extra space, so it should
get as much space as possible.
\snippet painting/concentriccircles/circlewidget.cpp 1
\codeline
\snippet painting/concentriccircles/circlewidget.cpp 2
The public \c setFloatBased() and \c setAntialiased() functions
update the widget's rendering preferences, i.e. whether the widget
should be rendered with integer or float based precision, and
whether the rendering should be anti-aliased or not.
The functions also generate a paint event by calling the
QWidget::update() function, forcing a repaint of the widget with
the new rendering preferences.
\snippet painting/concentriccircles/circlewidget.cpp 3
\codeline
\snippet painting/concentriccircles/circlewidget.cpp 4
The default implementations of the QWidget::minimumSizeHint() and
QWidget::sizeHint() functions return invalid sizes if there is no
layout for the widget, otherwise they return the layout's minimum and
preferred size, respectively.
We reimplement the functions to give the widget minimum and
preferred sizes which are reasonable within our application.
\snippet painting/concentriccircles/circlewidget.cpp 5
The nextAnimationFrame() slot simply increments the \c frameNo
variable's value, and calls the QWidget::update() function which
schedules a paint event for processing when Qt returns to the main
event loop.
\snippet painting/concentriccircles/circlewidget.cpp 6
A paint event is a request to repaint all or part of the
widget. The \c paintEvent() function is an event handler that can
be reimplemented to receive the widget's paint events. We
reimplement the event handler to apply the various combinations of
precision and anti-aliasing when rendering the widget, and to
support the animation.
First, we create a QPainter for the widget, and set its
antialiased flag to the widget's preferred aliasing. We also
translate the painters coordinate system, preparing to draw the
widget's cocentric circles. The translation ensures that the
center of the circles will be equivalent to the widget's center.
\snippet painting/concentriccircles/circlewidget.cpp 7
When painting a circle, we use the number of "animation frames" to
determine the alpha channel of the circle's color. The alpha
channel specifies the color's transparency effect, 0 represents a
fully transparent color, while 255 represents a fully opaque
color.
\snippet painting/concentriccircles/circlewidget.cpp 8
If the calculated alpha channel is fully transparent, we don't
draw anything since that would be equivalent to drawing a white
circle on a white background. Instead we skip to the next circle
still creating a white space. If the calculated alpha channel is
fully opaque, we set the pen (the QColor passed to the QPen
constructor is converted into the required QBrush by default) and
draw the circle. If the widget's preferred precision is float
based, we specify the circle's bounding rectangle using QRectF and
double values, otherwise we use QRect and integers.
The animation is controlled by the public \c nextAnimationFrame()
slot: Whenever the \c nextAnimationFrame() slot is called the
number of frames is incremented and a paint event is
scheduled. Then, when the widget is repainted, the alpha-blending
of the circles' colors change and the circles appear as animated.
\section1 Window Class Definition
The Window class inherits QWidget, and is the application's main
window rendering four \c {CircleWidget}s using different
combinations of precision and aliasing.
\snippet painting/concentriccircles/window.h 0
We declare the various components of the main window, i.e., the text
labels and a double array that will hold reference to the four \c
{CircleWidget}s. In addition we declare the private \c
createLabel() function to simplify the constructor.
\section1 Window Class Implementation
\snippet painting/concentriccircles/window.cpp 0
In the constructor, we first create the various labels and put
them in a QGridLayout.
\snippet painting/concentriccircles/window.cpp 1
Then we create a QTimer. The QTimer class is a high-level
programming interface for timers, and provides repetitive and
single-shot timers.
We create a timer to facilitate the animation of our concentric
circles; when we create the four CircleWidget instances (and add
them to the layout), we connect the QTimer::timeout() signal to
each of the widgets' \c nextAnimationFrame() slots.
\snippet painting/concentriccircles/window.cpp 2
Before we set the layout and window title for our main window, we
make the timer start with a timeout interval of 100 milliseconds,
using the QTimer::start() function. That means that the
QTimer::timeout() signal will be emitted, forcing a repaint of the
four \c {CircleWidget}s, every 100 millisecond which is the reason
the circles appear as animated.
\snippet painting/concentriccircles/window.cpp 3
The private \c createLabel() function is implemented to simlify
the constructor.
*/

View File

@ -2,7 +2,6 @@
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_example(basicdrawing)
qt_internal_add_example(concentriccircles)
qt_internal_add_example(affine)
# qt_internal_add_example(composition) # FIXME: Seems buggy wrt. usesOpenGL function
qt_internal_add_example(deform)

View File

@ -1,38 +0,0 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
project(concentriccircles LANGUAGES CXX)
if(NOT DEFINED INSTALL_EXAMPLESDIR)
set(INSTALL_EXAMPLESDIR "examples")
endif()
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/widgets/painting/concentriccircles")
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
qt_standard_project_setup()
qt_add_executable(concentriccircles
circlewidget.cpp circlewidget.h
main.cpp
window.cpp window.h
)
set_target_properties(concentriccircles PROPERTIES
WIN32_EXECUTABLE TRUE
MACOSX_BUNDLE TRUE
)
target_link_libraries(concentriccircles PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
)
install(TARGETS concentriccircles
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
)

View File

@ -1,84 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "circlewidget.h"
#include <QPainter>
#include <stdlib.h>
//! [0]
CircleWidget::CircleWidget(QWidget *parent)
: QWidget(parent)
{
floatBased = false;
antialiased = false;
frameNo = 0;
setBackgroundRole(QPalette::Base);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
//! [0]
//! [1]
void CircleWidget::setFloatBased(bool floatBased)
{
this->floatBased = floatBased;
update();
}
//! [1]
//! [2]
void CircleWidget::setAntialiased(bool antialiased)
{
this->antialiased = antialiased;
update();
}
//! [2]
//! [3]
QSize CircleWidget::minimumSizeHint() const
{
return QSize(50, 50);
}
//! [3]
//! [4]
QSize CircleWidget::sizeHint() const
{
return QSize(180, 180);
}
//! [4]
//! [5]
void CircleWidget::nextAnimationFrame()
{
++frameNo;
update();
}
//! [5]
//! [6]
void CircleWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, antialiased);
painter.translate(width() / 2, height() / 2);
//! [6]
//! [7]
for (int diameter = 0; diameter < 256; diameter += 9) {
int delta = abs((frameNo % 128) - diameter / 2);
int alpha = 255 - (delta * delta) / 4 - diameter;
//! [7] //! [8]
if (alpha > 0) {
painter.setPen(QPen(QColor(0, diameter / 2, 127, alpha), 3));
if (floatBased)
painter.drawEllipse(QRectF(-diameter / 2.0, -diameter / 2.0, diameter, diameter));
else
painter.drawEllipse(QRect(-diameter / 2, -diameter / 2, diameter, diameter));
}
}
}
//! [8]

View File

@ -1,36 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef CIRCLEWIDGET_H
#define CIRCLEWIDGET_H
#include <QWidget>
//! [0]
class CircleWidget : public QWidget
{
Q_OBJECT
public:
CircleWidget(QWidget *parent = nullptr);
void setFloatBased(bool floatBased);
void setAntialiased(bool antialiased);
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
public slots:
void nextAnimationFrame();
protected:
void paintEvent(QPaintEvent *event) override;
private:
bool floatBased;
bool antialiased;
int frameNo;
};
//! [0]
#endif // CIRCLEWIDGET_H

View File

@ -1,11 +0,0 @@
QT += widgets
HEADERS = circlewidget.h \
window.h
SOURCES = circlewidget.cpp \
main.cpp \
window.cpp
# install
target.path = $$[QT_INSTALL_EXAMPLES]/widgets/painting/concentriccircles
INSTALLS += target

View File

@ -1,14 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "window.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Window window;
window.show();
return app.exec();
}

View File

@ -1,56 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "circlewidget.h"
#include "window.h"
#include <QtWidgets>
//! [0]
Window::Window()
{
aliasedLabel = createLabel(tr("Aliased"));
antialiasedLabel = createLabel(tr("Antialiased"));
intLabel = createLabel(tr("Int"));
floatLabel = createLabel(tr("Float"));
QGridLayout *layout = new QGridLayout;
layout->addWidget(aliasedLabel, 0, 1);
layout->addWidget(antialiasedLabel, 0, 2);
layout->addWidget(intLabel, 1, 0);
layout->addWidget(floatLabel, 2, 0);
//! [0]
//! [1]
QTimer *timer = new QTimer(this);
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
circleWidgets[i][j] = new CircleWidget;
circleWidgets[i][j]->setAntialiased(j != 0);
circleWidgets[i][j]->setFloatBased(i != 0);
connect(timer, &QTimer::timeout,
circleWidgets[i][j], &CircleWidget::nextAnimationFrame);
layout->addWidget(circleWidgets[i][j], i + 1, j + 1);
}
}
//! [1] //! [2]
timer->start(100);
setLayout(layout);
setWindowTitle(tr("Concentric Circles"));
}
//! [2]
//! [3]
QLabel *Window::createLabel(const QString &text)
{
QLabel *label = new QLabel(text);
label->setAlignment(Qt::AlignCenter);
label->setMargin(2);
label->setFrameStyle(QFrame::Box | QFrame::Sunken);
return label;
}
//! [3]

View File

@ -1,33 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
QT_BEGIN_NAMESPACE
class QLabel;
QT_END_NAMESPACE
class CircleWidget;
//! [0]
class Window : public QWidget
{
Q_OBJECT
public:
Window();
private:
QLabel *createLabel(const QString &text);
QLabel *aliasedLabel;
QLabel *antialiasedLabel;
QLabel *intLabel;
QLabel *floatLabel;
CircleWidget *circleWidgets[2][2];
};
//! [0]
#endif // WINDOW_H

View File

@ -1,6 +1,5 @@
TEMPLATE = subdirs
SUBDIRS = basicdrawing \
concentriccircles \
affine \
composition \
deform \

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -63,6 +63,7 @@ struct MyWidget : public QWidget
void wrapper13();
void wrapper14();
void wrapper15();
void concentricCircles();
};
QLine drawingCode;
@ -355,4 +356,21 @@ painter.drawRect(rectangle.adjusted(0, 0, -pen.width(), -pen.width()));
} // MyWidget::wrapper15
} // src_gui_painting_qpainter2
void MyWidget::concentricCircles()
{
//! [renderHint]
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
//! [renderHint]
int diameter = 50;
//! [floatBased]
painter.drawEllipse(QRectF(-diameter / 2.0, -diameter / 2.0, diameter, diameter));
//! [floatBased]
//! [intBased]
painter.drawEllipse(QRect(-diameter / 2, -diameter / 2, diameter, diameter));
//! [intBased]
} // MyWidget::concentricCircles
} // src_gui_painting_qpainter2

View File

@ -1125,24 +1125,22 @@ void QPainterPrivate::updateState(QPainterState *newState)
The QPainter class also provides a means of controlling the
rendering quality through its RenderHint enum and the support for
floating point precision: All the functions for drawing primitives
has a floating point version. These are often used in combination
have floating point versions.
\snippet code/src_gui_painting_qpainter.cpp floatBased
These are often used in combination
with the \l {RenderHint}{QPainter::Antialiasing} render hint.
\snippet code/src_gui_painting_qpainter.cpp renderHint
\table 100%
\row
\li Comparing concentric circles with int and float, and with or without
anti-aliased rendering. Using the floating point precision versions
produces evenly spaced rings. Anti-aliased rendering results in
smooth circles.
\li \inlineimage qpainter-concentriccircles.png
\li
\b {Concentric Circles Example}
The \l {painting/concentriccircles}{Concentric Circles} example
shows the improved rendering quality that can be obtained using
floating point precision and anti-aliasing when drawing custom
widgets.
The application's main window displays several widgets which are
drawn using the various combinations of precision and
anti-aliasing.
\endtable
The RenderHint enum specifies flags to QPainter that may or may

View File

@ -63,7 +63,6 @@
"painting/affine Example", "examples/widgets/painting/affine", "affine", 0, -1
"painting/basicdrawing Example", "examples/widgets/painting/basicdrawing", "basicdrawing", 10, -1
"painting/composition Example", "examples/widgets/painting/composition", "composition", 0, -1
"painting/concentriccircles Example", "examples/widgets/painting/concentriccircles", "concentriccircles", 0, -1
"painting/deform Example", "examples/widgets/painting/deform", "deform", 0, -1
"painting/fontsampler Example", "examples/widgets/painting/fontsampler", "fontsampler", 0, -1
"painting/gradients Example", "examples/widgets/painting/gradients", "gradients", 0, -1