Convert color image to 24-bit or 8-bit grayscale

Share your advanced PureBasic knowledge/code with the community.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Convert color image to 24-bit or 8-bit grayscale

Post by Keya »

From what i've seen browsing the forums there are probably quite a few other grayscale conversion source codes already available for PB, but i wanted to write my own to help get my stupid head around it. Ive used direct buffer modification via pointers so the speed is ok (no Point()/Plot()'s), it supports 24-bit RGB and BGR, and in the name of equality has 6 different weighting algorithms to choose from (and a Prototype to directly point at the chosen algorithm procedure). Simple to add 32-bit support but i have no need at the moment, and knowing me id probably end up supporting ARGB or something instead of RGBA anyway :)
should support all OS

Code: Select all

EnableExplicit
UsePNGImageDecoder(): UseJPEGImageDecoder(): UseJPEG2000ImageDecoder()



;--- Color-to-Gray weighting algorithms -----------------------------------------------------------------------

Prototype.l protoRGBToGray(red,green,blue)
Global RGBToGray.protoRGBToGray


;-- Average
Procedure.l RGBtoAverage(red,green,blue)  ;Simplest but poorest result and slow division
  ProcedureReturn (red + green + blue) / 3
EndProcedure


;-- Mean
Procedure.l RGBtoMean(red,green,blue)     ;aka "Lightness method"
  Protected lum,max,min
  If green < red: min = green: Else: min = red: EndIf
  If blue < min: min = blue: EndIf 
  If green > red: max = green: Else: max = red: EndIf
  If blue > max: max = blue: EndIf   
  lum = (max + min) >> 1  ;/ 2.0
  ProcedureReturn lum
EndProcedure


;-- 601 linear
Procedure.l RGBtoLuma601(red,green,blue)  ;ITU-R BT.601, Matlab
  ProcedureReturn (red * 0.299)  + (green * 0.587)  + (blue * 0.114)
EndProcedure


;-- 709 linear
Procedure.l RGBtoLuma709(red,green,blue)  ;ITU-R BT.709
  ProcedureReturn (red * 0.2126) + (green * 0.7152) + (blue * 0.0722)
EndProcedure


;-- 709 sRGB -- by wilbert
Procedure.d inv_gam_sRGB(ic)
  Protected c.d = ic/255.0
  If c <= 0.04045
    ProcedureReturn c/12.92
  Else
    ProcedureReturn Pow((c+0.055)/1.055,2.4)
  EndIf
EndProcedure

Procedure gam_sRGB(v.d)
  If v <= 0.0031308
    v * 12.92
  Else
    v = 1.055*Pow(v,1.0/2.4)-0.055
  EndIf
  ProcedureReturn Int(v*255+0.5)
EndProcedure

Procedure RGBto709sRGB(r, g, b)
  ProcedureReturn gam_sRGB(0.2126*inv_gam_sRGB(r) + 0.7152*inv_gam_sRGB(g) + 0.0722*inv_gam_sRGB(b))
EndProcedure


;-- Square-Root sRGB  -- by wilbert
;-VERSION 1 (Simplified)
; Procedure.l RGBSqrt(red, green, blue)
;   ProcedureReturn Sqr(0.23*red*red + 0.70*green*green + 0.07*blue*blue)
; EndProcedure

;-VERSION 2 (Faster using lookup table and asm)
Global Dim SqrTable.a(65025)
Define i
For i = 0 To 65025
  SqrTable(i) = Sqr(i)
Next

Procedure.l RGBSqrt(red, green, blue);  weighted Euclidean distance
  !movzx eax, byte [p.v_red]
  !movzx ecx, byte [p.v_green]
  !movzx edx, byte [p.v_blue]
  !imul eax, eax
  !imul ecx, ecx
  !imul edx, edx
  !imul eax, 0x3ae1
  !imul ecx, 0xb333
  !imul edx, 0x11ec
  !add eax, ecx
  !lea eax, [eax + edx + 0x8000]
  !shr eax, 16
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
    !mov rdx, [a_SqrTable]
    !movzx eax, byte [rdx + rax] 
  CompilerElse
    !mov edx, [a_SqrTable]
    !movzx eax, byte [edx + eax]
  CompilerEndIf
  ProcedureReturn
EndProcedure


;---SELECT ALGORITHM---
Procedure SetWeightingAlgorithm(algo.i)
  Select algo
    Case 0: RGBToGray.protoRGBToGray = @RGBtoMean()
    Case 1: RGBToGray.protoRGBToGray = @RGBtoAverage()
    Case 2: RGBToGray.protoRGBToGray = @RGBtoLuma601()
    Case 3: RGBToGray.protoRGBToGray = @RGBtoLuma709()
    Case 4: RGBToGray.protoRGBToGray = @RGBto709sRGB()
    Case 5: RGBToGray.protoRGBToGray = @RGBSqrt()
  EndSelect
EndProcedure
SetWeightingAlgorithm(0)   ;Self-initialize at start so RGBToGray() isnt null ptr



;----------------------------------------------------------------------------------------------------------



Procedure ConvertImageToGrayscale(hOrigImg)
  Protected *pOrig,*pNew, *pNextNew.Ascii,*pNextOrig.Ascii, rowbytes, pixfmt, x,y, width,height, hNew, red,green,blue,gray
  If StartDrawing(ImageOutput(hOrigImg)) = 0
    MessageRequester("Error", "Couldnt read original image buffer"): ProcedureReturn 0
  EndIf
  *pOrig = DrawingBuffer()
  rowbytes = DrawingBufferPitch()
  pixfmt = DrawingBufferPixelFormat()     ;#PB_PixelFormat_ReversedY is ignored as not required
  StopDrawing()
  If pixfmt & #PB_PixelFormat_24Bits_RGB
    pixfmt = #PB_PixelFormat_24Bits_RGB
  ElseIf pixfmt & #PB_PixelFormat_24Bits_BGR
    pixfmt = #PB_PixelFormat_24Bits_BGR
  Else
    MessageRequester("Error", "Only 24-bit currently supported"): ProcedureReturn 0
  EndIf
 
  width = ImageWidth(hOrigImg): height = ImageHeight(hOrigImg) 
  hNew = CreateImage(#PB_Any, width, height, 24)
  If hNew = 0 Or StartDrawing(ImageOutput(hNew)) = 0
    MessageRequester("Error", "Couldnt create second image"): ProcedureReturn 0
  EndIf
  *pNew = DrawingBuffer()
  StopDrawing()
 
  Select pixfmt
    Case #PB_PixelFormat_24Bits_RGB     
      For y = 0 To height-1
        *pNextOrig = *pOrig + (y * rowbytes)
        *pNextNew  = *pNew  + (y * rowbytes)
        For x = 0 To width-1
          red   = *pNextOrig\a: *pNextOrig+1
          green = *pNextOrig\a: *pNextOrig+1
          blue  = *pNextOrig\a: *pNextOrig+1
          gray  = RGBToGray(red,green,blue)
          *pNextNew\a = gray: *pNextNew+1
          *pNextNew\a = gray: *pNextNew+1
          *pNextNew\a = gray: *pNextNew+1
        Next x
      Next y
    Case #PB_PixelFormat_24Bits_BGR
      For y = 0 To height-1
        *pNextOrig = *pOrig + (y * rowbytes)
        *pNextNew  = *pNew  + (y * rowbytes)
        For x = 0 To width-1
          blue  = *pNextOrig\a: *pNextOrig+1
          green = *pNextOrig\a: *pNextOrig+1
          red   = *pNextOrig\a: *pNextOrig+1
          gray  = RGBToGray(red,green,blue)
          *pNextNew\a = gray: *pNextNew+1
          *pNextNew\a = gray: *pNextNew+1
          *pNextNew\a = gray: *pNextNew+1
        Next x
      Next y         
  EndSelect
  ProcedureReturn hNew
EndProcedure


;--------------------------------------------------------------------


Define hImg1 = LoadImage(#PB_Any, "C:\temp\beach.png")
If hImg1 = 0: MessageRequester("Error","Couldnt load image"): End: EndIf

Define hImg2 = ConvertImageToGrayscale(hImg1)
If hImg2 = 0: MessageRequester("Error", "Couldnt convert to grayscale"): End: EndIf

If OpenWindow(0, 0, 0, ImageWidth(hImg1), ImageHeight(hImg1), "Convert image to grayscale", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ImageGadget(0, 0, 0, ImageWidth(hImg1), ImageHeight(hImg1), ImageID(hImg2))
  Repeat
    Define Event = WaitWindowEvent()
  Until Event = #PB_Event_CloseWindow
EndIf

FreeImage(hImg1): FreeImage(hImg2)
wilbert made this excellent image to demonstrate the RGB-to-gray conversions from the different weighting algorithms, you can see that both of his two algorithms are extremely close to Photoshops output so you probably should choose one of those two as your default!:
Image
Last edited by Keya on Thu Feb 25, 2016 11:06 am, edited 6 times in total.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Convert 24-bit color image to grayscale (8-BIT VERSION)

Post by Keya »

This version converts to 8bit grayscale instead of retaining 24bit like the above code. The only difference is that it uses AllocateMemory instead of CreateImage (as CreateImage can't be used for 8bit images), and of course it only needs to output the 8 bits per pixel instead of 24 so it's a bit faster and the buffer is one third the size.

PB loaded images can be 24bit or 32bit so you can't display 8bit directly in PB, but I borrowed the file header of another 8bit BMP and overwrote its contents with mine and checked in Photoshop to confirm.

Code: Select all

Procedure ConvertImageToGrayscale8bit(hOrigImg)
  Protected *pOrig,*pNew, *pNextNew.Ascii,*pNextOrig.Ascii, rowbytes, pixfmt, x,y, width,height, hNew, red,green,blue,gray
  If StartDrawing(ImageOutput(hOrigImg)) = 0
    MessageRequester("Error", "Couldnt read original image buffer"): ProcedureReturn 0
  EndIf
  *pOrig = DrawingBuffer()
  rowbytes = DrawingBufferPitch()
  pixfmt = DrawingBufferPixelFormat()     ;#PB_PixelFormat_ReversedY is ignored as not required
  StopDrawing()
  If pixfmt & #PB_PixelFormat_24Bits_RGB
    pixfmt = #PB_PixelFormat_24Bits_RGB
  ElseIf pixfmt & #PB_PixelFormat_24Bits_BGR
    pixfmt = #PB_PixelFormat_24Bits_BGR
  Else
    MessageRequester("Error", "Only 24-bit currently supported"): ProcedureReturn 0
  EndIf
  
  width = ImageWidth(hOrigImg): height = ImageHeight(hOrigImg)    
  *pNew = AllocateMemory(width*height)
  If *pNew = 0
    MessageRequester("Error", "Couldnt create second image"): ProcedureReturn 0
  EndIf
  *pNextNew = *pNew
  Select pixfmt
    Case #PB_PixelFormat_24Bits_RGB      
      For y = 0 To height-1
        *pNextOrig = *pOrig + (y * rowbytes)
        For x = 0 To width-1
          red   = *pNextOrig\a: *pNextOrig+1
          green = *pNextOrig\a: *pNextOrig+1
          blue  = *pNextOrig\a: *pNextOrig+1
          gray  = RGBToGray(red,green,blue)
          *pNextNew\a = gray: *pNextNew+1
        Next x
      Next y
    Case #PB_PixelFormat_24Bits_BGR
      For y = 0 To height-1
        *pNextOrig = *pOrig + (y * rowbytes)
        For x = 0 To width-1
          blue  = *pNextOrig\a: *pNextOrig+1
          green = *pNextOrig\a: *pNextOrig+1
          red   = *pNextOrig\a: *pNextOrig+1
          gray  = RGBToGray(red,green,blue)
          *pNextNew\a = gray: *pNextNew+1
        Next x
      Next y         
  EndSelect
  ProcedureReturn *pNew   ;use FreeMemory() instead of FreeImage()
EndProcedure
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 536
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: Convert color image to 24-bit or 8-bit grayscale

Post by BasicallyPure »

Nice post. :)

I liked the usage of prototypes because I never really understood how to use them.
With your example I think I'm beginning to understand.

What do you think of this as a way to eliminate the floating point math?

Code: Select all

Procedure.l RGBtoLuma601(red,green,blue)  ;ITU-R BT.601, Matlab
  ProcedureReturn ((red*306) + (green*601) + (blue*117)) >> 10
EndProcedure

Procedure.l RGBtoLuma709(red,green,blue)  ;ITU-R BT.709
  ProcedureReturn ((red*218) + (green*732) + (blue*74)) >> 10
EndProcedure
>>10 is divide by 1024 so 306/1024 = 0.2988, 601/1024 = 0.5869, 117/1024 = 0.1143.
Close enough?
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
infratec
Always Here
Always Here
Posts: 6818
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Convert color image to 24-bit or 8-bit grayscale

Post by infratec »

Hi,

for speed comparisson:
Have you tried CustomFilterCallback() :?:

Bernd
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Convert color image to 24-bit or 8-bit grayscale

Post by wilbert »

infratec wrote:for speed comparisson:
Have you tried CustomFilterCallback() :?:
I think drawingbuffer will be faster.
My own asm optimized grayscale routine which uses the drawingbuffer, converts a 20 megapixel image in about 30 milliseconds on my computer.
I don't think I would be able to achieve those speeds with a filter callback.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Convert color image to 24-bit or 8-bit grayscale

Post by Keya »

infratec,
CustomFilterCallback() i'd never even heard of before lol, thankyou for bringing it to my attention :)
It looks simple enough and probably simplifies what i was trying to do anyway so I'll give it a try now and see how it compares for speed! And then when i'm done with that i'm going to sit down and simply read through every function in the helpfile so that I don't miss any others like this.

[edit] I just noticed that wilbert replied re CustomFilterCallback() saying it would be slower, which makes sense i guess having another procedure to keep call/ret from? But it looks simple to call so i'll still give it a try now for speed comparison, back in a few! :)

Basicallypure,
BasicallyPure wrote:I liked the usage of prototypes because I never really understood how to use them. With your example I think I'm beginning to understand.
I love them because they have the efficiency of If/Else but without the overhead... here's an example which makes sense to me anyway lol

Say we have two functions... one is a normal oldschool CPU version, and the other is an SSE version requiring modern processor. So when our app starts we need to check if the CPU supports SSE, and if it does we point the prototype to the SSE version of the function, otherwise point it at the normal version...

Code: Select all

Prototype.i protoAddNumbers(a,b)
Global AddNumbers.protoAddNumbers
 
Procedure.i SSEAdd(a,b)
  ;SSE instructions (ask wilbert heehee)
EndProcedure

Procedure.i NormalAdd(a,b)
  ProcedureReturn a+b
EndProcedure

Procedure.i CPUSupportsSSE()
  ProcedureReturn 0  ;pretend cpuid reports no SSE support
EndProcedure


If CPUSupportsSSE() = 1
  AddNumbers = @SSEAdd()
Else
  AddNumbers = @NormalAdd()
EndIf

;Now the AddNumbers() function is pointing at the right function
Debug AddNumbers(2,3)
much better than an If/Else with every single call :)
BasicallyPure wrote:What do you think of this as a way to eliminate the floating point math?
I like it!! I hadn't thought of changing it to integer but that would be especially helpful for batch processing, and all those grayscale weightings are hard to tell apart anyway so i'm sure we can get away with it without anybody knowing lol :)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Convert color image to 24-bit or 8-bit grayscale

Post by wilbert »

Here's my asm version module also if you want to compare the speed of that as well.
This time no SSE :shock: It will run on even the oldest hardware, is cross platform and supports both 24 and 32 bit images.
Grayscale::Grayscale(MyImage)

Quality optimized version

Code: Select all

DeclareModule Grayscale
  
  Declare Grayscale(Image)
  
EndDeclareModule

Module Grayscale
  
  DisableDebugger
  EnableExplicit
  EnableASM
  
  Global Dim SqrTable.a(65025)
  Global *SqrTable = @SqrTable()
  Define i.i
  
  For i = 0 To 65025
    SqrTable(i) = Sqr(i)
  Next
  
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    Macro rax : eax : EndMacro
    Macro rbx : ebx : EndMacro
    Macro rcx : ecx : EndMacro
    Macro rsi : esi : EndMacro
    Macro rdi : edi : EndMacro  
  CompilerEndIf 
  
  Macro M_Grayscale(bgr, st)
    mov reg_bx, rbx
    mov reg_si, rsi
    mov reg_di, rdi
    mov rsi, *SqrTable
    mov rdi, *db
    mov rcx, h
    !grayscale.l_conv#bgr#st#_l0:
    mov rbx, dp
    !grayscale.l_conv#bgr#st#_l1:
    CompilerIf bgr = 0
      movzx eax, byte [rdi]
      !imul eax, eax
      !imul eax, 0x3ae1
      movzx edx, byte [rdi + 1]
      !imul edx, edx
      !imul edx, 0xb333
      !add eax, edx
      movzx edx, byte [rdi + 2]
      !imul edx, edx
      !imul edx, 0x11ec
    CompilerElse
      movzx eax, byte [rdi]
      !imul eax, eax
      !imul eax, 0x11ec
      movzx edx, byte [rdi + 1]
      !imul edx, edx
      !imul edx, 0xb333
      !add eax, edx
      movzx edx, byte [rdi + 2]
      !imul edx, edx
      !imul edx, 0x3ae1
    CompilerEndIf
    !lea eax, [eax + edx + 0x8000]
    !shr eax, 16   
    movzx eax, byte [rsi + rax]  
    mov [rdi], al
    mov [rdi + 1], al
    mov [rdi + 2], al
    add rdi, st
    sub rbx, st
    cmp rbx, st
    !jge grayscale.l_conv#bgr#st#_l1
    add rdi, rbx
    sub rcx, 1
    !jnz grayscale.l_conv#bgr#st#_l0
    mov rbx, reg_bx
    mov rsi, reg_si
    mov rdi, reg_di
  EndMacro
  
  Procedure Grayscale(Image)
    Protected reg_bx, reg_di, reg_si
    Protected *db, dp, h
    If IsImage(Image) And StartDrawing(ImageOutput(Image))
      *db = DrawingBuffer() : dp = DrawingBufferPitch()
      h = OutputHeight()
      If DrawingBufferPixelFormat() & (#PB_PixelFormat_32Bits_BGR | #PB_PixelFormat_24Bits_BGR)
        If OutputDepth() = 32
          M_Grayscale(1, 4)
        Else
          M_Grayscale(1, 3)
        EndIf
      Else
        If OutputDepth() = 32
          M_Grayscale(0, 4)
        Else
          M_Grayscale(0, 3)
        EndIf
      EndIf
      StopDrawing()  
    EndIf
  EndProcedure
  
EndModule
Speed optimized version

Code: Select all

DeclareModule Grayscale
  
  Declare Grayscale(Image)
  
EndDeclareModule

Module Grayscale
  
  DisableDebugger
  EnableExplicit
  EnableASM
  
  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    Macro rax : eax : EndMacro
    Macro rbx : ebx : EndMacro
    Macro rcx : ecx : EndMacro
    Macro rdi : edi : EndMacro  
  CompilerEndIf 
  
  Macro M_Grayscale(bgr, st)
    mov reg_bx, rbx
    mov reg_di, rdi
    mov rdi, *db
    mov rcx, h
    !grayscale.l_conv#bgr#st#_l0:
    mov rbx, dp
    !grayscale.l_conv#bgr#st#_l1:
    CompilerIf bgr = 0
      movzx eax, byte [rdi]
      !imul eax, 0x36a02d
      movzx edx, byte [rdi + 1]
      !imul edx, 0xb7c3ab
      !add eax, edx
      movzx edx, byte [rdi + 2]
      !imul edx, 0x128d18
    CompilerElse
      movzx eax, byte [rdi]
      !imul eax, 0x128d18
      movzx edx, byte [rdi + 1]
      !imul edx, 0xb7c3ab
      !add eax, edx
      movzx edx, byte [rdi + 2]
      !imul edx, 0x36a02d
    CompilerEndIf
    !lea eax, [eax + edx + 0x80000]
    !shr eax, 24
    mov [rdi], al
    mov [rdi + 1], al
    mov [rdi + 2], al
    add rdi, st
    sub rbx, st
    cmp rbx, st
    !jge grayscale.l_conv#bgr#st#_l1
    add rdi, rbx
    sub rcx, 1
    !jnz grayscale.l_conv#bgr#st#_l0
    mov rbx, reg_bx
    mov rdi, reg_di
  EndMacro
  
  Procedure Grayscale(Image)
    Protected reg_bx, reg_di 
    Protected *db, dp, h
    If IsImage(Image) And StartDrawing(ImageOutput(Image))
      *db = DrawingBuffer() : dp = DrawingBufferPitch()
      h = OutputHeight()
      If DrawingBufferPixelFormat() & (#PB_PixelFormat_32Bits_BGR | #PB_PixelFormat_24Bits_BGR)
        If OutputDepth() = 32
          M_Grayscale(1, 4)
        Else
          M_Grayscale(1, 3)
        EndIf
      Else
        If OutputDepth() = 32
          M_Grayscale(0, 4)
        Else
          M_Grayscale(0, 3)
        EndIf
      EndIf
      StopDrawing()  
    EndIf
  EndProcedure
  
EndModule
Last edited by wilbert on Thu Mar 10, 2016 8:21 am, edited 4 times in total.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Convert color image to 24-bit or 8-bit grayscale

Post by Keya »

Here is the CustomFilterCallback version, nice and cute too, lets you forget about the X's and Y's if you want to heehee

Code: Select all

Procedure FilterCallback(x, y, SourceColor, TargetColor)
  Protected gray = RGBToGray(Red(SourceColor), Green(SourceColor), Blue(SourceColor))
  ProcedureReturn RGB(gray,gray,gray)
EndProcedure

Procedure ConvertImageToGrayscale(hOrigImg)
  Protected *pOrig,*pNew, *pNextNew.Ascii,*pNextOrig.Ascii, rowbytes, pixfmt, x,y, width,height, hNew, red,green,blue,gray
  If StartDrawing(ImageOutput(hOrigImg)) = 0
    MessageRequester("Error", "Couldnt read original image buffer"): ProcedureReturn 0
  EndIf
  *pOrig = DrawingBuffer()
  rowbytes = DrawingBufferPitch()
  pixfmt = DrawingBufferPixelFormat()     ;#PB_PixelFormat_ReversedY is ignored as not required
  If pixfmt & #PB_PixelFormat_24Bits_RGB
    pixfmt = #PB_PixelFormat_24Bits_RGB
  ElseIf pixfmt & #PB_PixelFormat_24Bits_BGR
    pixfmt = #PB_PixelFormat_24Bits_BGR
  Else
    MessageRequester("Error", "Only 24-bit currently supported"): ProcedureReturn 0
  EndIf
  StopDrawing()
  
  width = ImageWidth(hOrigImg): height = ImageHeight(hOrigImg)  
  hNew = CreateImage(#PB_Any, width, height, 24)
  If hNew = 0 Or StartDrawing(ImageOutput(hNew)) = 0
    MessageRequester("Error", "Couldnt create second image"): ProcedureReturn 0
  EndIf
  
  DrawingMode(#PB_2DDrawing_CustomFilter)      
  CustomFilterCallback(@FilterCallback())
  DrawImage(ImageID(hOrigImg),0,0,width,height)
  StopDrawing()
  
  ProcedureReturn hNew
EndProcedure
Last edited by Keya on Wed Feb 17, 2016 2:40 pm, edited 3 times in total.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Convert color image to 24-bit or 8-bit grayscale

Post by wilbert »

@Keya, I didn't realize you outputted to a new image.
The drawingbuffer isn't guaranteed to be valid anymore after you call StopDrawing() so while your copy approach might work, it may also fail.
I'm surprised to see CustomFilter outperform the DrawingBuffer :shock:
And no, my code uses no MMX either.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Convert color image to 24-bit or 8-bit grayscale

Post by Keya »

hrm interesting! i didnt think there was any reason for an image buffer to move (if i hadn't 'touched' it in any way) ... why would it move anyway!? so i assumed it would be safe ... i guess its easy fixed by moving my StopDrawings to the end, although when im using two images hrmm...
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Convert color image to 24-bit or 8-bit grayscale

Post by wilbert »

If Fred could confirm the buffer stays valid, that would make things easier. Otherwise, take a look at this thread http://www.purebasic.fr/english/viewtop ... 13&t=64898
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Convert color image to 24-bit or 8-bit grayscale

Post by Keya »

*blush* sorry guys! I had Debugger Enabled when doing the timings. Some people just shouldn't be allowed to program, y'know?

Now with debugger off, DrawingBuffer is faster and the world makes a bit more sense...
CustomFilter: 451, 462, 477
DrawingBuffer: 349, 329, 319
Wilberts: 25, 25, 23

BasicallyPure btw it looks like wilbert is using integer multiply in his version too :)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Convert color image to 24-bit or 8-bit grayscale

Post by wilbert »

Keya wrote:BasicallyPure btw it looks like wilbert is using integer multiply in his version too :)
Yes. On modern computers, multiplying with a 32 bit constant value is very fast.
What should be avoided if possible is division since that is very slow.

By the way, the FilterCallback method can be made faster by writing a filter for each conversion method and assigning that filter.
That way you don't need a prototype and it's one function less to call from within the filter.
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Convert color image to 24-bit or 8-bit grayscale

Post by Keya »

wilbert wrote:Yes. On modern computers, multiplying with a 32 bit constant value is very fast.
What should be avoided if possible is division since that is very slow.
just last night I was learning how GCC optimizes (-O3) divisions, and yes it quite likes multiply! :) I wonder if an -O3'd program ever uses instructions like div/idiv!?

Code: Select all

;OPTIMISED DIVISION BY 31:
 mov ebx, 155  ;num to divide
 mov edx, $8421085  ;(1000010000100001000010000101)
 mov eax, ebx
 mul edx     
 sub ebx, edx
 shr ebx, 1  
 add edx, ebx
 shr edx, 4

;OPTIMISED DIVISION BY 32:
 mov eax, 224  ;num to divide
 shr eax, 5      ;/32

;OPTIMISED DIVISION BY 33:
 mov edx, $3E0F83E1  ;(111110000011111000001111100001) 
 mov eax, 165  ;num to divide
 mul edx                                  
 shr edx, 3
I was reading this page about it (and this page lets you generate the magic numbers, its javascript is interesting), I'm not sure how easy or hard it would be for Fred to implement but i think it would be an amazing addition to PB! (and multiply with lea, etc!)
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Convert 8-bit grayscale to 24-bit grayscale

Post by Keya »

Convert 8bit grayscale (like the one in my 2nd post above) back to a 24bit (or 32bit) grayscale so it can be viewed with ImageGadget:

Code: Select all

Procedure Convert8bitImageTo24bit(*p8bit.Ascii, width, height)  ;returns CreateImage handle of 24bit Image
  Protected hImg = CreateImage(#PB_Any, width, height, 24)
  If StartDrawing(ImageOutput(hImg))
    Protected *pNext.Ascii, *pBuf = DrawingBuffer()
    Protected rowbytes = DrawingBufferPitch()
    For y = 0 To height-1
      *pNext = *pBuf + (y * rowbytes)
      For x = 0 To width-1
        *pNext\a = *p8bit\a: *pNext+1  ;R
        *pNext\a = *p8bit\a: *pNext+1  ;G
        *pNext\a = *p8bit\a: *pNext+1  ;B
        ;*pNext\a = $FF: *pNext+1       ;A (for 32bit) uncomment and change 24 above to 32
        *p8bit+1
      Next x
    Next y
    StopDrawing()
    ProcedureReturn hImg
  EndIf
  ProcedureReturn 0
EndProcedure

hImg = Convert8bitImageTo24bit(*p8bit, width, height)
ImageGadget(0, 0, 0, width, height, ImageID(hImg))
... FreeImage(hImg)
Post Reply