Speed up loop

Just starting out? Need help? Post your questions and find answers here.
spacebuddy
Enthusiast
Enthusiast
Posts: 356
Joined: Thu Jul 02, 2009 5:42 am

Speed up loop

Post by spacebuddy »

Hi All,

I wrote a small program to take an image and convert it to ascii. I find the for loop top be a little
slow. Is there anyway to speed this code up?

Not sure how to display the code in a message :shock:

Code: Select all


; load image


UsePNGImageDecoder()
UseJPEGImageDecoder()
Global image.i
Global myString.s = "@A#*+=-,. "
Global File.s
Global Font1.i




Enumeration DWMWINDOWATTRIBUTE
  #DWMWA_USE_IMMERSIVE_DARK_MODE = 20
EndEnumeration

; https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
PrototypeC.i DwmSetWindowAttribute(hwnd.i, dwAttribute.l, *pvAttribute, cbAttribute.l)

Define.DwmSetWindowAttribute DwmSetWindowAttribute  
Define.i UseDarkMode

Procedure Resize_Window_0()
  Protected ScaleX.f, ScaleY.f
  ResizeGadget(10, WindowWidth(0) - 180, WindowHeight(0) - 150, 100, 25)
 
   
EndProcedure


Procedure AverageColor(Color.i)
  
  Define r.i,  g.i,  b.i,  Average.i
  r=Red(Color)
  g= Green(Color)
  b= Blue(Color)
  average = (r + g + b) / 3 
  ProcedureReturn Average
EndProcedure



Procedure AsciiToIMage(File$)
  Define S.s
  Define x.i,y.i
  Define Ratio.d
  Define NewWidth.i
  Define NewHeight.i
  Define width.i
  Define Height.i
  Define Color.i
  Define r.i
  Define g.i
  Define b.i
  Define Average.i
  Define CharIndex.i


 
Dim ASCII_CHARS.s(Len(myString)) 
For x = 1 To Len(myString)
  Ascii_CHARS(x-1) = Mid(myString, x, 1)
Next
image = LoadImage( #PB_Any, File$ )
ratio=ImageHeight( image ) / ImageWidth( image )
NewWidth=256
NewHeight=Ratio * NewWidth;
If NewHeight<=0
  NewHeight=256
EndIf
ResizeImage(image, NewWidth, NewHeight,#PB_Image_Smooth ) ;use new dimensions
ImageGadget(20,  1000, 10, NewWidth,NewHeight, ImageID(image))                      
SetGadgetState(20,ImageID(image)) ;update the image
width = ImageWidth( image )
height = ImageHeight( image )
StartDrawing( ImageOutput( image ) )
For y=0 To Height -1 ;Loop image height
 For x = 0 To width - 1 ;Loop image width
   Color=Point(x,y)     ;Get pixel color
   Average=AverageColor(Color) ;Get Average
   If Average>0 And Average<>255
    Plot(x,y,RGB(Average,Average,Average)) ;Turn to Gray scale
    Color=RGB(Average,Average,Average) ;get the new color values
    Average=AverageColor(Color)        ;Get Average again
    CharIndex= Average * Len(Mystring) / 256;  ;Get index to chars
  EndIf
  
  If Average=255
     Charindex=Len(MyString)
    EndIf
   S=S+ASCII_CHARS(CharIndex); ;Append new text
   CharIndex=0
 Next x
 S=S+Chr(13) 
Next y
StopDrawing()
AddGadgetItem(12, 0,S)
SetGadgetState(20,ImageID(image)) ;Show Gray scale image
EndProcedure


Procedure EventHandler()
 
    Select Event()
      Case #PB_Event_CloseWindow
        End
        
      Case #PB_Event_Gadget
        File = OpenFileRequester("Please choose image to load", "", "", 0)
        If IsImage(image)
          FreeImage(image)
          EndIf
        SetGadgetText(12, "")
        AsciiToIMage(File)
    EndSelect
  EndProcedure





  OpenWindow(0, 100, 10, 1280, 850, "ImageToAscii", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget)
   SetWindowColor(0, RGB(32, 32, 32))
   EditorGadget(12, 20,10, 539, 869)
   Font1 = LoadFont(#PB_Any, "Courier New"  ,  2) ;need a mono font
   SetGadgetFont(12, FontID(Font1))

   ButtonGadget(10, 1100, 750, 100, 25, "Open Image")
   BindGadgetEvent(10, @EventHandler())
   BindEvent(#PB_Event_SizeWindow, @Resize_Window_0(), 0)
   UseDarkMode = #True

If OpenLibrary(0, "dwmapi")  
  DwmSetWindowAttribute = GetFunction(0, "DwmSetWindowAttribute")  
  DwmSetWindowAttribute(WindowID(0), #DWMWA_USE_IMMERSIVE_DARK_MODE, @UseDarkMode, SizeOf(UseDarkMode))
  CloseLibrary(0)
  EndIf


Repeat
    Event = WaitWindowEvent()

    If Event = #PB_Event_CloseWindow  ; If the user has pressed on the close button
      Quit = 1
    EndIf

  Until Quit = 1
  


End   








Thanks
BarryG
Addict
Addict
Posts: 4123
Joined: Thu Apr 18, 2019 8:17 am

Re: Speed up loop

Post by BarryG »

This part is always recalculating "height-1" and "width-1" with each loop iteration, which is not optimal:

Code: Select all

For y=0 To Height -1 ;Loop image height
  For x = 0 To width - 1 ;Loop image width
So do the calculation for them just once, like this:

Code: Select all

h1 = Height - 1
w1 = width - 1
For y=0 To h1 ;Loop image height
  For x = 0 To w1 ;Loop image width
That's all I can suggest. Someone else might have more. BTW, it's not slow for me; it works very fast here, even with photos in 4032 x 3024 resolution. It converts those to ASCII in about one second.
AZJIO
Addict
Addict
Posts: 2143
Joined: Sun May 14, 2017 1:48 am

Re: Speed up loop

Post by AZJIO »

I could rewrite everything, but I'm too lazy.
1. Len(myString) is calculated 2 times in a loop. Does the length of the string change somewhere?
2. S=S+ASCII_CHARS(CharIndex)
S+ASCII_CHARS(CharIndex)
3. Every time you add a character, you over-allocate memory. It is necessary to make a buffer and write without over allocating memory.
4. Use numbers instead of a string. Here is an example https://www.purebasic.fr/english/viewtopic.php?t=79007
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

I loaded a GIF and .... failed.

You should restict the files like this:

Code: Select all

File = OpenFileRequester("Please choose image to load", "", "Image|*.bmp;*.jpg;*.png", 0)
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

A first step:

Code: Select all

Procedure AsciiToIMage(File$)
  
  Structure Character_Structure
    CharString.s{1}
  EndStructure
  
  Protected S.s
  Protected.i x, y, LoopCountX, LoopCountY
  Protected Ratio.d
  Protected.i NewWidth, NewHeight.i
  Protected.i width, Height.i
  Protected Color.i
  Protected.i r, g, b
  Protected Average.i
  Protected.i CharIndex, StringLen, LoopCount
  Protected *String.Character_Structure
  
  
  StringLen = Len(myString)
  
  Dim ASCII_CHARS.s(StringLen)
  *String = @myString
  LoopCount = StringLen - 1
  For x = 0 To LoopCount
    Ascii_CHARS(x) = *String\CharString
    *String + SizeOf(Character)
  Next
  
  image = LoadImage( #PB_Any, File$)
  If image
    width = ImageWidth(image)
    height = ImageHeight(image)
    ratio = Height / width
    NewWidth = 256
    NewHeight = Ratio * NewWidth
    If NewHeight <= 0
      NewHeight = 256
    EndIf
    
    ;Debug Str(NewWidth) + "x" + Str(NewHeight)
    
    ResizeImage(image, NewWidth, NewHeight, #PB_Image_Smooth)
    ImageGadget(20, 1000, 10, NewWidth,NewHeight, ImageID(image))                      
    
    If StartDrawing(ImageOutput(image))
      LoopCountX = NewWidth - 1
      LoopCountY = NewHeight - 1
      
      S = Space((NewWidth + 1) * NewHeight) ; + 1 for CR per line
      ;Debug StringByteLength(S, #PB_Unicode)
      *String = @S
      
      For y=0 To LoopCountY ;Loop image height
        For x = 0 To LoopCountX ;Loop image width
          Color = Point(x,y)     ;Get pixel color
          Average = AverageColor(Color) ;Get Average
          If Average > 0 And Average <> 255
            Plot(x,y,RGB(Average,Average,Average)) ;Turn to Gray scale
            Color = RGB(Average,Average,Average)   ;get the new color values
            Average = AverageColor(Color)          ;Get Average again
            CharIndex= Average * StringLen / 256   ;Get index to chars
          EndIf
          
          If Average = 255
            Charindex = StringLen - 1
          EndIf
          
          ;S + ASCII_CHARS(CharIndex)  ;Append new text
          *String\CharString = ASCII_CHARS(CharIndex)
          *String + SizeOf(Character)
          CharIndex = 0
        Next x
        ;S + #CR$
        *String\CharString = #CR$
        *String + SizeOf(Character)
      Next y
      StopDrawing()
    EndIf
    ;ShowMemoryViewer(@S, StringByteLength(S, #PB_Unicode))
    AddGadgetItem(12, 0, S)
    SetGadgetState(20,ImageID(image)) ;Show Gray scale image
  EndIf
EndProcedure
791ms -> 38ms
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

Second step:

Code: Select all

Procedure AsciiToIMage(File$)
  
  Structure Character_Structure
    CharString.s{1}
  EndStructure
  
  Protected S.s
  Protected.i x, y, LoopCountX, LoopCountY
  Protected Ratio.d
  Protected.i NewWidth, NewHeight.i
  Protected.i width, Height.i
  Protected Color.i
  Protected.i r, g, b
  Protected Average.i
  Protected.i CharIndex, StringLen, LoopCount
  Protected *String.Character_Structure
  
  
  StringLen = Len(myString)
  
  Dim ASCII_CHARS.s(StringLen)
  *String = @myString
  LoopCount = StringLen - 1
  For x = 0 To LoopCount
    Ascii_CHARS(x) = *String\CharString
    *String + SizeOf(Character)
  Next
  
  image = LoadImage( #PB_Any, File$)
  If image
    width = ImageWidth(image)
    height = ImageHeight(image)
    ratio = Height / width
    NewWidth = 256
    NewHeight = Ratio * NewWidth
    If NewHeight <= 0
      NewHeight = 256
    EndIf
    
    ;Debug Str(NewWidth) + "x" + Str(NewHeight)
    
    ResizeImage(image, NewWidth, NewHeight, #PB_Image_Smooth)
    ImageGadget(20, 1000, 10, NewWidth,NewHeight, ImageID(image))                      
    
    If StartDrawing(ImageOutput(image))
      LoopCountX = NewWidth - 1
      LoopCountY = NewHeight - 1
      
      S = Space((NewWidth + 1) * NewHeight) ; + 1 for CR per line
      ;Debug StringByteLength(S, #PB_Unicode)
      *String = @S
      
      For y=0 To LoopCountY ;Loop image height
        For x = 0 To LoopCountX ;Loop image width
          Color = Point(x,y)     ;Get pixel color
          ;Average = AverageColor(Color) ;Get Average
          Average = (Red(Color) + Green(Color) + Blue(Color)) / 3
          If Average > 0 And Average <> 255
            Plot(x,y,RGB(Average,Average,Average)) ;Turn to Gray scale
            Color = RGB(Average,Average,Average)   ;get the new color values
            ;Average = AverageColor(Color)          ;Get Average again
            Average = (Red(Color) + Green(Color) + Blue(Color)) / 3
            CharIndex= Average * StringLen / 256   ;Get index to chars
          EndIf
          
          If Average = 255
            Charindex = StringLen - 1
          EndIf
          
          ;S + ASCII_CHARS(CharIndex)  ;Append new text
          *String\CharString = ASCII_CHARS(CharIndex)
          *String + SizeOf(Character)
          CharIndex = 0
        Next x
        ;S + #CR$
        *String\CharString = #CR$
        *String + SizeOf(Character)
      Next y
      StopDrawing()
    EndIf
    ;ShowMemoryViewer(@S, StringByteLength(S, #PB_Unicode))
    AddGadgetItem(12, 0, S)
    SetGadgetState(20,ImageID(image)) ;Show Gray scale image
  EndIf
EndProcedure
->29ms
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

Last step:

Code: Select all

Procedure AsciiToIMage(File$)
  
  Structure Character_Structure
    CharString.s{1}
  EndStructure
  
  Protected S.s
  Protected.i x, y, LoopCountX, LoopCountY
  Protected Ratio.d
  Protected.i NewWidth, NewHeight.i
  Protected.i width, Height.i
  Protected Color.i
  Protected.i r, g, b
  Protected Average.i
  Protected.i CharIndex, StringLen, LoopCount
  Protected *String.Character_Structure
  
  
  StringLen = Len(myString)
  
  Dim ASCII_CHARS.s(StringLen)
  *String = @myString
  LoopCount = StringLen - 1
  For x = 0 To LoopCount
    Ascii_CHARS(x) = *String\CharString
    *String + SizeOf(Character)
  Next
  
  image = LoadImage( #PB_Any, File$)
  If image
    width = ImageWidth(image)
    height = ImageHeight(image)
    ratio = Height / width
    NewWidth = 256
    NewHeight = Ratio * NewWidth
    If NewHeight <= 0
      NewHeight = 256
    EndIf
    
    ResizeImage(image, NewWidth, NewHeight, #PB_Image_Smooth)
    ImageGadget(20, 1000, 10, NewWidth, NewHeight, ImageID(image))
    
    If StartDrawing(ImageOutput(image))
      LoopCountX = NewWidth - 1
      LoopCountY = NewHeight - 1
      
      S = Space((NewWidth + 1) * NewHeight) ; + 1 for CR per line
      *String = @S
      
      For y=0 To LoopCountY ;Loop image height
        For x = 0 To LoopCountX ;Loop image width
          Color = Point(x,y)     ;Get pixel color
          ;Average = AverageColor(Color) ;Get Average
          Average = (Red(Color) + Green(Color) + Blue(Color)) / 3
          ;If Average > 0 And Average <> 255
            Plot(x, y, RGB(Average,Average,Average)) ;Turn to Gray scale
            Color = RGB(Average,Average,Average)   ;get the new color values
            ;Average = AverageColor(Color)          ;Get Average again
            ;Average = (Red(Color) + Green(Color) + Blue(Color)) / 3
            ;CharIndex = Average * StringLen / 256   ;Get index to chars
            
            If Average = 255
              Charindex = StringLen - 1
            Else
              CharIndex = (Average * StringLen) >> 8  ;Get index to chars
            EndIf
          ;EndIf
          
          ;S + ASCII_CHARS(CharIndex)  ;Append new text
          *String\CharString = ASCII_CHARS(CharIndex)
          *String + SizeOf(Character)
          ;CharIndex = 0
        Next x
        ;S + #CR$
        *String\CharString = #CR$
        *String + SizeOf(Character)
      Next y
      
      StopDrawing()
    EndIf
    ;ShowMemoryViewer(@S, StringByteLength(S, #PB_Unicode))
    AddGadgetItem(12, 0, S)
    SetGadgetState(20, ImageID(image)) ;Show Gray scale image
  EndIf
EndProcedure
-> 25ms
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

Meassured with:

Code: Select all

Procedure EventHandler()
  
  Protected.d StartTime, EndTime
  
  Select Event()
    Case #PB_Event_CloseWindow
      End
      
    Case #PB_Event_Gadget
      File = OpenFileRequester("Please choose image to load", "", "Image|*.bmp;*.jpg;*.png", 0)
      If IsImage(image)
        FreeImage(image)
      EndIf
      SetGadgetText(12, "")
      
      StartTime = ElapsedMilliseconds()
      AsciiToIMage(File)
      EndTime = ElapsedMilliseconds()
      
      MessageRequester("Time", Str(EndTime - StartTime))
      
  EndSelect
EndProcedure
And don't use the Debugger :wink:
User avatar
Bisonte
Addict
Addict
Posts: 1305
Joined: Tue Oct 09, 2007 2:15 am

Re: Speed up loop

Post by Bisonte »

Nice.... on my PC from 1000ms to 25ms, with the same picture of course ;)
PureBasic 6.21 (Windows x64) | Windows 11 Pro | AsRock B850 Steel Legend Wifi | R7 9800x3D | 64GB RAM | RTX 5080 | ThermaltakeView 270 TG ARGB | build by vannicom​​
English is not my native language... (I often use DeepL.)
spacebuddy
Enthusiast
Enthusiast
Posts: 356
Joined: Thu Jul 02, 2009 5:42 am

Re: Speed up loop

Post by spacebuddy »

Thank you all, I learned a lot :D :D :D
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Speed up loop

Post by skywalk »

Tried to run the topic but ran into some pitfalls.
Where is MyString defined?
No EnableExplicit :(

What is the goal of the textual output?
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

Simply search for myString :wink:

Code: Select all

Global myString.s = "@A#*+=-,. "
I only showed the 'optimized' part.
You had to copy the code from the first post and replace the corresponding part, after checked the original speed.

But here it is a complete version with EnableExplicit:

Code: Select all

EnableExplicit


Global image.i
Global myString.s = "@A#*+=-,. "


Procedure Resize_Window_0()
  ResizeGadget(10, WindowWidth(0) - 180, WindowHeight(0) - 150, 100, 25)
EndProcedure


Procedure ImageToAscii(File$)
  
  Structure Character_Structure
    CharString.s{1}
  EndStructure
  
  Protected S.s
  Protected.i x, y, LoopCountX, LoopCountY, NewWidth, NewHeight, width, Height
  Protected Ratio.d
  Protected.i r, g, b, color, Average
  Protected.i CharIndex, StringLen, LoopCount
  Protected *String.Character_Structure
  Protected *Ascii.Character_Structure
  
  
  StringLen = Len(myString)
  
  image = LoadImage(#PB_Any, File$)
  If image
    width = ImageWidth(image)
    height = ImageHeight(image)
    ratio = Height / width
    NewWidth = 256
    NewHeight = Ratio * NewWidth
    If NewHeight <= 0
      NewHeight = 256
    EndIf
    
    ResizeImage(image, NewWidth, NewHeight, #PB_Image_Smooth)
    
    If StartDrawing(ImageOutput(image))
      LoopCountX = NewWidth - 1
      LoopCountY = NewHeight - 1
      
      S = Space((NewWidth + 1) * NewHeight) ; + 1 for CR per line
      *String = @S
      
      For y = 0 To LoopCountY   ;Loop image height
        For x = 0 To LoopCountX ;Loop image width
          Color = Point(x, y)   ;Get pixel color
          Average = (Red(Color) + Green(Color) + Blue(Color)) / 3
          Plot(x, y, RGB(Average, Average, Average))  ;Turn to Gray scale
          
          If Average = 255
            Charindex = StringLen - 1
          Else
            CharIndex = (Average * StringLen) >> 8    ;Get index to chars
          EndIf
          
          *Ascii = @myString + (CharIndex << 1)
          *String\CharString = *Ascii\CharString
          *String + SizeOf(Character)
        Next x
        *String\CharString = #CR$
        *String + SizeOf(Character)
      Next y
      
      StopDrawing()
    EndIf
    SetGadgetText(12, S)
    SetGadgetState(20, ImageID(image))  ;Show Gray scale image
  EndIf
EndProcedure



Procedure EventHandler()
  
  Protected File.s
  Protected.d StartTime, EndTime
  
  Select Event()
    Case #PB_Event_CloseWindow
      End
      
    Case #PB_Event_Gadget
      File = OpenFileRequester("Please choose image to load", "", "Image|*.bmp;*.jpg;*.png", 0)
      If IsImage(image)
        FreeImage(image)
      EndIf
      SetGadgetText(12, "")
      
      StartTime = ElapsedMilliseconds()
      ImageToAscii(File)
      EndTime = ElapsedMilliseconds()
      
      MessageRequester("Time", Str(EndTime - StartTime))
      
  EndSelect
EndProcedure




Define.i Event, UseDarkMode, Quit, Font1


UsePNGImageDecoder()
UseJPEGImageDecoder()

OpenWindow(0, 0, 0, 1280, 850, "ImageToAscii", #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)

EditorGadget(12, 10, 10, 530, 830)
Font1 = LoadFont(#PB_Any, "Courier New", 2) ;need a mono font
SetGadgetFont(12, FontID(Font1))

ImageGadget(20, 1000, 10, 0, 0, 0)

ButtonGadget(10, 1100, 750, 100, 25, "Open Image")
BindGadgetEvent(10, @EventHandler())
BindEvent(#PB_Event_SizeWindow, @Resize_Window_0(), 0)

Repeat
  Event = WaitWindowEvent()
  
  If Event = #PB_Event_CloseWindow  ; If the user has pressed on the close button
    Quit = #True
  EndIf
  
Until Quit
The goal is to convert a 'normal' image into ASCII art, which is viewable, for example, in a text editor.
Last edited by infratec on Mon Apr 22, 2024 7:34 am, edited 2 times in total.
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: Speed up loop

Post by skywalk »

Cool, that's what I thought.
I just made too many mistakes cutting and pasting.
I got compiles from 28ms to 78ms on various png files.
But, I declared a single @ char for MyString, which resulted in a mono character output. Ill run your explicit code now.
Thanks!

EDIT:
Infratec's enableexplicit code just needs the prototype.

Code: Select all

; https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute
PrototypeC.i DwmSetWindowAttribute(hwnd.i, dwAttribute.l, *pvAttribute, cbAttribute.l)
Define.DwmSetWindowAttribute DwmSetWindowAttribute
Doctor Who looks great in ASCII.
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
infratec
Always Here
Always Here
Posts: 7577
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Speed up loop

Post by infratec »

I removed the Define.

Personally I don't need a 'DarkTheme' :wink:
Post Reply