Tablet example: smoother, rotation stylus, better airbrush
The drawing is antialiased and with rounded caps and joins. Rotation stylus acts like a felt marker. Airbrush sprays a radial gradient of color, and its alpha can depend on the tangential pressure if so chosen. Task-number: QTBUG-46694 Change-Id: I4fb16eb621707e3c56a75aa302dd0d3bf418a745 Reviewed-by: Laszlo Agocs <laszlo.agocs@theqtcompany.com> Reviewed-by: Topi Reiniö <topi.reinio@digia.com>
This commit is contained in:
parent
ad4d25589f
commit
6a991f9cce
@ -259,24 +259,18 @@
|
||||
|
||||
\snippet widgets/tablet/tabletcanvas.cpp 5
|
||||
|
||||
In this function we draw on the pixmap based on the movement of the
|
||||
device. If the device used on the tablet is a stylus we want to draw a
|
||||
line between the positions of the stylus recorded in \c polyLine. We
|
||||
also assume that this is a reasonable handling of any unknown device,
|
||||
but update the statusbar with a warning so that the user can see that
|
||||
for his tablet he might have to implement special handling.
|
||||
If it is an airbrush we want to draw a circle of points with a
|
||||
point density based on the tangential pressure, which is the position
|
||||
of the finger wheel on the airbrush. We use the Qt::BrushStyle to
|
||||
draw the points as it has styles that draw points with different
|
||||
density; we select the style based on the tangential pressure in
|
||||
\c brushPattern().
|
||||
In this function we draw on the pixmap based on the movement of the device.
|
||||
If the device used on the tablet is a stylus, we want to draw a line from
|
||||
the last-known position to the current position. We also assume that this
|
||||
is a reasonable handling of any unknown device, but update the status bar
|
||||
with a warning. If it is an airbrush, we want to draw a circle filled with
|
||||
a soft gradient, whose density can depend on various event parameters.
|
||||
By default it depends on the tangential pressure, which is the position of
|
||||
the finger wheel on the airbrush. If it is a rotation stylus, we simulate
|
||||
a felt marker by drawing trapezoidal strokes.
|
||||
|
||||
\snippet widgets/tablet/tabletcanvas.cpp 6
|
||||
|
||||
We return a brush style with a point density that increases with
|
||||
the tangential pressure.
|
||||
|
||||
In \c updateBrush() we set the pen and brush used for drawing
|
||||
to match \c alphaChannelType, \c lineWidthType, \c
|
||||
colorSaturationType, and \c myColor. We will examine the code to
|
||||
|
@ -52,7 +52,7 @@ MainWindow::MainWindow(TabletCanvas *canvas)
|
||||
|
||||
myCanvas->setColor(Qt::red);
|
||||
myCanvas->setLineWidthType(TabletCanvas::LineWidthPressure);
|
||||
myCanvas->setAlphaChannelType(TabletCanvas::NoAlpha);
|
||||
myCanvas->setAlphaChannelType(TabletCanvas::AlphaTangentialPressure);
|
||||
myCanvas->setColorSaturationType(TabletCanvas::NoSaturation);
|
||||
|
||||
setWindowTitle(tr("Tablet Example"));
|
||||
@ -75,6 +75,8 @@ void MainWindow::alphaActionTriggered(QAction *action)
|
||||
{
|
||||
if (action == alphaChannelPressureAction) {
|
||||
myCanvas->setAlphaChannelType(TabletCanvas::AlphaPressure);
|
||||
} else if (action == alphaChannelTangentialPressureAction) {
|
||||
myCanvas->setAlphaChannelType(TabletCanvas::AlphaTangentialPressure);
|
||||
} else if (action == alphaChannelTiltAction) {
|
||||
myCanvas->setAlphaChannelType(TabletCanvas::AlphaTilt);
|
||||
} else {
|
||||
@ -157,15 +159,19 @@ void MainWindow::createActions()
|
||||
alphaChannelPressureAction = new QAction(tr("&Pressure"), this);
|
||||
alphaChannelPressureAction->setCheckable(true);
|
||||
|
||||
alphaChannelTangentialPressureAction = new QAction(tr("T&angential Pressure"), this);
|
||||
alphaChannelTangentialPressureAction->setCheckable(true);
|
||||
alphaChannelTangentialPressureAction->setChecked(true);
|
||||
|
||||
alphaChannelTiltAction = new QAction(tr("&Tilt"), this);
|
||||
alphaChannelTiltAction->setCheckable(true);
|
||||
|
||||
noAlphaChannelAction = new QAction(tr("No Alpha Channel"), this);
|
||||
noAlphaChannelAction->setCheckable(true);
|
||||
noAlphaChannelAction->setChecked(true);
|
||||
|
||||
alphaChannelGroup = new QActionGroup(this);
|
||||
alphaChannelGroup->addAction(alphaChannelPressureAction);
|
||||
alphaChannelGroup->addAction(alphaChannelTangentialPressureAction);
|
||||
alphaChannelGroup->addAction(alphaChannelTiltAction);
|
||||
alphaChannelGroup->addAction(noAlphaChannelAction);
|
||||
connect(alphaChannelGroup, SIGNAL(triggered(QAction*)),
|
||||
@ -259,6 +265,7 @@ void MainWindow::createMenus()
|
||||
|
||||
alphaChannelMenu = tabletMenu->addMenu(tr("&Alpha Channel"));
|
||||
alphaChannelMenu->addAction(alphaChannelPressureAction);
|
||||
alphaChannelMenu->addAction(alphaChannelTangentialPressureAction);
|
||||
alphaChannelMenu->addAction(alphaChannelTiltAction);
|
||||
alphaChannelMenu->addAction(noAlphaChannelAction);
|
||||
|
||||
|
@ -79,6 +79,7 @@ private:
|
||||
|
||||
QActionGroup *alphaChannelGroup;
|
||||
QAction *alphaChannelPressureAction;
|
||||
QAction *alphaChannelTangentialPressureAction;
|
||||
QAction *alphaChannelTiltAction;
|
||||
QAction *noAlphaChannelAction;
|
||||
|
||||
|
@ -47,14 +47,13 @@
|
||||
TabletCanvas::TabletCanvas()
|
||||
{
|
||||
resize(500, 500);
|
||||
myBrush = QBrush();
|
||||
myPen = QPen();
|
||||
myColor = Qt::red;
|
||||
myBrush = QBrush(myColor);
|
||||
myPen = QPen(myBrush, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
|
||||
initPixmap();
|
||||
setAutoFillBackground(true);
|
||||
deviceDown = false;
|
||||
myColor = Qt::red;
|
||||
myTabletDevice = QTabletEvent::Stylus;
|
||||
alphaChannelType = NoAlpha;
|
||||
alphaChannelType = AlphaTangentialPressure;
|
||||
colorSaturationType = NoSaturation;
|
||||
lineWidthType = LineWidthPressure;
|
||||
}
|
||||
@ -99,24 +98,23 @@ void TabletCanvas::tabletEvent(QTabletEvent *event)
|
||||
case QEvent::TabletPress:
|
||||
if (!deviceDown) {
|
||||
deviceDown = true;
|
||||
polyLine[0] = polyLine[1] = polyLine[2] = event->pos();
|
||||
lastPoint.pos = event->posF();
|
||||
lastPoint.rotation = event->rotation();
|
||||
}
|
||||
break;
|
||||
case QEvent::TabletRelease:
|
||||
if (deviceDown)
|
||||
deviceDown = false;
|
||||
break;
|
||||
case QEvent::TabletMove:
|
||||
polyLine[2] = polyLine[1];
|
||||
polyLine[1] = polyLine[0];
|
||||
polyLine[0] = event->pos();
|
||||
|
||||
if (deviceDown) {
|
||||
updateBrush(event);
|
||||
QPainter painter(&pixmap);
|
||||
paintPixmap(painter, event);
|
||||
lastPoint.pos = event->posF();
|
||||
lastPoint.rotation = event->rotation();
|
||||
}
|
||||
break;
|
||||
case QEvent::TabletRelease:
|
||||
if (deviceDown && event->buttons() == Qt::NoButton)
|
||||
deviceDown = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -135,23 +133,44 @@ void TabletCanvas::paintEvent(QPaintEvent *)
|
||||
//! [5]
|
||||
void TabletCanvas::paintPixmap(QPainter &painter, QTabletEvent *event)
|
||||
{
|
||||
QPoint brushAdjust(10, 10);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
|
||||
switch (myTabletDevice) {
|
||||
switch (event->device()) {
|
||||
//! [6]
|
||||
case QTabletEvent::Airbrush:
|
||||
myBrush.setColor(myColor);
|
||||
myBrush.setStyle(brushPattern(event->pressure()));
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(myBrush);
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
painter.drawEllipse(QRect(polyLine[i] - brushAdjust,
|
||||
polyLine[i] + brushAdjust));
|
||||
{
|
||||
painter.setPen(Qt::NoPen);
|
||||
QRadialGradient grad(lastPoint.pos, myPen.widthF() * 10.0);
|
||||
QColor color = myBrush.color();
|
||||
color.setAlphaF(color.alphaF() * 0.25);
|
||||
grad.setColorAt(0, myBrush.color());
|
||||
grad.setColorAt(0.5, Qt::transparent);
|
||||
painter.setBrush(grad);
|
||||
qreal radius = grad.radius();
|
||||
painter.drawEllipse(event->posF(), radius, radius);
|
||||
}
|
||||
break;
|
||||
case QTabletEvent::RotationStylus:
|
||||
{
|
||||
myBrush.setStyle(Qt::SolidPattern);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(myBrush);
|
||||
QPolygonF poly;
|
||||
qreal halfWidth = myPen.widthF();
|
||||
QPointF brushAdjust(qSin(qDegreesToRadians(lastPoint.rotation)) * halfWidth,
|
||||
qCos(qDegreesToRadians(lastPoint.rotation)) * halfWidth);
|
||||
poly << lastPoint.pos + brushAdjust;
|
||||
poly << lastPoint.pos - brushAdjust;
|
||||
brushAdjust = QPointF(qSin(qDegreesToRadians(event->rotation())) * halfWidth,
|
||||
qCos(qDegreesToRadians(event->rotation())) * halfWidth);
|
||||
poly << event->posF() - brushAdjust;
|
||||
poly << event->posF() + brushAdjust;
|
||||
painter.drawConvexPolygon(poly);
|
||||
}
|
||||
break;
|
||||
//! [6]
|
||||
case QTabletEvent::Puck:
|
||||
case QTabletEvent::FourDMouse:
|
||||
case QTabletEvent::RotationStylus:
|
||||
{
|
||||
const QString error(tr("This input device is not supported by the example."));
|
||||
#ifndef QT_NO_STATUSTIP
|
||||
@ -174,40 +193,13 @@ void TabletCanvas::paintPixmap(QPainter &painter, QTabletEvent *event)
|
||||
}
|
||||
// FALL-THROUGH
|
||||
case QTabletEvent::Stylus:
|
||||
painter.setBrush(myBrush);
|
||||
painter.setPen(myPen);
|
||||
painter.drawLine(polyLine[1], event->pos());
|
||||
painter.drawLine(lastPoint.pos, event->posF());
|
||||
break;
|
||||
}
|
||||
}
|
||||
//! [5]
|
||||
|
||||
//! [6]
|
||||
Qt::BrushStyle TabletCanvas::brushPattern(qreal value)
|
||||
{
|
||||
int pattern = int((value) * 100.0) % 7;
|
||||
|
||||
switch (pattern) {
|
||||
case 0:
|
||||
return Qt::SolidPattern;
|
||||
case 1:
|
||||
return Qt::Dense1Pattern;
|
||||
case 2:
|
||||
return Qt::Dense2Pattern;
|
||||
case 3:
|
||||
return Qt::Dense3Pattern;
|
||||
case 4:
|
||||
return Qt::Dense4Pattern;
|
||||
case 5:
|
||||
return Qt::Dense5Pattern;
|
||||
case 6:
|
||||
return Qt::Dense6Pattern;
|
||||
default:
|
||||
return Qt::Dense7Pattern;
|
||||
}
|
||||
}
|
||||
//! [6]
|
||||
|
||||
//! [7]
|
||||
void TabletCanvas::updateBrush(QTabletEvent *event)
|
||||
{
|
||||
@ -220,7 +212,13 @@ void TabletCanvas::updateBrush(QTabletEvent *event)
|
||||
|
||||
switch (alphaChannelType) {
|
||||
case AlphaPressure:
|
||||
myColor.setAlpha(int(event->pressure() * 255.0));
|
||||
myColor.setAlphaF(event->pressure());
|
||||
break;
|
||||
case AlphaTangentialPressure:
|
||||
if (event->device() == QTabletEvent::Airbrush)
|
||||
myColor.setAlphaF(qMax(0.01, (event->tangentialPressure() + 1.0) / 2.0));
|
||||
else
|
||||
myColor.setAlpha(255);
|
||||
break;
|
||||
case AlphaTilt:
|
||||
myColor.setAlpha(maximum(abs(vValue - 127), abs(hValue - 127)));
|
||||
@ -271,5 +269,4 @@ void TabletCanvas::updateBrush(QTabletEvent *event)
|
||||
void TabletCanvas::resizeEvent(QResizeEvent *)
|
||||
{
|
||||
initPixmap();
|
||||
polyLine[0] = polyLine[1] = polyLine[2] = QPoint();
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ class TabletCanvas : public QWidget
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum AlphaChannelType { AlphaPressure, AlphaTilt, NoAlpha };
|
||||
enum AlphaChannelType { AlphaPressure, AlphaTangentialPressure, AlphaTilt, NoAlpha };
|
||||
enum ColorSaturationType { SaturationVTilt, SaturationHTilt,
|
||||
SaturationPressure, NoSaturation };
|
||||
enum LineWidthType { LineWidthPressure, LineWidthTilt, NoLineWidth };
|
||||
@ -107,7 +107,11 @@ private:
|
||||
QBrush myBrush;
|
||||
QPen myPen;
|
||||
bool deviceDown;
|
||||
QPoint polyLine[3];
|
||||
|
||||
struct Point {
|
||||
QPointF pos;
|
||||
qreal rotation;
|
||||
} lastPoint;
|
||||
};
|
||||
//! [0]
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user