Arbitrary tonal reproduction curves

Questions and postings pertaining to the usage of ImageMagick regardless of the interface. This includes the command-line utilities, as well as the C and C++ APIs. Usage questions are like "How do I use ImageMagick to create drop shadows?".
Post Reply
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Arbitrary tonal reproduction curves

Post by snibgo »

IM contains tools such as "-level" for adjusting tones, but doesn't contain an in-built facility for arbitrary curves.

It is possible to use IM for this, but with limitations (see below). This Windows script specifies a curve from two segments. The curve looks like one that might be created in Gimp or Photoshop, but my own interest is in programmatic generation of curves. The script uses IM to make a visualisation of the curve, and then to make a one-dimensional file for CLUTting against an image.

Code: Select all

set seg1=-draw "stroke White fill None bezier 0,4000 0,0 400,2800 800,3200"
set seg2=-draw "stroke White fill None bezier 800,3200 1600,4000 3999,1600 3999,0"

set curve=%seg1% %seg2%

set SQDIM=4000

rem Create curveL.tiff, just so we can see the curve.
"%IMG%convert" ^
    -size %SQDIM%x%SQDIM% xc:Black ^
    +antialias ^
    %curve% ^

rem Create one-dimensional curveClut.tiff, to be used as a clut file.
"%IMG%convert" ^
  -size %SQDIM%x%SQDIM% gradient: ^
  ( -size %SQDIM%x%SQDIM% xc:Black ^
    +antialias ^
    %curve% ^
    -alpha off ^
  ) ^
  -compose CopyOpacity -composite ^
  -resize "%SQDIM%x1^!" ^
  -alpha off ^

"%IMG%identify" curveClut.tiff

"%IMG%convert" ^
  rose: ^
  curveClut.tiff ^
  -clut ^

1. I would like 16-bit precision, but my computer is too small to handle images of 64k x 64k pixels.

2. The generated curve sometimes has two pixels in the vertical dimension, so the resize will take an average of the two values. This is reasonable behaviour, but raises the question of how to get the maximum precision from the process. I don't know enough about how IM resizes with transparency. Perhaps "-antialias" would give a more correct result.

3. For the example, I specify the curves with Bezier control points. These can of course be generated from more user-friendly parameters, while ensuring the curve doesn't contain multiple y-values for any x-value.

If IM contained this as a built-in facility, it could have full 16-bit precision without needing to generate 64k x 64k files. It would be nice to see this, perhaps in V7.
snibgo's IM pages:
User avatar
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Arbitrary tonal reproduction curves

Post by fmw42 »

see and

or if on Linux, Mac or Windows w/Cygwin, see my script, curves, at the link below
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Arbitrary tonal reproduction curves

Post by snibgo »

Thanks for the links.

I've discovered that I like to specify tone curves as triplets {x, y, angle} where 0<=x,y<=1 (bottom-left is 0,0) and angle is degrees for the tangent at x,y. (0 is upwards, 90 is to the right, 180 is downwards, >180 isn't needed for tone curves.)

So a sigmoidal curve could be {{0,0,90}, {0.25,0.25,0}, {1,1,90}}. A straight line is {{0,0,45}, {1,1,45}}.

Bezier control points can then be calculated:

Code: Select all

typedef struct {
  float x;
  float y;
  BOOL HasAng;
  float ang;
  float xOut, yOut, xIn, yIn; // Control points.
} PointT;

#define NUM_POINTS 100
static PointT Points[NUM_POINTS];
static int nPoints = 0;

{blah blah}

static void CalcPoints (void)
  // y-coords have been flipped so y=0 is top.

  int i;
  for (i = 0; i < nPoints-1; i++) {
    PointT p0 = Points[i];
    PointT p1 = Points[i+1];
    printf ("x=%g y=%g a=%g  ", p0.x, p0.y, p0.ang);
    printf ("x=%g y=%g a=%g\n", p1.x, p1.y, p1.ang);
    float dx = p1.x - p0.x;
    float dy = p1.y - p0.y;
    float dist = sqrt (dx*dx + dy*dy) / 3;
    printf ("dist=%g\n", dist);

    // Calculate control points: exit for p0 and entry for p1.
    float a0 = p0.ang * M_PI / 180;
    p0.xOut = p0.x + dist * sin(a0);
    p0.yOut = p0.y - dist * cos(a0);
    float a1 = p1.ang * M_PI / 180;
    p1.xIn = p1.x - dist * sin(a1);
    p1.yIn = p1.y + dist * cos(a1);
    printf ("Controls: %g %g  %g %g\n", p0.xOut, p0.yOut, p1.xIn, p1.yIn);

    // If any controls are outside 0..1, we should clip the lines,
    // to ensure the curve stays within bounds.
    // This cheap and cheerful method will generally change the gradient:

    BOOL c = FALSE;

    if      (p0.xOut < 0) {p0.xOut = 0; c = TRUE;}
    else if (p0.xOut > 1) {p0.xOut = 1; c = TRUE;}

    if      (p0.yOut < 0) {p0.yOut = 0; c = TRUE;}
    else if (p0.yOut > 1) {p0.yOut = 1; c = TRUE;}

    if      (p1.xIn  < 0) {p1.xIn = 0; c = TRUE;}
    else if (p1.xIn  > 1) {p1.xIn = 1; c = TRUE;}

    if      (p1.yIn  < 0) {p1.yIn = 0; c = TRUE;}
    else if (p1.yIn  > 1) {p1.yIn = 1; c = TRUE;}

    if (c) WarningError ("One or more control points were clipped.");
    Points[i]   = p0;
    Points[i+1] = p1;
snibgo's IM pages:
User avatar
Posts: 8883
Joined: 2004-05-31T19:27:03-07:00
Authentication code: 8675308
Location: Brisbane, Australia

Re: Arbitrary tonal reproduction curves

Post by anthony »

Added a link to this discussion from the Curves examples section.
(give it a couple of hours to appear)
Anthony Thyssen -- Webmaster for ImageMagick Example Pages
Post Reply