I didn't find good options at first, nor good algorithms. So with a bit of experimentation, I came up with the algorithm described below.
For this blog, I'll use the following image, a pre-desaturated version as input to the sepia algos:
Searching the web, I found the following algorithm which, apparently, comes from Microsoft :
newRed = 0.393*R + 0.769*G + 0.189*B newGreen = 0.349*R + 0.686*G + 0.168*B newBlue = 0.272*R + 0.534*G + 0.131*BI found this transform many times on the web. It effectively does both a desaturation and a sepia toning.
I verified that GIMP 2.10 uses this same algorithm in Colors -> Desaturate -> Sepia... (with sRGB checked). Here's the result along with a standard (luminance-based) desaturation of the sepia. This gives following:
To my liking, this is too yellow. But the bigger problem is that when you desaturate again (second image above), you get a result that is significantly brighter than the original desaturation, meaning that this sepia toning affects luminance.
I think that a sepia toning should preserve luminance, or perhaps one of the other measures in Colors -> Desaturate -> Desaturate.... A good test is to repeatedly do the sepia transform, the image should stay essentially the same.
Then I tried a little experiment in GIMP, Colors -> Curves:
This suggested that three parallel straight curves with tapering off at the ends seem to do the trick. So I wrote an algorithm with two parameters: the distance between the red and green lines, and between the green and blue lines. Varying these two parms (within reasonable limits) allows one to vary the sepia tint. The assumption is that the red curve is highest (on the "curves" graph) and blue curve lowest, so as to get a sepia tint. The green, being the main component of luminance, is in the center, between red and blue.
rg = 19 # distance between red and green lines -- my original default values (I've evolved somewhat since) gb = 27 # distance between green and blue lines # overall, a distance of about 50 between red and blue seems about rightWe now calculate by how much the red line is above the desaturated value (luminance) by requiring that luminance be preserved. This gives the following equations:
# ar is adjustment for red that we want to compute; l is an arbritrary luminance # the following equation expresses the wish that luminance be invariant # ie. luminance of sepia is same as luminance of original (l + ar) * .30 + (l + ar - rg) * .59 + (l + ar - rg - gb) * .11 == l # remove 'l' ar * .30 + (ar - rg) * .59 + (ar - rg - gb) * .11 == 0 # regroup things ar - rg * 0.70 - gb * 0.11 == 0 # isolate ar ar = rg * 0.70 + gb * 0.11 # adjustments for green and blue are now simply ag = ar - rg ab = ar - rg - gband the conversion becomes simply:
l = (30*r + 59*g + 11*b) # compute luminance of pixel r = l + ar # new sepia values of RGB g = l + ag b = l + abThe tapering off at the ends is left as an exercise for the reader :-). This gives the resulting image, and the following re-desaturation (very close to original):
I think this is better than GIMP's algorithm. After this work, I discovered that Preview (MacOS) does quite a good job.
Here's an image from the web which I feel has a nice sepia toning : image (lonely cattle).
Here's a profile of the RGB values on column at x = 2222 on the blurred original image (the vertical green arrow on above). On the left of the profile (top of green arrow), we see the sky, the right half would look like noise without some blurring (here 11 pixels). And we see the same parallel lines for the three colors as I have above.
The distance (in the sky on the left) between the red and the green lines is about 21, and between green and blue lines, about 31. Note how the RGB curves come closer together in the blacks, showing a tapering off as discussed above. If I redo the sepia processing (using the algorithm below) using those numbers on the original image, I get essentially the same image back. This sepia has a greenish tint.
~/Library/Application Support/Gimp/2.10/plug-ins/sepia-lew.py. Note also the first line, which is part of how I got it working. Don't forget to make the file executable.
#!/Users/lew/Applications/GIMP-2.10.app/Contents/MacOS/python from gimpfu import * # converts spline curve to 256 points array; for now, still with sharp angles, but that's not really visible (it's in the differential def splineToPoints(spline): points = [ k/255.0 for k in range(256) ] x0 = 0.0 y0 = 0.0 ix = 0 x0 = spline.pop(0) # remove initial (0.0,0.0) y0 = spline.pop(0) while spline: x = spline.pop(0) y = spline.pop(0) while ix < 256: xi = ix / 255. if xi > x: break points[ix] = ((x-xi) * y0 + (xi-x0) * y) / (x-x0) ix += 1 x0 = x y0 = y return points def do_sepia(img, layer, desat, red_green, green_blue) : gimp.progress_init("Converting " + layer.name + " to sepia...") red_green /= 255. # comvert parms to range 0..1 green_blue /= 255. # compute the desired adjustments to preserve luminance ar = red_green * 0.70 + green_blue * 0.11 # expected to be +ve ag = ar - red_green # could be either ab = ar - red_green - green_blue # expected to be -ve if red_green < 0.0 or green_blue < 0.0 or abs(ar) > 0.4 or abs(ag) > 0.4 or abs(ab) > 0.4 : pdb.gimp_message('Unexpected parms, results may surprise you!') # return # TBD : find the proper way to handle this # Set up an undo group, so the operation will be undone in one step. pdb.gimp_undo_push_group_start(img) # pdb.gimp_message('ar = ' + str(round(ar,3)) + ', ag = ' + str(round(ag,3)) + ', ab = ' + str(round(ab,3))) if desat: pdb.gimp_drawable_desaturate(layer, DESATURATE_LUMINANCE) # in case not previously done # versions 1 and 2 of this program used pdb.gimp_drawable_curves_spline(), but pdb.gimp_drawable_curves_explicit() gives me fewer artefacts # 0.93 is a factor that was useful with pdb.gimp_drawable_curves_spline() to counter a slight buldging # red curve moves up pdb.gimp_drawable_curves_explicit(layer, HISTOGRAM_RED, 256, splineToPoints([0.0, 0.0, 0.03-0.93*ab, 0.03-0.93*ab+0.93*ar, 0.5-ar/2, 0.5+ar/2, 0.97-0.93*ar, 0.97, 1.0, 1.0])) # blue curve moves down pdb.gimp_drawable_curves_explicit(layer, HISTOGRAM_BLUE, 256, splineToPoints([0.0, 0.0, 0.03-0.93*ab, 0.03, 0.5-ab/2, 0.5+ab/2, 0.97-0.93*ar, 0.97-0.93*ar+0.93*ab, 1.0, 1.0])) if abs(ag) > 0.003 : # don't bother if green moves less than 1 pdb.gimp_drawable_curves_explicit(layer, HISTOGRAM_GREEN, 256, splineToPoints([0.0, 0.0, 0.03-0.93*ab, 0.03-0.93*ab+0.93*ag, 0.5-ag/2, 0.5+ag/2, 0.97-0.93*ar, 0.97-0.93*ar+0.93*ag, 1.0, 1.0])) # https://stackoverflow.com/questions/58772647/adjust-color-curves-in-python-similar-to-gimp pdb.gimp_displays_flush() #this will update the image. # Close the undo group. pdb.gimp_undo_push_group_end(img) register( "python_fu_do_sepia", "Tone sepia (lew)", "Converts an image to sepia (https://leware.net/photo/blogSepia.html)", "Pierre Lewis", "Pierre Lewis", "2020", "Sepia(lew)...", "*", # Alternately use RGB, RGB*, GRAY*, INDEXED etc. [ (PF_IMAGE, "image", "Input Image", None), (PF_DRAWABLE, "drawable", "Input Layer", None), (PF_BOOL, "desat", "Desaturate", True), (PF_INT, "red_green", "Red-green", 23), (PF_INT, "green_blue", "Green-Blue", 19) ], , do_sepia, menu="/Colors/Desaturate") main()The GIMP plug-in has an extra parameter, whether to do a desaturation first (defaults to yes). Not needed if the image is already desaturated. With an image that has some color, it can lead to interesting effects with option set to "no".
The plug-in appears under Color -> Desaturate as "Sepia(lew)...". But it's easy to move it elsewhere.
October 2021 update: I recently did a clean install of GIMP 2.10.24 (after an upgrade to Catalina), and had to re-install this plugin. A few additional notes.
/Applications/, this first line would be:
do_sepia, menu="<Image>/Colors/Desaturate"to make the plug-in appear in the menus.
Here's the profile of the mapping of grey scale values (x axis) to RGB values (y axis) of the above algorithm. This was obtained by looking at any row of the a gradient image here passed thru above algo.
With the current default parameters (23, 19), the toning will have a red tint, my current preference.
For comparison, here's the profile for GIMP's implementation (Microsoft's) of sepia. Very different. This was with sRGB checked. Without this option, the resulting curves are very similar, only closer to the center diagonal.
And here is the profile of Preview's implementation of sepia. Somewhat similar to what I have.
P.P.S. I might have confused some terms, eg. "luminosity", "luminance". In the above discussion, "luminance" means "
lum = (30*r + 59*g + 11*b)" and I think that matches GIMP's "Colors -> Desaturate -> Desaturate... -> Luminance".
Home. Email: (franšais, English, Deutsch).