Page 1 of 2

SwapImageColor(Image, OldColor, NewColor)

Posted: Thu Apr 23, 2009 12:49 pm
by nco2k
i needed a function, to swap a color of a loaded image. the usual Point() + Plot() method was very slow, so i searched for an alternative and came across this -> thread. unfortunately the snippet provided by JLC, didnt work on 24bit images. after a short consultation with him and some thought, here is the result.

note that this function doesnt work on images with less than 24bit or greater than 32bit!

Code: Select all

EnableExplicit

Macro RGB(Red, Green, Blue)
  (((Blue << 8 + Green) << 8 ) + Red)
EndMacro

Macro Red(Color)
  (Color & $FF)
EndMacro

Macro Green(Color)
  ((Color & $FFFF) >> 8)
EndMacro

Macro Blue(Color)
  (Color >> 16)
EndMacro

Procedure SwapImageColor(Image, OldColor, NewColor)
  
  Protected NewBlue, NewGreen, NewRed, CurrentBlue, CurrentGreen, CurrentRed
  Protected bmp.BITMAP, Size, Bits, *Memory, Offset, i
  
  If GetObject_(Image, SizeOf(BITMAP), @bmp)
    
    Size = (bmp\bmWidth * bmp\bmHeight) - 1
    Bits = Int(bmp\bmBitsPixel / 8)
    *Memory = bmp\bmBits
    
    If Bits > 2 And Bits < 5
      
      NewBlue = Blue(NewColor)
      NewGreen = Green(NewColor)
      NewRed = Red(NewColor)
      
      For i=0 To Size
        
        CurrentBlue = PeekB(*Memory + Offset) & $FF
        CurrentGreen = PeekB(*Memory + Offset + 1) & $FF
        CurrentRed = PeekB(*Memory + Offset + 2) & $FF
        
        If RGB(CurrentRed, CurrentGreen, CurrentBlue) = OldColor
          PokeB(*Memory + Offset, NewBlue)
          PokeB(*Memory + Offset + 1, NewGreen)
          PokeB(*Memory + Offset + 2, NewRed)
        EndIf
        
        Offset + Bits
        
      Next
      
    EndIf
    
  EndIf
  
EndProcedure

Define SwapColor

If CreateImage(0, 128, 128, 24) And StartDrawing(ImageOutput(0))
  Box(16, 0, 16, 128, RGB(255, 255, 255))
  Box(32, 0, 16, 128, RGB(255, 0, 0))
  Box(48, 0, 16, 128, RGB(0, 255, 0))
  Box(64, 0, 16, 128, RGB(0, 0, 255))
  Box(80, 0, 16, 128, RGB(0, 255, 255))
  Box(96, 0, 16, 128, RGB(255, 0, 255))
  Box(112, 0, 16, 128, RGB(255, 255, 0))
  StopDrawing()
  
  If OpenWindow(0, 0, 0, 138, 168, "Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    ImageGadget(0, 5, 5, 128, 128, ImageID(0))
    ButtonGadget(1, 5, 138, 128, 25, "Swap")
    
    Repeat
      Select WaitWindowEvent()
        
        Case #PB_Event_Gadget
          If EventGadget() = 1
            If SwapColor
              SwapImageColor(ImageID(0), RGB(128, 0, 128), RGB(255, 0, 255))
            Else
              SwapImageColor(ImageID(0), RGB(255, 0, 255), RGB(128, 0, 128))
            EndIf
            SwapColor ! 1
            InvalidateRect_(GadgetID(0), 0, #True)
            UpdateWindow_(GadgetID(0))
          EndIf
          
        Case #PB_Event_CloseWindow
          Break
          
      EndSelect
    ForEver
    
  EndIf
  
EndIf : End
i compared the speed against Point() + Plot() on a random 1920x1280 image:

Code: Select all

Point() + Plot() took 2882ms
SwapImageColor() took 27ms
not bad eh? :)


edit1: RGB Macros added.
edit2: only one For : Next loop is used now. hope it works the way it should. feel free to report otherwise.
edit3: macros updated and removed unused variables.

c ya,
nco2k

Re: SwapImageColor(Image, OldColor, NewColor)

Posted: Thu Apr 23, 2009 12:59 pm
by PB
Thanks, I needed something like this! :)

BTW, it works fine here without InvalidateRect, so is that obsolete?

Re: SwapImageColor(Image, OldColor, NewColor)

Posted: Thu Apr 23, 2009 1:07 pm
by nco2k
PB wrote:BTW, it works fine here without InvalidateRect, so is that obsolete?
not on vista or my pc at least. :)

c ya,
nco2k

Posted: Thu Apr 23, 2009 1:34 pm
by milan1612
I modified your code to use a structured pointer instead of Peek/PokeB.
This should give you some extra performance, although I did not test so it's just a guess...

Code: Select all

Procedure SwapImageColor(Image, OldColor, NewColor) 
  
  Protected NewBlue, NewGreen, NewRed, CurrentBlue, CurrentGreen, CurrentRed 
  Protected bmp.BITMAP, x, y, Width, Height, Bits, *Memory, Offset, *Temp.Byte
  
  If GetObject_(Image, SizeOf(BITMAP), @bmp) 
    
    Width = bmp\bmWidth - 1 
    Height = bmp\bmHeight - 1 
    Bits = Int(bmp\bmBitsPixel / 8) 
    *Memory = bmp\bmBits 
    
    If Bits > 2 
      
      NewBlue = Blue(NewColor) 
      NewGreen = Green(NewColor) 
      NewRed = Red(NewColor) 
      
      For y=0 To Height 
        For x=0 To Width 

          *Temp = *Memory + Offset
          CurrentBlue = *Temp\b & $FF
          *Temp + 1
          CurrentGreen = *Temp\b & $FF
          *Temp + 1
          CurrentRed = *Temp\b & $FF
          
          If RGB(CurrentRed, CurrentGreen, CurrentBlue) = OldColor 
            *Temp = *Memory + Offset
            *Temp\b = NewBlue
            *Temp + 1
            *Temp\b = NewGreen
            *Temp + 1
            *Temp\b = NewRed
          EndIf
          
          Offset + Bits 
          
        Next 
      Next 
      
    EndIf 
    
  EndIf 
  
EndProcedure 
EDIT: Great code by the way :P

Posted: Thu Apr 23, 2009 1:46 pm
by eesau
It should be even faster if you include these macros:

Code: Select all

Macro RGB(Red, Green, Blue)
    (((Blue << 8 + Green) << 8 ) + Red)
EndMacro

Macro Red(Color)
   (Color & 16777215 >> 16)
EndMacro

Macro Green(Color)
   ((Color & 65535) >> 8)
EndMacro

Macro Blue(Color)
   (Color >> 16)
EndMacro
That eliminates all calls from the loop. (It should be even faster if you eliminate the y/x-loops, and have a single loop only).

Posted: Thu Apr 23, 2009 2:11 pm
by nco2k
@eesau
yep, those macros give a nice boost. i didnt know, that purebasic's RGB() is that "slow". :shock:

> It should be even faster if you eliminate the y/x-loops, and have a single loop only
yep, i thought of that already once, but somehow i missed a part of the image and i was way too lazy to figure out why. :)

@milan1612
thanks for the interesting idea, i really thought this would speed it up, but somehow its even a little bit slower on my pc... strange. :?

c ya,
nco2k

Posted: Thu Apr 23, 2009 2:31 pm
by milan1612
nco2k wrote:@milan1612
thanks for the interesting idea, i really thought this would speed it up, but somehow its even a little bit slower on my pc... strange. :?
I just ran a few tests and you're right. Mine is a little slower than yours :?
I can only imagine that the compiler optimizes those Peek/Poke calls away,
but in a more effective way than me. Dunno...

As of those macros, they give quite a nice boost here!

Posted: Thu Apr 23, 2009 2:37 pm
by Kaeru Gaman
I'm sorry, I think there is a typo in eesau's macros....?

Code: Select all

Macro Red(Color)
   (Color & 16777215 >> 16)
EndMacro

Macro Green(Color)
   ((Color & 65535) >> 8)
EndMacro

Macro Blue(Color)
   (Color >> 16)
EndMacro 
shouldn't RED just be ( Color & 255 ) ...?

the MostSignificantNibble is Blue... and the mask for it is $FF0000 = 16711680

also, I would prefer writing $FF0000, $00FF00 and $0000FF instead of crumpy decimal numbers.

Posted: Fri Apr 24, 2009 6:58 am
by eesau
Kaeru Gaman wrote:shouldn't RED just be ( Color & 255 ) ...?

the MostSignificantNibble is Blue... and the mask for it is $FF0000 = 16711680

also, I would prefer writing $FF0000, $00FF00 and $0000FF instead of crumpy decimal numbers.
You could be right, I just copied the macros from an old post and didn't test them. And you're right, hex is much more readable.

Posted: Fri Apr 24, 2009 8:25 am
by djes
I wonder how you could be sure of the colour components order (BGR<>RGB) ...?

Posted: Sat Apr 25, 2009 4:54 am
by idle
It's so much easier working with 32 bit images.

Code: Select all


Procedure SwapImageColor(Image, OldColor, NewColor)
 
  Protected NewBlue, NewGreen, NewRed, CurrentBlue, CurrentGreen, CurrentRed
  Protected bmp.BITMAP, Size, Bits, *Memory, Offset, i,*px.long 
 
  If GetObject_(Image, SizeOf(BITMAP), @bmp)
   
    Size = (bmp\bmWidth * bmp\bmHeight) - 1
    Bits = Int(bmp\bmBitsPixel / 8)
    *Memory = bmp\bmBits
   
    Select bits 
      Case 4 
       
        For i=0 To Size
          *px = *memory + offset  
          If *px\l = oldcolor 
            *px\l =  newcolor 
          EndIf   
          Offset + Bits
        Next
      
      Case 3      
        NewBlue = Blue(NewColor)
        NewGreen = Green(NewColor)
        NewRed = Red(NewColor)

        For i=0 To Size 
          CurrentBlue = PeekB(*Memory + Offset) & $FF
          CurrentGreen = PeekB(*Memory + Offset + 1) & $FF
          CurrentRed = PeekB(*Memory + Offset + 2) & $FF
         
          If RGB(CurrentRed, CurrentGreen, CurrentBlue) = OldColor
            PokeB(*Memory + Offset, NewBlue)
            PokeB(*Memory + Offset + 1, NewGreen)
            PokeB(*Memory + Offset + 2, NewRed)
          EndIf
         
          Offset + Bits
        Next
    EndSelect      
 
 EndIf
 
EndProcedure

[/code]

Posted: Sat Apr 25, 2009 7:19 am
by Michael Vogel
I kept some code from the forum which also works for source bitmaps which have a palette...

Code: Select all

Procedure CopyImageToMemory(ImageNumber,Memory)

	Protected TemporaryDC.L,TemporaryBitmap.BITMAP,TemporaryBitmapInfo.BITMAPINFO

	TemporaryDC=CreateDC_("DISPLAY",#Null,#Null,#Null)

	GetObject_(ImageID(ImageNumber),SizeOf(BITMAP),TemporaryBitmap.BITMAP)

	TemporaryBitmapInfo\bmiHeader\biSize       =SizeOf(BITMAPINFOHEADER)
	TemporaryBitmapInfo\bmiHeader\biWidth      =TemporaryBitmap\bmWidth
	TemporaryBitmapInfo\bmiHeader\biHeight     =-TemporaryBitmap\bmHeight
	TemporaryBitmapInfo\bmiHeader\biPlanes     =1
	TemporaryBitmapInfo\bmiHeader\biBitCount   =32
	TemporaryBitmapInfo\bmiHeader\biCompression=#BI_RGB

	GetDIBits_(TemporaryDC,ImageID(ImageNumber),0,TemporaryBitmap\bmHeight,Memory,TemporaryBitmapInfo,#DIB_RGB_COLORS)

	DeleteDC_(TemporaryDC)

EndProcedure
Procedure CopyMemoryToImage(Memory,ImageNumber)

	Protected TemporaryDC.L,TemporaryBitmap.BITMAP,TemporaryBitmapInfo.BITMAPINFO

	TemporaryDC=CreateDC_("DISPLAY",#Null,#Null,#Null)

	GetObject_(ImageID(ImageNumber),SizeOf(BITMAP),TemporaryBitmap.BITMAP)

	TemporaryBitmapInfo\bmiHeader\biSize       =SizeOf(BITMAPINFOHEADER)
	TemporaryBitmapInfo\bmiHeader\biWidth      =TemporaryBitmap\bmWidth
	TemporaryBitmapInfo\bmiHeader\biHeight     =-TemporaryBitmap\bmHeight
	TemporaryBitmapInfo\bmiHeader\biPlanes     =1
	TemporaryBitmapInfo\bmiHeader\biBitCount   =32
	TemporaryBitmapInfo\bmiHeader\biCompression=#BI_RGB

	SetDIBits_(TemporaryDC,ImageID(ImageNumber),0,TemporaryBitmap\bmHeight,Memory,TemporaryBitmapInfo,#DIB_RGB_COLORS)

	DeleteDC_(TemporaryDC)

EndProcedure
So I added SwapColor now:

Code: Select all

Macro PurifyColor(Color)
	((Color&$ff)<<16+(Color&$ff00)+(Color&$ff0000)>>16)
EndMacro
Procedure SwapColor(ImageNumber,OldColor,NewColor)

	Protected MemorySize,*Memory
	Protected Counter,Color

	MemorySize=(ImageWidth(ImageNumber) * ImageHeight(ImageNumber) << 2)
	*Memory=AllocateMemory(MemorySize)

	CopyImageToMemory(ImageNumber,*Memory)

	OldColor=PurifyColor(OldColor)
	NewColor=PurifyColor(NewColor)

	For Counter=0 To MemorySize - 1 Step 4
		If PeekL(*Memory + Counter)&$ffffff=OldColor
			PokeL(*Memory + Counter,NewColor)
		EndIf
	Next

	CopyMemoryToImage(*Memory,ImageNumber)
	FreeMemory(*Memory)

EndProcedure
And a simple code to check some image functions:

Code: Select all

Macro ShowImage()
	SetGadgetState(#gadget2,ImageID(#image2))
	Delay(500)
EndMacro

OpenWindow(0,20,20,370,340,"Image functions")

Enumeration
	#gadget1
	#gadget2
	#image1
	#image2
EndEnumeration

LoadImage(#image1,"C:\bm.bmp")
ImageGadget(#gadget1,0,0,368,170,ImageID(#image1))

CopyImage(#image1,#image2)
ImageGadget(#gadget2,0,170,368,170,ImageID(#image2))

SwapColor(#image2,RGB(150,192,210),#White)
ShowImage()
;Brightness(#image2,50)
;ShowImage()
;Mirror(#image2)
;ShowImage()
;Flip(#image2)
;ShowImage()
;Greyscale(#image2)
;ShowImage()

Repeat : Until WaitWindowEvent()=#WM_CHAR

Posted: Sat Apr 25, 2009 9:39 am
by Kaeru Gaman
djes wrote:I wonder how you could be sure of the colour components order (BGR<>RGB) ...?
RGB means, the channels are in memory Red - Green - Blue
when you combine this to a 24bit number, the LowByte comes first in Memory.
a HEX-Number is always written High-Mid-Low. (every number is, also DEC)

you write it $BBGGRR, but it is RGB.

a BGR would be written $RRGGBB.

Posted: Sat Apr 25, 2009 7:51 pm
by nco2k
@idle
a structured pointer is slower in this case and milan1612 already explained why. so better use PeekL() instead.

@Michael Vogel
i needed a small and fast function, without the need for extra memory. using AllocateMemory() for an already loaded image, was no option for me. :)

c ya,
nco2k

Posted: Sat Apr 25, 2009 10:25 pm
by djes
Kaeru Gaman wrote:
djes wrote:I wonder how you could be sure of the colour components order (BGR<>RGB) ...?
RGB means, the channels are in memory Red - Green - Blue
when you combine this to a 24bit number, the LowByte comes first in Memory.
a HEX-Number is always written High-Mid-Low. (every number is, also DEC)

you write it $BBGGRR, but it is RGB.

a BGR would be written $RRGGBB.
No, I mean that (if I remember correctly) in the bitmap, you can't be sure that the data are always stored in that order. It's the device that give you the order.