How to send a pBitmap between scripts?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

How to send a pBitmap between scripts?

Post by c7aesa7r » 22 May 2022, 07:42

I'm sending some data together a gdi pBitmap from a script to another using WM_COPYDATA, I'm converting the data to json using the lib CocoJson

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
   }



;}

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 22 May 2022, 09:17

pBitmap is an address in the address space of your first script. It doesn't make sence in the address space of another script.

User avatar
Smile_
Posts: 858
Joined: 03 May 2020, 00:51

Re: How to send a pBitmap between scripts?

Post by Smile_ » 22 May 2022, 09:24

You can use PostMessage / SendMessage and OnMessage() to notify your other script to load the pBitmap on it is own.
or maybe copy the image to clipboard from the first script, then load it from clipboard in the second script.

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 22 May 2022, 09:34

If I need to send image data from one script to another, I'd get a pixel array using LockBits, send it to another script and recreate pBitmap.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 22 May 2022, 11:42

teadrinker wrote:
22 May 2022, 09:34
If I need to send image data from one script to another, I'd get a pixel array using LockBits, send it to another script and recreate pBitmap.
Someone else also suggests using LockBits, but I don't understand how to properly do it.
If it's not much trouble could you give an example?

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 22 May 2022, 13:38

No problem.
The first script:

Code: Select all

DetectHiddenWindows, On

imageFilePath := "D:\Images\MyPicture.png"
secondScriptPath := "D:\Scripts\SecondScript.ahk"

Run, % secondScriptPath,,, PID
WinWait, ahk_pid %PID%
hwnd := WinExist()

GDIp := new GDIplus
if !pBitmap := GDIp.CreateBitmapFromFile(imageFilePath)
   throw "Failed to create pBitmap from file"

GDIp.GetImageDimensions(pBitmap, width, height)
pixelFormat := GDIp.GetImagePixelFormat(pBitmap)
GDIp.LockBits(pBitmap, 0, 0, width, height, stride, scan0, bitmapData,, pixelFormat)
size := stride * height

VarSetCapacity(data, size + 16, 0)
DllCall("RtlCopyMemory", "Ptr", &data + 16, "Ptr", scan0, "Ptr", size)
GDIp.UnlockBits(pBitmap, bitmapData)
GDIp.DisposeImage(pBitmap)
GDIp := ""

NumPut(pixelFormat, data)
NumPut(width, data, 4)
NumPut(height, data, 8)
NumPut(stride, data, 12, "UInt")

if !SendData(hwnd, &data, size + 16)
   MsgBox, Failed to send data

SendData(hWnd, pData, size) {
   static WM_COPYDATA := 0x004A
   VarSetCapacity(COPYDATASTRUCT, A_PtrSize*3, 0)
   NumPut(size, COPYDATASTRUCT, A_PtrSize)
   NumPut(pData, COPYDATASTRUCT, A_PtrSize*2)
   while A_Index < 11 && !res := DllCall("SendMessage", "Ptr", hWnd, "UInt", WM_COPYDATA
                                                      , "Ptr", A_ScriptHwnd, "Ptr", &COPYDATASTRUCT)
      Sleep, 10
   Return res
}

class GDIplus
{
   __New() {
      this.hLib := DllCall("LoadLibrary", "Str", "gdiplus", "Ptr")
      VarSetCapacity(si, 8 + A_PtrSize*2, 0), si := Chr(1)
      DllCall("gdiplus\GdiplusStartup", "UPtrP", pToken, "Ptr", &si, "Ptr", 0)
      this.token := pToken
   }
   __Delete() {
      DllCall("gdiplus\GdiplusShutdown", "Ptr", this.token)
      DllCall("FreeLibrary", "Ptr", this.hLib)
   }
   CreateBitmapFromFile(sFile) {
      DllCall("gdiplus\GdipCreateBitmapFromFile", "WStr", sFile, "PtrP", pBitmap)
      Return pBitmap
   }
   GetImageDimensions(pBitmap, ByRef Width, ByRef Height) {
      DllCall("gdiplus\GdipGetImageWidth", "Ptr", pBitmap, "UIntP", Width)
      DllCall("gdiplus\GdipGetImageHeight", "Ptr", pBitmap, "UIntP", Height)
   }
   GetImagePixelFormat(pBitmap) {
      DllCall("gdiplus\GdipGetImagePixelFormat", "Ptr", pBitmap, "PtrP", Format)
      return Format
   }
   LockBits(pBitmap, x, y, w, h, ByRef Stride, ByRef Scan0, ByRef BitmapData, LockMode := 3, PixelFormat := 0x26200a) {
      VarSetCapacity(Rect, 16)
      NumPut(x, Rect, 0, "UInt"), NumPut(y, Rect, 4, "UInt"), NumPut(w, Rect, 8, "UInt"), NumPut(h, Rect, 12, "UInt")
      VarSetCapacity(BitmapData, 16+2*(A_PtrSize ? A_PtrSize : 4), 0)
      E := DllCall("Gdiplus\GdipBitmapLockBits", "Ptr", pBitmap, "Ptr", &Rect, "UInt", LockMode, "Int", PixelFormat, "Ptr", &BitmapData)
      Stride := NumGet(BitmapData, 8, "Int")
      Scan0 := NumGet(BitmapData, 16)
      return E
   }
   UnlockBits(pBitmap, ByRef BitmapData) {
      return DllCall("Gdiplus\GdipBitmapUnlockBits", "Ptr", pBitmap, "Ptr", &BitmapData)
   }
   DisposeImage(pBitmap) {
      return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}
The second:

Code: Select all

global ImageData := ""
OnMessage(0x004A, "WM_COPYDATA")
Gui, -DpiScale
Gui, Add, Pic ; for test

GuiClose() {
   ExitApp
}

WM_COPYDATA(wp, lp) {
   size := NumGet(lp + A_PtrSize, "UInt")
   pData := NumGet(lp + A_PtrSize*2)
   ; there should be no long running actions in this function
   ; we need to return true as soon as possible, so we just copy the data
   VarSetCapacity(ImageData, size, 0)
   DllCall("RtlCopyMemory", "Ptr", &ImageData, "Ptr", pData, "Ptr", size)
   SetTimer, CreateBitmapFromData, -10
   Return true
}

CreateBitmapFromData() {
   pixelFormat := NumGet(ImageData, "UInt")
   width := NumGet(ImageData, 4, "UInt")
   height := NumGet(ImageData, 8, "UInt")
   stride := NumGet(ImageData, 12, "UInt")
   
   GDIp := new GDIplus
   pBitmap := GDIp.CreateBitmapFromScan0(width, height, stride, pixelFormat, &ImageData + 16)
   hBitmap := GDIp.CreateHBITMAPFromBitmap(pBitmap)
   GDIp.DisposeImage(pBitmap)
   GDIp := ""
   VarSetCapacity(ImageData, 0), ImageData := ""

   GuiControl, Move, Static1, x0 y0 w%width% h%height%
   GuiControl,, Static1, HBITMAP: %hBitmap%
   Gui, Show, w%width% h%height%
}

class GDIplus
{
   __New() {
      this.hLib := DllCall("LoadLibrary", "Str", "gdiplus", "Ptr")
      VarSetCapacity(si, 8 + A_PtrSize*2, 0), si := Chr(1)
      DllCall("gdiplus\GdiplusStartup", "UPtrP", pToken, "Ptr", &si, "Ptr", 0)
      this.token := pToken
   }
   __Delete() {
      DllCall("gdiplus\GdiplusShutdown", "Ptr", this.token)
      DllCall("FreeLibrary", "Ptr", this.hLib)
   }
   CreateBitmapFromScan0(width, height, stride := 0, pixelFormat := 0x26200A, pData := 0) {
      DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", width, "Int", height, "Int", stride
                                                 , "Int", pixelFormat, "Ptr", pData, "PtrP", pBitmap)
      Return pBitmap
   }
   CreateHBITMAPFromBitmap(pBitmap, Background := 0xffffffff) {
      DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Ptr", pBitmap, "PtrP", hbm, "Int", Background)
      return hbm
   }
   DisposeImage(pBitmap) {
      return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}
Last edited by teadrinker on 23 May 2022, 11:02, edited 1 time in total.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 23 May 2022, 09:02

@teadrinker in your example the pBitmap has the pixel format of 32-ARGB
when the pBitmap has the pixel format of 0x22009 "32-RGB" Gdip.LockBits return a negative value in the stride
causing the script to fail

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 09:15

This is interesting. Can you share the file causing the issue?

iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: How to send a pBitmap between scripts?

Post by iseahound » 23 May 2022, 09:21

If you get negative stride (extremely rare, please share your file), just use gdipCloneImage or CloneImageArea to correct it.

Sharing Memory: https://docs.microsoft.com/en-us/windows/win32/memory/sharing-files-and-memory
CreateDIBSection accepts a file mapping object.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 23 May 2022, 09:45

It was not a file i was using the pBitmap returned by this function:

Code: Select all

Gdip_BitmapFromHWND(hwnd, clientOnly:=0) { 

   ; Restore the window if minimized! Must be visible for capture.
   If DllCall("IsIconic", "ptr", hwnd) 
      DllCall("ShowWindow", "ptr", hwnd, "int", 4) 

   Static Ptr := "UPtr" 
   thisFlag := 0 
   If (clientOnly=1) {
      VarSetCapacity(rc, 16, 0) 
      DllCall("GetClientRect", "ptr", hwnd, "ptr", &rc) 
      Width := NumGet(rc, 8, "int") 
      Height := NumGet(rc, 12, "int") 
      thisFlag := 1 
   } 
   Else 
      GetWindowRect(hwnd, Width, Height) 

   hbm := CreateDIBSection(Width, Height) 
   hdc := CreateCompatibleDC()
   obm := SelectObject(hdc, hbm) 
   PrintWindow(hwnd, hdc, 2 + thisFlag) 

   pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm) 
   DeleteObject(hbm), DeleteObject(hdc), DeleteObject(obm)
   
   return pBitmap

}
I was able to solve it by changing pBitmap := Gdip_CreateBitmapFromHBITMAP(hbm)
to pBitmap := put_pBitmap(hbm) function by iseahound, that creates a pBitmap from a hbm containing transparency
and it creates the bitmap with the pixel format 32-ARGB



How to bind the value of ImageData into the timer? then I can avoid setting ImageData as global
my attempt:

Code: Select all

   
WM_COPYDATA(wp, lp) {

   size := NumGet(lp + A_PtrSize, "UInt")
   pData := NumGet(lp + A_PtrSize*2)
   ; there should be no long running actions in this function
   ; we need to return true as soon as possible, so we just copy the data
   VarSetCapacity(ImageData, size, 0)
   DllCall("RtlCopyMemory", "Ptr", &ImageData, "Ptr", pData, "Ptr", size)
   
   Timer := Func("CreateBitmapFromData").Bind(ImageData)
   SetTimer, %Timer%, -10
   Return true

}

CreateBitmapFromData(ImageData) {
   pixelFormat := NumGet(ImageData, "UInt")
   width       := NumGet(ImageData, 4, "UInt")
   height      := NumGet(ImageData, 8, "UInt")
   stride      := NumGet(ImageData, 12, "UInt")
The timer calls the function CreateBitmapFromData however, the value of height and stride are blank
while pixelFormat and width are not blank :beard:

I also tried setting Static ImageData inside of the function WM_COPYDATA, but these values are still blank.

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 11:01

I found a typo in the previouse code of the second script, instead of

Code: Select all

      DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", width, "Int", height, "Int", stride
                                                 , "Int", pixelFormat, "Ptr", &ImageData + 16, "PtrP", pBitmap)
must be

Code: Select all

      DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", width, "Int", height, "Int", stride
                                                 , "Int", pixelFormat, "Ptr", pData, "PtrP", pBitmap)
c7aesa7r wrote: How to bind the value of ImageData into the timer?
You can use the process heap instead:

Code: Select all

OnMessage(0x004A, "WM_COPYDATA")
Gui, -DpiScale
Gui, Add, Pic ; for test

GuiClose() {
   ExitApp
}

WM_COPYDATA(wp, lp) {
   static flags := HEAP_ZERO_MEMORY := 0x00000008
        , hHeap := DllCall("GetProcessHeap", "Ptr")
   size := NumGet(lp + A_PtrSize, "UInt")
   pData := NumGet(lp + A_PtrSize*2)
   ; there should be no long running actions in this function
   ; we need to return true as soon as possible, so we just copy the data
   pHeap := DllCall("HeapAlloc", "Ptr", hHeap, "UInt", flags, "Ptr", size, "Ptr")
   DllCall("RtlCopyMemory", "Ptr", pHeap, "Ptr", pData, "Ptr", size)
   timer := Func("CreateBitmapFromData").Bind(hHeap, pHeap)
   SetTimer, % timer, -10
   Return true
}

CreateBitmapFromData(hHeap, pImageData) {
   pixelFormat := NumGet(pImageData + 0, "UInt")
   width := NumGet(pImageData + 4, "UInt")
   height := NumGet(pImageData + 8, "UInt")
   stride := NumGet(pImageData + 12, "UInt")
   
   GDIp := new GDIplus
   pBitmap := GDIp.CreateBitmapFromScan0(width, height, stride, pixelFormat, pImageData + 16)
   hBitmap := GDIp.CreateHBITMAPFromBitmap(pBitmap)
   GDIp.DisposeImage(pBitmap)
   GDIp := ""
   DllCall("HeapFree", "Ptr", hHeap, "UInt", 0, "Ptr", pImageData)

   GuiControl, Move, Static1, x0 y0 w%width% h%height%
   GuiControl,, Static1, HBITMAP: %hBitmap%
   Gui, Show, w%width% h%height%
}

class GDIplus
{
   __New() {
      this.hLib := DllCall("LoadLibrary", "Str", "gdiplus", "Ptr")
      VarSetCapacity(si, 8 + A_PtrSize*2, 0), si := Chr(1)
      DllCall("gdiplus\GdiplusStartup", "UPtrP", pToken, "Ptr", &si, "Ptr", 0)
      this.token := pToken
   }
   __Delete() {
      DllCall("gdiplus\GdiplusShutdown", "Ptr", this.token)
      DllCall("FreeLibrary", "Ptr", this.hLib)
   }
   CreateBitmapFromScan0(width, height, stride := 0, pixelFormat := 0x26200A, pData := 0) {
      DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", width, "Int", height, "Int", stride
                                                 , "Int", pixelFormat, "Ptr", pData, "PtrP", pBitmap)
      Return pBitmap
   }
   CreateHBITMAPFromBitmap(pBitmap, Background := 0xffffffff) {
      DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Ptr", pBitmap, "PtrP", hbm, "Int", Background)
      return hbm
   }
   DisposeImage(pBitmap) {
      return DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)
   }
}
Last edited by teadrinker on 23 May 2022, 11:14, edited 1 time in total.

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 11:07

iseahound wrote: Sharing Memory
Do you think this way is preferable?

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 23 May 2022, 11:08

Thank you teadrinker!
I'm getting some huge values inside of CreateBitmap pixelFormat, width, height, stride that doesn't correspond to the bitmap being sent
does it work on your side?




I have multiple scripts sending at the same time wm_copydata to a main script that works like a panel where I can see data info
of all these scripts

There's the chance to the heap be overwritten before the function called with the SetTimer read it?
Last edited by c7aesa7r on 23 May 2022, 11:20, edited 1 time in total.

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 11:20

Edited a bit:

Code: Select all

pHeap := DllCall("HeapAlloc", "Ptr", hHeap, "UInt", flags, "Ptr", size, "Ptr")
instead of

Code: Select all

pHeap := DllCall("HeapAlloc", "Ptr", hHeap, "UInt", flags, "Ptr", size)
c7aesa7r wrote: There's the chance to the heap be overwritten before the function called with the SetTimer read it?
I don't think that this is possible. When HeapAlloc is called, it allocates new free memory area every time, and when HeapFree is called, it frees only that memory area.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 23 May 2022, 11:31

teadrinker wrote:
23 May 2022, 11:20
Edited a bit:

Code: Select all

pHeap := DllCall("HeapAlloc", "Ptr", hHeap, "UInt", flags, "Ptr", size, "Ptr")
Still getting some high values in the parameters I mentioned before

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 11:34

c7aesa7r wrote: I'm getting some huge values inside of CreateBitmap pixelFormat, width, height, stride that doesn't correspond to the bitmap being sent
does it work on your side?
I haven't experienced this kind of issue. Show your code.

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 23 May 2022, 11:46

I use to handle all window messages together inside of a class
I confirmed that this issue happens if i have the function inside of a class

Code: Select all

OnMessage(0x4a,  "onMessage.WM_COPYDATA")

;Global ImageData

Gui, Add, Picture, x0 y0
Return


Class onMessage {
   WM_COPYDATA(wp, lp) {

      static flags := HEAP_ZERO_MEMORY := 0x00000008
         , hHeap := DllCall("GetProcessHeap", "Ptr")

      size  := NumGet(lp + A_PtrSize, "UInt")
      pData := NumGet(lp + A_PtrSize*2)
      ; there should be no long running actions in this function
      ; we need to return true as soon as possible, so we just copy the data
      pHeap := DllCall("HeapAlloc", "Ptr", hHeap, "UInt", flags, "UPtr", size, "Ptr")
      DllCall("RtlCopyMemory", "Ptr", pHeap, "Ptr", pData, "Ptr", size)
      
      timer := Func("CreateBitmapFromData").Bind(hHeap, pHeap)
      SetTimer, % timer, -10
      Return true

   }
}


CreateBitmapFromData(hHeap, pImageData) {
   pixelFormat := NumGet(pImageData+0, "UInt")
   width       := NumGet(pImageData+4, "UInt")
   height      := NumGet(pImageData+8, "UInt")
   stride      := NumGet(pImageData+12,"UInt")
   
   DllCall("gdiplus\GdipCreateBitmapFromScan0", "Int", width, "Int", height, "Int", stride, "Int", pixelFormat, "Ptr", pImageData + 16, "PtrP", pBitmap)

   DllCall("gdiplus\GdipCreateHBITMAPFromBitmap", "Ptr", pBitmap, "PtrP", hbm, "Int", 0xffffffff)

   DllCall("gdiplus\GdipDisposeImage", "Ptr", pBitmap)

   DllCall("HeapFree", "Ptr", hHeap, "UInt", 0, "Ptr", pImageData)

   GuiControl, Move, Static1, x0 y0 w%width% h%height%
   GuiControl,, Static1, HBITMAP: %hbm%
   Gui, Show, w%width% h%height%
}


teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 11:51

I wouldn't do like this:

Code: Select all

OnMessage(0x004A, "onMessage.WM_COPYDATA")
This is not quite correct.
However, in this case the class code should be

Code: Select all

Class onMessage {
   WM_COPYDATA(lp) { ; without wp param

teadrinker
Posts: 4325
Joined: 29 Mar 2015, 09:41
Contact:

Re: How to send a pBitmap between scripts?

Post by teadrinker » 23 May 2022, 11:54

The correct way: OnMessage(0x004A, ObjBindMethod(onMessage, "WM_COPYDATA"))

c7aesa7r
Posts: 209
Joined: 02 Jun 2016, 21:09

Re: How to send a pBitmap between scripts?

Post by c7aesa7r » 23 May 2022, 12:03

Now it working correctly :thumbup:

I'm sending some other data together with the bitmaps, for this I'm converting the data to JSON
Is possible somehow to include the pData into the JSON string?

Post Reply

Return to “Ask for Help (v1)”