Programatically determine if an image is colour or grayscale

Windows specific forum
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Programatically determine if an image is colour or grayscale

Post by IdeasVacuum »

Import (Load) an image - it is usually a colour image. Sometimes though, it's grayscale. So I'm looking for a fast way to test every pixel until a non-grayscale pixel is found, equals image is colour. The below code, by Wilbert, counts the number of unique colours (it's fast!):

Code: Select all

Procedure CountColours()
;#------------------------
;by Wilbert
Protected.i x, y, max_x, max_y, c, count, m
Protected Dim m.a($1FFFFF)

               StartDrawing(ImageOutput(#Img))

               max_x = ImageWidth(#Img) - 1
               max_y = ImageHeight(#Img) - 1

               For y = 0 To max_y

                      For x = 0 To max_x

                              c = Point(x, y) & $FFFFFF

                              If m(c >> 3) & 1 << (c & 7) = 0

                                        m(c >> 3) | 1 << (c & 7)

                                        count + 1
                              EndIf
                      Next
               Next

               StopDrawing()

               ProcedureReturn count
EndProcedure
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: Programatically determine if an image is colour or grays

Post by IdeasVacuum »

Actually, it doesn't take long to save the image as PNG (if not already PNG) and then read the header, which specifically identifies colour type:

DWORD Width: ;Width of image in pixels
DWORD Height: ;Height of image in pixels
BYTE BitDepth: ;Bits per pixel or per sample
BYTE ColorType: ;Color interpretation indicator
BYTE Compression: ;Compression type indicator
BYTE Filter: ;Filter type indicator
BYTE Interlace: ;Type of interlacing scheme used

ColorType:
0 grayscale
2 truecolor
3 indexed color
4 grayscale with alpha data
6 truecolor with alpha data
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
pjay
Enthusiast
Enthusiast
Posts: 163
Joined: Thu Mar 30, 2006 11:14 am

Re: Programatically determine if an image is colour or grays

Post by pjay »

I would've thought encoding and saving PNGs would be anything but fast mate.

This should be pretty much as fast as you can get without ASM (which, co-incidently, Wilbert happens to be brilliant at.)

Code: Select all

Procedure.a IsImageGrayscale(Img) ;/ for 24 or 32 bit images
  Protected.l X, Y,Width, Height, *pos.rgba, PixelStride = 3, isGrayscale = #True

  If Not IsImage(Img) : ProcedureReturn : EndIf
 
  StartDrawing(ImageOutput(Img))
    If (DrawingBufferPixelFormat() & #PB_PixelFormat_32Bits_RGB) > 0 Or (DrawingBufferPixelFormat() & #PB_PixelFormat_32Bits_BGR) : PixelStride = 4 : EndIf
    Width = ImageWidth(Img) - 1 : Height = ImageHeight(Img) - 1
   
    For Y = 0 To Height
      *pos = DrawingBuffer() + (Y * DrawingBufferPitch())
      For X = 0 To Width
        If *pos\r <> *pos\g Or *pos\g <> *pos\b : isGrayscale = #False : Break 2 : EndIf
        *pos + PixelStride
      Next
    Next
  StopDrawing()
  
  ProcedureReturn isGrayscale
EndProcedure

Edited to account for buffer pitch.
Edited again to account to catch ReversedY DrawingBufferPixelFormat.
Last edited by pjay on Sat Jun 17, 2023 12:37 pm, edited 3 times in total.
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: Programatically determine if an image is colour or grays

Post by IdeasVacuum »

Hello pjay
I would've thought encoding and saving PNGs would be anything but fast mate
It is surprisingly fast, save the file, read back the first 26 bytes. It's not pretty or efficient though.

Thanks for sharing your code, it is an excellent method.
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Programatically determine if an image is colour or grays

Post by wilbert »

pjay wrote:

Code: Select all

Procedure.a IsImageGrayscale(Img)

  If Not IsImage(Img) : ProcedureReturn -1 : EndIf

  Structure rgba : r.a : g.a : b.a : EndStructure  
  Protected.i OnPixel, *pos.rgba, PixelStride, PixelCount, isGrayscale = #True
  
  StartDrawing(ImageOutput(Img))
    *pos = DrawingBuffer()
    PixelStride = 3
    
    If DrawingBufferPixelFormat() = #PB_PixelFormat_32Bits_RGB : PixelStride = 4 : EndIf
    If DrawingBufferPixelFormat() = #PB_PixelFormat_32Bits_BGR : PixelStride = 4 : EndIf
    
    PixelCount = (ImageWidth(Img) * ImageHeight(Img)) - 1
    
    For OnPixel = 0 To PixelCount
      If *pos\r <> *pos\g Or *pos\g <> *pos\b : isGrayscale = #False : Break : EndIf
      *pos + PixelStride
    Next
    
  StopDrawing()

  ProcedureReturn isGrayscale
EndProcedure
You can't do it like this. It won't work for 24 bit images. You have to consider padding as well.
If you have for example a 3 x 3 pixel 24 bit image, a line isn't 9 bytes but 12 bytes.
Windows (x64)
Raspberry Pi OS (Arm64)
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Programatically determine if an image is colour or grays

Post by wilbert »

IdeasVacuum wrote:Actually, it doesn't take long to save the image as PNG (if not already PNG) and then read the header, which specifically identifies colour type:
EncodeImage would be more efficient compared to saving and loading again.
But I doubt if it is a reliable method.
A black and white image could also be encoded as indexed.
Windows (x64)
Raspberry Pi OS (Arm64)
pjay
Enthusiast
Enthusiast
Posts: 163
Joined: Thu Mar 30, 2006 11:14 am

Re: Programatically determine if an image is colour or grays

Post by pjay »

wilbert wrote: You can't do it like this. It won't work for 24 bit images. You have to consider padding as well.
If you have for example a 3 x 3 pixel 24 bit image, a line isn't 9 bytes but 12 bytes.
You're quite right, edited original code to correct.
User avatar
Lunasole
Addict
Addict
Posts: 1091
Joined: Mon Oct 26, 2015 2:55 am
Location: UA
Contact:

Re: Programatically determine if an image is colour or grays

Post by Lunasole »

I didn't compared it, but encoding/saving to PNG anyway should be much slower than loop which checks all pixels.
Also you probably will be unable to differentiate PNG grayscale and PNG indexed colors (in case if all colors of indexed palette will be grayscale).
Variant using drawing buffer should be fastest, if not try to write own parsers for different formats and checking raw bytes^^
"W̷i̷s̷h̷i̷n̷g o̷n a s̷t̷a̷r"
infratec
Always Here
Always Here
Posts: 6818
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Programatically determine if an image is colour or grays

Post by infratec »

A bit more optimized:

Code: Select all

Procedure.i IsImageGrayscale(Img)
  
  Structure rgba : r.a : g.a : b.a : EndStructure
  
  Protected.i X, Y, Width, Height, PixelBytes, LinePaddingBytes, isGrayscale
  Protected *pos.rgba
  
  
  If IsImage(Img)
    
    If StartDrawing(ImageOutput(Img))
      
      isGrayscale = #True
      
      PixelBytes = 3
      If DrawingBufferPixelFormat() = #PB_PixelFormat_32Bits_RGB : PixelBytes = 4 : EndIf
      If DrawingBufferPixelFormat() = #PB_PixelFormat_32Bits_BGR : PixelBytes = 4 : EndIf
      
      Width = ImageWidth(Img)
      LinePaddingBytes = DrawingBufferPitch() - (PixelBytes * Width)
      Width - 1
      Height = ImageHeight(Img) - 1
      
      *pos = DrawingBuffer()
      For Y = 0 To Height
        For X = 0 To Width
          If *pos\r <> *pos\g Or *pos\g <> *pos\b : isGrayscale = #False : Break 2 : EndIf
          *pos + PixelBytes
        Next X
        *pos + LinePaddingBytes
      Next Y
      
      StopDrawing()
    Else
      isGrayscale = - 1
    EndIf
  Else
    isGrayscale = - 1
  EndIf
  
  ProcedureReturn isGrayscale
  
EndProcedure
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4637
Joined: Sun Apr 12, 2009 6:27 am

Re: Programatically determine if an image is colour or grays

Post by RASHAD »

Hi
I am not sure if it is right but I tested it with gray Images made by PB and downloaded images too
Unless it's merged image :)

Code: Select all


UseJPEG2000ImageDecoder()
UseJPEG2000ImageEncoder()
UseJPEGImageDecoder()
UseJPEGImageEncoder()
UsePNGImageDecoder()
UsePNGImageEncoder()
UseTGAImageDecoder()
UseTIFFImageDecoder()

Procedure.i IsImageGrayscale(Img)
  StartDrawing(ImageOutput(img))
  For i = 0 To ImageWidth(0)-1
    color = Point(10,i)
    If color <> 0 And color <> $FFFFFF
      flag = 1
      Break
    EndIf
  Next
  r = Red(color)
  g = Green(color)
  b = Blue(color)
  StopDrawing()
  If flag = 1 And r = g And g = b
    flag = 0
    Debug "Image is gray scaled"
  Else
    Debug "Not gray or Color = Black or White "
  EndIf
  If IsImage(0)
    FreeImage(0)
  EndIf
EndProcedure

flags = #PB_Window_SystemMenu| #PB_Window_MaximizeGadget| #PB_Window_MinimizeGadget| #PB_Window_ScreenCentered | #PB_Window_SizeGadget
OpenWindow(0,0,0,400,300,"Test",Flags)

ButtonGadget(1,10,270,60,20,"Load")

Repeat
  
  Select WaitWindowEvent()
      
    Case #PB_Event_CloseWindow
      Quit = 1 
      
    Case #PB_Event_Gadget
      Select EventGadget()
        Case 1
          File$ = OpenFileRequester("Please choose file to load", "C:\image.bmp", "All files (*.*)|*.*", 0)
          If File$
            LoadImage(0,File$)
            If IsImage(0)
              IsImageGrayscale(0)
            EndIf
          EndIf
          
      EndSelect
      
  EndSelect
  
Until Quit = 1
End
Modified
Last edited by RASHAD on Thu Jan 21, 2021 1:59 am, edited 1 time in total.
Egypt my love
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: Programatically determine if an image is colour or grays

Post by IdeasVacuum »

Hi Rashad

I think we have to find a colour pixel rather than a grayscale one, since a colour image could have areas of grey, but a grayscale picture cannot have areas of colour.
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4637
Joined: Sun Apr 12, 2009 6:27 am

Re: Programatically determine if an image is colour or grays

Post by RASHAD »

Hi IV
Previous post updated
I think Color image can include segment of GrayScale image and GrayScale image can include segment of Color image as well
Egypt my love
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: Programatically determine if an image is colour or grays

Post by IdeasVacuum »

Hi Rashad

Well, for the very first time, I have to disagree with you
and GrayScale image can include segment of Color image as well
The definition of a grayscale image:

A grayscale (or gray level) image is simply one in which the only colours are shades of gray. Often, the grayscale intensity is stored as an 8-bit integer giving 256 possible different shades of gray from black to white.

So if you take a grayscale image and add a segment of colour, you no longer have a grayscale image.

That also means the only way to find out if the image is only black and white is to test every pixel until you hit a non-black or non-white pixel.
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: Programatically determine if an image is colour or grays

Post by IdeasVacuum »

That also means the only way to find out if the image is only black and white is to test every pixel until you hit a non-black or non-white pixel.
... I got that wrong. Once the image is verified as grayscale, if it has only 2 unique colours, it is black and white if you find just one white pixel and just one black pixel. :mrgreen:
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: Programatically determine if an image is colour or grays

Post by Keya »

You can't just assume that if the image palette only has 2 colours that it's B&W - it might be red & blue for example.

To determine B&W, ensure every pixel = RGB(0,0,0) or RGB(255,255,255)
To determine grayscale, ensure every pixel = ((R = G) & (G = B)) .... that is, R=G=B

Here's a routine i wrote a few years ago which determines the percentage of grayscale pixels (100% = confirmed grayscale). It can easily be tweaked to detect B&W instead, but I haven't included that. It's pretty fast, working directly with memory pointers in the image data.

Code: Select all

Structure RGBA
  r.a
  g.a
  b.a
  a.a
EndStructure


Procedure.f DetermineFileGrayscalePct(sPNGfile.s)

  Protected *inrgba.RGBA, *red.Ascii, *green.Ascii, *blue.Ascii, *alpha.Ascii, x,y
  Protected hImg, depth, ipixfmt, irowlen, steprow, width, height, bytesperpixel, pixels
  
  hImg = LoadImage(#PB_Any, sPNGfile)
  If hImg = 0
    MessageRequester("Error", "Couldnt load " + sPNGfile): End
  EndIf
  
  width = ImageWidth(hImg)
  height = ImageHeight(hImg)
  If width < 1 Or height < 1
    MessageRequester("Error", "Invalid dimensions " + Str(width) + " x " + Str(height)): End
  EndIf
  pixels = width * height
  depth = ImageDepth(hImg)
  bytesperpixel = depth >> 3
  
  If StartDrawing(ImageOutput(hImg))
    
    *inrgba = DrawingBuffer()    
    irowlen = DrawingBufferPitch()
    ipixfmt = DrawingBufferPixelFormat()
    
    If ipixfmt & #PB_PixelFormat_ReversedY  ;start from the bottom and work upwards
      steprow = (0-irowlen-irowlen) + (irowlen - (width * bytesperpixel))
      *inrgba + (irowlen * height) - irowlen
    Else  ;start from the top and work down
      steprow = (irowlen - (width * bytesperpixel)) 
    EndIf 
    
    For y = 1 To height
      For x = 1 To width        
        If (*inrgba\r = *inrgba\g) And (*inrgba\g = *inrgba\b)  ;if R=G and G=B, it's a grayscale pixel
          GrayCnt+1
        Else
          NonGray+1
        EndIf            
        *inrgba + bytesperpixel
      Next x
      *inrgba + steprow
    Next y    
    StopDrawing()
    
  EndIf
  FreeImage(hImg)
  MessageRequester("Pixels", "Pixels=" + Str(width*height) + " GrayCnt=" + Str(GrayCnt) + " Non=" + Str(Nongray))
  ProcedureReturn (NonGray/GrayCnt) * 100
EndProcedure


UsePNGImageDecoder()
sFile.s = "H:\halfgray.png"
Pct.f = DetermineFileGrayscalePct(sFile)
MessageRequester("Grayscale Detect", "This image is " + StrF(Pct,3)+"% grayscale")
Post Reply