Function to create a grayscale version of an image

Share your advanced PureBasic knowledge/code with the community.
Axeman
User
User
Posts: 89
Joined: Mon Nov 03, 2003 5:34 am

Function to create a grayscale version of an image

Post by Axeman »

[Edited to fix issues discussed below.]

This function will create a grayscale version of the specified input image and output it via the output image. See the comments in the function for more information.

; Converts the pixels in 'input_image' to grayscale and stores them in 'output_image'. The two images can either be the same image or different images.
; The output image should already exist and must be the same size as the input image. A zero will be returned if these conditions aren't met.
; Returns 1 on success and 0 on failure.

Code: Select all

Procedure.i GrayscaleImage( input_image, output_image )
; Converts the pixels in 'input_image' to grayscale and stores them in 'output_image'. The two images can be the same image or different images.
; The output image should already exist and must be the same size as the input image. A zero will be returned if these conditions aren't met.
; Returns 1 on success and 0 on failure.

If ( IsImage( input_image ) = 0 ) Or ( IsImage( output_image ) = 0 ) : ProcedureReturn 0 : EndIf ; Ensure that both images exist.

Protected image_width = ImageWidth( input_image )
Protected image_height = ImageHeight( input_image )

If ( image_width <> ImageWidth( output_image ) ) Or ( image_height <> ImageHeight( output_image ) ) : ProcedureReturn 0 : EndIf ; Ensure that both images are the same size.

Protected image_max_x = image_width - 1
Protected image_max_y = image_height - 1

Protected Dim TempImageArray.a( image_max_x, image_max_y )

Protected pixel, result

If StartDrawing( ImageOutput( input_image ) )
	For x = 0 To image_max_x
		For y = 0 To image_max_y
			pixel = Point( x, y )
			; https://www.purebasic.fr/english/viewtopic.php?t=79054
			; http://poynton.ca/notes/colour_and_gamma/ColorFAQ.txt
			; https://www.tutorialspoint.com/dip/grayscale_to_rgb_conversion.htm
			; https://stackoverflow.com/questions/687261/converting-rgb-to-grayscale-intensity
			TempImageArray( x, y ) = Red( pixel ) * 0.2126 + Green( pixel ) * 0.7152 + Blue( pixel ) * 0.0722 ; ; Y = 0.2126 * R + 0.7152 * G + 0.0722 * B;
		Next
	Next
	StopDrawing()
	If StartDrawing( ImageOutput( output_image ) )
		For x = 0 To image_max_x
			For y = 0 To image_max_y
				pixel = TempImageArray( x, y )
				Plot( x, y, RGB( pixel, pixel, pixel ) )
			Next
		Next
		StopDrawing()
		result = 1
	EndIf
EndIf

ProcedureReturn result
EndProcedure

; --


This version uses 'GrayscaleImageCallback' for a 50% speed boost. Unlike the previous version, it creates a new separate output image within the function and returns the PureBasic ID of the resulting grayscale output image.

; Converts the pixels in 'input_image' to grayscale and returns the PureBasic ID of the resulting output image. The input image will be unaffected with a new image being created for the grayscale version.
; This function requires the 'GrayscaleImageCallback' function above.
; Returns the PureBasic ID of the resulting output image on success, and 0 on failure.

Code: Select all

; - Method 2 using 'CustomFilterCallback' -


Procedure.i GrayscaleImageCallback( x, y, source_color, target_color )
Protected brightness.a = Red( source_color ) * 0.2126 + Green( source_color ) * 0.7152 + Blue( source_color ) * 0.0722
ProcedureReturn RGBA( brightness.a, brightness.a, brightness.a, 255 )
EndProcedure



Procedure.i GrayscaleImage( input_image )
; Converts the pixels in 'input_image' to grayscale and returns the PureBasic ID of the resulting output image. The input image will be unaffected with a new image being created for the grayscale version.
; This function requires the 'GrayscaleImageCallback' function above.
; Returns the PureBasic ID of the resulting output image on success, and 0 on failure.

If IsImage( input_image ) = 0 : ProcedureReturn 0 : EndIf ; Ensure that the input image exists.

Protected image_width = ImageWidth( input_image )
Protected image_height = ImageHeight( input_image )

Protected output_image = CreateImage( #PB_Any, image_width, image_height, 24 )
If output_image = 0 : ProcedureReturn 0 : EndIf ; Ensure that the output image was created.

If StartDrawing( ImageOutput( output_image ) )
	DrawingMode( #PB_2DDrawing_CustomFilter )      
	CustomFilterCallback( @GrayscaleImageCallback() )
	DrawImage( ImageID( input_image ), 0, 0 )
	StopDrawing()
Else
	FreeImage( output_image ) : output_image = 0
EndIf

ProcedureReturn output_image
EndProcedure

; --


Test code using both methods.

Code: Select all



; NOTE: Change the filepath stored in 'G_input_image_filepath.s' to that of a JPEG image that is available on your computer.


UseJPEGImageDecoder()
UseJPEGImageEncoder()

ElapsedMilliseconds() ; Run this once to initialize it.

Global G_input_image_filepath.s = "C:\Users\axe73\Desktop\My Files\Pixabay Animal Images\USED\rabbit-1903016.jpg"

Global G_input_image = LoadImage( #PB_Any, G_input_image_filepath.s )
If G_input_image = 0 : MessageRequester( "FATAL ERROR", "Unable to load input image." ) : End : EndIf

Global G_output_image


; ================


; - Method 1 using an array as an intermediate -


Procedure.i GrayscaleImage1( input_image, output_image )
; Converts the pixels in 'input_image' to grayscale and stores them in 'output_image'. The two images can be the same image or different images.
; The output image should already exist and must be the same size as the input image. A zero will be returned if these conditions aren't met.
; Returns 1 on success and 0 on failure.

If ( IsImage( input_image ) = 0 ) Or ( IsImage( output_image ) = 0 ) : ProcedureReturn 0 : EndIf ; Ensure that both images exist.

Protected image_width = ImageWidth( input_image )
Protected image_height = ImageHeight( input_image )

If ( image_width <> ImageWidth( output_image ) ) Or ( image_height <> ImageHeight( output_image ) ) : ProcedureReturn 0 : EndIf ; Ensure that both images are the same size.

Protected image_max_x = image_width - 1
Protected image_max_y = image_height - 1

Protected Dim TempImageArray.a( image_max_x, image_max_y )

Protected pixel, result

If StartDrawing( ImageOutput( input_image ) )
	For x = 0 To image_max_x
		For y = 0 To image_max_y
			pixel = Point( x, y )
			; https://www.purebasic.fr/english/viewtopic.php?t=79054
			; http://poynton.ca/notes/colour_and_gamma/ColorFAQ.txt
			; https://www.tutorialspoint.com/dip/grayscale_to_rgb_conversion.htm
			; https://stackoverflow.com/questions/687261/converting-rgb-to-grayscale-intensity
			TempImageArray( x, y ) = Red( pixel ) * 0.2126 + Green( pixel ) * 0.7152 + Blue( pixel ) * 0.0722 ; ; Y = 0.2126 * R + 0.7152 * G + 0.0722 * B;
		Next
	Next
	StopDrawing()
	If StartDrawing( ImageOutput( output_image ) )
		For x = 0 To image_max_x
			For y = 0 To image_max_y
				pixel = TempImageArray( x, y )
				Plot( x, y, RGB( pixel, pixel, pixel ) )
			Next
		Next
		StopDrawing()
		result = 1
	EndIf
EndIf

ProcedureReturn result
EndProcedure


; ================


; - Method 2 using 'CustomFilterCallback' -


Procedure.i GrayscaleImageCallback( x, y, source_color, target_color )
Protected brightness.a = Red( source_color ) * 0.2126 + Green( source_color ) * 0.7152 + Blue( source_color ) * 0.0722
ProcedureReturn RGBA( brightness.a, brightness.a, brightness.a, 255 )
EndProcedure



Procedure.i GrayscaleImage2( input_image )
; Converts the pixels in 'input_image' to grayscale and returns the PureBasic ID of the resulting output image. The input image will be unaffected with a new image being created for the grayscale version.
; This function requires the 'GrayscaleImageCallback' function above.
; Returns the PureBasic ID of the resulting output image on success, and 0 on failure.

If IsImage( input_image ) = 0 : ProcedureReturn 0 : EndIf ; Ensure that the input image exists.

Protected image_width = ImageWidth( input_image )
Protected image_height = ImageHeight( input_image )

Protected output_image = CreateImage( #PB_Any, image_width, image_height, 24 )
If output_image = 0 : ProcedureReturn 0 : EndIf ; Ensure that the output image was created.

If StartDrawing( ImageOutput( output_image ) )
	DrawingMode( #PB_2DDrawing_CustomFilter )      
	CustomFilterCallback( @GrayscaleImageCallback() )
	DrawImage( ImageID( input_image ), 0, 0 )
	StopDrawing()
Else
	FreeImage( output_image ) : output_image = 0
EndIf

ProcedureReturn output_image
EndProcedure


; ================


t1 = ElapsedMilliseconds()
G_output_image = CreateImage( #PB_Any, ImageWidth( G_input_image ), ImageHeight( G_input_image ), 24 ) : If G_output_image = 0 : MessageRequester( "FATAL ERROR", "Unable to create output image for GrayscaleImage1." ) : End : EndIf
Global G_result = GrayscaleImage1( G_input_image, G_output_image )
t2 = ElapsedMilliseconds()
If G_result = 0 : MessageRequester( "FATAL ERROR", "GrayscaleImage1 failed." ) : End : EndIf
SaveImage( G_output_image, "output-1.jpg", #PB_ImagePlugin_JPEG, 10, 24 )

; --

t3 = ElapsedMilliseconds()
G_output_image = GrayscaleImage2( G_input_image )
t4 = ElapsedMilliseconds()
If G_output_image
	SaveImage( G_output_image, "output-2.jpg", #PB_ImagePlugin_JPEG, 10, 24 )
Else
	MessageRequester( "FATAL ERROR", "GrayscaleImage2 failed." ) : End
EndIf


MessageRequester( "Time Results (millisecs)", "GrayscaleImage1: " + Str( t2 - t1 ) + "  |  GrayscaleImage2: " + Str( t4 - t3 ) )
Last edited by Axeman on Fri Apr 29, 2022 6:39 am, edited 1 time in total.
User avatar
mk-soft
Always Here
Always Here
Posts: 5386
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Function to create a grayscale version of an image

Post by mk-soft »

Sorry,
but your calculation is wrong

Red: 30%
Green: 59%
Blue: 11%

And yow can use the fast method CustomFilterCallback.

Example

Code: Select all


;-TOP

; ***************************************************************************************

; Comment   : 
; Author    : mk-soft
; File      : 
; Version   : v1.0
; Date      : 28.10.2018

; ***************************************************************************************


EnableExplicit

UseJPEGImageDecoder()
UsePNGImageDecoder()

; Fenster
Enumeration
  #Main
EndEnumeration

; Gadgets
Enumeration
  #ScrollArea
  #Canvas
EndEnumeration

Global exit

Procedure ScaleGrayCallback(x, y, SourceColor, TargetColor)
  Protected light
  light = ((Red(TargetColor) * 30 + Green(TargetColor) * 59 + Blue(TargetColor) * 11) / 100)
  ProcedureReturn RGBA(light, light, light, 255)
EndProcedure

Procedure Draw(image)
  
  Protected x, y, dx, dy, time
  
  dx = GadgetWidth(#Canvas)
  dy = GadgetHeight(#Canvas)
  
  time = ElapsedMilliseconds()
  If StartDrawing(CanvasOutput(#Canvas))
    
    DrawImage(ImageID(image), 0, 0, dx, dy)
    
    DrawingMode(#PB_2DDrawing_CustomFilter)
    CustomFilterCallback(@ScaleGrayCallback())
    
    Box(0, 0, dx, dy)
    
    StopDrawing()
    
  EndIf
  time = ElapsedMilliseconds() - time
  MessageRequester("Time", "" + time + "ms")
EndProcedure

Procedure Main()
  
  Protected Event, file.s, image
  
  If OpenWindow(#Main, #PB_Any, #PB_Any, 1024, 768, "CanvasGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    
    file = OpenFileRequester("Picture", "", "", 0)
    If file
      image = LoadImage(#PB_Any, file)
    Else
      End
    EndIf
    
    ScrollAreaGadget(#ScrollArea, 0, 0, WindowWidth(#Main), WindowHeight(#main), 3840, 2160)
    CanvasGadget(#Canvas, 0, 0, 3840, 2160)
    CloseGadgetList()
      
    Draw(image)
    
    Repeat
      Event = WaitWindowEvent()
      
      Select Event
          
        Case #PB_Event_CloseWindow
          exit = #True
          
      EndSelect
      
    Until exit
    
  EndIf
    
EndProcedure : Main()
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Bitblazer
Enthusiast
Enthusiast
Posts: 736
Joined: Mon Apr 10, 2017 6:17 pm
Location: Germany
Contact:

Re: Function to create a grayscale version of an image

Post by Bitblazer »

TempImageArray( x, y ) = ( Red( pixel) + Green( pixel ) + Blue( pixel ) ) / 3.0
The individual color weights of R, G and B are different, check for example slashdot or this explanation.

For anything about colors, i look into Poynton's color FAQ - see 9. WHAT WEIGHTING OF RED, GREEN AND BLUE CORRESPONDS TO BRIGHTNESS?"
Axeman
User
User
Posts: 89
Joined: Mon Nov 03, 2003 5:34 am

Re: Function to create a grayscale version of an image

Post by Axeman »

Thanks for the replies. I've updated the original post with your suggestions.
BarryG
Addict
Addict
Posts: 3318
Joined: Thu Apr 18, 2019 8:17 am

Re: Function to create a grayscale version of an image

Post by BarryG »

Is it possible to tweak the callback grayscale routine above to make a negative version of an image? Thanks.

[Edit] I worked it out! You just subtract each pixel color from 255 (https://www.geeksforgeeks.org/negative- ... in-matlab/).

So here's what I did:

Code: Select all

Procedure.i ImageNegativeCallback(x,y,source_color,target_color)
  ProcedureReturn RGBA(255-Red(source_color),255-Green(source_color),255-Blue(source_color),255)
EndProcedure
Procedure.i ImageNegative(input_image)
  ; Converts the pixels in 'input_image' to negative and returns the PureBasic ID of the resulting output image.
  ; The input image will be unaffected with a new image being created for the negative version.
  ; This function requires the 'ImageNegativeCallback' function above.
  ; Returns the PureBasic ID of the resulting output image on success,and 0 on failure.
  If IsImage(input_image)=0
    ProcedureReturn 0
  EndIf ; Ensure that the input image exists.
  Protected image_width=ImageWidth(input_image)
  Protected image_height=ImageHeight(input_image)
  Protected output_image=CreateImage(#PB_Any,image_width,image_height,24)
  If output_image=0
    ProcedureReturn 0
  EndIf ; Ensure that the output image was created.
  If StartDrawing(ImageOutput(output_image))
    DrawingMode(#PB_2DDrawing_CustomFilter)
    CustomFilterCallback(@ImageNegativeCallback())
    DrawImage(ImageID(input_image),0,0)
    StopDrawing()
  Else
    FreeImage(output_image)
    output_image=0
  EndIf
  ProcedureReturn output_image
EndProcedure
User avatar
mk-soft
Always Here
Always Here
Posts: 5386
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Function to create a grayscale version of an image

Post by mk-soft »

Looks like it ;)

Code: Select all


;-TOP

; ***************************************************************************************

; Comment   : 
; Author    : mk-soft
; File      : 
; Version   : v2.0
; Date      : 28.10.2018
; Update    : 20.11.2022

; ***************************************************************************************


EnableExplicit

UseJPEGImageDecoder()
UsePNGImageDecoder()

; Fenster
Enumeration
  #Main
EndEnumeration

; Gadgets
Enumeration
  #ScrollArea
  #Canvas
EndEnumeration

Global exit

Procedure ScaleGrayCallback(x, y, SourceColor, TargetColor)
  Protected light
  light = ((Red(TargetColor) * 30 + Green(TargetColor) * 59 + Blue(TargetColor) * 11) / 100)
  ProcedureReturn RGBA(light, light, light, 255)
EndProcedure

Procedure ScaleNegGrayCallback(x, y, SourceColor, TargetColor)
  Protected light
  light = ((Red(TargetColor) * 30 + Green(TargetColor) * 59 + Blue(TargetColor) * 11) / 100)
  light = 255 - light
  ProcedureReturn RGBA(light, light, light, 255)
EndProcedure

Procedure Draw(image)
  
  Protected x, y, dx, dy, time
  
  dx = GadgetWidth(#Canvas)
  dy = GadgetHeight(#Canvas)
  
  time = ElapsedMilliseconds()
  If StartDrawing(CanvasOutput(#Canvas))
    
    DrawImage(ImageID(image), 0, 0, dx, dy)
    
    DrawingMode(#PB_2DDrawing_CustomFilter)
    ;CustomFilterCallback(@ScaleGrayCallback())
    CustomFilterCallback(@ScaleNegGrayCallback())
    
    Box(0, 0, dx, dy)
    
    StopDrawing()
    
  EndIf
  time = ElapsedMilliseconds() - time
  MessageRequester("Time", "" + time + "ms")
EndProcedure

Procedure Main()
  
  Protected Event, file.s, image
  
  If OpenWindow(#Main, #PB_Any, #PB_Any, 1024, 768, "CanvasGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
    
    file = OpenFileRequester("Picture", "", "", 0)
    If file
      image = LoadImage(#PB_Any, file)
    Else
      End
    EndIf
    
    ScrollAreaGadget(#ScrollArea, 0, 0, WindowWidth(#Main), WindowHeight(#main), 1920, 1080)
    CanvasGadget(#Canvas, 0, 0, 1920, 1080)
    CloseGadgetList()
      
    Draw(image)
    
    Repeat
      Event = WaitWindowEvent()
      
      Select Event
          
        Case #PB_Event_CloseWindow
          exit = #True
          
      EndSelect
      
    Until exit
    
  EndIf
    
EndProcedure : Main()
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
jacdelad
Addict
Addict
Posts: 1473
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: Function to create a grayscale version of an image

Post by jacdelad »

There are also some other recommendations how to weigh each channel, depending on wehere it's used. I expanded the code from the first post a bit:

Code: Select all

Enumeration Greyscale_Filter
  #Greyscale_Unweighted     ;All channels equal (not recommended)
  #Greyscale_Weighted       ;=BT709 (standard)
  #Greyscale_CCIR601        ;CCIR 601, Most SD videoformats
  #Greyscale_BT709          ;BT-709, ITU-R recommendation (modern HDTVstandard)
  #Greyscale_SMPTERP145     ;SMPTE RP 145, trasitional HDTV (1035i)
EndEnumeration
Procedure.i GIC_Unweighted(x,y,source_color,target_color)
  Protected brightness.a=(Red(source_color)+Green(source_color)+Blue(source_color))/3
  ProcedureReturn RGB(brightness,brightness,brightness)
EndProcedure
Procedure.i GIC_Weighted(x,y,source_color,target_color)
  Protected brightness.a=Red(source_color)*0.2126+Green(source_color)*0.7152+Blue(source_color)*0.0722
  ProcedureReturn RGB(brightness,brightness,brightness)
EndProcedure
Procedure.i GIC_CCIR601(x,y,source_color,target_color)
  Protected brightness.a=Red(source_color)*0.299+Green(source_color)*0.587+Blue(source_color)*0.114
  ProcedureReturn RGB(brightness,brightness,brightness)
EndProcedure
Procedure.i GIC_SMPTERP145(x,y,source_color,target_color)
  Protected brightness.a=Red(source_color)*0.212+Green(source_color)*0.701+Blue(source_color)*0.087
  ProcedureReturn RGB(brightness,brightness,brightness)
EndProcedure
Procedure.i GrayscaleImage(input_image,Filter=#Greyscale_Weighted);Gibt eine Graustufenversion des Bildes zurück
  If IsImage(input_image)
    Protected image_width=ImageWidth(input_image),image_height=ImageHeight(input_image),output_image=CreateImage(#PB_Any,image_width,image_height,24)
    If IsImage(output_image)
      If StartDrawing(ImageOutput(output_image))
        DrawingMode(#PB_2DDrawing_CustomFilter)      
        Select Filter
          Case #Greyscale_Unweighted
            CustomFilterCallback(@GIC_Unweighted())
          Case #Greyscale_Weighted,#Greyscale_BT709
            CustomFilterCallback(@GIC_Weighted())
          Case #Greyscale_CCIR601
            CustomFilterCallback(@GIC_CCIR601())
          Case #Greyscale_SMPTERP145
            CustomFilterCallback(@GIC_SMPTERP145())
        EndSelect
        DrawImage(ImageID(input_image),0,0)
        StopDrawing()
      Else
        FreeImage(output_image):output_image=0
      EndIf
    EndIf
    ProcedureReturn output_image
  Else
    ProcedureReturn 0
  EndIf
EndProcedure
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
Post Reply