blob: b3f7bd72f639ecbbe972eb5eca2adc491fb470eb [file] [log] [blame]
/* Various functions relating to tone curve adjustment.
*
* Author: John Cupitt
* Written on: 18/7/1995
* 17/9/96 JC
* - restrictions on Ps, Pm, Ph relaxed
* - restrictions on S, M, H relaxed
* 25/7/01 JC
* - patched for im_extract_band() change
* 11/7/04
* - generalised to im_tone_build_range() ... so you can use it for any
* image, not just LabS
* 26/3/10
* - cleanups
* - gtkdoc
*/
/*
This file is part of VIPS.
VIPS is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vips/vips.h>
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/
/* Parameters for tone curve formation.
*/
typedef struct {
/* Parameters.
*/
double Lb, Lw;
double Ps, Pm, Ph;
double S, M, H;
/* Derived values.
*/
double Ls, Lm, Lh;
} ToneShape;
/* Calculate shadow curve.
*/
static double
shad( ToneShape *ts, double x )
{
double x1 = (x - ts->Lb) / (ts->Ls - ts->Lb);
double x2 = (x - ts->Ls) / (ts->Lm - ts->Ls);
double out;
if( x < ts->Lb )
out = 0;
else if( x < ts->Ls )
out = 3.0 * x1 * x1 - 2.0 * x1 * x1 * x1;
else if( x < ts->Lm )
out = 1.0 - 3.0 * x2 * x2 + 2.0 * x2 * x2 * x2;
else
out = 0;
return( out );
}
/* Calculate mid-tone curve.
*/
static double
mid( ToneShape *ts, double x )
{
double x1 = (x - ts->Ls) / (ts->Lm - ts->Ls);
double x2 = (x - ts->Lm) / (ts->Lh - ts->Lm);
double out;
if( x < ts->Ls )
out = 0;
else if( x < ts->Lm )
out = 3.0 * x1 * x1 - 2.0 * x1 * x1 * x1;
else if( x < ts->Lh )
out = 1.0 - 3.0 * x2 * x2 + 2.0 * x2 * x2 * x2;
else
out = 0;
return( out );
}
/* Calculate highlight curve.
*/
static double
high( ToneShape *ts, double x )
{
double x1 = (x - ts->Lm) / (ts->Lh - ts->Lm);
double x2 = (x - ts->Lh) / (ts->Lw - ts->Lh);
double out;
if( x < ts->Lm )
out = 0;
else if( x < ts->Lh )
out = 3.0 * x1 * x1 - 2.0 * x1 * x1 * x1;
else if( x < ts->Lw )
out = 1.0 - 3.0 * x2 * x2 + 2.0 * x2 * x2 * x2;
else
out = 0;
return( out );
}
/* Generate a point on the tone curve. Everything is 0-100.
*/
static double
tone_curve( ToneShape *ts, double x )
{
double out;
out = x +
ts->S * shad( ts, x ) + ts->M * mid( ts, x ) +
ts->H * high( ts, x );
return( out );
}
/**
* im_tone_build_range:
* out: output image
* in_max: input range
* out_max: output range
* Lb: black-point [0-100]
* Lw: white-point [0-100]
* Ps: shadow point (eg. 0.2)
* Pm: mid-tone point (eg. 0.5)
* Ph: highlight point (eg. 0.8)
* S: shadow adjustment (+/- 30)
* M: mid-tone adjustment (+/- 30)
* H: highlight adjustment (+/- 30)
*
* im_tone_build_range() generates a tone curve for the adjustment of image
* levels. It is mostly designed for adjusting the L* part of a LAB image in
* way suitable for print work, but you can use it for other things too.
*
* The curve is an unsigned 16-bit image with (@in_max + 1) entries,
* each in the range [0, @out_max].
*
* @Lb, @Lw are expressed as 0-100, as in LAB colour space. You
* specify the scaling for the input and output images with the @in_max and
* @out_max parameters.
*
* See also: im_ismonotonic(), im_tone_map(), im_tone_analyse().
*
* Returns: 0 on success, -1 on error
*/
int
im_tone_build_range( IMAGE *out,
int in_max, int out_max,
double Lb, double Lw,
double Ps, double Pm, double Ph,
double S, double M, double H )
{
ToneShape *ts;
unsigned short lut[65536];
int i;
/* Check args.
*/
if( !(ts = IM_NEW( out, ToneShape )) ||
im_outcheck( out ) )
return( -1 );
if( in_max < 0 || in_max > 65535 ||
out_max < 0 || out_max > 65535 ) {
im_error( "im_tone_build",
"%s", _( "bad in_max, out_max parameters" ) );
return( -1 );
}
if( Lb < 0 || Lb > 100 || Lw < 0 || Lw > 100 || Lb > Lw ) {
im_error( "im_tone_build",
"%s", _( "bad Lb, Lw parameters" ) );
return( -1 );
}
if( Ps < 0.0 || Ps > 1.0 ) {
im_error( "im_tone_build",
"%s", _( "Ps not in range [0.0,1.0]" ) );
return( -1 );
}
if( Pm < 0.0 || Pm > 1.0 ) {
im_error( "im_tone_build",
"%s", _( "Pm not in range [0.0,1.0]" ) );
return( -1 );
}
if( Ph < 0.0 || Ph > 1.0 ) {
im_error( "im_tone_build",
"%s", _( "Ph not in range [0.0,1.0]" ) );
return( -1 );
}
if( S < -30 || S > 30 ) {
im_error( "im_tone_build",
"%s", _( "S not in range [-30,+30]" ) );
return( -1 );
}
if( M < -30 || M > 30 ) {
im_error( "im_tone_build",
"%s", _( "M not in range [-30,+30]" ) );
return( -1 );
}
if( H < -30 || H > 30 ) {
im_error( "im_tone_build",
"%s", _( "H not in range [-30,+30]" ) );
return( -1 );
}
/* Note params.
*/
ts->Lb = Lb;
ts->Lw = Lw;
ts->Ps = Ps;
ts->Pm = Pm;
ts->Ph = Ph;
ts->S = S;
ts->M = M;
ts->H = H;
/* Note derived params.
*/
ts->Ls = Lb + Ps * (Lw - Lb);
ts->Lm = Lb + Pm * (Lw - Lb);
ts->Lh = Lb + Ph * (Lw - Lb);
/* Generate curve.
*/
for( i = 0; i <= in_max; i++ ) {
int v = (out_max / 100.0) *
tone_curve( ts, 100.0 * i / in_max );
if( v < 0 )
v = 0;
else if( v > out_max )
v = out_max;
lut[i] = v;
}
/* Make the output image.
*/
im_initdesc( out,
in_max + 1, 1, 1, IM_BBITS_SHORT, IM_BANDFMT_USHORT,
IM_CODING_NONE, IM_TYPE_HISTOGRAM, 1.0, 1.0, 0, 0 );
if( im_setupout( out ) )
return( -1 );
if( im_writeline( 0, out, (PEL *) lut ) )
return( -1 );
return( 0 );
}
/**
* im_tone_build:
* out: output image
* Lb: black-point [0-100]
* Lw: white-point [0-100]
* Ps: shadow point (eg. 0.2)
* Pm: mid-tone point (eg. 0.5)
* Ph: highlight point (eg. 0.8)
* S: shadow adjustment (+/- 30)
* M: mid-tone adjustment (+/- 30)
* H: highlight adjustment (+/- 30)
*
* As im_tone_build_range(), but set 32767 and 32767 as values for @in_max
* and @out_max. This makes a curve suitable for correcting LABS
* images, the most common case.
*
* See also: im_tone_build_range().
*
* Returns: 0 on success, -1 on error
*/
int
im_tone_build( IMAGE *out,
double Lb, double Lw,
double Ps, double Pm, double Ph,
double S, double M, double H )
{
IMAGE *t1;
if( !(t1 = im_open_local( out, "im_tone_build", "p" )) ||
im_tone_build_range( t1, 32767, 32767,
Lb, Lw, Ps, Pm, Ph, S, M, H ) ||
im_clip2fmt( t1, out, IM_BANDFMT_SHORT ) )
return( -1 );
return( 0 );
}
/**
* im_ismonotonic:
* @lut: lookup-table to test
* @out: set non-zero if @lut is monotonic
*
* Test @lut for monotonicity. @out is set non-zero if @lut is monotonic.
*
* See also: im_tone_build_range().
*
* Returns: 0 on success, -1 on error
*/
int
im_ismonotonic( IMAGE *lut, int *out )
{
IMAGE *t[2];
INTMASK *mask;
double m;
if( im_check_hist( "im_ismonotonic", lut ) ||
im_open_local_array( lut, t, 2, "im_ismonotonic", "p" ) )
return( -1 );
if( lut->Xsize == 1 )
mask = im_create_imaskv( "im_ismonotonic", 1, 2, -1, 1 );
else
mask = im_create_imaskv( "im_ismonotonic", 2, 1, -1, 1 );
if( !(mask = im_local_imask( lut, mask )) )
return( -1 );
mask->offset = 128;
/* We want >=128 everywhere, ie. no -ve transitions.
*/
if( im_conv( lut, t[0], mask ) ||
im_moreeqconst( t[0], t[1], 128 ) ||
im_min( t[1], &m ) )
return( -1 );
*out = m;
return( 0 );
}
/**
* im_tone_map:
* @in: input image
* @out: output image
* @lut: look-up table
*
* Map the first channel of @in through @lut. If @in is IM_CODING_LABQ, unpack
* to LABS, map L and then repack.
*
* @in should be a LABS or LABQ image for this to work
* sensibly.
*
* See also: im_maplut().
*
* Returns: 0 on success, -1 on error
*/
int
im_tone_map( IMAGE *in, IMAGE *out, IMAGE *lut )
{
IMAGE *t[8];
if( im_check_hist( "im_tone_map", lut ) ||
im_open_local_array( out, t, 8, "im_tone_map", "p" ) )
return( -1 );
/* If in is IM_CODING_LABQ, unpack.
*/
if( in->Coding == IM_CODING_LABQ ) {
if( im_LabQ2LabS( in, t[0] ) )
return( -1 );
}
else
t[0] = in;
/* Split into bands.
*/
if( im_extract_band( t[0], t[1], 0 ) )
return( -1 );
if( t[0]->Bands > 1 ) {
if( im_extract_bands( t[0], t[2], 1, t[0]->Bands - 1 ) )
return( -1 );
}
/* Map L.
*/
if( im_maplut( t[1], t[3], lut ) )
return( -1 );
/* Recombine bands.
*/
if( t[0]->Bands > 1 ) {
if( im_bandjoin( t[3], t[2], t[4] ) )
return( -1 );
}
else
t[4] = t[3];
/* If input was LabQ, repack.
*/
if( in->Coding == IM_CODING_LABQ ) {
if( im_LabS2LabQ( t[4], t[5] ) )
return( -1 );
}
else
t[5] = t[4];
return( im_copy( t[4], out ) );
}
/**
* im_tone_analyse:
* in: input image
* out: output image
* Ps: shadow point (eg. 0.2)
* Pm: mid-tone point (eg. 0.5)
* Ph: highlight point (eg. 0.8)
* S: shadow adjustment (+/- 30)
* M: mid-tone adjustment (+/- 30)
* H: highlight adjustment (+/- 30)
*
* As im_tone_build(), but analyse the histogram of @in and use it to
* pick the 0.1% and 99.9% points for @Lb and @Lw.
*
* See also: im_tone_build().
*
* Returns: 0 on success, -1 on error
*/
int
im_tone_analyse(
IMAGE *in,
IMAGE *out,
double Ps, double Pm, double Ph,
double S, double M, double H )
{
IMAGE *t[4];
int low, high;
double Lb, Lw;
if( im_open_local_array( out, t, 4, "im_tone_map", "p" ) )
return( -1 );
/* If in is IM_CODING_LABQ, unpack.
*/
if( in->Coding == IM_CODING_LABQ ) {
if( im_LabQ2LabS( in, t[0] ) )
return( -1 );
}
else
t[0] = in;
/* Should now be 3-band short.
*/
if( im_check_uncoded( "im_tone_analyse", t[0] ) ||
im_check_bands( "im_tone_analyse", t[0], 3 ) ||
im_check_format( "im_tone_analyse", t[0], IM_BANDFMT_SHORT ) )
return( -1 );
if( im_extract_band( t[0], t[1], 0 ) ||
im_clip2fmt( t[1], t[2], IM_BANDFMT_USHORT ) ||
im_histgr( t[2], t[3], -1 ) )
return( -1 );
if( im_mpercent_hist( t[3], 0.1 / 100.0, &high ) ||
im_mpercent_hist( t[3], 99.9 / 100.0, &low ) )
return( -1 );
Lb = 100 * low / 32768;
Lw = 100 * high / 32768;
im_diag( "im_tone_analyse", "set Lb = %g, Lw = %g", Lb, Lw );
return( im_tone_build( out, Lb, Lw, Ps, Pm, Ph, S, M, H ) );
}