B4J Question predominant color

Blueforcer

Well-Known Member
Licensed User
Longtime User
I need to find the predominant instead of the average color in a picture.
To find the average color i resize the picture to 1x1 and get this pixelcolor.

Can anyone help me to get the predominant?
Thanks
 

MarkusR

Well-Known Member
Licensed User
Longtime User
i would reduce the pixel colors into a table with some colors.
read pixel > coarse color > find in table (if not there add) > count +1
sort the list, the most count wins.
B4X:
type mycoarsecolor(r as byte,g as byte,b as byte,count as int)
dim List1 as List
dim CoarseColor as mycoarsecolor
List1.add(CoarseColor)
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime User
This may do what you want
B4X:
Sub Process_Globals
 Private fx As JFX
 Private MainForm As Form
 Dim im As Image
 Dim iv As ImageView
 Dim colours As Map
 Dim la As Label
End Sub
Sub AppStart (Form1 As Form, Args() As String)
 MainForm = Form1
 MainForm.Show
 im.Initialize("c:/temp","icontest.png") ' the image file
 iv.Initialize("") ' just for display on form
 iv.SetImage(im)
 MainForm.RootPane.AddNode(iv,10,50,150,150)
 la.Initialize("") ' to display the colour (text colour is the main colour found)
 la.Text = "This is the main colour in the image"
 MainForm.RootPane.AddNode(la,10,10,200,20)
 colours.Initialize ' initialize the map
 Dim st As Long = DateTime.now ' get start time
 For x = 0 To im.width-1 ' read pixels in the image
  For y = 0 To im.height-1
   Dim c As String
   c = im.GetPixel(x,y) ' get the pixel colour
   If colours.ContainsKey(c) Then  ' check if in map already
    Dim cnt As Int =  colours.Get(c)+1  ' increment count
    colours.Put(c,cnt)
   Else
    colours.Put(c,1) ' set count to 1
   End If
  Next
 Next
 Dim pixelcount As Int = 0 ' the count of pixels
 Dim maincolour As Int = 0 ' main colour found so far
 For t = 0 To colours.Size -1
  If colours.GetValueAt(t)> pixelcount Then ' read the map for the largest pixel count and store the colour
   pixelcount = colours.GetValueAt(t)
   maincolour = colours.GetKeyAt(t)
  End If
 Next
 Log("Took "&(DateTime.Now - st)&"ms") ' display timing info
 la.TextColor = fx.Colors.From32Bit(maincolour)  ' set label text to main colour found
 CSSUtils.SetBackgroundColor(la,fx.Colors.From32Bit(-Bit.And(0x00ffffff, maincolour))) ' set bg to opposite colour
 Log("The main colour 0x"&Bit.ToHexString(maincolour)&" occurs "&pixelcount&" times in the image")
 Log("There were "&colours.Size&" different colours in the image")
End Sub
'Return true to allow the default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
 Return True
End Sub

If you want to ignore the transparency value of the pixels then use
B4X:
c = Bit.And(0x00ffffff,im.GetPixel(x,y))
Currently the example code treats same colour with varying transparency as different colours.
 
Last edited:
Upvote 0

Blueforcer

Well-Known Member
Licensed User
Longtime User
Thanks this work. combined Daestrum`s coode with jBitmapCreator
But its quit a heavy process. my whole process took 90ms to find the predominant color instead of 14ms for the average color

i found this:
because i cant read JAVA, maybe someone can eplain me how he done it:
https://github.com/SvenWoltmann/color-thief-java
0.712 ms is nice
 
Last edited:
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
about time, i would scale the image before reading the pixels or using step .. at the x,y loops.
 
Upvote 0

Blueforcer

Well-Known Member
Licensed User
Longtime User
great i resize the pic to the half and its nearly the same time now.
to understand the background:
I code an ambilight server wich capture given zones on the screen and send it to Wifi RGB bulbs. So its has to run as fast as possible
 
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
me thought scaling a full hd into 256x256 pixels by gpu in realtime :)
 
Upvote 0

Blueforcer

Well-Known Member
Licensed User
Longtime User
one further question.
is there a faster way:

B4X:
Sub getcolor As Int
    Private awt As AWTRobot
    Dim capture As B4XBitmap
    Dim resizeCapture As Image
    awt.ScreenCurrentRectangleSetAsArbitrary(CaptureValues(2),CaptureValues(3),CaptureValues(4),CaptureValues(5))
    capture = BytesToImage(awt.ScreenCaptureAsByteArray)
    resizeCapture = capture.Resize(1,1,True)
    Return resizeCapture.GetPixel(0,0)
End Sub

Public Sub BytesToImage(bytes() As Byte) As Image
    Dim in As InputStream
    in.InitializeFromBytesArray(bytes, 0, bytes.Length)
    Dim bmp As Image
    bmp.Initialize2(in)
    Return bmp
End Sub
 
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
what is your current time
desktop capture in full hd + resize to 256x256 in ms ?

my current experiment
B4X:
ImageView1.SetImage(CaptureIt)

Sub CaptureIt As Image

    Dim start As Long
    Dim ende As Long

    start = DateTime.Now

    Dim OutputStream As OutputStream
    OutputStream.InitializeToBytesArray(0)
 
     Dim robot, toolkit, rectangle, ImageIO As JavaObject
     robot.InitializeNewInstance("java.awt.Robot", Null)
     toolkit.InitializeStatic("java.awt.Toolkit")
     Dim rectangle As JavaObject
     rectangle.InitializeNewInstance("java.awt.Rectangle", Array As Object( _
       toolkit.RunMethodJO("getDefaultToolkit", Null).RunMethod("getScreenSize", Null)))
     Dim image As JavaObject = robot.RunMethod("createScreenCapture", Array As Object(rectangle))
     ImageIO.InitializeStatic("javax.imageio.ImageIO").RunMethod("write", Array As Object( _
       image, "png", OutputStream)) 'the image is written to the response
  
    Dim Buf() As Byte = OutputStream.ToBytesArray

    ende = DateTime.Now
    Log("desktop capture " & (ende-start) & " ms")

    start = DateTime.Now
    Dim InputStream As InputStream
    InputStream.InitializeFromBytesArray(Buf,0,Buf.Length)
 
    Dim MyDesktop As Image
    MyDesktop.InitializeSample2(InputStream,256,256)

    ende = DateTime.Now
    Log("Resize " & (ende-start) & " ms")
 
    Return MyDesktop
 
End Sub

with a library
B4X:
ImageView1.SetImage(DesktopCapture)

Sub DesktopCapture As Image

    Dim robot As AWTRobot 'using jAWTRobot Library 1.55
    robot.ScreenCurrentRectangleSetAsLeftScreen
  
    Dim Buf() As Byte
    Buf = robot.ScreenCaptureAsByteArray

    Dim InputStream As InputStream
    InputStream.InitializeFromBytesArray(Buf,0,Buf.Length)
  
    Dim MyDesktop As Image
    MyDesktop.InitializeSample2(InputStream,256,256) '< resize
  
    Return MyDesktop
  
End Sub
 
Last edited:
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
yes, i think the desktop capture takes a half second. very much time.
 
Upvote 0

MarkusR

Well-Known Member
Licensed User
Longtime User
ok

i made a simple stop watch modul
Waiting for debugger to connect...
Program started.
Init Robot 78 ms
Init Toolkit 0 ms
Rectangle 14 ms
BufferedImage 51 ms
Make PNG 341 ms
PNG to ByteArray 0 ms
Resize 74 ms

B4X:
'Static code module Watch
Sub Process_Globals
    Dim mStart As Long
    Dim mEnd As Long
End Sub

public Sub Start
    mStart = DateTime.Now
End Sub

public Sub Stop(title As String)
    mEnd = DateTime.Now
    Log(title & " " & (mEnd-mStart) & " ms")
    Start
End Sub
 
Upvote 0

Blueforcer

Well-Known Member
Licensed User
Longtime User
i test to stream the screen with VLC (use FFMPEG) and this works really fast:
MRL: "screen://"
Options: ":screen-fps=60 :live-caching=0 :screen-width=1920 :screen-height=1080 :screen-left=0 :screen-top=0"
Maybe we could use the B4JVlcj Lib to get the data.
 
Upvote 0
Top