Page 1 of 1

Insert Image to EditorGadget Using TOM [Windows]

Posted: Mon Sep 02, 2024 2:46 pm
by Zapman

Code: Select all

;************************************************************************
;
;                 Insert an image into an EditorGadget
;               No clipboard, No query file in the disk
;                         Using a TOM method
;

;************************************************************************
;     This code creates its own iDataObject interface in order to
;        make a call to the function: OleCreateStaticFromData
;************************************************************************
;
Procedure.s GetWinErrorMessage(errorCode)
  Protected messageBuffer.s = Space(256) ; Buffer pour le message
  Protected messageLength

  messageLength = FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM | #FORMAT_MESSAGE_IGNORE_INSERTS, #Null, errorCode, 0, @messageBuffer, 256, #Null)
  
  If messageLength > 0
    ; Supprime les retours à la ligne à la fin du message
    messageBuffer = ReplaceString(messageBuffer, Chr(10), "")
    messageBuffer = ReplaceString(messageBuffer, Chr(13), "")
    messageBuffer = Trim(messageBuffer)
  Else
    messageBuffer = "Unknown error code:"
  EndIf

  ProcedureReturn messageBuffer + "  ($" + Hex(errorCode) + ")"
EndProcedure
;
Procedure.l TOM_InsertFromImage(Image, Rich_Edit_Hdl, PosInText)
  ; 'Rich_Edit_Hdl' peut être un numéro de Gadget ou un handle (pointeur) vers un RicheEdit Control.
  ; 'Image' peut être un numéro d'image PureBasic ou un handle vers celle-ci.
  ; 
  #STGM_SHARE_EXCLUSIVE=$00000010
  #STGM_READWRITE = $00000002
  #STGM_CREATE = $00001000
  #REO_CP_SELECTION=$FFFFFFFF
  #REO_RESIZABLE=1
  #REO_BELOWBASELINE=2
  #OLERENDER_FORMAT=2
  ;
  Protected RichEditOleObject.IRichEditOle
  Protected       lpLockBytes.ILockBytes
  Protected         lpStorage.IStorage
  Protected      lpClientSite.IOleClientSite
  Protected       lpOleObject.IOleObject
  Protected      *pDataObject.IDataObject
  Protected                sc
  
  Protected              hBitmap
  Protected    *pTextDocument.ITextDocument
  Protected       *pTextRange.ITextRange
  Protected               var.VARIANT
  ;
  Structure iData
    *pIntf.IDataobject
    m_stgmed.STGMEDIUM
    m_format.FORMATETC
  EndStructure
  ;
  Protected          mDataObj.idata
  ;
  If IsImage(Image)
    Image = ImageID(Image)
  EndIf
  ;
  If IsGadget(Rich_Edit_Hdl)
    Rich_Edit_Hdl = GadgetID(Rich_Edit_Hdl)
  EndIf
  ;
  hBitmap = CopyImage_(Image, #IMAGE_BITMAP, 0, 0, #LR_COPYRETURNORG)
  If hBitmap
    ;
    ; Get the RichOLEInterface on our EditorGadget:
    SendMessage_(Rich_Edit_Hdl, #EM_GETOLEINTERFACE, 0, @RichEditOleObject)
   ;
    If RichEditOleObject
      ; We are going to create an IDataObject that will contain the image data.
      ; Creating such an object is quite complex, as it is an interface object
      ; that must be provided with a collection of methods to read data, return
      ; data, provide the list of contained objects, etc.
      ;
      ; The IDataObject we are going to create will implement only one of these methods: GetData
      ; as it is the only one necessary to call OleCreateStaticFromData_
      ;
      ; The method table for our IDataObject, VTable_IDataObject, is at the end of this file.
      ; If you look at it, you will see that it only points to 'dataobject_GetData()'
      ; or to a dummy method I named 'NotImplemented', which is used
      ; for all the non-implemented methods.
      ;
      ; If our IDataObject were complete, it would include the following methods:

      ;   dataobject_AddRef(*dataobject.iData)
      ;   dataobject_Release(*dataobject.iData)
      ;   dataobject_QueryInterface(*dataobject.IDataobject, iid, *ppvObject.Integer)
      ;   dataobject_GetDataHere(*dataobject, *pformatetc, *pmedium)
      ;   dataobject_QueryGetData(*dataobject, *pformatetc)
      ;   dataobject_GetCanonicalFormatEtc(*dataobject, *pformatectIn ,*pformatetcOut)
      ;   dataobject_SetData(*dataobject.iData, *pformatetc.FORMATETC , *pmedium.STGMEDIUM , fRelease)
      ;   dataobject_EnumFormatEtc(*dataobject, dwDirection , *ppenumFormatEtc)
      ;   dataobject_DAdvise(*dataobject,*pformatetc, advf, *pAdvSink, *pdwConnection)
      ;   dataobject_DUnadvise(*dataobject, dwConnection)
      ;   dataobject_EnumDAdvise(*dataobject, *ppenumAdvise)
      ;
      ; It would take several hundred lines of code to implement all of that!
      ;
      ; Instead, we will take a shortcut by asking OleCreateStaticFromData_ to do the most of
      ; the work for us :)
      ;
      ; First create our makeshift IDataObject:
      ;
      mDataObj\pIntf = ?VTable_IDataObject
      ;
      mDataObj\m_stgmed\tymed = #TYMED_GDI        ;  Storage medium must be set to #TYMED_GDI for #CF_BITMAP
                                                      ;    and to #TYMED_HGLOBAL for all other formats.
                                                      ;  m_stgmed and m_format MUST have the same value
                                                      ;    in their respective 'tymed' fields!
      
      mDataObj\m_stgmed\hBitmap = hBitmap             ;  Data handle
                                                      ;  You should have mDataObj\m_stgmed\hGlobal = hData for other data than BITMAP
      
      mDataObj\m_stgmed\pUnkForRelease = #Null        ;  Use ReleaseStgMedium
  
      mDataObj\m_format\cfFormat = #CF_BITMAP         ;  An IDataObject can have values like #CF_DIB,
                                                      ;   #CF_BITMAP, #CF_TEXT, or #CF_RTF, among others.

      mDataObj\m_format\ptd = #Null                   ;  Target Device = Screen
      mDataObj\m_format\dwAspect = #DVASPECT_CONTENT  ;  Level of detail = Full content
      mDataObj\m_format\lindex = -1                   ;  Index = Not applicaple
      mDataObj\m_format\tymed = #TYMED_GDI            ;  Storage medium must be set to #TYMED_GDI for #CF_BITMAP
      ;
      ; Then, we prepare what is necessary for a call to OleCreateStaticFromData
      ; Create an ILockBytes to be able to obtain an IStorage:
      sc = CreateILockBytesOnHGlobal_(#Null, #True, @lpLockBytes)
      If sc = #S_OK
        ; Creat an empty IStorage:
        sc = StgCreateDocfileOnILockBytes_(lpLockBytes, #STGM_SHARE_EXCLUSIVE | #STGM_CREATE | #STGM_READWRITE, 0, @lpStorage)
        ; Free ILockBytes. We don't need it anymore:
        lpLockBytes\Release()
        If sc = #S_OK
          ;
          ; Create an IOleClientSite:
          sc = RichEditOleObject\GetClientSite(@lpClientSite)
          If sc <> #S_OK
            Debug "Unable to create IOleClientSite from the RichEditOleObject. Error: " + GetWinErrorMessage(SC)
            lpStorage\Release()
          Else
            sc = OleCreateStaticFromData_(mDataObj, ?IID_IOleObject, #OLERENDER_FORMAT, mDataObj\m_format, lpClientSite, lpStorage, @lpOleObject)
            If sc <> #S_OK
              Debug "Error OleCreateStaticFromData_: " + GetWinErrorMessage(SC)
              lpStorage\Release()
              lpClientSite\Release()
            Else
              ; From our makeshift IDataObject, OleCreateStaticFromData_()
              ; has just created an lpOleObject.
              ; This powerful object can itself create an IDataObject
              ; from the information we provided to it:
              SC = lpOleObject\QueryInterface(?IID_IDataObject, @*pDataObject.IDataObject)
              If SC <> #S_OK
                Debug "Unable to create IDataObject. Error: " + GetWinErrorMessage(SC)
                lpOleObject\Release()
                lpStorage\Release()
                lpClientSite\Release()
              Else
                ; Here we have a fully functional IDataObject 
                ; (with all the possible methods, or nearly so).
                ; Other thank to OleCreateStaticFromData_() : the Bitmap we've given
                ; to our makeshift IDataObject had been automatically converted into
                ; DIB format wich is necessary to the ITextRange\Paste() method!
                ;
                ; Final step: we need to create a TOM interface on our RichEdit
                SC = RichEditOleObject\QueryInterface(?IID_ITextDocument, @*pTextDocument.ITextDocument)
                If SC <>  #S_OK
                  Debug "Unable to create ITextDocument: " + GetWinErrorMessage(SC)
                  *pDataObject\Release()
                Else
                  ;
                  ; Get the text range:
                  SC = *pTextDocument\Range(PosInText, PosInText, @*pTextRange.ITextRange)
                  *pTextDocument\Release()
                  If SC <> #S_OK
                    Debug "Unable to get the ITextRange. Error : " + GetWinErrorMessage(SC)
                  Else
                    ; Create a VARIANT with a pointer to our new and complete IDataObject
                    var.VARIANT
                    var\vt = #VT_UNKNOWN ; | #VT_BYREF
                    var\ppunkVal = *pDataObject
                    ;
                    ; Paste our IDataObject into the range:
                    ; Clipboard in NOT used. The paste operation is done from our IDataobject.
                    SC = *pTextRange\Paste(@var, #CF_DIB)
                    If sc <> #S_OK
                      Debug "Range does'nt accept Data : " + GetWinErrorMessage(SC)
                    EndIf
                    *pTextRange\Release()
                  EndIf
                  *pDataObject\Release() ; Destroy our beautifull object (snif!)
                EndIf
                ;
                ; Cleanup :
                lpOleObject\Release()
                lpStorage\Release()
                lpClientSite\Release()
              EndIf
            EndIf
          EndIf
        EndIf
      EndIf
      ;
      RichEditOleObject\Release()
    EndIf
    ;
    DeleteObject_(hBitmap)
    ;
  EndIf
  ;
  ProcedureReturn SC
EndProcedure
; 
Procedure dataobject_GetData(*dataobject.iData, *pformatetcIn.FORMATETC, *pmedium.STGMEDIUM)
  ; This procedure is essential for the operation of the previous one.
  ; It implements the 'GetData' method for the IDataObject we create in this program.
  ;
  ; Its role is to fill the '*pmedium.STGMEDIUM' structure passed as a parameter
  ; with the data from *dataobject\m_stgmed (the data of our image).
  ; The hbitmap or hGlobal content of *dataobject\m_stgmed is duplicated, and a pointer to this
  ; duplicated content is placed in *pmedium.STGMEDIUM.
  ;
  ; GetData will work only if the requested data format (*pformatetcIn\cfFormat)
  ; matches the one in our IDataObject, which is either: #CF_BITMAP or #CF_DIB.
  ;
  Protected hbitmap
  If *pformatetcIn\tymed = *dataobject\m_format\tymed
    If *pformatetcIn\cfFormat = *dataobject\m_format\cfFormat
      ; The format requested by the procedure is indeed the one we have in our data.
      *pmedium\tymed = *dataobject\m_stgmed\tymed
      *pmedium\pUnkForRelease = *dataobject\m_stgmed\pUnkForRelease
      If *dataobject\m_format\cfFormat = #CF_BITMAP And *dataobject\m_stgmed\tymed = #TYMED_GDI
        ;Debug "Format Bitmap asked"
        hbitmap = OleDuplicateData_(*dataobject\m_stgmed\hBitmap, *dataobject\m_format\cfFormat, #Null)
        *pmedium\hBitmap = hbitmap
        ProcedureReturn #S_OK
      ElseIf *dataobject\m_stgmed\tymed = #TYMED_HGLOBAL
        ;Debug "Format Dib asked"
        hGlobal = OleDuplicateData_(*dataobject\m_stgmed\hGlobal, *dataobject\m_format\cfFormat, #Null)
        *pmedium\hGlobal = hGlobal
        ProcedureReturn #S_OK
      Else
        *pmedium\hGlobal = 0
        ProcedureReturn #DV_E_FORMATETC
      EndIf
    EndIf
  EndIf
  ProcedureReturn #DV_E_FORMATETC
EndProcedure
;
Procedure NotImplemented(a, b, c = 0, d = 0, e = 0)
  Debug "This procedure shoudn't be called."
  ProcedureReturn #E_NOTIMPL
EndProcedure
;
DataSection
  VTable_IDataObject:
    Data.i @NotImplemented(), @NotImplemented(), @NotImplemented()
    Data.i @dataobject_GetData(),@NotImplemented(),@NotImplemented()
    Data.i @NotImplemented(),@NotImplemented(),@NotImplemented()
    Data.i @NotImplemented(),@NotImplemented(),@NotImplemented()
  
  IID_IOleObject:   ;"{00000112-0000-0000-C000-000000000046}"
    Data.l $00000112
    Data.w $0000,$0000
    Data.b $C0,$00,$00,$00,$00,$00,$00,$46
    
  IID_IDataObject:
    Data.l $0000010e
    Data.w $0000, $0000
    Data.b $C0, $00, $00, $00, $00, $00, $00, $46
    
  IID_ITextDocument:
    Data.l $8CC497C0
    Data.w $A1DF, $11CE
    Data.b $80, $98, $00, $AA, $00, $47, $BE, $5D

EndDataSection

; ****************************************************************************
;
;                                   DEMO
;
; ****************************************************************************
;
If OpenWindow(0, 0, 0, 700, 500, "RichEdit", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
  ;
  EGadget = EditorGadget(#PB_Any, 10, 10, WindowWidth(0)-20, WindowHeight(0)-20)
  ; EditorGadget MUST be set to #TM_RICHTEXT in order to allow TOM ITextRange to work.
  SendMessage_(GadgetID(EGadget), #EM_SETTEXTMODE, #TM_RICHTEXT, 0) ; Absolutely necessary!
  SetGadgetText(EGadget,"essai")
  ;
  Filters$ = "Bitmap|*.bmp|JPeg|*.jpg;*.jpeg" 
  Filter  = 1 
  Fichier$ = OpenFileRequester("Choose an image file", "", Filters$, Filter)
  If Fichier$
    UseJPEGImageDecoder()
    MImage = LoadImage(#PB_Any, Fichier$)
    
    If MImage
      TOM_InsertFromImage(MImage, EGadget,0)
    Else
      Debug "Image isn't loaded"
    EndIf

  EndIf
  Repeat
    Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf
End

; ADDITIONAL COMMENT: Another way of using TOM to insert an image into an EditorGadget
; is to call the ITEXTRANGE\InsertImage() method. However, this approach has a significant
; drawback: ITEXTRANGE\InsertImage() inserts the image from an IStream, which must
; be kept active as long as the EditorGadget is in use. This requires cleanup only at the
; end of the program (after closing the EditorGadget or removing the image). However, we all
; know how easy it is to forget such things, and the trouble of leaving resources active due
; to negligence. If you still prefer to use the IStream method, here's how to proceed:
;
;
; Import "gdiplus.lib"
;   GdiplusStartup(*token, *input, *output)
;   GdiplusShutdown(token)
;   GdipCreateBitmapFromStream(pStream.IStream, *GdiPImage)
;   GdipCreateBitmapFromFile(FileName.p-bstr, *GdiPImage)
;   GdipGetImageWidth(GdiPImage, *Width) ; *width is a pointer to LONG (.l)
;   GdipGetImageHeight(GdiPImage, *Height) ; *Height is a pointer to LONG (.l)
;   GdipDisposeImage(GdiPImage)
; EndImport
; ;
; Structure GdiplusStartupInput
;   GdiplusVersion.l
;   DebugEventCallback.l
;   SuppressBackgroundThread.l
;   SuppressExternalCodecs.l
; EndStructure
; ;
; Procedure GdiP_Init()
;   Shared GdiplusToken
;   Protected GdiplusStartupInput.GdiplusStartupInput
;   
;   If GdiplusToken = 0
;     GdiplusStartupInput\GdiplusVersion = 1
;     
;     SC = GdiplusStartup(@GdiplusToken, @GdiplusStartupInput, #Null)
;     If SC <> 0
;       Debug "Erreur GDI : "+Str(SC)
;       ProcedureReturn 0
;     Else
;       ProcedureReturn GdiplusToken
;     EndIf
;   Else
;     ProcedureReturn GdiplusToken
;   EndIf
; EndProcedure
; 
; Procedure GdiP_Shutdown()
;   Shared GdiplusToken
;   GdiplusShutdown(GdiplusToken)
;   GdiplusToken = 0
; EndProcedure
; 
; Structure StreamAndGlobal
;   pIStream.IStream
;   GlobalMemory.i
; EndStructure
; 
; Procedure ReleaseStreamData(*StreamData.StreamAndGlobal)
;   If *StreamData\pIStream
;     *StreamData\pIStream\Release()
;     *StreamData\pIStream = 0
;   EndIf
;   If *StreamData\GlobalMemory
;     GlobalFree_(*StreamData\GlobalMemory)
;     *StreamData\GlobalMemory = 0
;   EndIf
;   GdiP_Shutdown()
; EndProcedure
; 
; 
; Procedure InsertImageFromOLEStream(Gadget, StartPos, EndPos, Fichier.s, *StreamData.StreamAndGlobal)
;   Protected RichEditOleObject.IRichEditOle
;   Protected *pTextDocument.ITextDocument
;   Protected *pTextRange.ITextRange2
;   Protected ImageSize, *pGlobalMemory
;   Protected ImageWidth.l, ImageHeight.l, Bitmap.i
;   Protected hFile, type, ascent
;   Protected SC
;   ;
;   If Gadget = 0 Or Fichier = "" Or *StreamData = 0
;     Debug "Erreur dans les paramètres."
;     ProcedureReturn #False
;   EndIf
;   ;
;   If IsGadget(Gadget)
;     Gadget = GadgetID(Gadget)
;   EndIf
;   ;
;   SendMessage_(Gadget, #EM_GETOLEINTERFACE, 0, @RichEditOleObject)
;   ;
;   If RichEditOleObject = 0
;     Debug "Erreur lors de la récupération de l'interface RichEdit OLE."
;   Else
;     SC = RichEditOleObject\QueryInterface(?IID_ITextDocument2, @*pTextDocument)
;     If SC <>  #S_OK
;       Debug "Erreur lors de la récupération de l'interface ITextDocument2 : " + Str(SC)
;     Else
;       ; Il n'est pas utile de conserver RichEditOleObject une fois ITextDocument2 obtenue.
;       RichEditOleObject\Release()
;       ;
;       ; Récupérer l'image du fichier dans un 'GlobalMemory' :
;       ;
;       hFile = ReadFile(#PB_Any, Fichier)
; 
;       If hFile = 0
;         Debug "L'ouverture du fichier a échoué."
;         *pTextDocument\Release()
;         ProcedureReturn #False
;       Else
;         ImageSize = Lof(hFile)
;         *StreamData\GlobalMemory = GlobalAlloc_(#GMEM_MOVEABLE, ImageSize)
;         *pGlobalMemory = GlobalLock_(*StreamData\GlobalMemory)
;         
;         If *pGlobalMemory = 0
;           Debug "GlobalAlloc_ ou GlobalLock_ a échoué."
;           ReleaseStreamData(*StreamData)
;           *pTextDocument\Release()
;           ProcedureReturn #False
;         Else
;           ; Lire directement le fichier dans la mémoire allouée
;           ReadData(hFile, *pGlobalMemory, ImageSize)
;           CloseFile(hFile)
;           GlobalUnlock_(*StreamData\GlobalMemory)
;           ;
;           ; Créer le flux à partir de la mémoire globale
;           If CreateStreamOnHGlobal_(*StreamData\GlobalMemory, #False, @*StreamData\pIStream) <> #S_OK
;             Debug "Erreur lors de la création du IStream."
;             ReleaseStreamData(*StreamData)
;             *pTextDocument\Release()
;             ProcedureReturn #False
;           EndIf
;         EndIf
;       EndIf
;       ;           
;       ; Déplacer le curseur du flux au début
;       *StreamData\pIStream\Seek(0, #STREAM_SEEK_SET, #Null)
;       ; Obtenir les dimensions du Bitmap contenu dans le flux :
;       GdiP_Init()
;       If GdipCreateBitmapFromStream(*StreamData\pIStream, @Bitmap) = #S_OK
;         GdipGetImageWidth(Bitmap,  @ImageWidth.l)
;         GdipGetImageHeight(Bitmap, @ImageHeight.l)
;         GdipDisposeImage(Bitmap)
;         GdiP_Shutdown()
;       Else
;         Debug "Erreur GDI"
;         ReleaseStreamData(*StreamData)
;         *pTextDocument\Release()
;         ProcedureReturn #False
;       EndIf
;       ;
;       ; Convert dimensions to metrics :
;       ImageWidth * 2540 / 96
;       ImageHeight * 2540 / 96
;       ;
;       ; Obtenir la plage de texte actuelle
;       SC = *pTextDocument\Range(StartPos, EndPos, @*pTextRange)
;       *pTextDocument\Release()
;       If SC <> #S_OK
;         Debug "Impossible d'obtenir le TextRange."
;         ReleaseStreamData(*StreamData)
;         ProcedureReturn #False
;       Else
;         ; Insérer l'image
;         ;
;         ; On rembobine le Stream, pour pouvoir le lire à nouveau
;         *StreamData\pIStream\Seek(0, #STREAM_SEEK_SET, #Null)
;         ;
;         #TA_BASELINE = 24
;         #TA_BOTTOM = 8
;         #TA_TOP = 0
;         ; Comme le spécifie la documentation, ascent ne change quelque chose que lorsque type = #TA_BASELINE
;         type = #TA_TOP
;         ascent = 0
;         If *pTextRange\InsertImage(ImageWidth, ImageHeight, ascent, type, "Image", *StreamData\pIStream) <> #S_OK
;           Debug "Erreur lors de l'insertion de l'image."
;         EndIf
;         *pTextRange\Release()
;       EndIf
;       ;
;       ; ReleaseStreamData(*StreamData)
;       ; La ligne précédente a été mise en commentaire car le RichEdit (Editorgadget)
;       ; a absolument besoin que le IStream reste ouvert, faute de quoi l'image
;       ; disparaît dans le contenu (on a unectangle tout blanc à la place de l'image).
;       ; Il faudra donc penser à faire  ReleaseStreamData(StreamData)
;       ; à la fin du programme qui appelle cette procédure (une fois le Editorgadget fermé).
;       ;
;     EndIf
;   EndIf
;   ProcedureReturn #True
; EndProcedure
; 
;
; 
; EGadget = EditorGadget(#PB_Any, 10, 10, WindowWidth(0)-20, WindowHeight(0)-20)
;
; Define StreamData.StreamAndGlobal
; InsertImageFromOLEStream(EGadget, 0, 0, Fichier$, StreamData)
; .........
;       Use your Editorgadget Here
;       Then close your window
; .........
; ReleaseStreamData(StreamData)

  

Re: Insert Image to EditorGadget Using TOM

Posted: Mon Sep 02, 2024 6:33 pm
by RASHAD
Tested it
Looks nice
Thanks for sharing :)

Re: Insert Image to EditorGadget Using TOM

Posted: Mon Sep 02, 2024 7:52 pm
by Zapman
Thanks for the test, Rashad.
I found a way to avoid creating the Dib. The code from the first post is updated. It is a little simpler than the previous version. Now, the data included in the first makeshift IDataObject is hBitmap.

Re: Insert Image to EditorGadget Using TOM

Posted: Wed Sep 25, 2024 8:25 pm
by Kwai chang caine
Works great, thanks for sharing 8)

Re: Insert Image to EditorGadget Using TOM

Posted: Tue Oct 29, 2024 2:17 pm
by Zapman
For a complete set of functions based on TOM (Text Object Model), you can download a library from this address: https://www.editions-humanis.com/downlo ... ads_EN.htm

A post describing this library is published here:
viewtopic.php?t=85162