For the bitmap (900x500px) I'm converting it to base64, I was trying to send her pointer(?) but looks like the script receiving the msg is not able to use this pointer to get the bitmap
Each msg is taking around 200ms (encode: 40ms, dump: 50ms, send: 80)
I wonder if it could be done any 'faster' way, another way to send the bitmap than converting it to b64?
Code: Select all
Time := (A_TickCount)
Object.pBitmap := put_base64(pBitmap, "png")
Time1 := (A_TickCount - Time)
; ---------------------------------------
Time2 := (A_TickCount)
Dump := CoCoJson.Dump(Object)
Time2 := (A_TickCount - Time2)
; ---------------------------------------
Time3 := (A_TickCount)
Send_WM_COPYDATA(Dump, PID)
Time3 := (A_TickCount - Time3)
; ---------------------------------------
Time := (A_TickCount - Time)
MsgBox Encode: %Time1% Dump: %Time2% Send: %Time3%`nTotal: %Time%
; ---------
; FUNCTIONS
; ---------
Send_WM_COPYDATA(String, PID) {
; Set up the structure's memory area.
VarSetCapacity(CopyDataStruct, 3*A_PtrSize, 0)
; First set the structure's cbData member to the size of the string, including its zero terminator:
SizeInBytes := (StrLen(String) + 1) * (A_IsUnicode ? 2 : 1)
; OS requires that this be done.
NumPut(SizeInBytes, CopyDataStruct, A_PtrSize)
; Set lpData to point to the string itself.
NumPut(&String, CopyDataStruct, 2*A_PtrSize)
; 0x4a is WM_COPYDATA.
; Must use Send not Post.
Static TimeOutTime := 0
SendMessage, 0x4a, 0, &CopyDataStruct,, ahk_pid %PID%,,,, %TimeOutTime%
; ErrorLevel can store a repply data.
;Return ErrorLevel
}
; Function from iseahound lib ImagePut.
put_base64(pBitmap, extension := "", quality:= 100) {
; Thanks noname - https://www.autohotkey.com/boards/viewtopic.php?style=7&p=144247#p144247
; Default extension is PNG for small sizes!
if (extension == "")
extension := "png"
; Select Codec
; ------------
; 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
}
; Create a Stream
; ------------
DllCall("ole32\CreateStreamOnHGlobal", "ptr", 0, "int", True, "ptr*", pStream:=0, "uint")
DllCall("gdiplus\GdipSaveImageToStream", "ptr", pBitmap, "ptr", pStream, "ptr", pCodec, "ptr", (ep) ? &ep : 0)
; Get a pointer to binary data.
DllCall("ole32\GetHGlobalFromStream", "ptr", pStream, "ptr*", hbin:=0, "uint")
bin := DllCall("GlobalLock", "ptr", hbin, "ptr")
size := DllCall("GlobalSize", "ptr", bin, "uptr")
; Calculate the length of the base64 string.
flags := 0x40000001 ; CRYPT_STRING_NOCRLF | CRYPT_STRING_BASE64
length := 4 * Ceil(size/3) + 1 ; An extra byte of padding is required.
VarSetCapacity(str, length)
; Using CryptBinaryToStringA is faster and saves about 2MB in memory.
DllCall("crypt32\CryptBinaryToStringA", "ptr", bin, "uint", size, "uint", flags, "ptr", &str, "uint*", length)
; Release binary data and stream.
DllCall("GlobalUnlock", "ptr", hbin)
ObjRelease(pStream)
; Return encoded string length minus 1.
return StrGet(&str, length, "CP0")
}
CocoJson lib:
Code: Select all
/*
Class: JSON
The JSON object contains methods For parsing JSON and converting values
to JSON.
Callable - NO
Instantiable - YES
Subclassable - YES
Nestable(via #Include) - NO.
Methods:
Load() - see relevant documentation before method definition header
Dump() - see relevant documentation before method definition header
*/
Class CocoJson
{
/**
* Method: Load
* Parses a JSON string into an AHK value
* Syntax:
* value := JSON.Load( text [, reviver ] )
* Parameter(s):
* value [retval] - parsed value
* text [in, ByRef] - JSON formatted string
* reviver [in, opt] - function object, similar to JavaScript's
* JSON.parse() 'reviver' parameter
*/
Class Load extends CocoJson.Functor
{
Call(self, ByRef text, reviver:="")
{
this.rev := IsObject(reviver) ? reviver : false
; Object keys(and array indices) are temporarily stored in arrays so that
; we can enumerate them in the order they appear in the document/text instead
; of alphabetically. Skip if no reviver function is specified.
this.keys := this.rev ? {} : false
static quot := Chr(34), bashq := "\" . quot
, json_value := quot . "{[01234567890-tfn"
, json_value_or_array_closing := quot . "{[]01234567890-tfn"
, object_key_or_object_closing := quot . "}"
key := ""
is_key := false
root := {}
stack := [root]
next := json_value
pos := 0
while ((ch := SubStr(text, ++pos, 1)) != "") {
If InStr(" `t`r`n", ch)
Continue
If !InStr(next, ch, 1)
this.ParseError(next, text, pos)
holder := stack[1]
is_array := holder.IsArray
If InStr(",:", ch) {
next := (is_key := !is_array && ch == ",") ? quot : json_value
} else If InStr("}]", ch) {
ObjRemoveAt(stack, 1)
next := stack[1]==root ? "" : stack[1].IsArray ? ",]" : ",}"
} else {
If InStr("{[", ch) {
; Check if Array() is overridden and if its return value has
; the 'IsArray' property. If so, Array() will be called normally,
; otherwise, use a custom base object for arrays
static json_array := Func("Array").IsBuiltIn || ![].IsArray ? {IsArray: true} : 0
; sacrifice readability for minor(actually negligible) performance gain
(ch == "{")
? ( is_key := true
, value := {}
, next := object_key_or_object_closing )
; ch == "["
: ( value := json_array ? new json_array : []
, next := json_value_or_array_closing )
ObjInsertAt(stack, 1, value)
If (this.keys)
this.keys[value] := []
} else {
If (ch == quot) {
i := pos
while (i := InStr(text, quot,, i+1)) {
value := StrReplace(SubStr(text, pos+1, i-pos-1), "\\", "\u005c")
static tail := A_AhkVersion<"2" ? 0 : -1
If (SubStr(value, tail) != "\")
Break
}
If (!i)
this.ParseError("'", text, pos)
value := StrReplace(value, "\/", "/")
, value := StrReplace(value, bashq, quot)
, value := StrReplace(value, "\b", "`b")
, value := StrReplace(value, "\f", "`f")
, value := StrReplace(value, "\n", "`n")
, value := StrReplace(value, "\r", "`r")
, value := StrReplace(value, "\t", "`t")
pos := i ; update pos
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)
}
If (is_key) {
key := value, next := ":"
Continue
}
} else {
npos := pos
i := RegExMatch(text, "[\]\},\s]|$",, npos)
i -= npos
value := SubStr(text, pos, i)
If value is number
{
If value is integer
value += 0
}
else If (value = "true" || value = "false")
value := %value% + 0
else If (value = "null")
value := ""
else
; we can do more here to pinpoint the actual culprit
; but that's just too much extra work.
this.ParseError(next, text, npos, i)
pos := npos
pos += i-1
}
next := holder==root ? "" : is_array ? ",]" : ",}"
} ; If InStr("{[", ch) { ... } else
is_array? key := ObjPush(holder, value) : holder[key] := value
If (this.keys && this.keys.HasKey(holder))
this.keys[holder].Push(key)
}
} ; while ( ... )
return this.rev ? this.Walk(root, "") : root[""]
}
ParseError(expect, ByRef text, pos, len:=1)
{
static quot := Chr(34), qurly := quot . "`}`"
line := StrSplit(SubStr(text, 1, pos), "`n", "`r").Length()
col := pos - InStr(text, "`n",, -(StrLen(text)-pos+1))
msg := Format("`{`1`}``n`nLine:`t`{`2`}`nCol:`t`{`3`}`nChar:`t`{`4`}`"
, (expect == "") ? "Extra data"
: (expect == "'") ? "Unterminated string starting at"
: (expect == "\") ? "Invalid \escape"
: (expect == ":") ? "Expecting ':' delimiter"
: (expect == quot) ? "Expecting object key enclosed in double quotes"
: (expect == qurly) ? "Expecting object key enclosed in double quotes or object closing '`}`'"
: (expect == ",`}`") ? "Expecting ',' delimiter or object closing '`}`'"
: (expect == ",]") ? "Expecting ',' delimiter or array closing ']'"
: InStr(expect, "]") ? "Expecting JSON value or array closing ']'"
: "Expecting JSON value(string, number, true, false, null, object or array)"
, line, col, pos)
static offset := A_AhkVersion<"2" ? -3 : -4
;throw Exception(msg, offset, SubStr(text, pos, len))
return ""
}
Walk(holder, key)
{
value := holder[key]
If IsObject(value) {
For i, k in this.keys[value] {
; check if ObjHasKey(value, k) ??
v := this.Walk(value, k)
If (v != CocoJson.Undefined)
value[k] := v
else
ObjDelete(value, k)
}
}
return this.rev.Call(holder, key, value)
}
}
/**
* Method: Dump
* Converts an AHK value into a JSON string
* Syntax:
* str := CocoJson.Dump( value [, replacer, space ] )
* Parameter(s):
* str [retval] - JSON representation of an AHK value
* value [in] - any value(object, string, number)
* replacer [in, opt] - function object, similar to JavaScript's
* CocoJson.stringify() 'replacer' parameter
* space [in, opt] - similar to JavaScript's CocoJson.stringify()
* 'space' parameter
*/
Class Dump extends CocoJson.Functor
{
Call(self, value, replacer:="", space:="")
{
this.rep := IsObject(replacer) ? replacer : ""
this.gap := ""
If (space) {
If space is integer
Loop, % ((n := Abs(space))>10 ? 10 : n)
this.gap .= " "
else
this.gap := SubStr(space, 1, 10)
this.indent := "`n"
}
return this.Str({"": value}, "")
}
Str(holder, key)
{
value := holder[key]
If (this.rep)
value := this.rep.Call(holder, key, ObjHasKey(holder, key) ? value : CocoJson.Undefined)
If IsObject(value) {
; Check object type, skip serialization for other object types such as
; ComObject, Func, BoundFunc, FileObject, RegExMatchObject, Property, etc.
static type := A_AhkVersion<"2" ? "" : Func("Type")
If (type ? type.Call(value) == "Object" : ObjGetCapacity(value) != "") {
If (this.gap) {
stepback := this.indent
this.indent .= this.gap
}
is_array := value.IsArray
; Array() is not overridden, rollback to old method of
; identifying array-like objects. Due to the use of a for-loop
; sparse arrays such as '[1,,3]' are detected as objects({}).
If (!is_array) {
For i in value
is_array := i == A_Index
until !is_array
}
str := ""
If (is_array) {
Loop, % value.Length() {
If (this.gap)
str .= this.indent
v := this.Str(value, A_Index)
str .= (v != "") ? v . "," : "null,"
}
} else {
colon := this.gap ? ": " : ":"
For k in value {
v := this.Str(value, k)
If (v != "") {
If (this.gap)
str .= this.indent
str .= this.Quote(k) . colon . v . ","
}
}
}
If (str != "") {
str := RTrim(str, ",")
If (this.gap)
str .= stepback
}
If (this.gap)
this.indent := stepback
return is_array ? "[" . str . "]" : "{" . str . "}"
}
} else ; is_number ? value : "value"
return ObjGetCapacity([value], 1)=="" ? value : this.Quote(value)
}
Quote(string)
{
static quot := Chr(34), bashq := "\" . quot
If (string != "") {
string := StrReplace(string, "\", "\\")
; , string := StrReplace(string, "/", "\/") ; optional in ECMAScript
, string := StrReplace(string, quot, bashq)
, string := StrReplace(string, "`b", "\b")
, string := StrReplace(string, "`f", "\f")
, string := StrReplace(string, "`n", "\n")
, string := StrReplace(string, "`r", "\r")
, string := StrReplace(string, "`t", "\t")
static rx_escapable := A_AhkVersion<"2" ? "O)[^\x20-\x7e]" : "[^\x20-\x7e]"
while RegExMatch(string, rx_escapable, m)
string := StrReplace(string, m.Value, Format("\u{1:04x}", Ord(m.Value)))
}
return quot . string . quot
}
}
/**
* Property: Undefined
* Proxy For 'undefined' type
* Syntax:
* undefined := CocoJson.Undefined
* Remarks:
* For use with reviver and replacer functions since AutoHotkey does not
* have an 'undefined' type. Returning blank("") or 0 won't work since these
* can't be distnguished from actual JSON values. This leaves us with objects.
* Replacer() - the caller may return a non-serializable AHK objects such as
* ComObject, Func, BoundFunc, FileObject, RegExMatchObject, and Property to
* mimic the behavior of returning 'undefined' in JavaScript but For the sake
* of code readability and convenience, it's better to do 'return CocoJson.Undefined'.
* Internally, the property returns a ComObject with the variant type of VT_EMPTY.
*/
Undefined[]
{
get {
static empty := {}, vt_empty := ComObject(0, &empty, 1)
return vt_empty
}
}
Class Functor
{
__Call(method, ByRef arg, args*)
{
; When casting to Call(), use a new instance of the "function object"
; so as to avoid directly storing the properties(used across sub-methods)
; into the "function object" itself.
If IsObject(method)
return (new this).Call(method, arg, args*)
else If (method == "")
return (new this).Call(arg, args*)
}
}
}
; Check if the variable is a valid Json string.
isJsonValid(string) {
; https://www.autohotkey.com/boards/viewtopic.php?t=81247
static doc := ComObjCreate("htmlfile")
, __ := doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
, parse := ObjBindMethod(doc.parentWindow.JSON, "parse")
try %parse%(string)
catch
return false
return true
}
;}