Page 1 of 2

Programatically determine if an image is colour or grayscale

Posted: Tue Jan 12, 2021 4:13 pm
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

Re: Programatically determine if an image is colour or grays

Posted: Tue Jan 12, 2021 4:41 pm
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

Re: Programatically determine if an image is colour or grays

Posted: Tue Jan 12, 2021 11:20 pm
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.

Re: Programatically determine if an image is colour or grays

Posted: Wed Jan 13, 2021 4:41 am
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.

Re: Programatically determine if an image is colour or grays

Posted: Wed Jan 13, 2021 9:20 am
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.

Re: Programatically determine if an image is colour or grays

Posted: Wed Jan 13, 2021 10:07 am
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.

Re: Programatically determine if an image is colour or grays

Posted: Wed Jan 13, 2021 11:11 am
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.

Re: Programatically determine if an image is colour or grays

Posted: Tue Jan 19, 2021 10:35 pm
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^^

Re: Programatically determine if an image is colour or grays

Posted: Wed Jan 20, 2021 5:06 pm
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

Re: Programatically determine if an image is colour or grays

Posted: Wed Jan 20, 2021 6:14 pm
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

Re: Programatically determine if an image is colour or grays

Posted: Thu Jan 21, 2021 1:22 am
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.

Re: Programatically determine if an image is colour or grays

Posted: Thu Jan 21, 2021 2:04 am
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

Re: Programatically determine if an image is colour or grays

Posted: Fri Jan 22, 2021 1:42 am
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.

Re: Programatically determine if an image is colour or grays

Posted: Fri Jan 22, 2021 3:52 am
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:

Re: Programatically determine if an image is colour or grays

Posted: Sun Jan 24, 2021 11:00 am
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")