태블릿 예제
이 예는 Qt 애플리케이션에서 Wacom 타블렛을 사용하는 방법을 보여줍니다.
Qt 애플리케이션에서 태블릿을 사용하면 QTabletEvent가 생성됩니다. 태블릿 이벤트를 처리하려면 tabletEvent() 이벤트 핸들러를 다시 구현해야 합니다. 이벤트는 그리기에 사용되는 도구(스타일러스)가 태블릿 근처에 들어오고 나갈 때(즉, 태블릿을 닫았지만 태블릿을 누르지 않았을 때), 도구를 눌렀다가 놓을 때, 태블릿에서 도구를 이동할 때, 도구의 버튼 중 하나를 누르거나 놓을 때 생성됩니다.
QTabletEvent 에서 제공되는 정보는 사용하는 장치에 따라 다릅니다. 이 예에서는 스타일러스, 에어브러시, 아트펜 등 최대 세 가지 그리기 도구가 있는 태블릿을 처리할 수 있습니다. 이 중 하나에 대해 이벤트에는 도구의 위치, 태블릿의 압력, 버튼 상태, 수직 기울기 및 수평 기울기(태블릿 하드웨어에서 제공할 수 있는 경우 장치와 태블릿의 수직 각도)가 포함됩니다. 에어브러시에는 손가락 휠이 있으며, 이 휠의 위치는 태블릿 이벤트에서도 확인할 수 있습니다. 아트 펜은 태블릿 표면에 수직인 축을 중심으로 회전할 수 있으므로 캘리그래피에 사용할 수 있습니다.
이 예에서는 그림 그리기 프로그램을 구현합니다. 종이에 연필을 사용하듯이 스타일러스를 사용하여 태블릿에 그림을 그릴 수 있습니다. 에어브러시로 그림을 그리면 가상 페인트가 스프레이되고 손가락 휠을 사용하여 스프레이의 농도를 변경할 수 있습니다. 아트 펜으로 그리면 펜의 회전에 따라 선의 폭과 끝점 각도가 달라지는 선이 나타납니다. 압력과 기울기를 지정하여 색상의 알파 및 채도 값과 획의 너비를 변경할 수도 있습니다.
예제는 다음과 같이 구성됩니다:
MainWindow
클래스는 QMainWindow 을 상속하여 메뉴를 생성하고 슬롯과 신호를 연결합니다.TabletCanvas
클래스는 QWidget 을 상속하고 태블릿 이벤트를 받습니다. 이 클래스는 이벤트를 사용하여 화면 밖의 픽셀맵에 페인팅한 다음 렌더링합니다.TabletApplication
클래스는 QApplication 을 상속합니다. 이 클래스는 태블릿 근접 이벤트를 처리합니다.main()
함수는MainWindow
을 생성하여 최상위 창으로 표시합니다.
메인윈도우 클래스 정의
MainWindow
함수는 TabletCanvas
을 생성하고 중앙 위젯으로 설정합니다.
class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(TabletCanvas *canvas); private slots: void setBrushColor(); void setAlphaValuator(QAction *action); void setLineWidthValuator(QAction *action); void setSaturationValuator(QAction *action); void setEventCompression(bool compress); bool save(); void load(); void clear(); void about(); private: void createMenus(); TabletCanvas *m_canvas; QColorDialog *m_colorDialog = nullptr; };
createMenus()
액션이 있는 메뉴를 설정합니다. 알파 채널, 채도 및 선 너비를 각각 변경하는 액션에는 QActionGroup 이 있습니다. 액션 그룹은 setAlphaValuator()
, setSaturationValuator()
, setLineWidthValuator()
슬롯에 연결되며, 이 슬롯은 TabletCanvas
에서 함수를 호출합니다.
메인창 클래스 구현
생성자 MainWindow()
를 살펴보는 것부터 시작하겠습니다:
MainWindow::MainWindow(TabletCanvas *canvas) : m_canvas(canvas) { createMenus(); setWindowTitle(tr("Tablet Example")); setCentralWidget(m_canvas); QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents); }
생성자에서 createMenus()
를 호출하여 모든 액션과 메뉴를 생성하고 캔버스를 중앙 위젯으로 설정합니다.
void MainWindow::createMenus() { QMenu *fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(tr("&Open..."), QKeySequence::Open, this, &MainWindow::load); fileMenu->addAction(tr("&Save As..."), QKeySequence::SaveAs, this, &MainWindow::save); fileMenu->addAction(tr("&New"), QKeySequence::New, this, &MainWindow::clear); fileMenu->addAction(tr("E&xit"), QKeySequence::Quit, this, &MainWindow::close); QMenu *brushMenu = menuBar()->addMenu(tr("&Brush")); brushMenu->addAction(tr("&Brush Color..."), tr("Ctrl+B"), this, &MainWindow::setBrushColor);
createMenus()
의 시작 부분에 파일 메뉴를 채웁니다. Qt 5.6에 도입된 addAction()의 오버로드를 사용하여 바로 가기(및 선택적으로 아이콘)가 있는 메뉴 항목을 만들고, 해당 메뉴에 추가하고, 슬롯에 연결하는 모든 작업을 코드 한 줄로 처리합니다. 이러한 공통 메뉴 항목에 대한 플랫폼별 표준 단축키를 얻으려면 QKeySequence 을 사용합니다.
브러시 메뉴도 채웁니다. 브러시를 변경하는 명령에는 일반적으로 표준 바로 가기가 없으므로 tr()를 사용하여 애플리케이션의 언어 번역과 함께 바로 가기를 번역할 수 있도록 합니다.
이제 그리는 선의 반투명도(알파 채널) 또는 에어브러시되는 색상을 변경하는 데 사용할 각 QTabletEvent 속성을 선택하기 위해 태블릿 메뉴의 하위 메뉴에서 상호 배타적인 작업 그룹을 만드는 방법을 살펴 보겠습니다.
QMenu *alphaChannelMenu = tabletMenu->addMenu(tr("&Alpha Channel")); QAction *alphaChannelPressureAction = alphaChannelMenu->addAction(tr("&Pressure")); alphaChannelPressureAction->setData(TabletCanvas::PressureValuator); alphaChannelPressureAction->setCheckable(true); QAction *alphaChannelTangentialPressureAction = alphaChannelMenu->addAction(tr("T&angential Pressure")); alphaChannelTangentialPressureAction->setData(TabletCanvas::TangentialPressureValuator); alphaChannelTangentialPressureAction->setCheckable(true); alphaChannelTangentialPressureAction->setChecked(true); QAction *alphaChannelTiltAction = alphaChannelMenu->addAction(tr("&Tilt")); alphaChannelTiltAction->setData(TabletCanvas::TiltValuator); alphaChannelTiltAction->setCheckable(true); QAction *noAlphaChannelAction = alphaChannelMenu->addAction(tr("No Alpha Channel")); noAlphaChannelAction->setData(TabletCanvas::NoValuator); noAlphaChannelAction->setCheckable(true); QActionGroup *alphaChannelGroup = new QActionGroup(this); alphaChannelGroup->addAction(alphaChannelPressureAction); alphaChannelGroup->addAction(alphaChannelTangentialPressureAction); alphaChannelGroup->addAction(alphaChannelTiltAction); alphaChannelGroup->addAction(noAlphaChannelAction); connect(alphaChannelGroup, &QActionGroup::triggered, this, &MainWindow::setAlphaValuator);
사용자가 태블릿 압력, 기울기 또는 에어브러시 도구의 썸휠 위치에 따라 그리기 색상의 알파 성분을 변조할지 여부를 선택할 수 있기를 원합니다. 각 선택에 대해 하나의 액션과 알파를 변경하지 않거나 색상을 불투명하게 유지하도록 선택하는 추가 액션이 있습니다. 액션을 체크할 수 있게 만들면 alphaChannelGroup
은 언제든지 액션 중 하나만 체크되도록 합니다. 액션이 체크되면 그룹에서 triggered()
신호가 방출되므로 이를 MainWindow::setAlphaValuator()
에 연결합니다. 이제부터는 QTabletEvent 의 어떤 속성(평가자)에 주목해야 하는지 알아야 하므로 QAction::data 속성을 사용하여 이 정보를 전달합니다. (이 작업이 가능하려면 Valuator
열거형이 등록된 메타타입이어야 QVariant 에 삽입할 수 있습니다. 이는 tabletcanvas.h의 Q_ENUM
선언을 통해 이루어집니다).
다음은 setAlphaValuator()
의 구현입니다:
void MainWindow::setAlphaValuator(QAction *action) { m_canvas->setAlphaChannelValuator(qvariant_cast<TabletCanvas::Valuator>(action->data())); }
QAction::data()에서 Valuator
열거형을 검색하여 TabletCanvas::setAlphaChannelValuator()
로 전달하기만 하면 됩니다. data
속성을 사용하지 않는다면 스위치 문에서 QAction 포인터 자체를 비교해야 할 것입니다. 하지만 비교를 위해 클래스 변수에 각각의 QAction 포인터를 유지해야 합니다.
다음은 setBrushColor()
의 구현입니다:
void MainWindow::setBrushColor() { if (!m_colorDialog) { m_colorDialog = new QColorDialog(this); m_colorDialog->setModal(false); m_colorDialog->setCurrentColor(m_canvas->color()); connect(m_colorDialog, &QColorDialog::colorSelected, m_canvas, &TabletCanvas::setColor); } m_colorDialog->setVisible(true); }
사용자가 메뉴 또는 작업 바로 가기를 통해 브러시 색상...을 처음 선택할 때 QColorDialog 의 지연 초기화를 수행합니다. 대화 상자가 열려 있는 동안 사용자가 다른 색상을 선택할 때마다 TabletCanvas::setColor()
을 호출하여 그리기 색상을 변경합니다. 모달이 아닌 대화 상자이므로 사용자는 색상 대화 상자를 열어두면 편리하고 자주 색상을 변경하거나 닫았다가 나중에 다시 열 수 있습니다.
다음은 save()
의 구현입니다:
bool MainWindow::save() { QString path = QDir::currentPath() + "/untitled.png"; QString fileName = QFileDialog::getSaveFileName(this, tr("Save Picture"), path); bool success = m_canvas->saveImage(fileName); if (!success) QMessageBox::information(this, "Error Saving Picture", "Could not save the image"); return success; }
QFileDialog 을 사용하여 사용자가 그림을 저장할 파일을 선택한 다음 TabletCanvas::saveImage()
을 호출하여 실제로 파일에 씁니다.
다음은 load()
의 구현입니다:
void MainWindow::load() { QString fileName = QFileDialog::getOpenFileName(this, tr("Open Picture"), QDir::currentPath()); if (!m_canvas->loadImage(fileName)) QMessageBox::information(this, "Error Opening Picture", "Could not open picture"); }
사용자가 QFileDialog 으로 열 이미지 파일을 선택하게 한 다음 loadImage()
으로 이미지를 로드하도록 캔버스에 요청합니다.
다음은 about()
의 구현입니다:
void MainWindow::about() { QMessageBox::about(this, tr("About Tablet Example"), tr("This example shows how to use a graphics drawing tablet in Qt.")); }
예제에 대한 간단한 설명과 함께 메시지 상자를 표시합니다.
TabletCanvas 클래스 정의
TabletCanvas
클래스는 사용자가 태블릿으로 그릴 수 있는 표면을 제공합니다.
class TabletCanvas : public QWidget { Q_OBJECT public: enum Valuator { PressureValuator, TangentialPressureValuator, TiltValuator, VTiltValuator, HTiltValuator, NoValuator }; Q_ENUM(Valuator) TabletCanvas(); bool saveImage(const QString &file); bool loadImage(const QString &file); void clear(); void setAlphaChannelValuator(Valuator type) { m_alphaChannelValuator = type; } void setColorSaturationValuator(Valuator type) { m_colorSaturationValuator = type; } void setLineWidthType(Valuator type) { m_lineWidthValuator = type; } void setColor(const QColor &c) { if (c.isValid()) m_color = c; } QColor color() const { return m_color; } void setTabletDevice(QTabletEvent *event) { updateCursor(event); } protected: void tabletEvent(QTabletEvent *event) override; void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void initPixmap(); void paintPixmap(QPainter &painter, QTabletEvent *event); Qt::BrushStyle brushPattern(qreal value); static qreal pressureToWidth(qreal pressure); void updateBrush(const QTabletEvent *event); void updateCursor(const QTabletEvent *event); Valuator m_alphaChannelValuator = TangentialPressureValuator; Valuator m_colorSaturationValuator = NoValuator; Valuator m_lineWidthValuator = PressureValuator; QColor m_color = Qt::red; QPixmap m_pixmap; QBrush m_brush; QPen m_pen; bool m_deviceDown = false; struct Point { QPointF pos; qreal pressure = 0; qreal rotation = 0; } lastPoint; };
캔버스는 알파 채널, 채도 및 획의 선 너비를 변경할 수 있습니다. 이를 변조할 수 있는 QTabletEvent 속성을 나열하는 열거형이 있습니다. 각각에 대해 비공개 변수를 유지합니다: m_alphaChannelValuator
, m_colorSaturationValuator
및 m_lineWidthValuator
, 그리고 이에 대한 접근자 함수를 제공합니다.
m_pen
을 사용하여 QPixmap 을, m_brush
을 사용하여 m_color
을 그립니다. QTabletEvent 을 받을 때마다 lastPoint
에서 현재 QTabletEvent 에 지정된 지점까지 획을 그린 다음 다음 번을 위해 lastPoint
에 위치와 회전을 저장합니다. saveImage()
및 loadImage()
함수는 QPixmap 를 디스크에 저장하고 로드합니다. 픽셀맵은 paintEvent()
의 위젯에 그려집니다.
태블릿의 이벤트 해석은 tabletEvent()
에서 이루어지며 paintPixmap()
, updateBrush()
, updateCursor()
은 tabletEvent()
에서 사용하는 헬퍼 함수입니다.
TabletCanvas 클래스 구현
생성자부터 살펴보겠습니다:
TabletCanvas::TabletCanvas() : QWidget(nullptr), m_brush(m_color) , m_pen(m_brush, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin) { resize(500, 500); setAutoFillBackground(true); setAttribute(Qt::WA_TabletTracking); }
생성자에서는 대부분의 클래스 변수를 초기화합니다.
다음은 saveImage()
의 구현입니다:
bool TabletCanvas::saveImage(const QString &file) { return m_pixmap.save(file); }
QPixmap 는 디스크에 저장하는 기능을 구현하므로 save()를 호출하기만 하면 됩니다.
다음은 loadImage()
의 구현입니다:
bool TabletCanvas::loadImage(const QString &file) { bool success = m_pixmap.load(file); if (success) { update(); return true; } return false; }
load()를 호출하면 file 에서 이미지를 로드합니다.
다음은 tabletEvent()
의 구현입니다:
void TabletCanvas::tabletEvent(QTabletEvent *event) { switch (event->type()) { case QEvent::TabletPress: if (!m_deviceDown) { m_deviceDown = true; lastPoint.pos = event->position(); lastPoint.pressure = event->pressure(); lastPoint.rotation = event->rotation(); } break; case QEvent::TabletMove: #ifndef Q_OS_IOS if (event->pointingDevice() && event->pointingDevice()->capabilities().testFlag(QPointingDevice::Capability::Rotation)) updateCursor(event); #endif if (m_deviceDown) { updateBrush(event); QPainter painter(&m_pixmap); paintPixmap(painter, event); lastPoint.pos = event->position(); lastPoint.pressure = event->pressure(); lastPoint.rotation = event->rotation(); } break; case QEvent::TabletRelease: if (m_deviceDown && event->buttons() == Qt::NoButton) m_deviceDown = false; update(); break; default: break; } event->accept(); }
이 함수에는 세 가지 종류의 이벤트가 발생합니다: TabletPress
, TabletRelease
, TabletMove
으로, 태블릿에서 그리기 도구를 누르거나 들어 올리거나 이동할 때 생성됩니다. 태블릿을 눌렀을 때 m_deviceDown
을 true
으로 설정하면 이동 이벤트를 받을 때 그림을 그려야 한다는 것을 알 수 있습니다. 사용자가 주목하기로 선택한 태블릿 이벤트 속성에 따라 m_brush
및 m_pen
을 업데이트하도록 updateBrush()
을 구현했습니다. updateCursor()
함수는 사용 중인 그리기 도구를 나타내는 커서를 선택하므로 태블릿에 커서를 갖다 대면 어떤 종류의 획을 그릴지 확인할 수 있습니다.
void TabletCanvas::updateCursor(const QTabletEvent *event) { QCursor cursor; if (event->type() != QEvent::TabletLeaveProximity) { if (event->pointerType() == QPointingDevice::PointerType::Eraser) { cursor = QCursor(QPixmap(":/images/cursor-eraser.png"), 3, 28); } else { switch (event->deviceType()) { case QInputDevice::DeviceType::Stylus: if (event->pointingDevice()->capabilities().testFlag(QPointingDevice::Capability::Rotation)) { QImage origImg(QLatin1String(":/images/cursor-felt-marker.png")); QImage img(32, 32, QImage::Format_ARGB32); QColor solid = m_color; solid.setAlpha(255); img.fill(solid); QPainter painter(&img); QTransform transform = painter.transform(); transform.translate(16, 16); transform.rotate(event->rotation()); painter.setTransform(transform); painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); painter.drawImage(-24, -24, origImg); painter.setCompositionMode(QPainter::CompositionMode_HardLight); painter.drawImage(-24, -24, origImg); painter.end(); cursor = QCursor(QPixmap::fromImage(img), 16, 16); } else { cursor = QCursor(QPixmap(":/images/cursor-pencil.png"), 0, 0); } break; case QInputDevice::DeviceType::Airbrush: cursor = QCursor(QPixmap(":/images/cursor-airbrush.png"), 3, 4); break; default: break; } } } setCursor(cursor); }
아트펜(RotationStylus
)을 사용 중인 경우 TabletMove
이벤트마다 updateCursor()
함수가 호출되어 펜촉의 각도를 볼 수 있도록 회전된 커서를 렌더링합니다.
다음은 paintEvent()
의 구현입니다:
void TabletCanvas::initPixmap() { qreal dpr = devicePixelRatio(); QPixmap newPixmap = QPixmap(qRound(width() * dpr), qRound(height() * dpr)); newPixmap.setDevicePixelRatio(dpr); newPixmap.fill(Qt::white); QPainter painter(&newPixmap); if (!m_pixmap.isNull()) painter.drawPixmap(0, 0, m_pixmap); painter.end(); m_pixmap = newPixmap; } void TabletCanvas::paintEvent(QPaintEvent *event) { if (m_pixmap.isNull()) initPixmap(); QPainter painter(this); QRect pixmapPortion = QRect(event->rect().topLeft() * devicePixelRatio(), event->rect().size() * devicePixelRatio()); painter.drawPixmap(event->rect().topLeft(), m_pixmap, pixmapPortion); }
Qt가 paintEvent()를 처음 호출하면 m_pixmap이 기본 구성되므로 QPixmap::isNull()는 true
을 반환합니다. 이제 렌더링할 화면을 알았으니 적절한 해상도의 픽셀맵을 만들 수 있습니다. 이 예제에서는 확대/축소를 지원하지 않으므로 창을 채우는 픽셀맵의 크기는 화면 해상도에 따라 달라지며, 한 화면은 DPI가 높은 반면 다른 화면은 그렇지 않을 수 있습니다. 기본값은 회색이므로 배경도 그려야 합니다.
그런 다음 위젯의 왼쪽 상단에 픽셀맵을 그리기만 하면 됩니다.
다음은 paintPixmap()
의 구현입니다:
void TabletCanvas::paintPixmap(QPainter&painter, QTabletEvent *event) { static qreal maxPenRadius = pressureToWidth(1.0); painter.setRenderHint(QPainter::앤티앨리어싱); switch (event->deviceType()) { case QInputDevice::DeviceType::Airbrush: { painter.setPen(Qt::NoPen); QRadialGradient grad(lastPoint.pos, m_pen.widthF() * 10.0); QColor color = m_brush.color(); color.setAlphaF(color.alphaF() * 0.25); grad.setColorAt(0, m_brush.color()); grad.setColorAt(0.5, Qt::투명); painter.setBrush(grad); qreal radius = grad.radius(); painter.drawEllipse(event->position(), radius, radius); update(QRect(event->position().toPoint() -. QPoint(반지름, 반지름), QSize(radius * 2, radius * 2)); } break; case QInputDevice::디바이스유형::퍽: case QInputDevice::DeviceType::Mouse: { const QString error(tr("이 입력 장치는 이 예제에서 지원되지 않습니다."));#if QT_CONFIG(statustip) QStatusTipEvent status(error); QCoreApplication::sendEvent(this, &status);#else qWarning() << error; #endif} break; default: { const QString error(tr("알 수 없는 태블릿 디바이스 - 스타일러스로 처리"));#if QT_CONFIG(statustip) QStatusTipEvent status(error); QCoreApplication::sendEvent(this, &status);#else qWarning() << error; #endif} Q_FALLTHROUGH(); case QInputDevice::DeviceType::Stylus: if (event->pointingDevice()->capabilities().testFlag(QPointingDevice::Capability::Rotation)) { m_brush.setStyle(Qt::SolidPattern); painter.setPen(Qt::NoPen); painter.setBrush(m_brush); QPolygonF poly; qreal halfWidth = pressureToWidth(lastPoint.pressure); QPointF brushAdjust(qSin(qDegreesToRadians(-lastPoint.rotation)) * halfWidth, qCos(qDegreesToRadians(-lastPoint.rotation)) * halfWidth); poly<< lastPoint.pos + brushAdjust; poly<< lastPoint.pos - brushAdjust; halfWidth = m_pen.widthF(); brushAdjust = QPointF(qSin(qDegreesToRadians(-event->rotation())) * 반 너비, qCos(qDegreesToRadians(-event->rotation())) * halfWidth); poly<< event->position() - brushAdjust; poly<< event->position() + brushAdjust; painter.drawConvexPolygon(poly); update(poly.boundingRect().toRect()); } else { painter.setPen(m_pen); painter.drawLine(lastPoint.pos, event->position()); update(QRect(lastPoint.pos.toPoint(), event->position().toPoint()).normalized() .adjusted(-maxPenRadius, -maxPenRadius, maxPenRadius, maxPenRadius)); } break; } }
이 함수에서는 도구의 움직임에 따라 픽셀맵에 그림을 그립니다. 태블릿에서 사용하는 도구가 스타일러스인 경우, 마지막으로 알려진 위치에서 현재 위치까지 선을 그리고 싶습니다. 또한 알 수 없는 디바이스에 대한 합리적인 처리라고 가정하지만 경고와 함께 상태 표시줄을 업데이트합니다. 에어브러시인 경우 다양한 이벤트 매개변수에 따라 밀도가 달라질 수 있는 부드러운 그라데이션으로 채워진 원을 그리고 싶습니다. 기본적으로 에어브러시에서 손가락 휠의 위치인 접선 압력에 따라 달라집니다. 도구가 회전 스타일러스인 경우 사다리꼴 스트로크 세그먼트를 그려서 펠트 마커를 시뮬레이션합니다.
case QInputDevice::DeviceType::Airbrush: { painter.setPen(Qt::NoPen); QRadialGradient grad(lastPoint.pos, m_pen.widthF() * 10.0); QColor color = m_brush.color(); color.setAlphaF(color.alphaF() * 0.25); grad.setColorAt(0, m_brush.color()); grad.setColorAt(0.5, Qt::transparent); painter.setBrush(grad); qreal radius = grad.radius(); painter.drawEllipse(event->position(), radius, radius); update(QRect(event->position().toPoint() - QPoint(radius, radius), QSize(radius * 2, radius * 2))); } break;
updateBrush()
에서 그리는 데 사용되는 펜과 브러시를 m_alphaChannelValuator
, m_lineWidthValuator
, m_colorSaturationValuator
, m_color
와 일치하도록 설정합니다. 이러한 각 변수에 대해 m_brush
및 m_pen
을 설정하는 코드를 살펴보겠습니다:
void TabletCanvas::updateBrush(const QTabletEvent *event) { int hue, saturation, value, alpha; m_color.getHsv(&hue, &saturation, &value, &alpha); int vValue = int(((event->yTilt() + 60.0) / 120.0) * 255); int hValue = int(((event->xTilt() + 60.0) / 120.0) * 255);
hValue
및 vValue
은 수평 및 수직 기울기를 0에서 255 사이의 숫자로 설정합니다. 원래 값은 -60에서 60까지의 각도 단위로, 즉 0은 -60, 127은 0, 255는 60도입니다. 측정된 각도는 장치와 태블릿의 수직 사이입니다(그림은 QTabletEvent 참조).
switch (m_alphaChannelValuator) { case PressureValuator: m_color.setAlphaF(event->pressure()); break; case TangentialPressureValuator: if (event->deviceType() == QInputDevice::DeviceType::Airbrush) m_color.setAlphaF(qMax(0.01, (event->tangentialPressure() + 1.0) / 2.0)); else m_color.setAlpha(255); break; case TiltValuator: m_color.setAlpha(std::max(std::abs(vValue - 127), std::abs(hValue - 127))); break; default: m_color.setAlpha(255); }
QColor 의 알파 채널은 0에서 255 사이의 숫자(0은 투명, 255는 불투명) 또는 0은 투명, 1.0은 불투명인 부동 소수점 숫자로 지정됩니다. pressure()는 압력을 0.0에서 1.0 사이의 실수 값으로 반환합니다. 펜이 태블릿에 수직일 때 가장 작은 알파 값(즉, 색상이 가장 투명함)을 얻습니다. 수직 및 수평 기울기 값 중 가장 큰 값을 선택합니다.
switch (m_colorSaturationValuator) { case VTiltValuator: m_color.setHsv(hue, vValue, value, alpha); break; case HTiltValuator: m_color.setHsv(hue, hValue, value, alpha); break; case PressureValuator: m_color.setHsv(hue, int(event->pressure() * 255.0), value, alpha); break; default: ; }
HSV 색상 모델의 채도는 0에서 255 사이의 정수 또는 0에서 1 사이의 부동 소수점 값으로 지정할 수 있습니다. 알파를 정수로 표현하기로 선택했기 때문에 setHsv()를 정수 값으로 호출합니다. 즉, 압력을 0에서 255 사이의 숫자로 곱해야 합니다.
switch (m_lineWidthValuator) { case PressureValuator: m_pen.setWidthF(pressureToWidth(event->pressure())); break; case TiltValuator: m_pen.setWidthF(std::max(std::abs(vValue - 127), std::abs(hValue - 127)) / 12); break; default: m_pen.setWidthF(1); }
이렇게 선택하면 펜 스트로크의 너비가 압력에 따라 증가할 수 있습니다. 그러나 펜 너비가 기울기로 제어되는 경우 도구와 태블릿의 수직 사이의 각도에 따라 너비가 증가하도록 합니다.
if (event->pointerType() == QPointingDevice::PointerType::Eraser) { m_brush.setColor(Qt::white); m_pen.setColor(Qt::white); m_pen.setWidthF(event->pressure() * 10 + 1); } else { m_brush.setColor(m_color); m_pen.setColor(m_color); } }
마지막으로 포인터가 스타일러스인지 지우개인지 확인합니다. 지우개인 경우 색상을 픽셀맵의 배경색으로 설정하고 압력에 따라 펜 너비가 결정되도록 하고, 그렇지 않으면 함수에서 이전에 결정한 색상을 설정합니다.
태블릿 애플리케이션 클래스 정의
event() 함수를 다시 구현하기 위해 이 클래스에서 QApplication 을 상속합니다.
class TabletApplication : public QApplication { Q_OBJECT public: using QApplication::QApplication; bool event(QEvent *event) override; void setCanvas(TabletCanvas *canvas) { m_canvas = canvas; } private: TabletCanvas *m_canvas = nullptr; };
TabletApplication
는 태블릿 근접 이벤트를 수신하여 TabletCanvas
로 전달하기 위해 QApplication 의 서브클래스로 존재합니다. TabletEnterProximity
와 TabletLeaveProximity
이벤트는 QApplication 객체로 전송되고, 다른 태블릿 이벤트는 QWidget 의 event()
핸들러로 전송되어 tabletEvent()로 전달됩니다.
TabletApplication 클래스 구현
다음은 event()
의 구현입니다:
bool TabletApplication::event(QEvent *event) { if (event->type() == QEvent::TabletEnterProximity || event->type() == QEvent::TabletLeaveProximity) { m_canvas->setTabletDevice(static_cast<QTabletEvent *>(event)); return true; } return QApplication::event(event); }
이 함수는 그리기 도구가 태블릿 근처에 들어오거나 나갈 때 생성되는 TabletEnterProximity
및 TabletLeaveProximity
이벤트를 처리하는 데 사용합니다. 여기서는 TabletCanvas::setTabletDevice()
를 호출한 다음 updateCursor()
을 호출하여 적절한 커서를 설정합니다. 근접 이벤트가 필요한 유일한 이유는 올바른 그리기를 위해 TabletCanvas
가 수신하는 각 이벤트에서 device() 및 pointerType()를 관찰하는 것으로 충분하기 때문입니다.
main()
함수
다음은 예제의 main()
함수입니다:
int main(int argv, char *args[]) { TabletApplication app(argv, args); TabletCanvas *canvas = new TabletCanvas; app.setCanvas(canvas); MainWindow mainWindow(canvas); mainWindow.resize(500, 500); mainWindow.show(); return app.exec(); }
여기서는 MainWindow
을 만들어 최상위 창으로 표시합니다. TabletApplication
클래스를 사용합니다. 애플리케이션이 생성된 후에 캔버스를 설정해야 합니다. QApplication 객체가 인스턴스화되기 전에는 이벤트 처리를 구현하는 클래스를 사용할 수 없습니다.
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.