Sign in to follow this  
MJW

Plugin to Average Color of Selection

Recommended Posts

Here's a CodeLab plugin I wrote to average the color over a selection. I realize it's pretty simple, and could probably be done more efficiently, but it was useful to me, and may be useful to someone else.

#region UICode
#endregion

// The sums may overflow if the selection contains more than 16 MB pixels.
// The solution would be to use doubles for r, g, and b.

private bool firstTime = true;
private ColorBgra averageColor = new ColorBgra();

void Render(Surface dst, Surface src, Rectangle rect)
{
   PdnRegion selectionRegion = EnvironmentParameters.GetSelection(src.Bounds);
   Rectangle selection = selectionRegion.GetBoundsInt();
   ColorBgra CurrentPixel;

   if (firstTime)
   {
       uint r = 0, g = 0, b = 0, n = 0;

       firstTime = false;

       for (int y = selection.Top; y < selection.Bottom; y++)
       {
           for (int x = selection.Left; x < selection.Right; x++)
           {
               if (selectionRegion.IsVisible(x, y))
               {
                   CurrentPixel = src[x, y];
                   b += CurrentPixel.B;
                   g += CurrentPixel.G;
                   r += CurrentPixel.R;
                   n++;
               }
           }
       }

       if (n > 0)
       {
           double recipN = 1.0 / (double)n;
           averageColor = ColorBgra.FromBgr((byte)(recipN * (double)b + 0.5),
                                   (byte)(recipN * (double)g + 0.5),
                                   (byte)(recipN * (double)r + 0.5));
       }
   }

   for (int y = rect.Top; y < rect.Bottom; y++)
   {
       for (int x = rect.Left; x < rect.Right; x++)
       {
               averageColor.A = src[x, y].A;
               dst[x, y] = averageColor;
       }
   }
}

---ADDED DEC 1, 2008---

Here is a pre-built DLL. It's the same as the version in my first reply. It has an icon, and uses ulongs instead of uints to sum r, g, b, so there's no overflow problem with very large selections.

AverageSelectionColor.zip

---ADDED NOV 20, 2009---

Here is a new version (1.2), which I believe will behave correctly on multiple core systems. The old version could potentially give incorrect results due to a race condition between threads running on different cores. This is discussed in detail here. (I don't have a multiple-core system, so I wasn't able to test whether the fix achieved its purpose.) I also made another slight change. I renamed the effect "Average Color (RGB)" for two reasons. First, I thought the name "Average Color of Selection" was unnecessarily wordy; and second, Neil Cassidy has written a similar effect that works in HSL space called "Average Color (HSL)" so I thought it'd be nice to have parallel names.

---ADDED NOV 21, 2009---

I realized there was a problem for multicore systems with the way I handled the alpha value. I fixed it, and downloaded the corrected version. Since the file had only been downloaded once, I decided not to change the version number, which is still 1.2.

AverageSelectionColor1.2.zip

Share this post


Link to post
Share on other sites

What is CodLab? Is it some sort of scientific facility dedicated to ... fish?

If data size is a concern, why not use ulongs rather than uints for r, g, and b?

Share this post


Link to post
Share on other sites

Thanks to Simon Brown for making a DLL version. I tend to forget that not everyone has -- or wants to have -- CodeLab.

As to why I used uints instead of ulongs, it's mostly that I didn't think of it. The 16 megapixel limitation didn't seem significant when I wrote the routine, since I'm very unlikely to use selections that are that big. Just before I posted it, I decided I ought to mention the limitation in a comment.

Since ulongs would be better, I've included a new version of the DLL using ulongs instead of uints. I also added a little icon. I haven't tried uploading a file before, so I've got my fingers crossed.

AverageSelectionColor.zip

I'm curious as to why "isVisible" is the test for whether a pixel is selected. I thought maybe there'd be an "isSelected" property, but there wasn't. Does anyone know if "isVisible" is only testing for whether the pixel is selected, or if not, whether there's a more direct test for selection?

Also, I edited the title to omit the CodeLab (or CodLab) part, since with the availability of the DLL it really doesn't matter.

Share this post


Link to post
Share on other sites
Also, is there someway I can edit the title to say CodeLab instead of CodLab?

There's a little button near the bottom of your post that looks like this:

icon_post_edit.gif

Hit that, and you can change anything in your post. :-)

Share this post


Link to post
Share on other sites
I'm curious as to why "isVisible" is the test for whether a pixel is selected. I thought maybe there'd be an "isSelected" property, but there wasn't. Does anyone know if "isVisible" is only testing for whether the pixel is selected, or if not, whether there's a more direct test for selection?

IsVisible = is visible in the selection. You can remove this check altogether. Paint.NET won't be passing non-selected pixels to your plugin at all, and this check is redundant (and slow).

Thanks David. I've edited the title to be less fishy.

I see what you did thar.

Share this post


Link to post
Share on other sites
isVisible = is visible in the selection. You can remove this check altogether. Paint.NET won't be passing non-selected pixels to your plugin at all, and this check is redundant (and slow).

I don't see how the "isVisible" check can be removed. I can see why it's not needed on the output side (which is why I didn't include it), since Paint.NET only sends updated pixels to the selected region. But when I'm computing the average color, I only want to average pixels that are actually within the selection. The loop is operating over a bounding rectangle for the selection (defined by selection.Left, etc.), so for non-rectangular selections some sort of extra test is needed to exclude pixels that lie within the rectangle but outside the selection.

Let me add (via an edit) that it seems to me that without the test, the pixel count, n, would always be (selection.Right - selection.Left) * (selection.Bottom - selection.Top), which certainly won't work.

Share this post


Link to post
Share on other sites

If the selection is non-rectangular, Paint.NET splits it into dozens or hundreds of rectangular selections. The regions passed to your CodeLab script are guaranteed to be in the selection.

By calculating the entire selection's average color every time one of these sub-selections is given to your plugin, you're making it do a lot more work than it needs to. You only need to calculate the selection's average once. In this version of CodeLab, though, it's kind of unavoidable. (Older versions had a "hack"...)

Still, to only even bother looking at the selections pixels, and never need to call IsVisible, use selection.getregionscansint() (or something like that) which returns the array of rectangles that defines the selection.

Share this post


Link to post
Share on other sites
If the selection is non-rectangular, Paint.NET splits it into dozens or hundreds of rectangular selections. The regions passed to your CodeLab script are guaranteed to be in the selection.

But the part of the code that uses the isVisible test isn't the output loop; it's the section that calculates the average. And that's got to be done for all the selected pixels, not just those that fall within the specific slice that the call is responsible for outputting. The only way I can see to know which pixels to include is with the isVisible calls.

By calculating the entire selection's average color every time one of these sub-selections is given to your plugin, you're making it do a lot more work than it needs to. You only need to calculate the selection's average once. In this version of CodeLab, though, it's kind of unavoidable.

The purpose of the global firstTime flag is to avoid recalculating the average each time a new slice is passed. I'm almost certain the purpose is achieved, since I tested the method with the following routine:

#region UICode
#endregion

private byte r = 255, g = 255, b = 255;

void Render(Surface dst, Surface src, Rectangle rect)
{
   for (int y = rect.Top; y < rect.Bottom; y++)
   {
       for (int x = rect.Left; x < rect.Right; x++)
       {
           dst[x,y] = ColorBgra.FromBgra(b, g, r, 255);
       }
   }
   if (r < 250)
   {
       r += 50;
   }
   else
   {
       r = 0;
       if (g < 250)
       {
           g += 50;
       }
       else
       {
           g = 0;
           if (b < 250)
           {
               b += 50;
           }
           else
           {
               b = 0;
           }
       }
   }
}

When I run this under CodeLab or as the generated DLL, the result is a bunch of different colored stripes, with a single white (255, 255, 255) stripe. This shows the r, g, b values are reset for the first slice, and then maintain their previous values for subsequent slices.

In the color averaging plugin, firstTime is true when the first slice is rendered, so the average color is computed and stored in a global variable, and firstTime is set to false. On subsequent slices, firstTime is false, so the average isn't recomputed.

(BTW, I didn't come up with the idea of using a global flag. It was contained in a comment by Curtis in a thread which was was, coincidentally, on the very same subject of computing the average color. Unfortunately, the example given, while very efficient, wasn't very flexible, since it always computed the average for the entire image.)

Share this post


Link to post
Share on other sites

I just wanted to recognize all of your guys' work. I installed Paint.net a few months ago (at the suggestion of my brother), but I'm just starting to use it now, and it's great.

Thanks for all the plug-ins (I just downloaded "Average Color") and, then, for the tutorial on how to actually install them.

Between Firefox, OpenOffice, Paint.net, and about five other programs I've got, I always feel blessed and grateful for all the coders who work on opensource projects. For people with limited financial resources (like me), they're just a complete lifesaver.

(One small suggestion: when you're resizing a selected image, ideally there'd be an option where you could lock in the proportions - so that the width-to-height ratio remains the same - because, right now, I'm eyeballing it.)

Thank you all so much.

Share this post


Link to post
Share on other sites
(One small suggestion: when you're resizing a selected image, ideally there'd be an option where you could lock in the proportions - so that the width-to-height ratio remains the same - because, right now, I'm eyeballing it.)

Hold down the shift key when resizing.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this