Quickly replace image colors?

Just starting out? Need help? Post your questions and find answers here.
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Quickly replace image colors?

Post by Dude »

I have a PNG image in my app, that I need to quickly replace specific colors with another. In the example image below, I want to replace all the pink with one specific color, and all the blue text with another specific color, based on what the user selects. Is there a quick way of doing this instantly, or do I really have to loop through all pixels and change them one by one? Thanks.

Image

Here's what I'm doing at the moment, but there must be a more efficient way, especially if the image is huge?

Code: Select all

If StartDrawing(ImageOutput(i))
  w=ImageWidth(i)-1
  h=ImageHeight(i)-1
  back=[whatever]
  text=[whatever]
  For x=0 To w
    For y=0 To h
      Select Point(x,y)
        Case #Magenta : Plot(x,y,back)
        Case #Blue : Plot(x,y,text)
      EndSelect
    Next
  Next
  StopDrawing()
EndIf
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: Quickly replace image colors?

Post by RASHAD »

Hi Dude

Code: Select all

Procedure FilterCallback(x, y, SourceColor, TargetColor)
  If SourceColor = $FF181FFE
    ProcedureReturn $FF76F6F1
  ElseIf SourceColor = $FF32FE18
    ProcedureReturn $FFFE3718
  Else
    ProcedureReturn SourceColor
  EndIf
EndProcedure
  
  CreateImage(1,400,200,32,$FF181FFE)
  StartDrawing(ImageOutput(1))
    Circle(200,100,100,$FF32FE18)
    Circle(200,100,50,$FF2A18FE)
  StopDrawing()

  If OpenWindow(0, 0, 0, 800, 200, "2DDrawing Example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)  
    If CreateImage(0, 800, 200) And StartDrawing(ImageOutput(0))
      DrawImage(ImageID(1), 0, 0, 400, 200)
      DrawingMode(#PB_2DDrawing_CustomFilter)      
      CustomFilterCallback(@FilterCallback())
      DrawImage(ImageID(1), 400, 0, 400, 200)      
      StopDrawing() 
      ImageGadget(0, 0, 0, 800, 200, ImageID(0))
    EndIf
    
    Repeat
      Event = WaitWindowEvent()
    Until Event = #PB_Event_CloseWindow
  EndIf
Egypt my love
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Quickly replace image colors?

Post by Dude »

Thanks Rashad (as always!). :)

Quick question: I haven't speed-tested this, but the manual for CustomFilterCallback() says: "This callback will be called many times (for every pixel to draw) so it should be very small and fast to not have a too big impact on the drawing performance."

I'm guessing this means the callback is repeatedly called for every pixel, which could end up being no faster than my For/next loop?
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: Quickly replace image colors?

Post by RASHAD »

Hi Dude
The callback is faster by about 30 to 45 %
Egypt my love
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Quickly replace image colors?

Post by Dude »

Cool, thanks. I'll switch to using it. :)
User avatar
Fig
Enthusiast
Enthusiast
Posts: 352
Joined: Thu Apr 30, 2009 5:23 pm
Location: Côtes d'Azur, France

Re: Quickly replace image colors?

Post by Fig »

If you want to avoid double loop (hidden or not), you can only create a new colored sprite before your main loop.
There are 2 methods to program bugless.
But only the third works fine.

Win10, Pb x64 5.71 LTS
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Quickly replace image colors?

Post by kenmo »

Is this any faster? Try it in a StartDrawing() block.

Code: Select all

Procedure.i ReplaceColor(OldColor.i, NewColor.i)
  Protected Result.i = #False
  
  OldColor & $00FFFFFF
  NewColor & $00FFFFFF
  If (NewColor = OldColor)
    ProcedureReturn (#True)
  EndIf
  
  Protected Format.i = DrawingBufferPixelFormat() & (~#PB_PixelFormat_ReversedY)
  If ((Format = #PB_PixelFormat_24Bits_BGR) Or (Format = #PB_PixelFormat_32Bits_BGR))
    OldColor = RGB(Blue(OldColor), Green(OldColor), Red(OldColor))
    NewColor = RGB(Blue(NewColor), Green(NewColor), Red(NewColor))
  EndIf
  
  If ((Format = #PB_PixelFormat_32Bits_BGR) Or (Format = #PB_PixelFormat_32Bits_RGB))
    Protected *Ptr.LONG = DrawingBuffer()
    Protected *End = *Ptr + OutputHeight() * DrawingBufferPitch()
    While (*Ptr < *End)
      If (*Ptr\l & $00FFFFFF = OldColor)
        *Ptr\l = (*Ptr\l & $FF000000) | NewColor
      EndIf
      *Ptr + 4
    Wend
    Result = #True
  ElseIf ((Format = #PB_PixelFormat_24Bits_BGR) Or (Format = #PB_PixelFormat_24Bits_RGB))
    *Ptr = DrawingBuffer()
    *End = *Ptr + OutputHeight() * DrawingBufferPitch()
    While (*Ptr < *End)
      If (CompareMemory(*Ptr, @OldColor, 3))
        CopyMemory(@NewColor, *Ptr, 3)
      EndIf
      *Ptr + 3
    Wend
    Result = #True
  EndIf
  
  ProcedureReturn (Result)
EndProcedure
firace
Addict
Addict
Posts: 946
Joined: Wed Nov 09, 2011 8:58 am

Re: Quickly replace image colors?

Post by firace »

It's actually possible to just update the PNG palette in-place, which should give pretty much instant results regardless of image size.
Here's an example in Python:

https://stackoverflow.com/a/1214765/
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: Quickly replace image colors?

Post by RASHAD »

- Debugger Disabled
- Iteration = 500
- Changing 2 colors

Dude 1100 ms
Callback 820 ms
kenmo 1350 ms
Egypt my love
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Quickly replace image colors?

Post by Dude »

Thanks for the speed test, Rashad.

Firace, thanks for the info on PNG palette changing, but I'll skip that one (it looks too hard, especially because I'm not changing the colors from a file, but from a PNG already loaded in memory). :oops:
Post Reply