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.

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")