Page 1 of 2

Is that the fastest way to image grayscale?

Posted: Fri Mar 07, 2008 10:54 am
by dige
hi folks,

I need a hint, how to improve this code:

Code: Select all

Procedure.b GrayScaleImg ( ImgID.l ); Works only with 32 Bit Images!!
  Protected Grey.l
  
  #lumared   = 3
  #lumagreen = 6
  #lumablue  = 1

  If Not IsImage( ImgID ) Or Not GetObject_(ImageID(ImgID), SizeOf(BITMAP), bmp.BITMAP) : ProcedureReturn #False : EndIf
    
  *ptr.RGBQUAD = bmp\bmBits
  Size = *ptr + ImageWidth(ImgID) * ImageHeight(ImgID) * 4 - 4
  
  Repeat
    Grey = (#lumared * *ptr\rgbRed&$FF + #lumagreen * *ptr\rgbGreen&$FF + #lumablue * *ptr\rgbBlue&$FF) / 10
    *ptr\rgbRed   = Grey
    *ptr\rgbGreen = *ptr\rgbRed
    *ptr\rgbBlue  = *ptr\rgbRed
    *ptr + 4
  Until *ptr > Size

  ProcedureReturn #True
EndProcedure


If CreateImage( 1, 4000, 4000, 32)
  time = ElapsedMilliseconds()
  GrayScaleImg( 1 )
  MessageRequester("", Str(ElapsedMilliseconds() - time), 0)
EndIf
Results:
---------

PB 4.10: 594ms
PB 4.20: 594ms
C-Lib: 188ms

Posted: Fri Mar 07, 2008 11:15 am
by srod
Here's another way. It makes a copy of the original image, for which I have opted for a 4-bit 16 color image etc.

It would be quicker to use more API, but I hadn't time. Whether this is quicker than your method I do not know? One thing is though that this should work with images of any depth.

Code: Select all

If OpenWindow(0, 100, 100, 500, 300, "PureBasic - Image")
  ;Set up a 'grey' colortable.
    Dim colors(15)  ;Used as RGBQUAD's.
    For i = 0 To 15
      color = i*16
      If color = 256
        color = 255
      EndIf
      colors(i) = color + color<<8 + color<<16
    Next

  LoadImage(1, "bm2.bmp") ;Change to your own image.
  ;Create a new image which will hold the 'grey scale' equivalent of the above image.
  ;We use a 4 bit image. Probably better with 8 bits.
    CreateImage(2, ImageWidth(1), ImageHeight(1), 4)
  ;Convert image 1 to grey scale.
    hdc = StartDrawing(ImageOutput(2))
    If hdc
      SetDIBColorTable_(hdc, 0, 16, @colors())
      DrawImage(ImageID(1),0,0)
      StopDrawing()
    EndIf
        
  Repeat
    EventID = WaitWindowEvent()
    
    If EventID = #PB_Event_Repaint
      ;Display both images.
        StartDrawing(WindowOutput(0))
          DrawImage(ImageID(1), 0, 0)
          DrawImage(ImageID(2), ImageWidth(1)+10, 0)
        StopDrawing()    
    EndIf
    
  Until EventID = #PB_Event_CloseWindow
EndIf

End

Posted: Fri Mar 07, 2008 11:19 am
by tinman
This works faster for me but you won't get anywhere near the timing for the C code if that is your idea.

Code: Select all

Structure RGBQUADLong
    StructureUnion
        rgbq.RGBQUAD
        rgbl.l
    EndStructureUnion
EndStructure

Procedure.b GrayScaleImg ( ImgID.l ); Works only with 32 Bit Images!! 
  Protected Grey.l 
  
  #lumared   = 3 
  #lumagreen = 6 
  #lumablue  = 1 

  If Not IsImage( ImgID ) Or Not GetObject_(ImageID(ImgID), SizeOf(BITMAP), bmp.BITMAP) : ProcedureReturn #False : EndIf 
  
  ;*ptr.RGBQUAD = bmp\bmBits 
  *ptr.RGBQUADLong = bmp\bmBits 
  Size = *ptr + ImageWidth(ImgID) * ImageHeight(ImgID) * 4 - 4 
  
  Repeat 
    Grey = (#lumared * *ptr\rgbq\rgbRed&$FF + #lumagreen * *ptr\rgbq\rgbGreen&$FF + #lumablue * *ptr\rgbq\rgbBlue&$FF) / 10
    Grey = Grey * $00010101
    *ptr\rgbl = Grey
    ;*ptr\rgbRed   = Grey
    ;*ptr\rgbGreen = *ptr\rgbRed 
    ;*ptr\rgbBlue  = *ptr\rgbRed 
    *ptr + 4 
  Until *ptr > Size 

  ProcedureReturn #True 
EndProcedure 


If CreateImage( 1, 4000, 4000, 32) 
  time = ElapsedMilliseconds() 
  GrayScaleImg( 1 ) 
  MessageRequester("", Str(ElapsedMilliseconds() - time), 0) 
EndIf 

Posted: Fri Mar 07, 2008 1:27 pm
by dige
@tinman: yeah! :D we'll come closer: 453ms
thank you!

Posted: Fri Mar 07, 2008 1:36 pm
by dige
359ms!!

But why is RGBQUADLong faster?

Code: Select all

Structure RGBQUADLong
    StructureUnion
        rgbq.RGBQUAD
        rgbl.l
    EndStructureUnion
EndStructure

Procedure.b GrayScaleImg ( ImgID.l ); Works only with 32 Bit Images!!
  Protected Grey.l
 
  #lumared   = 3
  #lumagreen = 6
  #lumablue  = 1

  If Not IsImage( ImgID ) Or Not GetObject_(ImageID(ImgID), SizeOf(BITMAP), bmp.BITMAP) : ProcedureReturn #False : EndIf
 
  *ptr.RGBQUADLong = bmp\bmBits
  Size = *ptr + ImageWidth(ImgID) * ImageHeight(ImgID) * 4 - 4
 
  Repeat
    *ptr\rgbl = $00010101 * ((#lumared * *ptr\rgbq\rgbRed&$FF + #lumagreen * *ptr\rgbq\rgbGreen&$FF + #lumablue * *ptr\rgbq\rgbBlue&$FF) / 10)
    *ptr + 4
  Until *ptr > Size

  ProcedureReturn #True
EndProcedure


  If CreateImage( 1, 4000, 4000, 32)
    time = ElapsedMilliseconds()
    GrayScaleImg( 1 )
    MessageRequester("", Str(ElapsedMilliseconds() - time), 0)
EndIf 

Posted: Fri Mar 07, 2008 1:53 pm
by tinman
dige wrote:But why is RGBQUADLong faster?
Because you write all pixels in one operation rather than 3.

Posted: Fri Mar 07, 2008 3:44 pm
by Trond
Hmm. I send in my 32-bit image and it fails.

Code: Select all

Procedure.b GrayScaleImg ( ImgID.l ); Works only with 32 Bit Images!!
  Protected Grey.l
 
  #lumared   = 3
  #lumagreen = 6
  #lumablue  = 1

  If Not IsImage( ImgID ) Or Not GetObject_(ImageID(ImgID), SizeOf(BITMAP), bmp.BITMAP) : ProcedureReturn #False : EndIf
  
  Debug bmp\bmBitsPixel ; Shows 32
  Debug bmp\bmBits      ; Shows 0
  *ptr.RGBQUADLong = bmp\bmBits
  Size = *ptr + ImageWidth(ImgID) * ImageHeight(ImgID) * 4 - 4
 
  Repeat
    *ptr\rgbl = $00010101 * ((#lumared * *ptr\rgbq\rgbRed&$FF + #lumagreen * *ptr\rgbq\rgbGreen&$FF + #lumablue * *ptr\rgbq\rgbBlue&$FF) / 10)
    *ptr + 4
  Until *ptr > Size

  ProcedureReturn #True
EndProcedure


LoadImage(1, "c:\map.bmp", #PB_Image_DisplayFormat)

Posted: Fri Mar 07, 2008 3:54 pm
by srod
That could be because LoadImage() may well not be creating a DIBSection etc. In such cases, GetObject_() will not return the pixel bits - you need GetDIBits_() for that.

Try drawing it to a 32-bit image created with CreateImage() first etc. Worth a shot!

Posted: Fri Mar 07, 2008 4:23 pm
by Trond
Slightly faster (compile in ascii mode):

Code: Select all

Structure CRGBQUAD
  r.c
  g.c
  b.c
EndStructure

Structure RGBQUADLong
    StructureUnion
        rgbq.CRGBQUAD
        rgbl.l
    EndStructureUnion
EndStructure

Procedure.l GrayScaleImgX ( ImgID.l ); Works only with 32 Bit Images!! 
  Protected Grey.l 
  
  #lumared   = 3 
  #lumagreen = 6 
  #lumablue  = 1 

  If Not IsImage( ImgID ) Or Not GetObject_(ImageID(ImgID), SizeOf(BITMAP), bmp.BITMAP) : ProcedureReturn #False : EndIf 
  
  ;*ptr.RGBQUAD = bmp\bmBits 
  *ptr.RGBQUADLong = bmp\bmBits 
  Size = *ptr + ImageWidth(ImgID) * ImageHeight(ImgID) * 4 - 4 
  
  Repeat 
    Grey = (#lumared * *ptr\rgbq\r + #lumagreen * *ptr\rgbq\g + #lumablue * *ptr\rgbq\b) / 10 
    Grey = Grey * $00010101 
    *ptr\rgbl = Grey 
    ;*ptr\rgbRed   = Grey 
    ;*ptr\rgbGreen = *ptr\rgbRed 
    ;*ptr\rgbBlue  = *ptr\rgbRed 
    *ptr + 4 
  Until *ptr > Size 

  ProcedureReturn #True 
EndProcedure

Posted: Fri Mar 07, 2008 5:18 pm
by Trond
It doesn't seem to get any much faster than this:

Code: Select all

Procedure.l GrayScaleImg ( ImgID.l ); Works only with 32 Bit Images!!
  Protected Grey.l
  
  #maskb = $000000FF
  #maskg = $0000FF00 ; >> 8
  #maskr = $00FF0000 ; >> 16
  
  If Not IsImage( ImgID ) Or Not GetObject_(ImageID(ImgID), SizeOf(BITMAP), bmp.BITMAP) : ProcedureReturn #False : EndIf
  
  *ptr.RGBQUADLong = bmp\bmBits
  Size = *ptr + ImageWidth(ImgID) * ImageHeight(ImgID) * 4 - 4
  
  Repeat
    d = *ptr\rgbl
    r = ((d & #maskr) >> 16) * 0.2989
    g = ((d & #maskg) >> 8)  * 0.5870
    d = ((d & #maskb))       * 0.1140
    r + g + d
    *Ptr\rgbl = r + (r<<8) + (r<<16)
    *ptr + 4
  Until *ptr > Size
  
  ProcedureReturn #True
EndProcedure

Posted: Fri Mar 07, 2008 6:40 pm
by hellhound66
Removed.

Posted: Fri Mar 07, 2008 7:38 pm
by dige
Take this, if you wanna use your own images..

Code: Select all

file.s = ""; Edit Filename
LoadImage( 0, file )
If CreateImage( 1, ImageWidth(0), ImageHeight(0), 32)
  If StartDrawing( ImageOutput(1))
    DrawImage(ImageID(0), 0, 0)  
    StopDrawing()
    GrayScaleImg ( 1 )
    SaveImage( file + ".bmp" ); Save Result
  EndIf
EndIf

Posted: Fri Mar 07, 2008 7:55 pm
by hellhound66
So there is a difference between a loaded and a created image? WTF!
So ImageID!=ImageID?

Posted: Sun Mar 09, 2008 12:52 am
by otto
Maybe this can help you to improve the speed a little bit:
First, Iam working with PB 4.0
use it only on CPUs with SSE2 and higher

Code: Select all

    Procedure bmpToGray(*bmp.BITMAP)
        ; *bmp\bmBits must be set and aligned to a mem64 / 8 bytes boundary
        ; inlineASM must be activated
        ; speed depends also on memory throughput
        
        pixels=*bmp\bmWidth**bmp\bmHeight
        addr=*bmp\bmBits
        If addr=0 Or addr&%111 Or *bmp\bmBitsPixel<>32
            ProcedureReturn 0 
        EndIf
        
        ; multipliers from 0.0 to 0.99999
        lumR=65536*0.3
        lumG=65536*0.6
        lumB=65536*0.1
        
        !pxor       xmm6,xmm6
        !pinsrw     xmm6,[p.v_lumR],2
        !pinsrw     xmm6,[p.v_lumG],1
        !pinsrw     xmm6,[p.v_lumB],0
        !punpcklqdq xmm6,xmm6
        
        !pcmpeqw    xmm5,xmm5
        !psrlq      xmm5,16
        !pxor       xmm7,xmm7
        MOV         Esi,addr
        MOV         Eax,pixels
        SHR         Eax,1
    !.greyLoop:
        !movq       xmm0,[Esi]
        !punpcklbw  xmm0,xmm7
        !pmulhuw    xmm0,xmm6
        !movdqa     xmm1,xmm0
        !movdqa     xmm2,xmm0
        !psllq      xmm1,16
        !paddw      xmm0,xmm1   ; add green
        !psllq      xmm2,32
        !paddw      xmm0,xmm2   ; add blue
        !pshuflw    xmm0,xmm0,10101010b
        !pshufhw    xmm0,xmm0,10101010b
        !pand       xmm0,xmm5
        
        !packuswb   xmm0,xmm7
        !movq       [Esi],xmm0
        
        ADD         Esi,8
        SUB         Eax,1
        !JNZ        .greyLoop
        
        ProcedureReturn 1
    EndProcedure


    hBmp2=CreateImage(1, 4000, 4000, 32)
    bmp2.BITMAP
    n=GetObject_(hBmp2, SizeOf(BITMAP), bmp2)
    
    
    perfFrequency.q
    perfCount.q
    lastPerfCount.q
    queryPerformanceFrequency_(@perfFrequency)
    queryPerformanceCounter_(@lastPerfCount.q)
    perfFrequency/1000
    
    rs=bmpToGray(bmp2)
  ;  GrayScaleImg(1)
    
    queryPerformanceCounter_(@perfCount.q)
    time.q=(perfCount-lastPerfCount)/perfFrequency
    If rs
        MessageRequester("Zeit", Str(time)+" milliseconds")
    Else
        MessageRequester("Error", "Error")
    EndIf

@ Dige
What Time do you get now?
I have 83ms on P4-925D

Posted: Sun Mar 09, 2008 1:21 am
by Michael Vogel
Hi otto,
good start (if your message count is not a fake;) for the show!

My notebook does obviously not have a SEwhateverCPU, I'll get an error. But thats not the point - more important is, that there is another "maniac" here in our forum, you're welcome on board! :wink:

Michael