赞
踩
需求:当我在看视频学习的时候,需要屏幕指定区域的内容保存起来,采用常见的XX截图软件,你需要选择区域选择路径保存,把文件命名为有意义的名称,效率极其低下。作为一名计算机专业人员强调思考能力、动手能力和内功修炼层级,所以这点事情还是很简单的。
软件界面
assist : 主界面,主要包含交互逻辑,多屏支持。
uiservant: 为界面提供辅助函数,其它逻辑处理
winhook: 设置键盘鼠标钩子,取点使用
network: 百度OCR API请求
采用集成工具QtCreator IDE设计师直接布局就OK了,Qt布局功能真心强大。
Qt主要有 QJsonObject,QJsonArray,QJsonParseError, QJsonDocument 实现Json解析与格式化以及保存功能,采用组合的结构性模式。
[
{
"automatic_recognition_name_area": "360 184 220 30",
"screenshot_doc_folder": "D:/CommonUser/Documents/",
"screenshot_picture_area": "360 150 1140 810",
"screenshot_picture_prefix_name": "线性代数",
"subject": "可汗学院_线性代数"
},
{
"automatic_recognition_name_area": "12 27 450 40",
"screenshot_doc_folder": "D:/CommonUser/Documents",
"screenshot_picture_area": "00 0 120 200",
"screenshot_picture_prefix_name": "机器学习",
"subject": "哈弗公开课_机器学习"
}
]
对应 Json解析代码
QString Assist::loadConf()
{
QString key;
QJsonParseError err;
QByteArray data;
QJsonObject obj;
QJsonObject::iterator it;
QString OK = m_uiservant.fileReadAll(m_uiservant.getMapString("conf_file_path"), data);
if(!m_uiservant.isOK(OK))
{
return OK;
}
QJsonDocument doc = QJsonDocument::fromJson(data,&err);
if(err.error != QJsonParseError::NoError)
{
return "parse json error!";
}
if (!doc.isArray())
{
return "assist_conf.json error format";
}
QJsonArray ary = doc.array();
for(int i=0;i< ary.size();++i)
{
stConf conf;
obj = ary.at(i).toObject();
key = "subject";
if((it = obj.find(key))==obj.end())
{
continue;
}
conf.qstrSubject = it.value().toString();
key = "screenshot_picture_prefix_name";
if((it = obj.find(key))==obj.end())
{
continue;
}
conf.qstrPicName = it.value().toString();
// 其它实现...
m_lsConf.push_back(conf);
}
return m_uiservant.SUCCEEDED_STRING;
}
Json 保存
QString Assist::saveConf()
{
QString qstrFilePath = m_uiservant.getMapString("conf_file_path");
QJsonObject obj;
QJsonDocument doc;
QJsonArray ary;
for(QList<stConf>::iterator it = m_lsConf.begin(); it!=m_lsConf.end(); ++it)
{
obj.insert("subject", it->qstrSubject);
obj.insert("screenshot_doc_folder", it->qstrDocFolder);
obj.insert("screenshot_picture_prefix_name", it->qstrPicName);
obj.insert("automatic_recognition_name_area", it->qstrPicNameArea);
obj.insert("screenshot_picture_area", it->qstrScrShotArea);
ary.append(obj);
obj = QJsonObject();
}
doc.setArray(ary);
QString ret = m_uiservant.fileWrite(qstrFilePath, doc.toJson());
if(!m_uiservant.isOK(ret))
{
return ret;
}
return m_uiservant.SUCCEEDED_STRING;
}
之前做过一个自动化工具,录制屏幕取点采用C#实现,生成自定义脚本文件,获取当鼠标处于停在某处时,按下 Alt 键就生成点坐标。现在由于交互比较简单,采用比较三种交互方式,鼠标左键录制x,y坐标,右键录制w,h, 中间键停止录制。
怎么实现HOOK,参考非常棒的一篇文章:QT中安装不使用dll的全局钩子
采用Windows底层API, SetWindowsHookEx 实现,具体实现参照工程源码。
以下为获取点之后
HHOOK WinHook::keyHook=NULL;
HHOOK WinHook::mouseHook=NULL;
QWidget* WinHook::m_pWidget = NULL;
LRESULT CALLBACK keyProc(int,WPARAM,LPARAM lParam )
{
KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT *) lParam;
if(pkbhs->vkCode==VK_F12)
{
WinHook::unHook();
}
return 0;
}
LRESULT CALLBACK mouseProc(int nCode, WPARAM wParam,LPARAM lParam)
{
if (nCode == HC_ACTION)
{
LPMOUSEHOOKSTRUCT p = (MOUSEHOOKSTRUCT*)lParam;
((Assist*)WinHook::m_pWidget)->acceptPoint(wParam, p->pt.x, p->pt.y);
}
return 0;
}
void WinHook::setHook(QWidget* widget)
{
m_pWidget = widget;
keyHook =SetWindowsHookEx( WH_KEYBOARD_LL,keyProc,GetModuleHandle(NULL),0);
mouseHook =SetWindowsHookEx( WH_MOUSE_LL,mouseProc,GetModuleHandle(NULL),0);
}
void WinHook::unHook()
{
UnhookWindowsHookEx(keyHook);
UnhookWindowsHookEx(mouseHook);
keyHook = NULL;
mouseHook = NULL;
m_pWidget = NULL;
}
当处于全屏时,需要提示采用保证是否成功,否则没有成功截图不是白忙活了吗?
label = new QLabel;
label->setWindowFlags(Qt::FramelessWindowHint|Qt::Popup);
label->adjustSize();
label->setWordWrap(true);
label->setAlignment(Qt::AlignCenter);
注意此处 setWindowFlags
Qt::FramelessWindowHint|Qt::Popup
,无边框,Qt::Popup 保证全屏下不会被遮挡,这一点很重要!
void Assist::showTips(QString tips, bool bStatusShow, int stayLong)
{
if(bStatusShow)
{
ui->status_label->setText(tips);
}
label->setText(tips);
QRect rect = m_uiservant.getScreenRect();
label->setMaximumWidth(200);
label->setMaximumHeight(100);
label->setMinimumWidth(200);
label->setMinimumHeight(100);
QFont font;
font.setPointSize(12);
label->setFont(font);
label->setStyleSheet(QLatin1String("color:red;"));
label->setGeometry(int(rect.width()-label->width()), int((rect.height()-label->height()-50)), label->width(), label->height());
label->show();
QTimer::singleShot(stayLong, this, SLOT(slotHideFinishedLabel()));
}
void Assist::slotHideFinishedLabel()
{
label->hide();
}
设置位置,采用计时器实现,是不是很简单呢?
Qt浏览器采用Chorm内核,对于网络请求自然不在话下,只不过Qt4与Qt5的迁移变化比较大,还是很痛苦的!前同事为了业务需要,竟然在一个软件里面插入两个Qt浏览器,真挺厉害的!
吐槽一下百度的官方API文档,请求与返回的字段有些出入,有些吐血。第一次使用它,感觉没有阿里/讯飞提供的服务体验棒。不过百度的 OCR 速度还可以,之前采用打码兔平台API(额,最近打码兔平台即将关闭了,辛亏只充5元人民币)
在获取图片文字主要做了三件事:
在第3步操作上花了好几个小时折腾,终于看清并理解含义。
图像数据,base64编码后进行urlencode,
要求base64编码和urlencode后大小不超过4M,
最短边至少15px,最长边最大4096px,
支持jpg/png/bmp格式,当image字段存在时url字段失效
为什么需要 base64
编码之后还要进行 urlencode
?
Base64编码 使用的字符:
大小写字母各26个,
加上10个数字,
加号“+”,斜杠“/”,
一共64个字符,
等号=用来作为后缀用途。
其中的+, /, = 都是需要urlencode的,所以无法取代。
采用 python编码只需要几行代码就实现了
from urllib import parse,request
import ssl
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=Bjm0X1hsLxrxsyA3hyuSUuke&client_secret=P6CBRGWnmTUSOboM0Dao5ZUI51SW8ODE'
req = request.Request(host,headers={'Content-Type':'application/json; charset=UTF-8'})
res = request.urlopen(req)
res = res.read()
print(res)
from urllib import parse,request
import ssl
import base64
host = 'https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic/token?access_token=24.ae2a4c83a7a4778a2d12395b62b68099.2592000.1529261360.282335-11263768'
with open(r"D:\CommonUser\Documents\BookCode\QT\Test\screen_assist\ss.png", 'rb') as f:
data = base64.b64encode(f.read())
textmod={'image': data,}
textmod = parse.urlencode(textmod).encode(encoding='utf-8')
req = request.Request(host,headers={'Content-Type':'application/x-www-form-urlencoded'},data=textmod,)
res = request.urlopen(req)
res = res.read()
print(res)
是不是很简答?此处使用我在百度注册的Id,请试一试就好,不然我不高兴就撤销这个应用。
OK,试着用Qt实现 Post 请求,实现第三步的功能:
EMRequestTypeFlags NetWorker::fetchPictureOCR(QString access_token,
QByteArray base64_img,
QString language_type,
QString detect_direction,
QString detect_language,
QString probability)
{
QNetworkRequest request;
QUrl url("https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic");
QUrlQuery query;
query.addQueryItem("access_token",access_token);
url.setQuery(query);
request.setUrl(url);
request.setRawHeader(QByteArray("Content-Type"), QByteArray("application/x-www-form-urlencoded"));
base64_img = base64_img.toPercentEncoding();
QByteArray postData;
postData.append(QString("image=").toLatin1()+base64_img);
postData.append(QString("&language_type="+language_type).toLatin1());
postData.append(QString("&detect_direction="+detect_direction).toLatin1());
postData.append(QString("&detect_language="+detect_language).toLatin1());
postData.append(QString("&probability="+probability).toLatin1());
QNetworkReply* reply = mNetmanager->post(request,postData);
reply->ignoreSslErrors();
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(slotError(QNetworkReply::NetworkError)));
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(slotSslErrors(QList<QSslError>)));
mRequest[reply] = emRequestAccessOCR;
return mRequest[reply];
}
application/x-www-form-urlencoded
格式是一种非常通用数据传输格式,关键字=值&关键字=值
的形式传输数据。
实现base64的urlencod很简单,base64_img.toPercentEncoding();
关键是一定要理解。
https和ssl协议广泛应用,Qt并没有直接去实现这些协议,但是内部具有支持调用协议的接口,没有 libeay32.dll
和 ssleay32.dll
会爆出一大堆警告,百度API里面明确支持 https 协议,必须要有这两个文件。在本地一搜索,一大堆软件包含这两个dll,比如:QQ,WeChat, IQIYI… 等都含有。我直接采用 WeChat的这两个文件不好使,找到了一篇文章,请参照下载并放在应用程序所在目录。
我一直使用Qt,主要是Qt支持跨平台,安卓,IOS,MAC, Linux, Window, WinCE,车载操作系统 …,而且还和Python完美融合, 具有一些 Python 的影子,事实上现代标准 C++ 也在向 Python学习,不过它花哨的语法,让我有些不爽。
亮点一:我尝试在C++ 中融入脚本的思考,采用典型的词典代替其它变量声明:
void setMapString(QString qstrKey, QString qstrValue);
QString getMapString(QString qstrKey, QString qstrDefaultValue="");
我不希望一个类里面充满了大量的变量声明,统统塞入map里面,整个类就瘦了,干净很多。
亮点二 :代码分层,那些以简单为由,直接把逻辑与界面揉在一起的开发指导人员是在误导他人。
一个看似很简单的软件,其实是花了好几天来完成,做一件事情,不急不躁,坚持做完,你就胜利了。
当我看到旋转的风车,走动的唐老鸭,米老鼠,我就在想他们只不过是绘画出来的,我怎么能根据一幅画实现线条拆离以及动画的重组? 那么,你有更好的思路吗?欢迎加好友讨论 。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。