SwapImageColor(Image, OldColor, NewColor)

Share your advanced PureBasic knowledge/code with the community.
User avatar
nco2k
Addict
Addict
Posts: 1344
Joined: Mon Sep 15, 2003 5:55 am

SwapImageColor(Image, OldColor, NewColor)

Post 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
Last edited by nco2k on Thu Apr 23, 2009 4:11 pm, edited 3 times in total.
If OSVersion() = #PB_OS_Windows_ME : End : EndIf
PB
PureBasic Expert
PureBasic Expert
Posts: 7581
Joined: Fri Apr 25, 2003 5:24 pm

Re: SwapImageColor(Image, OldColor, NewColor)

Post by PB »

Thanks, I needed something like this! :)

BTW, it works fine here without InvalidateRect, so is that obsolete?
I compile using 5.31 (x86) on Win 7 Ultimate (64-bit).
"PureBasic won't be object oriented, period" - Fred.
User avatar
nco2k
Addict
Addict
Posts: 1344
Joined: Mon Sep 15, 2003 5:55 am

Re: SwapImageColor(Image, OldColor, NewColor)

Post 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
If OSVersion() = #PB_OS_Windows_ME : End : EndIf
milan1612
Addict
Addict
Posts: 894
Joined: Thu Apr 05, 2007 12:15 am
Location: Nuremberg, Germany
Contact:

Post 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
Windows 7 & PureBasic 4.4
eesau
Enthusiast
Enthusiast
Posts: 589
Joined: Fri Apr 27, 2007 12:38 pm
Location: Finland

Post 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).
User avatar
nco2k
Addict
Addict
Posts: 1344
Joined: Mon Sep 15, 2003 5:55 am

Post 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
If OSVersion() = #PB_OS_Windows_ME : End : EndIf
milan1612
Addict
Addict
Posts: 894
Joined: Thu Apr 05, 2007 12:15 am
Location: Nuremberg, Germany
Contact:

Post 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!
Windows 7 & PureBasic 4.4
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post 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.
oh... and have a nice day.
eesau
Enthusiast
Enthusiast
Posts: 589
Joined: Fri Apr 27, 2007 12:38 pm
Location: Finland

Post 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.
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post by djes »

I wonder how you could be sure of the colour components order (BGR<>RGB) ...?
User avatar
idle
Always Here
Always Here
Posts: 5917
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Post 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]
User avatar
Michael Vogel
Addict
Addict
Posts: 2810
Joined: Thu Feb 09, 2006 11:27 pm
Contact:

Post 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
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post 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.
oh... and have a nice day.
User avatar
nco2k
Addict
Addict
Posts: 1344
Joined: Mon Sep 15, 2003 5:55 am

Post 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
If OSVersion() = #PB_OS_Windows_ME : End : EndIf
User avatar
djes
Addict
Addict
Posts: 1806
Joined: Sat Feb 19, 2005 2:46 pm
Location: Pas-de-Calais, France

Post 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.
Post Reply