Jump to content
How to Install Plugins ×

Average Color of Selection


MJW

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.

AverageColorOfSelection1.2.zip

 

Link to comment
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?

xZYt6wl.png

ambigram signature by Kemaru

[i write plugins and stuff]

If you like a post, upvote it!

Link to comment
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.

Link to comment
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. :-)

 

The Doctor: There was a goblin, or a trickster, or a warrior... A nameless, terrible thing, soaked in the blood of a billion galaxies. The most feared being in all the cosmos. And nothing could stop it, or hold it, or reason with it. One day it would just drop out of the sky and tear down your world.
Amy: But how did it end up in there?
The Doctor: You know fairy tales. A good wizard tricked it.
River Song: I hate good wizards in fairy tales; they always turn out to be him.

Link to comment
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.

xZYt6wl.png

ambigram signature by Kemaru

[i write plugins and stuff]

If you like a post, upvote it!

Link to comment
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.

Link to comment
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.

xZYt6wl.png

ambigram signature by Kemaru

[i write plugins and stuff]

If you like a post, upvote it!

Link to comment
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.)

Link to comment
Share on other sites

  • 9 months later...

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.

Link to comment
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.

Link to comment
Share on other sites

  • 2 months later...
  • 12 years later...
On 11/30/2008 at 10:14 AM, MJW said:

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 4.19 kB · 3,493 downloads

---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 4.33 kB · 3,540 downloads

Hey mate, is there an updated link for this as the download seems corrupt

Thanks

This is what downloads instead a8Kmx8d.png

Edited by jsoba77
  • Thanks 1
Link to comment
Share on other sites

  • MJW changed the title to Average Color of Selection
5 hours ago, jsoba77 said:

Hey mate, is there an updated link for this as the download seems corrupt

 

Thanks. I downloaded, renamed it, then uploaded the renamed version. I hope it has the correct name now.  I have no idea why the filename was changed to that random string of letters.

  • Like 1
Link to comment
Share on other sites

23 hours ago, ardneh said:

Rename the downloaded file to:
AverageSelectionColor.zip
 

 

18 hours ago, MJW said:

 

Thanks. I downloaded, renamed it, then uploaded the renamed version. I hope it has the correct name now.  I have no idea why the filename was changed to that random string of letters.

Thanks a lot guys

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...