Convert any bitmap to an RTF (rich text) string

Share your advanced PureBasic knowledge/code with the community.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Convert any bitmap to an RTF (rich text) string

Post by srod »

Update : 18th Jan 2009.

Problems with Wordpad recognising rtf with an embedded EMF (no problems with MS Word) have led me to switch to a basic Window's MetaFile (not an enhanced one). I have also removed all CRLF's from the rtf (for easier streaming) and have sped the code up no end!

The program now runs much much much... faster! :)

===================================


Hi,

the following will take any bitmap handle and convert the underlying image to an RTF string suitable for saving as an rtf file or for streaming into an OLE enabled EditorGadget (see http://www.purebasic.fr/english/viewtop ... tor+gadget)

This code can easily be adapted to convert any EMF (Enhanced MetaFile) into an RTF string.

Code: Select all

;'BMP2RTF'
;Stephen Rodriguez.
;
;Windows only.
;
;Convert a bitmap to an RTF string. This string can be saved to an rtf file or streamed into an OLE enabled
;editor gadget (see http://www.purebasic.fr/english/viewtopic.php?t=20691&highlight=images+editor+gadget).
;
;***********************************************************************************************

EnableExplicit

;Returns an empty string if an error.
Procedure.s BMP2RTF(hBmap)
  Protected rtf$, bitmap.BITMAP, screenDC, hdcMF, hMF, hdc
  Protected oldImage, widthTwips, heightTwips, numBytes, mem, *bytes.BYTE, *string.WORD, i, low, high
  If GetObject_(hBmap, SizeOf(BITMAP), bitmap)
    If bitmap\bmWidth And bitmap\bmHeight
      screenDC = GetDC_(0)
      ;Create an EMF to hold the bitmap.
        hdcMF = CreateMetaFile_(0)
      If hdcMF
        SetMapMode_(hdcMF, #MM_ANISOTROPIC)
        SetWindowOrgEx_(hdcMF, 0, 0, 0)
        SetWindowExtEx_(hdcMF, bitmap\bmWidth, bitmap\bmHeight, 0)
        hdc = CreateCompatibleDC_(screenDC)
        If hdc
          oldImage = SelectObject_(hdc, hBmap)
          BitBlt_(hdcMF, 0, 0, bitmap\bmWidth, bitmap\bmHeight, hdc, 0, 0, #SRCCOPY)
          SelectObject_(hdc, oldImage)
          DeleteDC_(hdc)
          hMF = CloseMetaFile_(hdcMF)
          If hMF
            ;Before creating the RTF header we need to calculate the image width/height in twips.
              widthTwips = MulDiv_(bitmap\bmWidth,1440,GetDeviceCaps_(screenDC, #LOGPIXELSX))
              heightTwips = MulDiv_(bitmap\bmHeight,1440,GetDeviceCaps_(screenDC, #LOGPIXELSY))
            ;Now the rtf header.
              rtf$ = "{\rtf1{\pict\wmetafile8\picw" + Str(bitmap\bmWidth) + "\pich" + Str(bitmap\bmHeight) + "\picwgoal" + Str(widthTwips) + "\pichgoal" + Str(heightTwips) + " "
            ;Add the MF bits as double-character hex.
            ;First retrieve the MF bits.
              numBytes = GetMetaFileBitsEx_(hMF, 0, 0)
            mem = AllocateMemory(numBytes*3)
            If mem
              If GetMetaFileBitsEx_(hMF, numBytes, mem) = numBytes
                *bytes=mem : *string = mem + numBytes
                For i = 0 To numBytes-1
                  low = (*bytes\b)&$f + '0': high = (*bytes\b)>>4&$f + '0'
                  If low > '9'
                    low + 7
                  EndIf
                  If high > '9'
                    high + 7
                  EndIf
                  *string\w = low<<8 + high
                  *string + 2
                  *bytes + 1
                Next
                rtf$ + PeekS(mem + numBytes, numBytes<<1, #PB_Ascii) + "}}"
              Else
                rtf$ = ""
              EndIf
              FreeMemory(mem)
            Else
              rtf$ = ""
            EndIf
            DeleteMetaFile_(hMF)
          EndIf      
        Else
          hMF = CloseEnhMetaFile_(hdcMF)
          DeleteEnhMetaFile_(hMF)
        EndIf
      EndIf
      ReleaseDC_(0, screenDC)
    EndIf
  EndIf
  ProcedureReturn rtf$
EndProcedure

DisableExplicit
A small test to create an rtf file containing a given bitmap :

Code: Select all

If LoadImage(1, "test.bmp")  ;Use your own bitmap here.
  rtf$ = BMP2RTF(ImageID(1))
  ;Save as rtf file (or you could stream this into an OLE enabled Editor gadget).
  If CreateFile(1, "test.rtf")
    WriteString(1, rtf$)
    CloseFile(1)
    MessageRequester("BMP2RTF", Chr(34) + "test.rtf" + Chr(34) + " created successfully!")
  EndIf
EndIf
Last edited by srod on Sun Jan 18, 2009 1:26 pm, edited 2 times in total.
I may look like a mule, but I'm not a complete ass.
User avatar
idle
Always Here
Always Here
Posts: 5098
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Post by idle »

Nice one.
User avatar
Hroudtwolf
Addict
Addict
Posts: 803
Joined: Sat Feb 12, 2005 3:35 am
Location: Germany(Hessen)
Contact:

Post by Hroudtwolf »

Thanks for sharing.
Psych
Enthusiast
Enthusiast
Posts: 228
Joined: Thu Dec 18, 2008 3:35 pm
Location: Wales, UK

Post by Psych »

From now on srod, I come to you for everything, LOL!!
----------------------------------------------------------------------------
Commenting your own code is admitting you don't understand it.
----------------------------------------------------------------------------
User avatar
Blue
Addict
Addict
Posts: 886
Joined: Fri Oct 06, 2006 4:41 am
Location: Canada

Post by Blue »

Interesting initiative...
but, using your code (function + example + sample bitmap pic) as is, all i get is an empty RTF file.

Tracing the code shows that the procedure goes through all its paces correctly.
However, comparing the resulting file with a similar RTF produced by including the pic as a paste operation shows that your output file looks nothing like the other one.


PS: I'm under Vista-32... in case it's relevant.
Last edited by Blue on Sun Jan 18, 2009 2:47 am, edited 1 time in total.
"That's not a bug..." said the programmer. "it's a feature! "
"Oh! I see..." replied the blind man.
User avatar
ts-soft
Always Here
Always Here
Posts: 5756
Joined: Thu Jun 24, 2004 2:44 pm
Location: Berlin - Germany

Post by ts-soft »

Is a bit slow but works fine under vista 64

Thanks for sharing :D
PureBasic 5.73 | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Old bugs good, new bugs bad! Updates are evil: might fix old bugs and introduce no new ones.
Image
Psych
Enthusiast
Enthusiast
Posts: 228
Joined: Thu Dec 18, 2008 3:35 pm
Location: Wales, UK

Post by Psych »

My editor says I dont have the #PB_Byte constant that is used with the hex function.
----------------------------------------------------------------------------
Commenting your own code is admitting you don't understand it.
----------------------------------------------------------------------------
SCRJ
User
User
Posts: 93
Joined: Sun Jan 15, 2006 1:36 pm

Post by SCRJ »

Hi

Nice idea, thanks for sharing!

But as ts-soft said, this procedure is really, really slow.
It takes about 17 minutes (!) to convert a 365x390px image on my system (also Vista x64).

To improve this, I've used a "string builder" (you can get it here http://purebasic-lounge.com/viewtopic.php?t=5717) for all the 'rtf$ + ' lines.
That is a simple solution without changing the rest of the procedure.

Another idea is to use a char array directly based on a precalculated size to store the whole image data. That could improve the speed much more.



Same procedure, but with string builder.

Code: Select all

;Returns an empty string if an error. 
Procedure.s BMP2RTF(hBmap) 
  Protected bitmap.BITMAP, hdcEMF, hEMF, hdc 
  Protected oldImage, widthTwips, heightTwips, numBytes, mem, *bytes.Byte, i 
  
  
  Protected *stringBuilder.StringBuilder = StringBuilder_CreateObject()
  
  
  If GetObject_(hBmap, SizeOf(BITMAP), bitmap) 
    If bitmap\bmWidth And bitmap\bmHeight 
      ;Create an EMF to hold the bitmap. 
      hdcEMF = CreateEnhMetaFile_(0, 0, 0, 0) 
      If hdcEMF 
        hdc = CreateCompatibleDC_(hdcEMF) 
        If hdc 
          oldImage = SelectObject_(hdc, hBmap) 
          BitBlt_(hdcEMF, 0, 0, bitmap\bmWidth, bitmap\bmHeight, hdc, 0, 0, #SRCCOPY) 
          SelectObject_(hdc, oldImage) 
          DeleteDC_(hdc) 
          hEMF = CloseEnhMetaFile_(hdcEMF) 
          If hEMF ;The EMF now holds the bitmap. 
            ;Before creating the RTF header we need to calculate the image width/height in twips. 
            hdc = CreateDC_("DISPLAY", 0, 0, 0) 
            widthTwips = MulDiv_(bitmap\bmWidth,1440,GetDeviceCaps_(hdc, #LOGPIXELSX)) 
            heightTwips = MulDiv_(bitmap\bmHeight,1440,GetDeviceCaps_(hdc, #LOGPIXELSY)) 
            DeleteDC_(hdc) 
            ;Now the rtf header. 
            *stringBuilder\Append("{\rtf1\pict\wmetafile8\picw" + Str(bitmap\bmWidth) + "\pich" + Str(bitmap\bmHeight) + "\picwgoal" + Str(widthTwips) + "\pichgoal" + Str(heightTwips))
            ;Add the EMF bits as double-character hex. 
            ;First retrieve the EMF bits. 
            numBytes = GetEnhMetaFileBits_(hEMF, 0, 0) 
            mem = AllocateMemory(numBytes) 
            If mem 
              If GetEnhMetaFileBits_(hEMF, numBytes, mem) = numBytes 
                *bytes=mem 
                For i = 0 To numBytes-1 
                  If (i>>6)<<6 = i 
                    *stringBuilder\Append(#CRLF$)
                  EndIf 
                  *stringBuilder\Append(RSet(Hex(*bytes\b, #PB_Byte), 2, "0"))
                  *bytes+1 
                Next 
                *stringBuilder\Append(#CRLF$ + "}")
              Else 
                *stringBuilder\Clean()
              EndIf 
              FreeMemory(mem) 
            Else 
              *stringBuilder\Clean()
            EndIf 
            DeleteEnhMetaFile_(hEMF) 
          EndIf 
        Else 
          hEMF = CloseEnhMetaFile_(hdcEMF) 
          DeleteEnhMetaFile_(hEMF) 
        EndIf 
      EndIf 
    EndIf 
  EndIf 
  
  Protected tmp$ = *stringBuilder\ToString()
  *stringBuilder\Release()
  ProcedureReturn tmp$
EndProcedure 
Psych
Enthusiast
Enthusiast
Posts: 228
Joined: Thu Dec 18, 2008 3:35 pm
Location: Wales, UK

Post by Psych »

In fact, my hex function only takes 1 parameter (the Value)
----------------------------------------------------------------------------
Commenting your own code is admitting you don't understand it.
----------------------------------------------------------------------------
User avatar
Hroudtwolf
Addict
Addict
Posts: 803
Joined: Sat Feb 12, 2005 3:35 am
Location: Germany(Hessen)
Contact:

Post by Hroudtwolf »

Yeah. It's running very fast now. Up to 50 times faster.

Thanks @SCRJ
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

@Blue : can you send me the bitmap you are having problems with? I am using Vista 32 as well without a problem.

@Psych : you need to be using PB 4.3.

@All : yes I was thinking about speeding it up; though it wasn't a priority as the original intention was for this routine to be run 'offline' anyhow, i.e. to build a collection of rtf files for use in an application. I'll take a look at the string builder class; or may just use my own string routines. :)
I may look like a mule, but I'm not a complete ass.
Psych
Enthusiast
Enthusiast
Posts: 228
Joined: Thu Dec 18, 2008 3:35 pm
Location: Wales, UK

Post by Psych »

I just removed the second parameter, seems to generate the RTF text fine.
----------------------------------------------------------------------------
Commenting your own code is admitting you don't understand it.
----------------------------------------------------------------------------
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

@Blue : yes there is a bug in the code; Wordpad does not recognise the rtf generated (MS Word recognises it okay). Nearly fixed...
I may look like a mule, but I'm not a complete ass.
Psych
Enthusiast
Enthusiast
Posts: 228
Joined: Thu Dec 18, 2008 3:35 pm
Location: Wales, UK

Post by Psych »

This may be nothing to do with it, but it took me ages to get the generated text to show in an EditorGadget (I think the CR/LF pairs mess something up) however when I compiled it using unicode, it worked fine.
----------------------------------------------------------------------------
Commenting your own code is admitting you don't understand it.
----------------------------------------------------------------------------
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

REMOVED.
Last edited by srod on Sun Jan 18, 2009 1:25 pm, edited 1 time in total.
I may look like a mule, but I'm not a complete ass.
Post Reply