POV-Ray : Newsgroups : povray.general : macro to find average colors of image_map : macro to find average colors of image_map Server Time
4 May 2024 04:40:55 EDT (-0400)
  macro to find average colors of image_map  
From: Kenneth
Date: 2 Aug 2017 22:05:01
Message: <web.5982834b9cc5ef1a883fb31c0@news.povray.org>
While working on my 'city buildings' scene, I had a need to find the average of
all the colors in a digital photo image. So I wrote this macro, which uses
eval_pigment.

(A note: This code doesn't actually check *every* pixel in the image. Instead,
it checks lots of random positions. My own theory is that such a 'statistical'
approach produces a result that is just about as good as checking each and every
pixel-- and it's faster.)

-----------
#macro AVG_COLOR(MY_PIG) // NOTE: evaluated pigment is within the image_map's
// 1X1 POV-Ray default area.
#local CC = seed(32);
#local LIM_C = 0;
#local SP = 1;
#local SUM=<.5,.5,.5>;
#while(SP <= 100) // 100 random points in the image are evaluated
#local LIM_C = LIM_C + 1;
#local TEMP_EVAL = eval_pigment(MY_PIG, <rand(CC),rand(CC),0>);
#if(
    (TEMP_EVAL.x < .1
    & TEMP_EVAL.y < .1
    & TEMP_EVAL.z < .1
    )
    |
    (TEMP_EVAL.x > .9
    & TEMP_EVAL.y > .9
    & TEMP_EVAL.z > .9
    )
    )
#local SP = SP - 1; // to IGNORE the found value, and set the main counter back
by 1
#else
#local SUM = SUM + TEMP_EVAL;
#end
#local SP = SP + 1;
#if(LIM_C > 5000)
#local SP = 100000; // A safety limiter, to purposely end the while loop
// if the main counter SP loops more than 5000 times total.
#else
#end
#end
srgb 1.0*vnormalize(<SUM.x,SUM.y,SUM.z>) // the actual VALUE of the macro.
//#debug concat("\n","Total while-loop iterations = ",str(LIM_C,1,0),"\n")
#end
-----------

An example of how to use the macro:

#declare P =
pigment{
image_map{jpeg "my_photo_image.jpg" gamma 2.2 interpolate 2}
}

pigment{AVG_COLOR(P)}

-------------------
SOME NOTES:

Interpolate 2 may not be needed in the image_map. I think interpolation is
solely a rendering-specific operation, in which case eval_pigment ignores it.

The #if(TEMP_EVAL...) section is optional-- to restrict the search for colors to
be within two threshold values. Very dark colors (or very bright ones) don't
have much *color* in them anyway-- being close to pure black or pure white
(i.e., greyish)--   and don't add much to the final color IMO, except to either
darken it with muddiness, or make it lighter and more pastel. (A debatable
point, I admit.) This keeps those found colors out of the average. To use EVERY
pixel color, just make the thresholds all 0.0's and 1.0's, respectively.

It's currently set up to evaluate 100 random locations in the image (SP.) You
can change that higher or lower. (The #if(TEMP_EVAL...) section actually
increases this number-- so that 100 *useful* colors are eventually found.)

LIM_C is a 'safety' counter, to end the while loop in the extreme case of not
finding ANY usuable colors in the image_map; I included it only because of the
#if(TEMP_EVAL...) thresholds, which naturally increase the number of #while-loop
iterations. Excluding some evaluated colors makes the #while loop work harder!

The use of vnormalize is an easy way of 'compressing' the (large!) final SUM
value, into the range from 0.0 to 1.0. (Interestingly, the final components of
SUM are somewhere between .4 and .6-- a natural result of the 'average' of all
the image colors.)

You could probably stick *any* three values into the initial SUM, as it will
ultimately be 'swamped' by the additional 100 colors that are found. I used
<.5,.5,.5>  *just in case* the total search for colors (SUM) ended up as
<0,0,0> -- which would probably never happen anyway, except when trying to
evaluate a perfectly BLACK image! But I didn't want vnormalize to be given
<0,0,0> as a final input, which is 'undefined' behavior. So it's not a good idea
to use <0,0,0> as the initial SUM.

Thinking of other uses of this macro: It could probably be re-written to be a
color-histogram tool. Or a color-pallette generator, to find *numerous* general
colors from an image_map background for applying to a scene's objects, to better
'harmonize' all the colors.  (I'm thinking about Old Master paintings, and how
well a painting's colors harmonize with each other.)

SOME EVAL_PIGMENT DETAILS...
In the macro, the output can be either RGB or SRGB; you can choose. But it made
me think about the subtleties of eval_pigment's behavior, AND of what the
image_map's gamma should be set to, in the code.

AFAIK, eval_pigment operates on LINEAR colors (i.e., 3.7xx's RGB color space),
not gamma-bent colors that are usually found in a typical digital photo; those
are generally gamma 2.2 or srgb. So my thinking was that eval-pigment would not
return the colors *as I see them* in the digital photo, but rather a gamma 1.0
version of the colors-- which I didn't want. So I changed the macro's output to
srgb.

(By the way, in the image_map's pigment block, adding either gamma 2.2 or srgb
after the image_map causes no change--none that I can see, anyway. So the
default gamma there must be 2.2 or so, which matches most photo images.  Or,
perhaps POV-ray automatically chooses the proper gamma value, from the image
itself?)

Comments (or corrections!) welcome!


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.