VBA – WIA – Convert Image to Grayscale

Convert To Grayscale

Today, I thought I’d add to my library of image manipulation functions

and I set out to see if there was a way, using WIA (Microsoft Windows Image Acquisition Library), to convert an image to black and white, well to be more precise to convert it to grayscale.

I originally searched high and low and could find nothing already done that I could utilize in any way. Oh there’s stuff out there for .net, C/C++, … just not VBA and certainly not relating to using WIA to perform the conversion. So I decided to try and create something myself (I have to stop doing this).

Not knowing a thing about the difference between a color image and a grayscale image (from a technical, pixel, standpoint) I set out to do some research.

I asked in specialized forums, but my questions went unanswered for days, even weeks. It took me a considerable amount of time as I didn’t have the proper terminology for performing searches, but eventually, I found what I was after!

Now, I was ready to build a VBA function to perform the conversion. After many attempts, I manage to create a function!
 

The Helper Functions

As you will see in the main conversion function, we need to retrieve each pixel’s ARGB data and break it down to convert it.  Originally, I started by using the functions found at:

That said, I ended up improving upon the GetAlpha(), GetRed(), GetGreen() & GetBlue() functions by creating a single function to retrieve all 4 values in 1 call.  This change alone greatly improved performance.

Below is what I am currently using:

Private Function Get4ByteHex(val) As String
    Dim s                     As String

    s = Hex(val)
    Do While Len(s) < 8
        s = "0" & s
    Loop
    Get4ByteHex = Right(s, 8)
End Function

Private Function Get1ByteHex(val) As String
    Dim s                     As String

    s = Hex(val)
    Do While Len(s) < 2
        s = "0" & s
    Loop
    Get1ByteHex = Right(s, 2)
End Function

Private Function BuildARGB(a, r, g, b) As Long
    Dim s                     As String

    s = "&h" & Get1ByteHex(a) & Get1ByteHex(r) & Get1ByteHex(g) & Get1ByteHex(b)
    BuildARGB = CLng(s)
End Function

Private Function GetARGB(val, ByRef lApha As Long, ByRef lRed As Long, ByRef lGreen As Long, _
                         ByRef lBlue As Long)
    Dim s                     As String

    s = Get4ByteHex(val)
    lApha = CLng("&h" & Left(s, 2))
    lRed = CLng("&h" & Mid(s, 3, 2))
    lGreen = CLng("&h" & Mid(s, 5, 2))
    lBlue = CLng("&h" & Right(s, 2))
End Function

 

The Conversion Function

The actual conversion function is:

'---------------------------------------------------------------------------------------
' Procedure : WIA_ConvertImageToGrayScale
' Author    : Daniel Pineault, CARDA Consultants Inc.
' Website   : http://www.cardaconsultants.com
' Purpose   : Convert a color image to grayscale
' Copyright : The following is release as Attribution-ShareAlike 4.0 International
'             (CC BY-SA 4.0) - https://creativecommons.org/licenses/by-sa/4.0/
' Req'd Refs: Late Binding  -> None required
'             Early Binding -> Microsoft Windows Image Acquisition Library vX.X
'
' Input Variables:
' ~~~~~~~~~~~~~~~~
' sOriginalFile : Fully qualified path and filename of the color image to convert
' sSaveAsFile   : Fully qualified path and filename for the grayscale output file
' bOverwrite    : Whether it should overwrite the output file if it already exists
'
' Usage:
' ~~~~~~
' ? WIA_ConvertImageToGrayScale("C:\Users\Daniel\flower.png", _
'                               "C:\Users\Daniel\Desktop\flower.png")
'   Returns -> True if the conversion is successful
'              False if the conversion fails
'
' Revision History:
' Rev       Date(yyyy-mm-dd)        Description
' **************************************************************************************
' 1         2022-12-16              Initial Release
'---------------------------------------------------------------------------------------
Public Function WIA_ConvertImageToGrayScale(sOriginalFile As String, _
                                            sSaveAsFile As String, _
                                            Optional bOverwrite As Boolean = False) As Boolean
    On Error GoTo Error_Handler
    #Const WIA_EarlyBind = False    'True => Early Binding / False => Late Binding
    #If WIA_EarlyBind = True Then
        Dim oIF               As WIA.ImageFile
        Dim oIP               As WIA.ImageProcess
        Dim oV                As WIA.Vector

        Set oIF = New WIA.ImageFile
        Set ooIP = New WIA.ImageProcess
    #Else
        Dim oIF               As Object
        Dim oIP               As Object
        Dim oV                As Object

        Set oIF = CreateObject("WIA.ImageFile")
        Set oIP = CreateObject("WIA.ImageProcess")
    #End If
    Dim sSaveAsFileWOExt      As String
    Dim sOriginalFileExt      As String
    Dim i                     As Long
    Dim lAlpha                As Long    '0-255
    Dim lRed                  As Long    '0-255
    Dim lGreen                As Long    '0-255
    Dim lBlue                 As Long    '0-255
    Dim lChannelValue         As Long    '0-255

    'Check to see if the sSaveAsFile exists of not
    If Len(Dir(sSaveAsFile)) > 0 Then
        If bOverwrite = True Then
            Kill sSaveAsFile
        Else
            Exit Function
        End If
    End If

    'Ensure we are outputting with the same extension as the original file
    sSaveAsFileWOExt = Left(sSaveAsFile, InStrRev(sSaveAsFile, ".") - 1)
    sOriginalFileExt = Right(sOriginalFile, Len(sOriginalFile) - InStrRev(sOriginalFile, "."))
    sSaveAsFile = sSaveAsFileWOExt & "." & sOriginalFileExt

    oIF.LoadFile sOriginalFile

    Set oV = oIF.ARGBData 'Retrieve the image's pixel ARGB data
    'Pixel by pixel, convert color ARGB to grayscale ARGB
    For i = 1 To oV.Count
        Call GetARGB(oV(i), lAlpha, lRed, lGreen, lBlue)

        If (lRed = 0 And lGreen = 0 And lBlue = 0) _
           Or (lRed = 255 And lGreen = 255 And lBlue = 255) Then
            'oV(i) = BuildARGB(lAlpha, lRed, lGreen, lBlue)
            'Nothing to do here as it is already white or black
            'Could optionally include cases where  lRed = lGreen = lBlue here as well
            '   but then it wouldn't follow the conversion equation/color tone
        Else
            'lChannel calc can be changed as long as the weights add up to a total of 1
            'produces different shades of gray
            lChannelValue = CLng(0.299 * lRed + 0.587 * lGreen + 0.114 * lBlue) 'YUV
'            lChannelValue = CLng(0.2126 * lRed + 0.7152 * lGreen + 0.0722 * lBlue) 'HDTV/ATSC
'            lChannelValue = CLng(0.2627 * lRed + 0.678 * lGreen + 0.0593 * lBlue) 'HDR
            'lChannelValue = CLng(0.333 * lRed + 0.333 * lGreen + 0.333 * lBlue)
            'lChannelValue = clng(0.22 * lRed + 0.44 * lGreen + 0.34 * lBlue)
            'lChannelValue = CLng(0.12 * lRed + 0.34 * lGreen + 0.54 * lBlue) 'darker grayscale
            lRed = lChannelValue
            lGreen = lChannelValue
            lBlue = lChannelValue
            oV(i) = BuildARGB(lAlpha, lRed, lGreen, lBlue)
        End If
    Next
    
    'Apply the grayscale ARGB data to the image
    oIP.Filters.Add oIP.FilterInfos("ARGB").FilterID
    Set oIP.Filters(1).Properties("ARGBData") = oV
    Set oIF = oIP.Apply(oIF)

    'Save the file
    oIF.SaveFile sSaveAsFile
    WIA_ConvertImageToGrayScale = True

Error_Handler_Exit:
    On Error Resume Next
    Set oV = Nothing
    Set oIP = Nothing
    Set oIF = Nothing
    Exit Function

Error_Handler:
    MsgBox "The following error has occurred" & vbCrLf & vbCrLf & _
           "Error Source: WIA_ConvertImageToGrayScale" & vbCrLf & _
           "Error Number: " & Err.Number & vbCrLf & _
           "Error DescroIPtion: " & Err.Description & _
           Switch(Erl = 0, "", Erl <> 0, vbCrLf & "Line No: " & Erl) _
           , vbOKOnly + vbCritical, "An Error has Occurred!"
    Resume Error_Handler_Exit
End Function

Notice that I included multiple variations of the lChannelValue calculation as you can modify to to suit the depth of color you are looking for. Thus, by modifying the calculation the end results change ever so slightly as demonstrated by the image below.

Grayscale Image Comparison

Usage Example

It's usage is very straightforward and would look like:

? WIA_ConvertImageToGrayScale("C:\Users\Daniel\flower.png", _
                              "C:\Users\Daniel\Desktop\flower.png")

and this will return a value of:

  • True if the conversion is successful
  • False if the conversion fails

 
Do note that this process can be a little slow depending on the size of the image being processed as it does have to process each pixel individually! So be patient.

This might be a good process to integrate a progress bar into?!

 

Other Resources on the Subject

7 responses on “VBA – WIA – Convert Image to Grayscale

  1. Dan

    I though had a left a comment on your Youtube video for WIA Resizing about the work I had been doing on WIA recently, but perhaps I forgot to press send.

    This is great article – I hadn’t thought about converting images to grey scale. I’ve been looking at manipulating the alpha channel recently – it’s really interesting how much control WIA gives you for so few lines of code.

      1. Dan

        Do you have a copy of the WIA SDK (zip file) containing sample scripts, etc and (importantly) the CHM Help File? It gives a bit more information.

        I use it for loading PNG files into userform controls, and recently have been exploring ways to use it to display animated GIFs.

          1. Dan

            My apologies – I had meant to respond sooner but ’tis the season for traffic jams. It wasn’t quite as easy to search for as I thought it was, and couldn’t remember name of the website. Then I recalled I sitll had the original ZIP file, and thanks to the magic of Alternative Data Streams (as per your video the other day), I managed to get the URL of it! Here it is:

            http://vbnet.mvps.org/code/imageapi/mswaidll.htm

            (FYI: it has the WIA DLL in the ZIP as well – there are two versions of it (I think), and as I understand it, MS were keen to replace v1 with v2 given that v1 was unintentionally effective at removing DRM protections on digital media!! I mention this in case you AV takes issue with it)

  2. yacov turak

    I apologize if my English is not clear enough, I use Google Translate.
    First of all I want to thank you for all the most helpful posts, in every topic you get to the root of the matter and are not afraid of hard work, so thank you very much.
    What I am trying to do is to determine whether the color of text displayed on an image will be white or black, based on the background of the image, that is, if the background of the image is generally light, the color of the text will be white, if the background is dark, the color of the text should be black.
    I found some examples that talk about it (for example here: https://stackoverflow.com/questions/1855884/determine-font-color-based-on-background-color), but many concepts like gamma and linear etc. were not understood by me , and all code examples are not in VBA.
    I assume that combining your code with the solutions discussed on stackoverflow may end the matter.
    I would greatly appreciate it if you could direct me to the next step.

  3. Ben

    Thank you for the post! I’m having trouble applying this on mass in PowerPoint. I have a program that takes screenshots from a folder and puts each into its own slide in the PowerPoint. In doing so, I want to both invert the colors and convert them to grayscale. VBA has msoPictureGrayscale built in that works great, but no built in color inversion that I can find.

    I’m trying to insert and edit your script to apply to my screenshots. Inversion is done by simply subtracting your color from white, so this is one of the changes I’ve made (see below). The other trouble is defining the variables since I’m not using a single image file.

    lRed = 255 – lChannelValue
    lGreen = 255 – lChannelValue
    lBlue = 255 – lChannelValue

    Any ideas would be much appreciated.