(qt)【学习记录】实现wacom压感绘图

news/2023/12/9 16:48:28

开头附上我博客上的链接
http://www.hbzmlab.tech/index.php/2019/03/09/45/
要做压感绘图要考虑很多综合的问题。

1。要有高效的图片绘制方法,一是后台对像素图片的绘制,二是如何高速的把修改完的图片显示出来
一开始我做这个选择的是c#,因为c#有wintabdn的样例。但是发现c#绘图效率有点低,尤其是对像素图片的编辑处理。而qt中的pixmap则非常高效

2.要有同步的无损失的笔的数据获取方法,如果说通过qt自带的压感笔事件来做的话,可能会因为绘图的时间过长而跳过了一部分笔事件,那就获取到的笔信息有损失了


这里获取笔信息的方法是通过监听windows系统消息,qt里是 nativeEventFilter

可以参考github上的这个项目
https://github.com/liuyanghejerry/Qt-TabletSupport

这一个项目调用的是wintab32.dll,这个方案支持大多数数位板,但是平板可能不会带这个库
还有一种方案是getpenpointinfo 是windows 自家的库,但是这个是只支持win8以上,但对平板的兼容性应该要高一点
还有一种方案是tablet pc api估计是windows原先的自家的库,但是资料挺难找,所以还没有研究过。


这里我暂时只尝试了wintab的方案 wintab具体的原理我也不太清楚,wacom现在官网也没法找到wintab的开发资料了,现在好像是个叫will的新的东西,

TabletSupport::TabletSupport(QtGuiApplication1 *window):wintab_module(nullptr),window_(window),logContext(nullptr)
{if(!loadWintab()) {return;}if(!mapWintabFuns()){qCritical()<<"Error with function mapping!";return;}if(!hasDevice()){qCritical()<<"No Device found!";return;}qCritical() << "No Devid!";logContext = new tagLOGCONTEXTA;auto handle = (HWND)window_->winId();callFunc().ptrWTInfoA(WTI_DEFSYSCTX, 0, logContext);logContext->lcOptions |= CXO_MESSAGES;//logContext->lcMoveMask = PACKETDATA;logContext->lcBtnUpMask = logContext->lcBtnDnMask;AXIS TabletX;AXIS TabletY;callFunc().ptrWTInfoA( WTI_DEVICES, DVC_X, &TabletX );callFunc().ptrWTInfoA( WTI_DEVICES, DVC_Y, &TabletY );logContext->lcInOrgX = 0;logContext->lcInOrgY = 0;logContext->lcInExtX = TabletX.axMax;logContext->lcInExtY = TabletY.axMax;/* output the data in screen coords */logContext->lcOutOrgX = logContext->lcOutOrgY = 0;logContext->lcOutExtX = GetSystemMetrics(SM_CXSCREEN);/* move origin to upper left */logContext->lcOutExtY = GetSystemMetrics(SM_CYSCREEN);
printf("\ntx%d\n", TabletY.axMax);logContext->lcPktData = PACKETDATA;logContext->lcPktMode = PACKETMODE;tabapis.context_ = callFunc().ptrWTOpenA(handle,(LPLOGCONTEXTA)logContext,true);
}

以上是从那个github项目中移植来的构造函数,


bool TabletSupport::loadWintab()
{wintab_module = LoadLibrary(L"wintab32.dll");if(!wintab_module) {DWORD err = GetLastError();printf("\nCannot load wintab32.dll:\n");return false;}return true;
}

第一步是加载dll


bool TabletSupport::mapWintabFuns()
{bool isOk = true;isOk = isOk && getProcAddr<WinTabAPI::WTINFOA>(tabapis.ptrWTInfoA, "WTInfoA");isOk = isOk && getProcAddr<WinTabAPI::WTOPENA>(tabapis.ptrWTOpenA, "WTOpenA");isOk = isOk && getProcAddr<WinTabAPI::WTGETA>(tabapis.ptrWTGetA, "WTGetA");isOk = isOk && getProcAddr<WinTabAPI::WTSETA>(tabapis.ptrWTSetA, "WTSetA");isOk = isOk && getProcAddr<WinTabAPI::WTOPENA>(tabapis.ptrWTOpenA, "WTOpenA");isOk = isOk && getProcAddr<WinTabAPI::WTCLOSE>(tabapis.ptrWTClose, "WTClose");isOk = isOk && getProcAddr<WinTabAPI::WTPACKET>(tabapis.ptrWTPacket, "WTPacket");isOk = isOk && getProcAddr<WinTabAPI::WTOVERLAP>(tabapis.ptrWTOverlap, "WTOverlap");isOk = isOk && getProcAddr<WinTabAPI::WTSAVE>(tabapis.ptrWTSave, "WTSave");isOk = isOk && getProcAddr<WinTabAPI::WTCONFIG>(tabapis.ptrWTConfig, "WTConfig");isOk = isOk && getProcAddr<WinTabAPI::WTRESTORE>(tabapis.ptrWTRestore, "WTRestore");isOk = isOk && getProcAddr<WinTabAPI::WTEXTSET>(tabapis.ptrWTExtSet, "WTExtSet");isOk = isOk && getProcAddr<WinTabAPI::WTEXTGET>(tabapis.ptrWTExtGet, "WTExtGet");isOk = isOk && getProcAddr<WinTabAPI::WTQUEUESIZESET>(tabapis.ptrWTQueueSizeSet, "WTQueueSizeSet");isOk = isOk && getProcAddr<WinTabAPI::WTDATAPEEK>(tabapis.ptrWTDataPeek, "WTDataPeek");isOk = isOk && getProcAddr<WinTabAPI::WTPACKETSGET>(tabapis.ptrWTPacketsGet, "WTPacketsGet");isOk = isOk && getProcAddr<WinTabAPI::WTMGROPEN>(tabapis.ptrWTMgrOpen, "WTMgrOpen");isOk = isOk && getProcAddr<WinTabAPI::WTMGRCLOSE>(tabapis.ptrWTMgrClose, "WTMgrClose");isOk = isOk && getProcAddr<WinTabAPI::WTMGRDEFCONTEXT>(tabapis.ptrWTMgrDefContext, "WTMgrDefContext");isOk = isOk && getProcAddr<WinTabAPI::WTMGRDEFCONTEXTEX>(tabapis.ptrWTMgrDefContextEx, "WTMgrDefContextEx");return isOk;
}

第二步是加载一大坨库函数


AXIS TabletX;AXIS TabletY;callFunc().ptrWTInfoA( WTI_DEVICES, DVC_X, &TabletX );callFunc().ptrWTInfoA( WTI_DEVICES, DVC_Y, &TabletY );logContext->lcInOrgX = 0;logContext->lcInOrgY = 0;logContext->lcInExtX = TabletX.axMax;logContext->lcInExtY = TabletY.axMax;

这里是获取数位板最大范围TabletX.axMax和TabletY.axMax,为以后屏幕坐标对应运算做准备


最后会
tabapis.context_ = callFunc().ptrWTOpenA(handle, (LPLOGCONTEXTA)logContext, true);
通过这个来 开启接收数据


void TabletSupport::start()
{if(hasDevice()) {auto dispacher = QAbstractEventDispatcher::instance(window_->thread());dispacher->installNativeEventFilter(this);}
}

这里注册系统消息监听


bool lastdown = 0, curdown = 0;
bool TabletSupport::nativeEventFilter(const QByteArray &eventType,void *message, long *)
{if (eventType == "windows_generic_MSG") {MSG* ev = static_cast<MSG *>(message);switch(ev->message){case WT_PACKET:PACKET pkt;if(!callFunc().ptrWTPacket((HCTX)ev->lParam,ev->wParam,&pkt)){return false;}auto preRange_s = normalPressureInfo();int preRange = preRange_s.axMax - preRange_s.axMin +1;auto tpreRange_s = tangentialPressureInfo();int tpreRange = tpreRange_s.axMax - tpreRange_s.axMin +1;QDesktopWidget* desktopWidget = QApplication::desktop();int curMonitor = desktopWidget->screenNumber(window_);QRectF deskRect = desktopWidget->screenGeometry(curMonitor);lastdown = curdown;curdown = pkt.down;QPointF toCurScreen(pkt.pkX*1.0*deskRect.width() / logContext->lcInExtX, pkt.pkY*1.0*deskRect.height() / logContext->lcInExtY);QPointF xx=window_->getCurCanvas()->mapToGlobal(QPoint(0,0));QPointF widgetToCurScreen(xx.x() - deskRect.x(), xx.y() - deskRect.y());QPointF curPen = toCurScreen - widgetToCurScreen;printf("x:%f y:%f %f\n", curPen.x(), curPen.y(),deskRect.height());//printf("w:%f h:%f\n", widgetToCurScreen.x(), widgetToCurScreen.y());//printf("x:%d y:%d\n", window_->getCurCanvas()->width(), window_->getCurCanvas()->height());//printf("test x:%f y:%f pre:%-5d down:%-5d mode:%-5d %-5d %-5d %-5d\n", pkt.pkX*logContext->lcInExtX*1.0/logContext->lcOutExtX, pkt.pkY*logContext->lcInExtY*1.0 / logContext->lcOutExtY, pkt.pkNormalPressure, pkt.down, pkt.pkMode,pkt.pkButtons,pkt.pkContext);//,pkt.pkOrientationauto btn_state = HIWORD(pkt.pkButtons);if (lastdown > curdown) {//1 0printf("release\n");points[writepos].pre = 0;points[writepos].x = window_->getCurCanvas()->transformX(curPen.x());points[writepos].y = window_->getCurCanvas()->transformY(curPen.y());writepos++; if (writepos == 10000)writepos = 0; pcount++;}else if (lastdown < curdown) { //0 1printf("down\n");points[writepos].pre = pkt.pkNormalPressure*1.0/preRange;points[writepos].x = window_->getCurCanvas()->transformX(curPen.x());points[writepos].y = window_->getCurCanvas()->transformY(curPen.y());writepos++; if (writepos == 10000)writepos = 0; pcount++;}else if (lastdown == 1) {//1 1printf("downMove\n");points[writepos].pre = pkt.pkNormalPressure*1.0 / preRange;points[writepos].x = window_->getCurCanvas()->transformX(curPen.x());points[writepos].y = window_->getCurCanvas()->transformY(curPen.y());writepos++; if (writepos == 10000)writepos = 0; pcount++;}return true;break;}}return false;
}

这里没有用事件系统,因为开头已经描述过事件系统会引起数据损失
packet的结构不能照着项目的写,因为我那样照着写了之后,数据的顺序是错乱的。具体原因不清楚,还请了解的大佬告知

typedef struct __TAG {UINT		pkOrientation;//unknownUINT		pkMode;UINT			down;LONG			pkX;LONG			pkY;DWORD			pkButtons;//unknownUINT		pkNormalPressure;HCTX			pkContext;//unknown} __TYPES ;

目前一共有8个变量,有三个是我还不知道干什么的变量,但是少一个就会出错,多一个好像没什么影响

mode变量是橡皮和笔的标志
down是笔有没有碰到屏幕
pkx,y是未转化的横纵坐标
pkNormalPressure就是压感值


if(!callFunc().ptrWTPacket((HCTX)ev->lParam,ev->wParam,&pkt)){return false;}

调用函数获取包内容到结构体内,


然后是坐标计算。获取到的坐标是数位板坐标。不是显示器像素,
举个例子:假设数位板x坐标的最大范围xmax是 10000;那这个获取到的x坐标就是0-10000的整数;要转换到显示器坐标就得是屏幕宽度w*x/xmax;
然后要再转换到相对控件的坐标,那就得先获取到控件的坐标,

QPointF xx=window_->getCurCanvas()->mapToGlobal(QPoint(0,0));

这个函数是将相对控件的(0,0)坐标转换为全局坐标。全局坐标的原点不一定是屏幕的左上角,因为可能是多个屏幕,所以我们要获取相对屏幕的坐标,就得获取屏幕左上角的坐标


QDesktopWidget* desktopWidget = QApplication::desktop();int curMonitor = desktopWidget->screenNumber(window_);QRectF deskRect = desktopWidget->screenGeometry(curMonitor);

通过以上的代码可以获取到控件对应的显示器编号,然后获取显示器对应的长方体


然后此时控件相对于屏幕的坐标就是

QPointF widgetToCurScreen(xx.x() - deskRect.x(), xx.y() - deskRect.y());

这个时候 【笔相对于控件的坐标】 = 【笔相对屏幕的坐标】-【控件相对屏幕的坐标】


if (lastdown > curdown) {//1 0printf("release\n");points[writepos].pre = 0;points[writepos].x = window_->getCurCanvas()->transformX(curPen.x());points[writepos].y = window_->getCurCanvas()->transformY(curPen.y());writepos++; if (writepos == 10000)writepos = 0; pcount++;}else if (lastdown < curdown) { //0 1printf("down\n");points[writepos].pre = pkt.pkNormalPressure*1.0/preRange;points[writepos].x = window_->getCurCanvas()->transformX(curPen.x());points[writepos].y = window_->getCurCanvas()->transformY(curPen.y());writepos++; if (writepos == 10000)writepos = 0; pcount++;}else if (lastdown == 1) {//1 1printf("downMove\n");points[writepos].pre = pkt.pkNormalPressure*1.0 / preRange;points[writepos].x = window_->getCurCanvas()->transformX(curPen.x());points[writepos].y = window_->getCurCanvas()->transformY(curPen.y());writepos++; if (writepos == 10000)writepos = 0; pcount++;}

这里就是状态判断,是刚按下,还是刚抬起,还是正在按着,然后把笔数据传入队列,交给绘制的线程读取,这里用的是环形数组,可能有点low。。


到此为止笔的数据收集就完成了

下次有空再写绘制部分


http://www.ppmy.cn/news/491973.html

相关文章

wacom怎么调压感_手绘板压感是什么 数位板压感怎么调【教程】

【手绘板压感】手绘板压感是什么 数位板压感怎么调 手绘板压感是什么 1 、 压感级别就是用笔轻重的感应灵敏度,捕捉画手力度变化的每个瞬间,并经过电脑计算表现出来;压感现在有三个等级,分别为 512 (入门)、 1024 (进阶)、 2048 (专家)简单的来说,压感级别就是对你下笔力度…

wacom怎么调压感_新买的数位板(手绘板)怎么设置,数位板没有压感没反应怎么办...

很多同学在买了数位板以后&#xff0c;总是怀着非常兴奋的心情&#xff0c;迫不及待地插上USB端口&#xff0c;就开始想要进行创作。 或者是还会先看看说明书&#xff0c;一步步按照书上的要求来。 然而到头来&#xff0c;还是有很多同学会发现&#xff0c;自己的数位板&#x…

WACOM数位板没有压感问题的解决步骤

首先&#xff0c;和您购买的厂商沟通并下载驱动以及需要的第三方库。 一、先安装驱动&#xff0c;确保是您从官网下载下来的驱动&#xff0c;不要安装一些不知名厂商的驱动。 我的是WACOM数位板&#xff0c;驱动在&#xff1a; http://support.wacom.com.cn/download/drivers…

1061: 【能量项链】

题目描述 在Mars星球上&#xff0c;每个Mars人都随身佩带着一串能量项链。在项链上有N颗能量珠。能量珠是一颗有头标记与尾标记的珠子&#xff0c;这些标记对应着某个正整数。并且&#xff0c;对于相邻的两颗珠子&#xff0c;前一颗珠子的尾标记一定等于后一颗珠子的头标记。因…

精选10个圣诞树效果,这个圣诞更有技术范

扫码或搜索添加文末公众号「搞前端的半夏」&#xff1a; &#x1f357; 硬核资料&#xff1a;领取1000PPT模板、100简历模板、行业经典书籍PDF。 &#x1f357; 回复 ”网站模板“&#xff0c;免费送网站模板&#xff01; &#x1f357; 回复 ”面试“&#xff1a;免费送你面试…

斯巴达勇士飓风赛来袭 告诉你什么是真勇士

中新网北京1月20日电 (邢蕊)19日&#xff0c;130名来自世界各地的斯巴达勇士在北京奥森公园集合&#xff0c;开启了自己新年的首场挑战——斯巴达勇士飓风赛。 勇士们在飓风赛现场。主办方供图。 飓风赛是斯巴达勇士耐力赛中一个类型的比赛&#xff0c;它分为4小时飓风赛&…

【放置奇兵】tips(神圣伤害)

文章目录 神圣伤害20200824 神圣伤害 20200824 今天发现一个小秘密&#xff0c;光弓貌似只有技能受神伤加成&#xff0c;追击不受神伤加成

1570:能量项链

【题目描述】 题做多了总会遇到重复的&#xff1a;原博客点击这里 const int N4005;int n,m,t;int i,j,k;int a[N];int head[N],tail[N];int dp[N][N];int main() {IOS;while(cin>>n){for(i1;i<n;i) cin>>head[i],head[in]head[i];for(i1;i<2*n-1;i) tail[i…

《魔法黎明(Dawn of Magic)》修改存档魔法技能、工匠技能、属性点、金币数量(一)

参考链接 好吧&#xff0c;实在不行&#xff0c;果断修改&#xff0c;魔法技能153点&#xff0c;属性100魔法黎明-内存修改 Dawn of Magic魔法黎明存档属性点修改二进制转16进制 魔法黎明&#xff08;Dawn of Magic&#xff09; 相关记录&#xff08;一&#xff09; 一、 前言…

圣剑传说 玛娜传奇(Legend of Mana)(LOM)AF推荐放置

原文链接 A: 16 10     22 海賊船バルド マドラ海岸 魔法都市ジオ   11 5 7 8 14 港町ポルポタ キルマ湖 ジャングル 月夜の町ロア ミンダス遺跡 20 6 3 2 26 21 ウルカン鉱山 断崖の町ガト リュオン街道 ドミナの町 マナの聖域 フィーグ雪原…

【圣诞节 圣诞树】--音乐版简洁圣诞树哦

<!DOCTYPE html> <html lang"en"> <head> <meta charset"utf-8"> <meta name"description" content"程序员的圣诞树" /> <meta name"author" content"les" /> …

百度飓风算法版本类型说明,如何破解飓风算法呢

最近很多学员和客户都给南帝seo老师反应&#xff0c;为啥我的网站最近索引一直在下降&#xff0c;而且下降的幅度还非常的大&#xff0c;而且收录也下降了&#xff0c;网站词库都下降了&#xff0c;关键词排名更是没有了&#xff0c;问我咋回事&#xff0c;怎么会这样&#xff…

320. 能量项链

在 Mars 星球上&#xff0c;每个 Mars 人都随身佩带着一串能量项链&#xff0c;在项链上有 N 颗能量珠。 能量珠是一颗有头标记与尾标记的珠子&#xff0c;这些标记对应着某个正整数。 并且&#xff0c;对于相邻的两颗珠子&#xff0c;前一颗珠子的尾标记一定等于后一颗珠子的…

盾神与条状项链

问题描述 有一天&#xff0c;盾神捡到了好多好多五颜六色的珠子&#xff01;他心想这些珠子这么漂亮&#xff0c;可以做成一条项链然后送给他心仪的女生~于是他用其中一些珠子做成了长度为n的项链。当他准备把项链首尾相接的时候&#xff0c;土方进来了。 “哇这么恶心的项链你…

商标被驳回,先别慌!挽回商标有办法

商标注册是一个漫长的等待过程&#xff0c;提交了注册申请之后不代表就能得心应手。商标局在接收到申请后&#xff0c;便会开始各阶段审查&#xff0c;面对不符合条件的商标会予以商标驳回。商标局基于什么原因而驳回注册申请呢?驳回后还有必要进行商标驳回复审吗?今天心周企…

android 恢复出厂设置 时间,安卓恢复出厂设置

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。 以华为荣耀20Pro手机为例&#xff0c;恢复出厂设置的方法是&#xff1a; 1、点击进入“设置”。 2、在设置界面&#xff0c;找到并点击“系统和更新”选项。 3、点击“重置”选项。…

恢复出厂设置android手机号码,安卓手机怎么恢复出厂设置

很多人的手机用久了&#xff0c;因为垃圾文件过多就会导致手机速度变慢&#xff0c;很多人就想通过恢复手机的出厂设置来提升手机的速度&#xff0c;就是把手机还原到刚买回来时候的那个样子&#xff0c;很多后来自己在手机上安装的软件和一些设置都会清除&#xff0c;这样可以…

安卓强制恢复出厂_怎样科学合理地下载、卸载APP?恢复出厂设置存在哪些问题?...

最让人讨厌的事情是什么?或许是约好了心爱的女孩子一起去看电影&#xff0c;结果突然之间&#xff0c;电闪雷鸣、天降大雨&#xff0c;计划泡汤了。或许是结束了一天的繁琐工作&#xff0c;像脱缰的野马一般开着小轿车回家&#xff0c;结果遇到了大规模堵车。亦或许是在你和小…

Android 8.0恢复出厂设置

在8.0之前做过恢复出厂的demo&#xff0c;执行代码如下&#xff1a; Intent intent new Intent(Intent.ACTION_MASTER_CLEAR); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(Intent.EXTRA_REASON, "MasterClearConfirm&quo…

Android(安卓)手机变砖复活的三种恢复方法

刷机是许多Android手机都要经历的一环&#xff0c;然而我们都知道“刷机有风险”&#xff0c;一不小心&#xff0c;手机就有可能刷成砖头。不过&#xff0c;高手自然有办法让“砖机”起死回生哟&#xff01; 砖机症状&#xff1a;刷机后可以开机但无法进入系统&#xff0c;一直…
最新文章