-
Sep 11th, 2014, 10:55 AM
#1
Thread Starter
Lively Member
[RESOLVED] Getting Average RGB Color Value of Entire Screen
Hi all
Does anyone know if there is a way of getting the average RGB colour value of the entire screen? I'm toying with the idea of writing some software that will emulate (very loosely) an 'ambilight' type TV for use while gaming.
The idea is, average the RGB colour value of the screen (the total value or at least a definable section of it) while gaming and output that RGB value via DMX protocol (standard protocol used to control lights via lighting consoles and software in clubs / at gigs / on TV etc) to a set of DMX enabled LED lights sat behind the monitor. Whatever method is used would need to be efficient so that it could react to screen changes in real time.
I've got all the required hardware and can code the DMX protocol no problems, I'm just missing the colour detection portion!
Any help would be great
-
Sep 11th, 2014, 11:06 AM
#2
Re: Getting Average RGB Color Value of Entire Screen
A very slow method would be to take a screen shot of the screen, loop through each pixel, get the value of that pixel using Bitmap.GetPixel, convert the color to it's RGB equivalent, store that RGB value into a collection, then get the average of that collection most likely using LINQ.
Edit:
Here is a function that returns a bitmap of the entire screen:
Code:
Private Function Screenshot() As Bitmap
Dim b As Bitmap = New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
Using g As Graphics = Graphics.FromImage(b)
g.CopyFromScreen(0, 0, 0, 0, b.Size, CopyPixelOperation.SourceCopy)
g.save
End Using
Return b
End Function
Here is a function that returns all RGB values in the bitmap:
Code:
Private Structure RGB
Public Property R As Integer
Public Property G As Integer
Public Property B As Integer
End Structure
Private Function GetAllRGB(ByVal img As Bitmap) As RGB()
Dim rgbCollection As List(Of RGB) = New List(Of RGB)
For x As Integer = 0 To img.Width
For y As Integer = 0 To img.Height
Dim pixelColor As Color = img.GetPixel(x, y)
Dim pixelRGB As RGB = New RGB With {.R = pixelColor.R, .G = pixelColor.G, .B = pixelColor.B}
rgbCollection.Add(pixelRGB)
Next
Next
Return rgbCollection.ToArray()
End Function
Getting the average is your job, I'm not at a computer with VS installed and I don't trust my LINQ skills enough to freehand the code like I did for the others.
Last edited by dday9; Sep 11th, 2014 at 11:22 AM.
-
Sep 11th, 2014, 11:55 AM
#3
Thread Starter
Lively Member
Re: Getting Average RGB Color Value of Entire Screen
Cheers for the pointer. That works fine, if a little slow (about 0.5 second delay), but it does do exactly what I want!
I'll work on it. I can certainly speed it up by just sampling every 10th pixel for example (on both axis, this cuts calculations down from averaging over a million pixels to ~10,000), and I'm damn sure there are other ways I can speed it up.
Cheers, you're a star
-
Sep 11th, 2014, 02:36 PM
#4
Re: Getting Average RGB Color Value of Entire Screen
You might want to consider converting the RGB values to luminance, which will give you the overall "brightness" of the image. Luminance = R * 0.3 + G * 0.59 + B * 0.11.
Also check out fastpix from boops boops. It really speeds up reading and writing bitmap RGB values:
http://www.vbforums.com/showthread.p...hlight=fastpix
Last edited by paulg4ije; Sep 11th, 2014 at 02:59 PM.
-
Sep 11th, 2014, 07:38 PM
#5
Re: Getting Average RGB Color Value of Entire Screen
Originally Posted by Duckfather
Cheers for the pointer. That works fine, if a little slow (about 0.5 second delay), but it does do exactly what I want!
I'll work on it. I can certainly speed it up by just sampling every 10th pixel for example (on both axis, this cuts calculations down from averaging over a million pixels to ~10,000), and I'm damn sure there are other ways I can speed it up.
Cheers, you're a star
Ok ... if you want to do this quickly you could:
Create a new bitmap that is smaller (1x1) ... turn InterpolationMode to high and draw the large screen shot on the smaller bitmap using drawImage and specifying the small 1x1 area then do your check on the one pixel... InterpolationMode means the downsized pixels when drawing a smaller image get averaged to the pixels they are fitting into.
Or ... use LockBits as this is much faster than getpixel...
I prefer the 2nd method ... but the top suggestion would probably be easier for a n00b, and prob just as fast.
Kris
-
Sep 12th, 2014, 01:21 AM
#6
Re: Getting Average RGB Color Value of Entire Screen
id suggest lockbits and calculating the sum of R/G/B while iterating over the pixels then divide each by the number of pixels.
this should be the fastest way.
-
Sep 12th, 2014, 05:37 AM
#7
Thread Starter
Lively Member
Re: Getting Average RGB Color Value of Entire Screen
Guys, I just wrote a quick mockup of the interpolation idea and that works great for my purposes. The response shows no physically observable delay. I have to put in a routine to very quickly fade the colours in / out anyway (from previous experience writing code for DMX controlled lights, just snapping to the new colour can be very jarring), so this will now be the limiting factor in the responsiveness of the code, not the speed of the calculation.
Great advice guys, you've just helped me take my gaming immersion to the next level, hehehe, cheers
Last edited by Duckfather; Sep 12th, 2014 at 05:57 AM.
Reason: Typos
-
Sep 12th, 2014, 01:11 PM
#8
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
I'm interested in seeing you implement this, is there anyway you could post a video or even a few screenshots of it in action?
-
Sep 13th, 2014, 08:11 PM
#9
Re: Getting Average RGB Color Value of Entire Screen
Originally Posted by digitalShaman
id suggest lockbits and calculating the sum of R/G/B while iterating over the pixels then divide each by the number of pixels.
this should be the fastest way.
As I stated ...
Also for everyone's info I wrote this quick comparison:
vb Code:
Using b As New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height) Using g = Graphics.FromImage(b) g.CopyFromScreen(0, 0, 0, 0, b.Size) End Using Dim OutColor As Color Dim st As Date Dim TotalTime As Double 'Interpolation Method Using bOut As New Bitmap(1, 1) '<<< - i keep the bout object outside of the loop ... because if you want speed you would not re-create the bitmap object all the time Using g = Graphics.FromImage(bOut) '<<< - same with this "" st = Now For i = 1 To 100 g.InterpolationMode = Drawing2D.InterpolationMode.High g.DrawImage(b, New Rectangle(0, 0, 1, 1)) OutColor = b.GetPixel(0, 0) Next TotalTime = Now.Subtract(st).TotalMilliseconds Debug.Print("Interpolation Method: " & OutColor.ToString & " " & TotalTime & "ms") End Using End Using 'LockBits Method st = Now For i = 1 To 100 Dim bmpData As System.Drawing.Imaging.BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb) Dim bitmapAddress As IntPtr = bmpData.Scan0 Dim iCount As Integer Dim Max = (b.Width * b.Height * 4) - 4 '<<< - Edit as faster than: CInt(bmpData.Stride * bmpData.Height) - 4 Dim TotalR = 0& Dim TotalG = 0& Dim TotalB = 0& For iPixel = 0 To Max Step 4 iCount += 1 TotalR += System.Runtime.InteropServices.Marshal.ReadByte(bitmapAddress, iPixel + 2) TotalG += System.Runtime.InteropServices.Marshal.ReadByte(bitmapAddress, iPixel + 1) TotalB += System.Runtime.InteropServices.Marshal.ReadByte(bitmapAddress, iPixel) Next Dim TotalPixels = (Max / 4) + 1 OutColor = Color.FromArgb(TotalR \ TotalPixels, TotalG \ TotalPixels, TotalB \ TotalPixels) b.UnlockBits(bmpData) Next TotalTime = Now.Subtract(st).TotalMilliseconds Debug.Print("LockBits Method: " & OutColor.ToString & " " & TotalTime & "ms") End Using
The colour variations differ slightly (as I 1/2 expected, the "LockBits Method" will of course be more accurate)... but also the "Interpolation Method" that I suggested was actually consistently faster... but only by a little...
I have changed the color of the word Color below so you can see the different results between the two methods.
EDIT: Added results for several of my machines...
Lenovo IdeaCenter Horizon 27 (GeForce GT620M/Intel HD Graphics 4000 / i7-3537U @ 1920x1080)
Code:
Interpolation Method: Color [A=255, R=214, G=219, B=233] 4234.5934ms
LockBits Method: Color [A=255, R=221, G=223, B=225] 4593.9872ms
Alienware X51 (GeForce GTX 650 Ti / i7-4770 @ 1920x1080)
Code:
Interpolation Method: Color [A=255, R=46, G=46, B=46] 8980.0117ms
LockBits Method: Color [A=255, R=79, G=79, B=79] 9205.0151ms
Custom Build (AMD Radeon R9200 / i7-4770 @ 1920x1080)
Code:
Interpolation Method: Color [A=255, R=1, G=61, B=111] 3165.1913ms
LockBits Method: Color [A=255, R=52, G=136, B=189] 3413.2514ms
Apple Retina Laptop (Original model) (GT650M / i7-3720QM @ 2880x1800)
Code:
Interpolation Method: Color [A=255, R=48, G=48, B=49] 9906.0174ms
LockBits Method: Color [A=255, R=48, G=48, B=49] 11091.6195ms
EDIT:
Times will be a little out as I modified the code as this seems a little faster:
Dim Max = (b.Width * b.Height * 4) - 4 '<<< - Edit as faster than: CInt(bmpData.Stride * bmpData.Height) - 4
... I should probably also state that the processing is done 100x per method, so times are for performing 100 of the operations.
Kris
Last edited by i00; Sep 13th, 2014 at 10:43 PM.
-
Sep 13th, 2014, 09:34 PM
#10
Re: Getting Average RGB Color Value of Entire Screen
Originally Posted by i00
The colour variations differ slightly (as I 1/2 expected, the "LockBits Method" will of course be more accurate)... but also the "Interpolation Method" that I suggested was actually consistently faster... but only by a little...
Kris,
No offense intended, but that is a very inefficient LockBits implementation using three interop calls to get a color value. At least read a single integer and feed it into a Color structure. Or better yet read multiple values at a time. I prefer to read a one row at a time (others the whole thing) into an array and process that.
Code:
' default new BitMap is 32 bit/pixel
Using bm As New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
Using g As Graphics = Graphics.FromImage(bm)
g.CopyFromScreen(Point.Empty, Point.Empty, bm.Size)
End Using
Dim sw As New Stopwatch
sw.Start()
Dim srcData As System.Drawing.Imaging.BitmapData
Dim tmpColor As Color
Dim Colors(0 To bm.Width - 1) As Int32
Dim sumR, sumG, sumB As Int64
Dim totalPixels As Int64 = bm.Width * bm.Height
For y As Int32 = 0 To bm.Height - 1
srcData = bm.LockBits(New Rectangle(0, y, bm.Width, 1), _
Drawing.Imaging.ImageLockMode.ReadWrite, _
bm.PixelFormat)
System.Runtime.InteropServices.Marshal.Copy(srcData.Scan0, Colors, 0, bm.Width)
For x As Int32 = 0 To bm.Width - 1
tmpColor = Color.FromArgb(Colors(x))
sumR += tmpColor.R
sumG += tmpColor.G
sumB += tmpColor.B
Next x
System.Runtime.InteropServices.Marshal.Copy(Colors, 0, srcData.Scan0, srcData.Width)
bm.UnlockBits(srcData)
srcData = Nothing
Next y
Dim avgColor As Color = Color.FromArgb(255, CType(sumR \ totalPixels, Int32), CType(sumG \ totalPixels, Int32), CType(sumB \ totalPixels, Int32))
sw.Stop()
Debug.WriteLine(sw.ElapsedMilliseconds)
End Using ' bm
-
Sep 13th, 2014, 09:51 PM
#11
Re: Getting Average RGB Color Value of Entire Screen
Originally Posted by TnTinMN
Kris,
No offense intended, but that is a very inefficient LockBits implementation using three interop calls to get a color value. At least read a single integer and feed it into a Color structure. Or better yet read multiple values at a time. I prefer to read a one row at a time (others the whole thing) into an array and process that.
Code...
Well your method crashed for me...
EDIT: This thing can't seem to attach images correctly without making them look like c*** ... anyway error is:
Code:
An unhandled exception of type 'System.ArgumentException' occurred in System.Drawing.dll
Additional information: Value of '437' is not valid for 'red'. 'red' should be greater than or equal to 0 and less than or equal to 255.
... but I did test this before with my LockBits loop set as this:
vb Code:
Dim bmpData As System.Drawing.Imaging.BitmapData = b.LockBits(New Rectangle(0, 0, b.Width, b.Height), Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb) Dim TotalR = 0& Dim TotalG = 0& Dim TotalB = 0& Dim TotalPixels = b.Height * b.Width Dim Colors(0 To TotalPixels) As Integer System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, Colors, 0, TotalPixels) For Each item In Colors Dim TheColor = Color.FromArgb(item) TotalR += TheColor.R TotalG += TheColor.G TotalB += TheColor.B Next OutColor = Color.FromArgb(TotalR \ TotalPixels, TotalG \ TotalPixels, TotalB \ TotalPixels) b.UnlockBits(bmpData)
... any problems with that?? ... because it seems to be about 2x as slow ??? .. I thought it would have been faster too!
EDIT 2:
Times are:
Code:
Interpolation Method: Color [A=255, R=214, G=219, B=233] 4866.2455ms
LockBits Multiple Interop Method: Color [A=255, R=218, G=220, B=224] 5074.3848ms
LockBits Single Interop Method: Color [A=255, R=218, G=220, B=224] 8212.4781ms
EDIT 3:
OK ... the reason that the method suggested by TnTinMN is really slow is because it creates a color object for each pixel ... which is EXTREMELY slow ... (tmpColor = Color.FromArgb(Colors(x))) ...
If you have any suggestions on how to better it or my method let me know. ...
Kris
Last edited by i00; Sep 13th, 2014 at 10:37 PM.
-
Sep 13th, 2014, 10:36 PM
#12
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Based on that blurry image you have shown, it looks like you pasted my code inside a loop. The the summation variables will need to be set to initialize to zero on each iteration.
-
Sep 13th, 2014, 10:45 PM
#13
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Originally Posted by TnTinMN
Based on that blurry image you have shown, it looks like you pasted my code inside a loop. The the summation variables will need to be set to initialize to zero on each iteration.
Did you read the whole post with the edits? ... all but the last would have been there judging by the time of your post.
I Modified my answer and my method is still faster as creating a color object for each pixel appears to be much slower....
... and yes the error message was a c&p of your code (which I stated: "Well your method crashed for me") ... I didn't look into the error at all to be honest tho as I already did this before as stated above ... just slightly differently ... I went to my shadowcopy backup and posted my original in the post also ...
But, as stated before, all of that would have been in there already when you looked @ it judging by the time on your reply.
EDIT: Also the image was not blury ... vbforums seems to poorly resize all images > 600px wide to be 600px wide
Kris
Last edited by i00; Sep 13th, 2014 at 10:58 PM.
-
Sep 13th, 2014, 11:30 PM
#14
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Looking for an efficient method? Scale the screen grab down to 1*1 pixel bitmap and get the colour of the pixel with Bitmap.GetPixel. There's no point in using LockBits for a small image because it has quite an overhead. BB
-
Sep 14th, 2014, 12:05 AM
#15
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
iOO ,
You are correct that using the color structure is slow and I got lazy in using that versus just typing out the bit shift/mask operations. A much faster result can be obtained by swapping out that code with this. It takes about half the time as using the structure.
Code:
For x As Int32 = 0 To bm.Width - 1
sumR += (Colors(x) >> 16) And &HFF
sumG += (Colors(x) >> 8) And &HFF
sumB += Colors(x) And &HFF
Next x
One issue that I have noticed with the color array method is that there appears to be a point where processing smaller parts of the image is faster than locking the whole image and getting all the values in an array at once. That is the reason I set my code up to process a single row at a time. This just may be a memory available issue though.
-
Sep 14th, 2014, 01:09 AM
#16
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Originally Posted by boops boops
Looking for an efficient method? Scale the screen grab down to 1*1 pixel bitmap and get the colour of the pixel with Bitmap.GetPixel. There's no point in using LockBits for a small image because it has quite an overhead. BB
Well it's still ALOT faster than your method ... as I discussed, and like I said when I originally posted, LockBits would be my preferred method, but the other way may be easier for n00bs.
Kris
-
Sep 14th, 2014, 01:10 AM
#17
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Originally Posted by TnTinMN
iOO ,
You are correct that using the color structure is slow and I got lazy in using that versus just typing out the bit shift/mask operations. A much faster result can be obtained by swapping out that code with this. It takes about half the time as using the structure.
Code:
For x As Int32 = 0 To bm.Width - 1
sumR += (Colors(x) >> 16) And &HFF
sumG += (Colors(x) >> 8) And &HFF
sumB += Colors(x) And &HFF
Next x
One issue that I have noticed with the color array method is that there appears to be a point where processing smaller parts of the image is faster than locking the whole image and getting all the values in an array at once. That is the reason I set my code up to process a single row at a time. This just may be a memory available issue though.
Will check it in a few hours ... on the road atm
... hrm ... faster processing for smaller parts ... I would have thought that processing the whole thing would be faster if you have the memory to handle it ... which shouldn't be that demanding ....
Kris
-
Sep 14th, 2014, 01:15 AM
#18
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Originally Posted by TnTinMN
A much faster result can be obtained by swapping out that code with this. It takes about half the time as using the structure.
Ya wow, went from around 45ms to 5ms on my PC.
Code:
Compiled x64, FW 4.0, Win7-64, 1920x1080, i7-3770.
i00: Color [A=255, R=156, G=149, B=173] 16.4221ms
i00: Color [A=255, R=156, G=149, B=173] 16.4107ms
i00: Color [A=255, R=156, G=149, B=173] 16.3723ms
i00: Color [A=255, R=156, G=149, B=173] 17.5805ms
i00: Color [A=255, R=156, G=149, B=173] 16.9348ms
TnTinMN: Color [A=255, R=156, G=149, B=173] 5.2121ms
TnTinMN: Color [A=255, R=156, G=149, B=173] 5.1542ms
TnTinMN: Color [A=255, R=156, G=149, B=173] 5.1653ms
TnTinMN: Color [A=255, R=156, G=149, B=173] 5.1776ms
TnTinMN: Color [A=255, R=156, G=149, B=173] 5.1758ms
Cool code guys!
-
Sep 14th, 2014, 01:43 AM
#19
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Originally Posted by TnTinMN
iOO ,
You are correct that using the color structure is slow and I got lazy in using that versus just typing out the bit shift/mask operations. A much faster result can be obtained by swapping out that code with this. It takes about half the time as using the structure.
Code:
For x As Int32 = 0 To bm.Width - 1
sumR += (Colors(x) >> 16) And &HFF
sumG += (Colors(x) >> 8) And &HFF
sumB += Colors(x) And &HFF
Next x
One issue that I have noticed with the color array method is that there appears to be a point where processing smaller parts of the image is faster than locking the whole image and getting all the values in an array at once. That is the reason I set my code up to process a single row at a time. This just may be a memory available issue though.
Just stoped and hooked into home ... you are correct ...
Interpolation Method: Color [A=255, R=214, G=219, B=233] 4467.9798ms
LockBits Multiple Interop Method: Color [A=255, R=218, G=221, B=224] 4722.1495ms
LockBits Single Interop Method: Color [A=255, R=218, G=221, B=224] 4274.852ms
Last edited by i00; Sep 14th, 2014 at 01:54 AM.
-
Sep 14th, 2014, 01:49 AM
#20
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
Sweet ... cutting it into chunks is faster! ...
Interpolation Method: Color [A=255, R=214, G=219, B=233] 4453.9713ms
LockBits Multiple Interop Method: Color [A=255, R=217, G=220, B=223] 4690.1284ms
LockBits Single Interop Method: Color [A=255, R=217, G=220, B=223] 4272.8512ms
LockBits Single Interop TnTinMN Method: Color [A=255, R=217, G=220, B=223] 2477.652ms
Kris
Last edited by i00; Sep 14th, 2014 at 01:53 AM.
-
Sep 14th, 2014, 03:39 AM
#21
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
what is the quickest code now? i have not seen any suggestion that is just using a 24bpp byte array or maybe i have not looked closely enough. when using a byte array there is no need for shifting around aso, just a for i to step 3 loop and adding array(x), array(x+1) and array(x+2) to the three different channels inside the loop. and it is what is inside the loop that matters (ok, if the source image is not 24bpp then it needs to be converterd upfront or the loop needs to do a step 4 in case of 32bpp)
Last edited by digitalShaman; Sep 14th, 2014 at 04:00 AM.
-
Sep 14th, 2014, 04:00 AM
#22
Re: [RESOLVED] Getting Average RGB Color Value of Entire Screen
this is what i meant:
Code:
Private Sub AvgColors(ByVal InBitmap As Bitmap)
Dim btPixels(InBitmap.Height * InBitmap.Width * 3 - 1) As Byte
Dim hPixels As GCHandle = GCHandle.Alloc(btPixels, GCHandleType.Pinned)
Dim bmp24Bpp As New Bitmap(InBitmap.Width, InBitmap.Height, InBitmap.Width * 3, _
Imaging.PixelFormat.Format24bppRgb, hPixels.AddrOfPinnedObject)
Using gr As Graphics = Graphics.FromImage(bmp24Bpp)
gr.DrawImageUnscaledAndClipped(InBitmap, New Rectangle(0, 0, _
bmp24Bpp.Width, bmp24Bpp.Height))
End Using
Dim sumRed As Int32
Dim sumGreen As Int32
Dim sumBlue As Int32
For i = 0 To btPixels.Length - 1 Step 3
sumRed += btPixels(i)
sumGreen += btPixels(i + 1)
sumBlue += btPixels(i + 2)
Next
hPixels.Free()
Dim avgRed As Byte = CByte(sumRed / (btPixels.Length / 3))
Dim avgGreen As Byte = CByte(sumGreen / (btPixels.Length / 3))
Dim avgBlue As Byte = CByte(sumBlue / (btPixels.Length / 3))
End Sub
it does also include the conversion to 24bpp which needs to be considered for fair speed comparisons and i have not verified the correct output (red and blue most likely switched).
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|