Page 1 of 1

Quickly replace image colors?

Posted: Mon Oct 01, 2018 6:53 am
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

Re: Quickly replace image colors?

Posted: Mon Oct 01, 2018 8:03 am
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

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 4:32 am
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?

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 5:08 am
by RASHAD
Hi Dude
The callback is faster by about 30 to 45 %

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 6:10 am
by Dude
Cool, thanks. I'll switch to using it. :)

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 6:53 am
by Fig
If you want to avoid double loop (hidden or not), you can only create a new colored sprite before your main loop.

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 1:34 pm
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

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 1:47 pm
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/

Re: Quickly replace image colors?

Posted: Tue Oct 02, 2018 7:41 pm
by RASHAD
- Debugger Disabled
- Iteration = 500
- Changing 2 colors

Dude 1100 ms
Callback 820 ms
kenmo 1350 ms

Re: Quickly replace image colors?

Posted: Wed Oct 03, 2018 2:10 am
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: