1、背景
由于在写搜狗翻译本地化的时候无意看到还有ocr,就顺带一起嫖过来了、
搜狗:年轻人耗子尾汁…
2、实现原理
调试搜狗所有网页发现必需获取特定的cookie和查看js代码中的部分算法才能上传图片,先请求三个地址获取cookie然后用ahk实现js的算法补全参数,调用上传地址上传图片获取返回的json ,然后解析json渲染到activeX控件中。
- 截图实现:使用snipast 截图 (优点截图功能强大)
- 上传实现:调用ctrl+c (snipast截图快捷键)把图片存到粘贴板,通过GDI+把图片处理后存为png文件有些过小的图片需要缩放
- post请求:ADODB.Stream来实现post的二进制上传
- 解析json:利用js的eval函数
- 显示:gui的activeX显示html文件,依旧使用扣取html的方法,掏空body把结果组成的标签放进去,剩下的就交给搜狗网页的兼容了。
优点:
- 不需依赖外部ahk文件或第三方文件,和注册三方云接口来实现。
- 可以识别很多语言包括中日韩英等,同时除了中文外的其它语言还可以得到翻译后的中文。
- 界面美观简洁,翻译结果准确率高。
缺点:
- 需要联网才能实现这些功能
- 由于高度依赖搜狗网站,如果搜狗网站结构发生或者代码发生变化可能导致无法使用。
- 第一次使用时要访问3个不必要网址速度慢,后面会缓存结果,只访问一个网址即可得到结果速度会提升。
3、使用方法
- 必须安装snipast 并且具备快捷键ctrl+c保存图(默认就是)
- 在snipast选中区域时按ctrl+1快捷键就可以识别图片(快捷键可自己在代码中修改,最好不要和snipast的冲突)
tip:其实自己也可以用gdi+实现屏幕截图,后来详想想没啥必要。一是比较麻烦代码量会增大,二是snipast确实很强大用了就不想用其它截图了。
4、效果演示
①.中文的识别
②.识别并翻译英语
③.是被并翻译其它语言
5、代码
#Persistent
#SingleInstance
#InstallMouseHook
#InstallKeybdHook
#KeyHistory 499
CoordMode,Mouse,Screen ;全局获取模式
OnMessage(0x100 , "keyboard_message_callback") ;键盘事件
;移动选框
WM_LBUTTONDOWN(){
Static init:=OnMessage(0x0201, "WM_LBUTTONDOWN")
if(A_Cursor="Arrow")
PostMessage, 0xA1, 2
return ;
}
;键盘事件的回调函数
keyboard_message_callback(wparam,lparma,msg)
{
WinGetActiveTitle, active_title
; tooltip ,% active_title ":" wParam
if((wparam=13 || wparam=27)) ;响应回车事件和esc事件
{
Gui sgocr:destroy
}
}
;复制原文
srcCopyButton1_OnClick()
{
; msgBox % SoGouOcr.result_source " :" SoGouOcr.result_trans
clipboard:=SoGouOcr.result_source
Gui sgocr:destroy
}
;复制翻译后
srcCopyButton2_OnClick()
{
; msgBox % SoGouOcr.result_source " :" SoGouOcr.result_trans
clipboard:=SoGouOcr.result_trans
Gui sgocr:destroy
}
;关闭窗口事件
exitBtn1_OnClick()
{
; msgBox click
Gui sgocr:destroy
}
;ico点击动画
label_ico_click_animate:
{
SoGouTrans.animate_timer_count:=SoGouTrans.animate_timer_count+1
if(SoGouTrans.html_ready || SoGouTrans.ico_new|| SoGouTrans.animate_timer_count*SoGouTrans.animate_timer_gap>SoGouTrans.animate_out_time)
{
setTimer ,label_ico_click_animate,off ; 关闭定时器
GuiControl,translate:hide ,Ico_wait_text1 ;停止显示动画
GuiControl,translate:hide ,Ico_wait_text2 ;停止显示动画
GuiControl,translate:hide ,Ico_wait_text3 ;停止显示动画
return
}
ico_wait_shape :=Util.decodeUtf8(SoGouTrans.ico_wait_shape)
ico_wait_shape :=SoGouTrans.ico_wait_shape
ico_size:=SoGouTrans.ico_size
wait1_offset_x:=1.5*ico_size
wait2_offset_x:=2*ico_size
wait3_offset_x:=2.5*ico_size
ico_color:=SoGouTrans.ico_color
i:=SoGouTrans.animate_timer_count ;INDEX 从1开始
if(i=1)
{
global Ico_wait_text1
global Ico_wait_text2
global Ico_wait_text3
}
if(i<=3)
{
offset_x:=(0.46*i+1)*ico_size-SoGouTrans.point_to_ico
Gui ,translate:Add, Text, vIco_wait_text%i% x%offset_x% y0 c%ico_color%, % ico_wait_shape ; label_gui_event标签处理gui事件
}else
{
loop ,% mod(i,4) ;显示
{
; tooltip loop1=Ico_wait_text%A_index%
GuiControl,translate:show ,Ico_wait_text%A_index%
}
loop ,% 3-(mod(i,4)) ;隐藏
{
hide_index:=4-A_index
GuiControl,translate:hide ,Ico_wait_text%hide_index%
}
}
; MouseGetPos ,x ,y
; toolTip % "x:" x " y:" y
return
}
;延时后去掉ico
label_destroy_ico:
{
SoGouTrans.ico_timer_count:=SoGouTrans.ico_timer_count+1
IfWinNotExist ,% SoGouTrans.html_title
{
if(SoGouTrans.ico_timer_count*50>=SoGouTrans.ico_destroy_time)
{
Gui, Translate:destroy
setTimer ,label_destroy_ico,off
setTimer, label_ico_click_animate ,off
}
}
return ;
}
;gui事件处理 https://ahkcn.sourceforge.net/docs/Variables.htm#GuiEvent
label_gui_event:
{
if(A_GuiControl="Ico_text" && A_GuiControlEvent="Normal") ;点击翻译图标时触发
{
;初始化并渲染翻译结果GUI
try SoGouTrans.init_transhtml_gui(1)
; msgBox ok
}
return ;
}
;只对snipast生效
#IfWinActive Snipper - Snipaste
$^1::
SoGouOcr.show()
return ;
#if
;搜狗图片文字识别
class SoGouOcr
{
static url1:="https://fanyi.sogou.com/" ;搜狗主页 ,获取带有SNUID相关cookie
static url2:="https://fanyi.sogou.com/picture" ;搜狗图片识别网页 ,获取uuid
static url3:="https://pb.sogou.com/cl.gif?" ;打开文件会发送一个get请求 ,获取带有SNUID相关cookie
static url4:="https://fanyi.sogou.com/api/transpc/picture/upload" ;上传文件接口,需要携带 snuid和FQV相关cookie
static boundary = "----WebKitFormBoundaryaEHpMn3lywBtjPfE" ;formData边界
static User_Agent:="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
static cookie:="" ;访问url产生的cookie
static uuid:="" ;方位url2返回的请求头中的uuid
static snipaste_title:="Snipper - Snipaste"
static html_header:="" ;<body>之前
static html_footer:="" ;</body>之后
static html_zoom:=0.7 ;网页缩放
static font_size:=25 ;设置字体大小
static html_top_color:="00ff00" ;顶部边框颜色
static pic_min_w:=40 ;图片最小允许宽度,小于这高度就会按照pic_scale放大
static pic_min_h:=30 ;图片最小允许高度,小于这高度就会按照pic_scale放大
static pic_scale:=2.5 ; 图片缩放比例
static result_source:="" ;返回的ocr识别
static result_trans:="" ;返回的ocr识别的翻译
static gui_w:=500 ;没有缩放之前的gui大小
;请求所有的url,
request_sogou_url(filepath)
{
req := ComObjCreate("WinHttp.WinHttpRequest.5.1")
if(!SoGouOcr.cookie)
SoGouOcr.get_url123(req)
ObjRelease(req) ;释放内存
return SoGouOcr.get_url4(req,filepath)
}
;打开搜狗翻译主页主要为了获取cookie中的snuid(url3,url4中),FQV(url中) ,wuid(url3中)使用
get_url123(req)
{
;①请求url1
; req.SetProxy(2, "127.0.0.1:8888") ;设置fiddler抓包服务器
req.Open("get", SoGouOcr.url1)
req.setRequestHeader("User-Agent",SoGouOcr.User_Agent) ;在open之后
req.send()
; req.WaitForResponse()
; fileAppend ,% req.GetAllResponseHeaders(),C:\Users\Administrator\Desktop\cookie.txt
headers:=req.GetAllResponseHeaders() ;获取所有相应头
; msgBox % headers "`r`n" Util.getCookieMap(headers)
SoGouOcr.cookie:=Util.getCookieMap(headers)
;②请求url2
req.Open("get", SoGouOcr.url2)
req.send()
; req.WaitForResponse()
SoGouOcr.uuid:=req.GetResponseHeader("UUID:") ;获取相关cookie
;③请求url3
Random, rand, 0.0, 1.0
_t:=Util.getTimeStamp()
_r:=floor(1000* rand)
uuid:=SoGouOcr.uuid
snuid:=SoGouOcr.cookie.snuid.value
wuid:=SoGouOcr.cookie.wuid.value
url3:=SoGouOcr.url3
url3_with_param = %url3%uigs_productid=vs_web&vstype=translate&snuid=%snuid%&pagetype=index&type=imgtrans&uuid=%uuid%&fr=default&terminal=web&onerror=true&wuid=%wuid%&overIe10=1&abtest=3&uigs_cl=upload_click_home&_t=%_t%&_r=%_r%&uigs_st=0
; msgBox % url3_with_param
req.Open("get", url3_with_param)
req.send()
; req.WaitForResponse()
}
;④.上传文件
get_url4(req,filepath)
{
current_cookie:=% "ABTEST="SoGouOcr.cookie.ABTEST.value ";IPLOC="SoGouOcr.cookie.IPLOC.value ";SNUID=" SoGouOcr.cookie.SNUID.value ";FQV=" SoGouOcr.cookie.FQV.value
; current_cookie=ABTEST=6|1673104302|v17; IPLOC=CN5101;SNUID=DD5759A6D2D620B6A66D186BD31414E5;FQV=c7862eb243f3dfbcf3b287dcc047f0e7
; src:="C:\Users\Administrator\Desktop\xx1.png"
; src:="C:\Users\Administrator\Desktop\xx2.png"
while(!FileExist(filepath)) ;延迟3s中就退出
{
sleep ,50
if(A_index>=60)
return ;
}
src:=filepath
extra_data={"from":"auto","to":"zh-CHS","imageName":"xx.png"}
sec_ch_ua= "Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"
objParam := {"fileData":[src],"fuuid":SoGouOcr.uuid,"extraData":extra_data} ;post数据
Util.getPostFormBinData(PostData, hdr_ContentType, objParam)
req.Open("POST", SoGouOcr.url4,true)
req.SetRequestHeader("Content-Type", hdr_ContentType)
req.SetRequestHeader("sec-ch-ua",sec_ch_ua)
req.SetRequestHeader("sec-ch-ua-mobile","?0")
req.SetRequestHeader("sec-ch-ua-platform","Windows")
req.SetRequestHeader("sec-Fetch-Dest","empty")
req.SetRequestHeader("sec-Fetch-Mode","cors")
req.SetRequestHeader("sec-Fetch-Site","same-origin")
req.SetRequestHeader("User-Agent",SoGouOcr.User_Agent)
req.SetRequestHeader("cookie",current_cookie)
req.Send(PostData)
req.WaitForResponse()
result:=req.ResponseText
; fileAppend ,% result ,C:\Users\Administrator\Desktop\xx2.json
return result
}
;获取关键数据,成功返回map {contents:"xxx" content_list:[xx1,xx2,xx3]} 主要针对多行数据
get_ocr_resultMap(filepath)
{
result:=SoGouOcr.request_sogou_url(filepath)
; msgBox % result
; fileAppend % result,ocr.json
rJson:=Util.parseJsonStr(result)
retMap :={}
if(rJson.status=0)
{
data:=rJson.data
try{
data["result"]
}catch e{
return
}
content_results:=data["result"]
; msgBox % content_results[0]
contents:=""
trans:=""
content_list:=[]
trans_list:=[]
for content in content_results
{
contents:=contents content.content "`r`n"
content_list[A_index]:=content.content
trans:=trans content.trans_content "`r`n"
trans_list[A_index]:=content.trans_content
}
retMap["contents"]:=RTrim(contents,"`r`n")
retMap["content_list"]:=content_list
retMap["trans"]:=RTrim(trans,"`r`n")
retMap["trans_list"]:=trans_list
; msgBox % retMap.contents
return retMap
}
}
;显示翻译结果
show(flag="raw")
{
; msgBox show
clipboard_save:=ClipboardAll
SetKeyDelay, 0
ControlSend, , ^c, % SoGouOcr.snipaste_title ;保存图片
clipWait,3,1 ;第一个参数是等待的s数,第二个参数1是等待任意类型,0(缺省) 等待文本文件等、
MouseGetPos ,xpos,ypos
SoGouTrans.create_ico_gui(xpos,ypos)
WinSet, AlwaysOnTop, ON, % SoGouTrans.ico_title
SoGouTrans.html_ready:=0 ;加载完成变成1
SoGouTrans.ico_new:=0 ;创建新的ico变成1
SoGouTrans.animate_timer_count:=0 ;执行一次加一次
SetTimer,label_ico_click_animate,% SoGouTrans.animate_timer_gap ;timer后面必须是一个timer不然无法异步执行
filepath:=SoGouOcr.save_pic()
; msgBox % filepath
fileAppend ,% "`n" filepath ,tmp.txt
if(!filepath) ;处理图片
{
clipboard:=clipboard_save ;恢复粘贴板
return
}
clipboard:=clipboard_save ;恢复粘贴板
; fileappend ,% filepath ,tmp.txt
SoGouOcr.result_trans:=""
SoGouOcr.result_source:=""
content_map:=SoGouOcr.get_ocr_resultMap(filepath) ;图片渲染的数据
SoGouOcr.result_source:=content_map.contents
SoGouOcr.result_trans:=content_map.trans
if(content_map)
SoGouOcr.create_ocr_html(content_map,flag) ;创建gui
IfExist ,% filepath ;清除tmp中图片
FileDelete, % filepath
SoGouTrans.html_ready:=1
Gui ,translate:destroy
}
;创建html的GUI
create_ocr_html(content_map,flag)
{
MouseGetPos ,xpos,ypos
xpos:=xpos-20
ypos:=ypos-20
result_html:=SoGouOcr.get_result_guihtml(content_map,flag)
global WB2 ;浏览器对象
global MenuHwnd2 ;句柄
global srcCopyButton1 ;复制按钮1
global srcCopyButton2 ;复制按钮2
global exitBtn1 ;退出按钮
; global convertBtn1 ;切换按钮
gui_width:=SoGouOcr.gui_w * SoGouOcr.html_zoom
html_title:="ahk_sogouocr_result_v1"
Gui ,sgocr:New,,% html_title
; sleep ,100
Gui ,sgocr:Add, ActiveX,x0 y0 w%gui_width% h1080 vWB2, Shell.Explorer ; 最后一个参数是ActiveX组件的名称。
Gui, sgocr:Color, 30f0ca
; Gui ,sgocr: -Caption +ToolWindow +HwndocrHwnd
Gui ,sgocr:+LastFound +AlwaysOnTop -Caption +ToolWindow +HwndocrHwnd
WB2.silent := true ;Surpress JS Error boxes
Util.Display(WB2,result_html)
while WB2.readystate != 4 or WB2.busy
sleep 10
div_h:=WB2.document.getElementById("mainDiv").offsetHeight ;当前div高度
srcCopyButton1 := WB2.document.getElementById("src-clipboard") ;复制按钮
srcCopyButton2 := WB2.document.getElementById("target-clipboard") ;复制按钮
exitBtn1 := WB2.document.getElementById("exitBtn1") ;退出
ComObjConnect(srcCopyButton1, "srcCopyButton1_")
ComObjConnect(srcCopyButton2, "srcCopyButton2_")
ComObjConnect(exitBtn1, "exitBtn1_")
; ComObjConnect(convertBtn1, "convertBtn1_")
div_h:=div_h*SoGouOcr.html_zoom
;边界检测
xpos:= xpos+gui_width>A_ScreenWidth?A_ScreenWidth-gui_width:xpos
ypos:= ypos+div_h>A_ScreenHeight-40?A_ScreenHeight-div_h-40:ypos
Gui sgocr:Show ,x%xpos% y%ypos% w%gui_width% h%div_h%
Util.FrameShadow(ocrHwnd)
; msgBox ok
}
;获取渲染html ,清空body然后放入构造的html
get_result_guihtml(content_map,flag)
{
url2:="https://fanyi.sogou.com/picture" ;搜狗图片识别网页 ,获取uuid
WebRequest := ComObjCreate("WinHttp.WinHttpRequest.5.1") ;创建http对象
WebRequest.Open("GET",url2) ;必须有http://
WebRequest.Send()
result := WebRequest.ResponseText
ObjRelease(WebRequest) ;释放内存
start_element=<!--[if lte IE 9]> <script> ;搜索结果只有一个
end_element=</div><script> ; 从开始元素开始搜索
StringReplace, result, result, "//,"https:// ,All ;把路径转换为绝对路径
if(!SoGouOcr.head_html||!SoGouOcr.footer_html)
{
start_pos:=instr(result,start_element,1,1) ;找到body开头
if(!start_pos)
return ;
end_pos:= instr(result,end_element,1,start_pos) ;参数依次是1.目标字符,2.要匹配的字符,3.是否大小写消息敏感,4.起始位置
if(!end_pos)
return
SoGouOcr.head_html:=subStr(result,1,start_pos-1)
SoGouOcr.footer_html:=subStr(result,end_pos+strLen("</div>")) ;substr不指定长度就是直接取到最后一个字符
}
zoom:=SoGouOcr.html_zoom
font_size:=SoGouOcr.font_size
if(strLen(SoGouOcr.result_source)>15)
font_size:=font_size*0.9
if(strLen(SoGouOcr.result_source)>30)
font_size:=font_size*0.9
if(strLen(SoGouOcr.result_source)>40)
font_size:=font_size*0.9
border_color:=SoGouOcr.html_top_color
span_elements:="" ;识别后的文
span_elements2:="" ;翻译后
exit_left:=zoom*SoGouOcr.gui_w-22,exit_top:=10,exit_zoom:=0.4
convert_left:=10, convert_bottom:=30,convert_zoom:=0.5
gui_w:=SoGouOcr.gui_w * SoGouOcr.html_zoom
gui_r:=gui_w+10 ;右滑块宽度10px
to_right=window.scrollTo(%gui_r%,0)
to_left=window.scrollTo(0,%gui_w%)
; main_div=<div id="mainDiv" style="zoom:%html_scala%;border-top:5px solid #%border_color%;width:%div_w%">
main_div=<div id="mainDiv" style="zoom:%zoom%;border-top:5px solid #%border_color%">
div_element_start=%main_div%<div class="trans-box pic"><div class="pic-result-box"><div class="pic-from"><div class="text-box" style="font-size:%font_size%px" ><p>
html_style=<style>.source_trans_convert{width: 30px;height: 30px;z-index:100;zoom:%convert_zoom%;background: url("https://search.sogoucdn.com/translate/pc/static/img/sprite_common_translate.ed1fb14.png") no-repeat;background-position: -372px -176px;}.source_trans_convert:hover{ cursor:pointer;background: url("https://search.sogoucdn.com/translate/pc/static/img/sprite_common_translate.ed1fb14.png") no-repeat; background-position: -372px -142px;zoom:0.51}.html_gui_exit{width: 38px;height: 38px;zoom:%exit_zoom%;position: fixed;left:%exit_left%px;top: %exit_top%px;z-index:2;background: url("https://search.sogoucdn.com/translate/pc/static/img/sprite_common_translate.ed1fb14.png") no-repeat;background-position: -78px -319px;}.html_gui_exit:hover{cursor:pointer;background: url("https://search.sogoucdn.com/translate/pc/static/img/sprite_common_translate.ed1fb14.png") no-repeat;background-position: -38px -319px;zoom:0.402}</style>
html_script=<script>var flag=true;window.onload = function(){var div_h = document.getElementById('mainDiv').offsetHeight;document.getElementById('convertBtn1').style.position="fixed";document.getElementById('convertBtn1').style.top= (div_h*%zoom%-%convert_bottom%)+"px";document.getElementById('convertBtn1').style.left="%convert_left%px";};function convert_click(){if(flag){%to_right%}else{%to_left%}flag=!flag}</script>
ico_div=<div class="html_gui_exit" id="exitBtn1"></div><div class="source_trans_convert" onclick="convert_click()" id="convertBtn1"></div>
;渲染的数据是<span id="left-17-s">SMS202301170423250412</span>
arr1:= content_map.content_list
arr2:=content_map.trans_list
; msgBox % "content:" content_map.contents
for k,v in arr1
span_elements=%span_elements% <span id="left-%A_index%-s">%v%</span>
for m,n in arr2
span_elements2=%span_elements2% <span id="right-%A_index%-s">%n%</span>
; msgBox % "html_frag:" span_elements
; msgBox % "html_frag:" span_elements2
copy:=Util.decodeUtf8("\u590D\u5236") ;复制
right_copy=<div id="target-clipboard" class="btn-copy" >%copy%</div>
right_element=<div class="pic-to"><div class="text-box"><p>%span_elements2%</p></div>%right_copy%</div>
div_element_end=</p></div><div id="src-clipboard" class="btn-copy">%copy%</div></div>%right_element%</div></div>
result_html:=SoGouOcr.head_html html_style html_script ico_div div_element_start span_elements div_element_end SoGouOcr.footer_html
fileAppend , % result_html ,C:\Users\Administrator\Desktop\result.html ,utf-8
return result_html
}
;保存图片到临时文件夹并返回文件
save_pic(filename:="")
{
imageUtil.gdiplusStartup() ;开始GDI
pBitmap:=imageUtil.from_clipboard()
; msgBox % "pBitmap:" pBitmap
if(pBitmap<0) ;获取粘贴板数据
return ;
DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", width:=0) ;获取图片宽度
DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", height:=0) ;获取图片高度
; msgBox % "width:height=" width ":" height
if(width<SoGouOcr.pic_min_w || height<SoGouOcr.pic_min_h )
{
imageUtil.BitmapScale(pBitmap,SoGouOcr.pic_scale) ;缩放
}
; msgBox % "pBitmap3:" pBitmap
if(!filename)
{
filename:=A_temp "\ahk_ocr_" A_YYYY "-" A_MM "-" A_DD "-" A_Hour "-" A_Min "-" A_Sec ".png"
filename:=imageUtil.put_file(pBitmap,filename)
}
imageUtil.gdiplusShutdown() ;关闭GDI
return filename
}
}
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Util
Class Util
{
;字符串转换为二进制
BinArr_FromString(str) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 2 ; adTypeText
oADO.Mode := 3 ; adModeReadWrite
oADO.Open
oADO.Charset := "UTF-8"
oADO.WriteText(str)
oADO.Position := 0
oADO.Type := 1 ; adTypeBinary
oADO.Position := 3 ; Skip UTF-8 BOM
return oADO.Read, oADO.Close
}
;把文件转换为二进制
BinArr_FromFile(FileName) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Open
oADO.LoadFromFile(FileName)
return oADO.Read, oADO.Close
}
;合并二进制数据
BinArr_Join(Arrays*) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Mode := 3 ; adModeReadWrite
oADO.Open
For i, arr in Arrays
oADO.Write(arr)
oADO.Position := 0
return oADO.Read, oADO.Close
}
;把二进制转换为字符串
BinArr_ToString(BinArr, Encoding := "UTF-8") {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Mode := 3 ; adModeReadWrite
oADO.Open
oADO.Write(BinArr)
oADO.Position := 0
oADO.Type := 2 ; adTypeText
oADO.Charset := Encoding
return oADO.ReadText, oADO.Close
}
;把二进制转换为文件
BinArr_ToFile(BinArr, FileName) {
oADO := ComObjCreate("ADODB.Stream")
oADO.Type := 1 ; adTypeBinary
oADO.Open
oADO.Write(BinArr)
oADO.SaveToFile(FileName, 2)
oADO.Close
}
;产生随机边界
RandomBoundary() {
str := "0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z"
Sort, str, D| Random
str := StrReplace(str, "|")
Return SubStr(str, 1, 12)
}
;判断当前类型
MimeType(FileName) {
n := FileOpen(FileName, "r").ReadUInt()
Return (n = 0x474E5089) ? "image/png"
: (n = 0x38464947) ? "image/gif"
: (n&0xFFFF = 0x4D42 ) ? "image/bmp"
: (n&0xFFFF = 0xD8FF ) ? "image/jpeg"
: (n&0xFFFF = 0x4949 ) ? "image/tiff"
: (n&0xFFFF = 0x4D4D ) ? "image/tiff"
: "application/octet-stream"
}
;解析json字符串、
parseJsonStr(jsonStr)
{
if(inStr(jsonStr,"`\")) ;包含转义字符
{
tmp_str=EABD9414432D47859B9BCD78ACA11848
StringReplace, jsonStr, jsonStr, `\ ,% tmp_str,replaceAll
StringReplace, jsonStr, jsonStr, `" ,`\`",replaceAll
StringReplace, jsonStr, jsonStr, % tmp_str ,`\`\,replaceAll
}else{
StringReplace, jsonStr, jsonStr, `" ,`\`",replaceAll
}
oSC := ComObjCreate("ScriptControl")
oSC.Language := "JScript"
Script = var Encoded = eval("(%jsonStr%)")
oSC.ExecuteStatement(Script)
result:=oSC.Eval("Encoded")
ObjRelease(oSC)
Return result
}
;返回一个cookie 的map={a:{value:"hello",path:"/" , expires:"Sun, 22 Jan 2023 03:46:59 GMT"},cookie:"xxx",size:n}
;req.GetAllResponseHeaders() 获取所有cookie
getCookieMap(headers)
{
cookie_map:={}
cookie_str:=""
; msgBox % headers
size:=0
Loop, parse, headers, `n, `r ; 在 `r 之前指定 `n, 这样可以同时支持对 Windows 和 Unix 文件的解析.
{
head_line:=trim(A_loopField)
; msgBox % head_line
tmp_map:={}
key:=""
if(inStr(head_line ,"Set-Cookie:")=1)
{
size:=size+1
cookie_str:=cookie_str head_line "`r`n"
StringReplace ,cookie_value,% head_line,Set-Cookie:
key_value_arr:=StrSplit(cookie_value ,"`;")
Loop ,% key_value_arr.MaxIndex()
{
part_cookie:=key_value_arr[A_index]
tmp_arr:=StrSplit(trim(part_cookie) ,"=")
if(tmp_arr.MaxIndex()>1)
{
if(A_index=1)
{
key := trim(tmp_arr[1])
tmp_map["value"]:=trim(tmp_arr[2])
}else{
tmp_map[trim(tmp_arr[1])]:=trim(tmp_arr[2])
}
}
}
cookie_map[key]:=tmp_map
}
}
cookie_map["cookie"]:=cookie_str
cookie_map["size"]:=size
return cookie_map
}
;获取post请求中payload ,在post慢中对应数据类型为body中的form-data ,数据类型为二进制
;retData 返回的二进制数据,retHeader请求头,objParam入参对象 [] 中会被认为是文件
;示例:objParam := {"fileData":[src],"fuuid":"aa409350-e00c-49df-9f20-517796457e68","extraData":extra_data}
;
getPostFormBinData(ByRef retData, ByRef retHeader, objParam) {
CRLF := "`r`n"
; Create a random Boundary
BoundaryLine := "------------------------------" . Boundary
; Loop input paramters
binArrs := []
For k, v in objParam
{
If IsObject(v) {
For i, FileName in v
{
;当前二进制数据来源于文件
str := BoundaryLine . CRLF
. "Content-Disposition: form-data; name=""" . k . """; filename=""" . FileName . """" . CRLF
. "Content-Type: " . Util.MimeType(FileName) . CRLF . CRLF
binArrs.Push( Util.BinArr_FromString(str) )
binArrs.Push( Util.BinArr_FromFile(FileName) )
binArrs.Push( Util.BinArr_FromString(CRLF) )
}
} Else {
str := BoundaryLine . CRLF
. "Content-Disposition: form-data; name=""" . k """" . CRLF . CRLF
. v . CRLF
binArrs.Push( Util.BinArr_FromString(str) )
}
}
str := BoundaryLine . "--" . CRLF
binArrs.Push( Util.BinArr_FromString(str) )
; Finish
retData := Util.BinArr_Join(binArrs*)
retHeader := "multipart/form-data; boundary=----------------------------" . Boundary
}
;获取当前时间戳
getTimeStamp()
{
startTime := "19700101000000"
nowTime := A_NowUTC
EnvSub, nowTime, startTime, seconds
return % nowTime*1000+A_MSec
}
;对utf-8解码
decodeUtf8(value)
{
i := 0
while (i := InStr(value, "\",, i+1)) {
if !(SubStr(value, i+1, 1) == "u")
this.ParseError("\", text, pos - StrLen(SubStr(value, i+1)))
uffff := Abs("0x" . SubStr(value, i+2, 4))
if (A_IsUnicode || uffff < 0x100)
value := SubStr(value, 1, i-1) . Chr(uffff) . SubStr(value, i+6)
}
Return,value
}
;窗口加上阴影
FrameShadow(HGui) {
DllCall("dwmapi\DwmIsCompositionEnabled","IntP",_ISENABLED) ; Get if DWM Manager is Enabled
if !_ISENABLED ; if DWM is not enabled, Make Basic Shadow
DllCall("SetClassLong","UInt",HGui,"Int",-26,"Int",DllCall("GetClassLong","UInt",HGui,"Int",-26)|0x20000)
else
VarSetCapacity(_MARGINS,16)
, NumPut(0,&_MARGINS,0,"UInt")
, NumPut(0,&_MARGINS,4,"UInt")
, NumPut(1,&_MARGINS,8,"UInt")
, NumPut(0,&_MARGINS,12,"UInt")
, DllCall("dwmapi\DwmSetWindowAttribute", "Ptr", HGui, "UInt", 2, "Int*", 2, "UInt", 4)
, DllCall("dwmapi\DwmExtendFrameIntoClientArea", "Ptr", HGui, "Ptr", &_MARGINS)
}
;展示html
Display(WB,html_str) {
Count:=0
while % FileExist(f:=A_Temp "\" A_TickCount A_NowUTC "-tmp" Count ".DELETEME.html")
Count+=1
FileAppend,%html_str%,%f%,UTF-8
; f=C:\Users\Administrator\Desktop\xx4.html
WB.Navigate("file://" . f)
; msgBox % WB.readystate
}
;获取粘贴板中png的流
get_clipboard_pngstream() {
; Open the clipboard with exponential backoff.
loop
if DllCall("OpenClipboard", "ptr", A_ScriptHwnd)
break
else
if A_Index < 6
Sleep (2**(A_Index-1) * 30)
else throw Exception("Clipboard could not be opened.")
png := DllCall("RegisterClipboardFormat", "str", "png", "uint")
if !DllCall("IsClipboardFormatAvailable", "uint", png)
pStream:=1
if !(hData := DllCall("GetClipboardData", "uint", png, "ptr"))
pStream:=2
; Allow the stream to be freed while leaving the hData intact.
; Please read: https://devblogs.microsoft.com/oldnewthing/20210930-00/?p=105745
DllCall("ole32\CreateStreamOnHGlobal", "ptr", hData, "int", false, "ptr*", pStream:=0, "uint")
DllCall("CloseClipboard")
return pStream
}
ToBase(n,b)
{
return (n < b ? "" : Util.ToBase(n//b,b)) . ((d:=Mod(n,b)) < 10 ? d : Chr(d+55))
}
}
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Util
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++图像操作类
class imageUtil{
;获取粘贴板数据返回bitmap的指针 pBitmap ,非图片时报错
from_clipboard() {
; Open the clipboard with exponential backoff.
loop
if DllCall("OpenClipboard", "ptr", A_ScriptHwnd)
break
else
if A_Index < 6
Sleep (2**(A_Index-1) * 30)
else throw Exception("Clipboard could not be opened.")
; Fallback to CF_BITMAP. This format does not support transparency even with put_hBitmap().
if !DllCall("IsClipboardFormatAvailable", "uint", 2)
{
DllCall("CloseClipboard")
return -1
}
if !(hbm := DllCall("GetClipboardData", "uint", 2, "ptr"))
{
DllCall("CloseClipboard")
return -2
}
DllCall("gdiplus\GdipCreateBitmapFromHBITMAP", "ptr", hbm, "ptr", 0, "ptr*", pBitmap:=0)
DllCall("DeleteObject", "ptr", hbm)
DllCall("CloseClipboard")
return pBitmap
}
;缩放图片传入bitmap的指针,scale缩放 [200,200] 或者1.5倍
BitmapScale(ByRef pBitmap, scale) {
if not (IsObject(scale) && ((scale[1] ~= "^\d+$") || (scale[2] ~= "^\d+$")) || (scale ~= "^\d+(\.\d+)?$"))
throw Exception("Invalid scale.")
; Get Bitmap width, height, and format.
DllCall("gdiplus\GdipGetImageWidth", "ptr", pBitmap, "uint*", width:=0)
DllCall("gdiplus\GdipGetImageHeight", "ptr", pBitmap, "uint*", height:=0)
DllCall("gdiplus\GdipGetImagePixelFormat", "ptr", pBitmap, "int*", format:=0)
if IsObject(scale) {
safe_w := (scale[1] ~= "^\d+$") ? scale[1] : Round(width / height * scale[2])
safe_h := (scale[2] ~= "^\d+$") ? scale[2] : Round(height / width * scale[1])
} else {
safe_w := Ceil(width * scale)
safe_h := Ceil(height * scale)
}
; Avoid drawing if no changes detected.
if (safe_w = width && safe_h = height)
return pBitmap
; Create a new bitmap and get the graphics context.
DllCall("gdiplus\GdipCreateBitmapFromScan0"
, "int", safe_w, "int", safe_h, "int", 0, "int", format, "ptr", 0, "ptr*", pBitmapScale:=0)
DllCall("gdiplus\GdipGetImageGraphicsContext", "ptr", pBitmapScale, "ptr*", pGraphics:=0)
; Set settings in graphics context.
DllCall("gdiplus\GdipSetPixelOffsetMode", "ptr", pGraphics, "int", 2) ; Half pixel offset.
DllCall("gdiplus\GdipSetCompositingMode", "ptr", pGraphics, "int", 1) ; Overwrite/SourceCopy.
DllCall("gdiplus\GdipSetInterpolationMode", "ptr", pGraphics, "int", 7) ; HighQualityBicubic
; Draw Image.
DllCall("gdiplus\GdipCreateImageAttributes", "ptr*", ImageAttr:=0)
DllCall("gdiplus\GdipSetImageAttributesWrapMode", "ptr", ImageAttr, "int", 3) ; WrapModeTileFlipXY
DllCall("gdiplus\GdipDrawImageRectRectI"
, "ptr", pGraphics
, "ptr", pBitmap
, "int", 0, "int", 0, "int", safe_w, "int", safe_h ; destination rectangle
, "int", 0, "int", 0, "int", width, "int", height ; source rectangle
, "int", 2
, "ptr", ImageAttr
, "ptr", 0
, "ptr", 0)
DllCall("gdiplus\GdipDisposeImageAttributes", "ptr", ImageAttr)
; Clean up the graphics context.
DllCall("gdiplus\GdipDeleteGraphics", "ptr", pGraphics)
DllCall("gdiplus\GdipDisposeImage", "ptr", pBitmap)
return pBitmap := pBitmapScale
}
;把bitmap输出为文件的 bitmap指针(pBitmap) ,filepath:目标位置 比如 c:\xx.png,返回文件位置
put_file(pBitmap, filepath := "", quality := "") {
; Thanks tic - https://www.autohotkey.com/boards/viewtopic.php?t=6517
extension := "png"
imageUtil.select_codec(pBitmap, extension, quality, pCodec, ep, ci, v)
; Write the file to disk using the specified encoder and encoding parameters with exponential backoff.
loop
if !DllCall("gdiplus\GdipSaveImageToFile", "ptr", pBitmap, "wstr", filepath, "ptr", pCodec, "ptr", (ep) ? &ep : 0)
break
else
if A_Index < 6
Sleep (2**(A_Index-1) * 30)
else throw Exception("Could not save file to disk.")
return filepath
}
select_codec(pBitmap, extension, quality, ByRef pCodec, ByRef ep, ByRef ci, ByRef v) {
; Fill a buffer with the available image codec info.
DllCall("gdiplus\GdipGetImageEncodersSize", "uint*", count:=0, "uint*", size:=0)
DllCall("gdiplus\GdipGetImageEncoders", "uint", count, "uint", size, "ptr", &ci := VarSetCapacity(ci, size))
; struct ImageCodecInfo - http://www.jose.it-berater.org/gdiplus/reference/structures/imagecodecinfo.htm
loop {
if (A_Index > count)
throw Exception("Could not find a matching encoder for the specified file format.")
idx := (48+7*A_PtrSize)*(A_Index-1)
} until InStr(StrGet(NumGet(ci, idx+32+3*A_PtrSize, "ptr"), "UTF-16"), extension) ; FilenameExtension
; Get the pointer to the clsid of the matching encoder.
pCodec := &ci + idx ; ClassID
; JPEG default quality is 75. Otherwise set a quality value from [0-100].
if (quality ~= "^-?\d+$") and ("image/jpeg" = StrGet(NumGet(ci, idx+32+4*A_PtrSize, "ptr"), "UTF-16")) { ; MimeType
; Use a separate buffer to store the quality as ValueTypeLong (4).
VarSetCapacity(v, 4), NumPut(quality, v, "uint")
; struct EncoderParameter - http://www.jose.it-berater.org/gdiplus/reference/structures/encoderparameter.htm
; enum ValueType - https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparametervaluetype
; clsid Image Encoder Constants - http://www.jose.it-berater.org/gdiplus/reference/constants/gdipimageencoderconstants.htm
VarSetCapacity(ep, 24+2*A_PtrSize) ; sizeof(EncoderParameter) = ptr + n*(28, 32)
NumPut( 1, ep, 0, "uptr") ; Count
DllCall("ole32\CLSIDFromString", "wstr", "{1D5BE4B5-FA4A-452D-9CDD-5DB35105E7EB}", "ptr", &ep+A_PtrSize, "uint")
NumPut( 1, ep, 16+A_PtrSize, "uint") ; Number of Values
NumPut( 4, ep, 20+A_PtrSize, "uint") ; Type
NumPut( &v, ep, 24+A_PtrSize, "ptr") ; Value
}
}
;开始GDI
gdiplusStartup()
{
DllCall("LoadLibrary", "str", "gdiplus")
VarSetCapacity(si, A_PtrSize = 4 ? 16:24, 0) ; sizeof(GdiplusStartupInput) = 16, 24
NumPut(0x1, si, "uint")
DllCall("gdiplus\GdiplusStartup", "ptr*", pToken:=0, "ptr", &si, "ptr", 0)
}
;关闭GDI释放内存
gdiplusShutdown()
{
DllCall("gdiplus\GdiplusShutdown", "ptr", pToken)
DllCall("FreeLibrary", "ptr", DllCall("GetModuleHandle", "str", "gdiplus", "ptr"))
}
}
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++图像操作类
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++搜狗翻译
class SoGouTrans
{
; static ico_shape :="H"
; static ico_shape :="\u2742" ;❂
; static ico_shape :="\u2664" ;♤
; static ico_shape :="\u2B1B" ;⬛
; static ico_shape :="\u2693" ;⚓
; static ico_shape :="\u2693" ;⚓
; static ico_shape :="\uD83D\uDC10" ;?
; static ico_shape :="\uD83C\uDF51" ;?
; static ico_shape :="\uD83C\uDFEF" ;?
; static ico_shape :="\u2764" ;❤
; static ico_shape :="\u265E" ;♞
; static ico_shape :="\u2663" ;♣
static ico_shape :="\u265C" ;♜
; static ico_shape :="\u2602" ;☂
; static ico_shape :="\u2660" ;♠ ;图形字符对应的unicode编码
; static ico_wait_shape :="\u25CF" ;● 等待的图标
static ico_wait_shape :="." ;● 等待的图标
static ico_size:= 15 ;图标大小, 对象变量必须在new中初始化
static ico_offset_x:= 10 ;翻译图标偏离鼠标点击位置x轴的距离
static ico_offset_y:= 6 ;翻译图标偏离鼠标点击位置y轴的距离
static ico_color:= "00ff00" ;翻译图标颜色,只支持16进制ffffff,不支持颜色单词
static ico_title:="ahk_translate_ico_v1" ;翻译的小图标
static html_title:="ahk_translate_result_v1" ;翻译的小图标
static html_scala:=0.8 ;缩放 范围(0-1] (0最小,1最大)
static ico_hwnd ;小图标的句柄
static html_hwnd ;html的句柄
static select_word ;选中字符
static ico_x ;图标所在x位置
static ico_y ;图标所在y位置
static hide_flag ;当前图标在单击其它区域后后影藏,除非是
static trans_doubclick_on:=0 ;是否支持双击选词 0不支持,1支持
static cursor_drag_gaptime:=300 ;判断鼠标按下到弹起时间差单位ms 来判断拖动
static cursor_dbclick_gaptime:=100 ;判断鼠标双击时间差单位ms 来判断双击
static html_head ;缓存头部
static html_foot ;缓存尾部
static gui_width:=520 ;显示html的宽度,缩放之前
static ignore_title ;忽略翻译的
static grid_width:=15 ;背景取样格子大小,单位像素
static LB_down_cursor ; 左键按下时鼠标形状
static timer_flag ; 判断当前是否还在定时器中执行
static ico_destroy_time :=3000 ;ico在多少ms内不点击的情况下消失
static ico_timer_count:=0 ;消失ico的timer执行的次数
static ico_new ;是否新建ico ,1是,0否
static html_ready :=0 ;当前html页面是否加载完成
static animate_timer_count:=0 ;当前动画计时器执行次数
static animate_timer_gap:=200 ;定时器周期时长 单位ms
static animate_out_time :=10000 ;动画超时时间 (超过时间关闭定时器),单位ms
static click_down_posx ;
static cursor_row_min_dist :=6 ;横向距离 单位px
static use_shortcut_trans :=0 ;是否使用快捷键翻译 ,1使用快捷键翻译,0使用图标翻译。
static point_to_ico := 5 ;点到ico的距离会减去该值
;在鼠标滑动位置创建一个图标
create_ico_gui(obs_x,obs_y)
{
SoGouTrans.ico_x :=obs_x+SoGouTrans.ico_offset_x+3
SoGouTrans.ico_y :=obs_y+SoGouTrans.ico_offset_y+13
obs_x:=obs_x+SoGouTrans.ico_offset_x
obs_y:=obs_y+SoGouTrans.ico_offset_y
ico_shape :=Util.decodeUtf8(SoGouTrans.ico_shape)
global Ico_text
bg_color :=SoGouTrans.calculate_bg_color(obs_x,obs_y,SoGouTrans.ico_size) ;取点分析背景颜色
; msgBox % bg_color
; bg_color :=2b2b2b
ico_size:=SoGouTrans.ico_size
ico_color:=SoGouTrans.ico_color
gui_w:=ico_size>16?(2*ico_size-16)+8:ico_size+8
gui_h:=gui_w
gui_title:=SoGouTrans.ico_title
wait3_offset_x:=2.5*ico_size
gui_w:=wait3_offset_x+10
Gui ,translate:New, ,% gui_title
Gui, translate:Color, %bg_color%
; Gui, translate:Color, ffffff
Gui ,translate:+LastFound +HwndMenuHwnd +AlwaysOnTop -Caption +ToolWindow
SoGouTrans.ico_hwnd:=MenuHwnd
Gui, translate:Font, c%ico_color% q1 thin s%ico_size% , Verdana
Gui ,translate:Add, Text, vIco_text x0 y0 c%ico_color% glabel_gui_event, % ico_shape ; label_gui_event标签处理gui事件
WinSet, TransColor, %bg_color% 250 ;设置颜色透明
Gui ,translate:Show, x%obs_x% y%obs_y% w%gui_w% h%gui_h% NoActivate ;NoActivate 让当前活动窗口继续保持活动状态.
WinSet, AlwaysOnTop, Off, % gui_title ;去掉总在最上面限制,在切换窗口的时候可以隐藏,但是并不会关闭
GuiControl,hide ,Ico_wait_text1
GuiControl,hide ,Ico_wait_text2
GuiControl,hide ,Ico_wait_text3
select_str:=Util.getSelectStr() ;获取光标选中字符
StringReplace, select_str,select_str, `r,, All
StringReplace, select_str,select_str, `n,, All
StringReplace, select_str,select_str, `t,, All
SoGouTrans.select_word:= select_str
setTimer ,label_destroy_ico,off ;去掉之前的定时器
setTimer ,label_destroy_ico ,50 ;延时后去掉ico
SoGouTrans.ico_timer_count:=0
SoGOuTrans.ico_new:=1
; fileappend ,% SoGouTrans.select_word,C:\Users\Administrator\Desktop\select.txt
; msgBox % strlen(trim(SoGouTrans.select_word,OmitChars = "`r`n"))
; msgBox % strLen(select_str)
return 1
}
;计算背景颜色,采集20个点,判断颜色最多的,去掉背景会残留只有融入背景才能让图形更圆滑
calculate_bg_color(obs_x,obs_y,font_size)
{
grid_width:=SoGouTrans.grid_width ;取样格子宽度
x:=obs_x+font_size+10
y:=obs_y+font_size+10
color_map:={}
loop , 5
{
i:=A_Index
loop 4
{
j:=A_Index
PixelGetColor,current_color, % x+j*grid_width,% obs_y+i*grid_width ,RGB
; msgBox % x+j*grid_width "," y+i*grid_width " color:" current_color
if(!color_map[current_color])
color_map[current_color]:=1
else
color_map[current_color]:=color_map[current_color]+1
}
}
tmp_count:=0
tmp_color:=0
for key,value in color_map
{
if(color_map[key]>tmp_count)
{
tmp_count:=color_map[key]
tmp_color:=key
}
}
; msgBox % msg "color:" Util.ToBase(tmp_color,16)
return Util.ToBase(tmp_color,16)
}
}
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++搜狗翻译
6、最后
以上代码部分参考的论坛的大佬的代码,部分是参考网上各路大神的代码,正因为这些个大佬的分享才能组成一个完整的功能,感谢各位大佬~~,其中动画部分参考的之前ahk实现翻译的文章,如果把两个功能结合在一起可以降低代码量。
感谢大佬捐赠?
感谢大佬捐赠?
oSC := ComObjCreate(“ScriptControl”) 这行代码报错,说是没有注册类.