Hello everyone,

Recently I had occasion to use Gimp's make-this-look-like-an-oil-painting
plug-in. The results left me a bit disappointed; the "oilified" image had
numerous artifacts, which ruined the effect. I'll show you what I mean:

Given this input image,


at an oilification size of 24, and using the intensity algorithm, I got


This made me sad :-(

So I had a look at gimp/plug-ins/common/oilify.c, and changed a few things
around. And now, I get something a little more convincing:


I will now describe the changes I made, which are attached in a gzipped
patch file against CVS-head. (The patch is a bit of a mess, I'm afraid;
diff(1) wasn't too smart about it. The changes, however, are quite

First, I should briefly cover how the plug-in worked previously. The method
is deviously simple: for each R/G/B channel in a pixel at (x,y), find the
most commonly occurring R/G/B value in a (mask_size)x(mask_size) square
centered on (x,y), and set that value. The "intensity algorithm" is only
slightly more complicated; instead of three separate histograms (R/G/B) per
pixel, it uses a single one based on color intensity.

Fine; so basically, each pixel takes on the color most prevalent in its 
immediate vicinity. Less prevalent colors are ignored, disregarded. If 
red occurs 90 times, and blue occurs 10 times---and there are no other 
colors---then the pixel is red. End of story.

But imagine a different set of circumstances... say that you still have
only red and blue, but red occurs 51 times, and blue 49. So by the
aforementioned rules, the pixel should still be red. But is that really
proper? Blue made a fairly strong showing, only a hair less than red;
shouldn't that count for something? Doesn't it feel wrong, in some way, to
completely disregard 49% of the pixels?

Consider, also, what happens when you move on to the next pixel. Some red
and blue pixels move out of the mask area, and some move in. And you may
end up with a tally of red 49, blue 51; blue wins. And then in the next
pixel, you have e.g. red 52, blue 48; red wins. When the colors are so
evenly matched, it's very easy for normal image variegation to lead to an
oscillating effect---which is exactly what the previously demonstrated
image artifacts _are_.

I addressed this problem by changing the histogram-evaluating algorithm:
instead of just picking the most frequently-occurring value, take a
weighted average---that is heavily biased toward the most
frequently-occurring values.

We give a weight of 1.0 to the most common value, and smaller weights to
rarer values, equal to their occurrence-ratio with the most common one,
raised to some power. Examples:

        red = 90, blue = 10:

                 red weight = (90 / 90)^8 = 1.0
                blue weight = (10 / 90)^8 = 2.32e-8

                         1.0 * red + 2.32e-8 * blue
                color = ---------------------------- = red
                               1.0 + 2.32e-8

        red = 51, blue = 49:

                 red weight = (51 / 51)^8 = 1.0
                blue weight = (49 / 51)^8 = 0.726

                         1.0 * red + 0.726 * blue
                color = -------------------------- = reddish magenta
                               1.0 + 0.726

Now you must be wondering, "Whoa, waitasecond, you said `raised to some
power'... where the heck did you get that 8?" And the answer is, trial and
error. Lower powers give fuzzier, less-well-defined edges; higher powers
start looking a little too hard-edged. Some samples:


(Eight is a good power, too, since you can raise to it using only three
floating-point multiplies. Then again, since we're dealing with values in 
[0,1] here, a look-up table is a possibility...)

That's the basic idea, then. The intensity algorithm, as before, is 
slightly more involved; when it is generating the histogram, it now 
averages together all the colors of a given intensity. When it takes the 
weighted average, the weights are based on the intensity histogram, but the 
colors are what get averaged together (a second time).

There are two other refinements/changes of note:

1. I added some code so that instead of sampling pixels in a square area
   centered at (x,y), it will do so in a circle centered at (x,y). The
   benefit of this is less obvious, but I think it is a more correct
   approach. This is implemented using what amounts to a bitmask, so the
   performance cost should be minimal.

2. I got rid of the separate oilify_rgb() and oilify_intensity() routines
   and merged them into a single oilify(). Really, they were already
   practically identical, save for a few critical lines. (Note: The "if" in
   the innermost loop has a negligible impact on performance.)

Oilification is slower now; my benchmarks show a runtime of 160% against
the original plug-in. The weighted-average routines use floating-point, but
bypassing that only brings things down to 140%. Hopefully this isn't a big
deal, in light of the improved results.

Now, I'm idly thinking... how feasible it would be to have an option
allowing a separate image to control Oilify's mask size, so that some areas
could come out more detailed in the oil painting, and others less so....


NAME   = Daniel Richard G.       ##  Remember, skunks       _\|/_  meef?
EMAIL1 = [EMAIL PROTECTED]        ##  don't smell bad---    (/o|o\) /
EMAIL2 = [EMAIL PROTECTED]      ##  it's the people who   < (^),>
WWW    = http://www.******.org/  ##  annoy them that do!    /   \
(****** = site not yet online)

Attachment: oilify.c.diff.gz
Description: Binary data

Gimp-developer mailing list

Reply via email to