1 /* Morph a lab image.
2  *
3  * 8/3/01
4  * 	- added
5  * 2/11/09
6  * 	- cleanups
7  * 	- gtkdoc
8  */
9 
10 /*
11 
12     This file is part of VIPS.
13 
14     VIPS is free software; you can redistribute it and/or modify
15     it under the terms of the GNU Lesser General Public License as published by
16     the Free Software Foundation; either version 2 of the License, or
17     (at your option) any later version.
18 
19     This program is distributed in the hope that it will be useful,
20     but WITHOUT ANY WARRANTY; without even the implied warranty of
21     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22     GNU Lesser General Public License for more details.
23 
24     You should have received a copy of the GNU Lesser General Public License
25     along with this program; if not, write to the Free Software
26     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27     02110-1301  USA
28 
29  */
30 
31 /*
32 
33     These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
34 
35  */
36 
37 #ifdef HAVE_CONFIG_H
38 #include <config.h>
39 #endif /*HAVE_CONFIG_H*/
40 #include <vips/intl.h>
41 
42 #include <stdio.h>
43 #include <string.h>
44 #include <assert.h>
45 #include <stdlib.h>
46 
47 #include <vips/vips.h>
48 #include <vips/vips7compat.h>
49 #include <vips/internal.h>
50 
51 int
im__colour_unary(const char * domain,IMAGE * in,IMAGE * out,VipsType type,im_wrapone_fn buffer_fn,void * a,void * b)52 im__colour_unary( const char *domain,
53 	IMAGE *in, IMAGE *out, VipsType type,
54 	im_wrapone_fn buffer_fn, void *a, void *b )
55 {
56 	IMAGE *t[1];
57 
58 	if( im_check_uncoded( domain, in ) ||
59 		im_check_bands( domain, in, 3 ) ||
60 		im_open_local_array( out, t, 1, domain, "p" ) ||
61 		im_clip2fmt( in, t[0], IM_BANDFMT_FLOAT ) )
62 		return( -1 );
63 
64 	if( im_cp_desc( out, t[0] ) )
65 		return( -1 );
66 	out->Type = type;
67 
68 	if( im_wrapone( t[0], out,
69 		(im_wrapone_fn) buffer_fn, a, b ) )
70 		return( -1 );
71 
72 	return( 0 );
73 }
74 
75 
76 typedef struct {
77 	IMAGE *in, *out;
78 
79 	double L_scale, L_offset;
80 
81 	double a_offset[101], b_offset[101];
82 	double a_scale, b_scale;
83 } Params;
84 
85 static int
morph_init(Params * parm,IMAGE * in,IMAGE * out,double L_scale,double L_offset,DOUBLEMASK * mask,double a_scale,double b_scale)86 morph_init( Params *parm,
87 	IMAGE *in, IMAGE *out,
88 	double L_scale, double L_offset,
89 	DOUBLEMASK *mask, double a_scale, double b_scale )
90 {
91 	int i, j;
92 
93 	parm->in = in;
94 	parm->out = out;
95 	parm->L_scale = L_scale;
96 	parm->L_offset = L_offset;
97 	parm->a_scale = a_scale;
98 	parm->b_scale = b_scale;
99 
100 	if( mask->xsize != 3 || mask->ysize < 1 || mask->ysize > 100 ) {
101 		im_error( "im_lab_morph", "%s",
102 			_( "bad greyscale mask size" ) );
103 		return( -1 );
104 	}
105 	for( i = 0; i < mask->ysize; i++ ) {
106 		double L = mask->coeff[i*3];
107 		double a = mask->coeff[i*3 + 1];
108 		double b = mask->coeff[i*3 + 2];
109 
110 		if( L < 0 || L > 100 || a < -120 || a > 120 ||
111 			b < -120 || b > 120 ) {
112 			im_error( "im_lab_morph",
113 				_( "bad greyscale mask value, row %d" ), i );
114 			return( -1 );
115 		}
116 	}
117 
118 	/* Generate a/b offsets.
119 	 */
120 	for( i = 0; i <= 100; i++ ) {
121 		double L_low = 0;
122 		double a_low = 0;
123 		double b_low = 0;
124 
125 		double L_high = 100;
126 		double a_high = 0;
127 		double b_high = 0;
128 
129 		/* Search for greyscale L just below i. Don't assume sorted by
130 		 * L*.
131 		 */
132 		for( j = 0; j < mask->ysize; j++ ) {
133 			double L = mask->coeff[j*3];
134 			double a = mask->coeff[j*3 + 1];
135 			double b = mask->coeff[j*3 + 2];
136 
137 			if( L < i && L > L_low ) {
138 				L_low = L;
139 				a_low = a;
140 				b_low = b;
141 			}
142 		}
143 
144 		/* Search for greyscale L just above i.
145 		 */
146 		for( j = mask->ysize - 1; j >= 0; j-- ) {
147 			double L = mask->coeff[j*3];
148 			double a = mask->coeff[j*3 + 1];
149 			double b = mask->coeff[j*3 + 2];
150 
151 			if( L >= i && L < L_high ) {
152 				L_high = L;
153 				a_high = a;
154 				b_high = b;
155 			}
156 		}
157 
158 		/* Interpolate.
159 		 */
160 		parm->a_offset[i] = a_low +
161 			(a_high - a_low) * ((i - L_low) / (L_high - L_low));
162 		parm->b_offset[i] = b_low +
163 			(b_high - b_low) * ((i - L_low) / (L_high - L_low));
164 	}
165 
166 	return( 0 );
167 }
168 
169 static void
morph_buffer(float * in,float * out,int width,Params * parm)170 morph_buffer( float *in, float *out, int width, Params *parm )
171 {
172 	int x;
173 
174 	for( x = 0; x < width; x++ ) {
175 		double L = in[0];
176 		double a = in[1];
177 		double b = in[2];
178 
179 		L = IM_CLIP( 0, L, 100 );
180 		a -= parm->a_offset[(int) L];
181 		b -= parm->b_offset[(int) L];
182 
183 		L = (L + parm->L_offset) * parm->L_scale;
184 		L = IM_CLIP( 0, L, 100 );
185 
186 		a *= parm->a_scale;
187 		b *= parm->b_scale;
188 
189 		out[0] = L;
190 		out[1] = a;
191 		out[2] = b;
192 
193 		in += 3;
194 		out += 3;
195 	}
196 }
197 
198 /**
199  * im_lab_morph:
200  * @in: input image
201  * @out: output image
202  * @mask: cast correction table
203  * @L_offset: L adjustment
204  * @L_scale: L adjustment
205  * @a_scale: a scale
206  * @b_scale: b scale
207  *
208  * Morph an image in CIELAB colour space. Useful for certain types of gamut
209  * mapping, or correction of greyscales on some printers.
210  *
211  * We perform three adjustments:
212  *
213  * <itemizedlist>
214  *   <listitem>
215  *     <para>
216  *       <emphasis>cast</emphasis>
217  *
218  * Pass in @mask containing CIELAB readings for a neutral greyscale. For
219  * example:
220  *
221  * <tgroup cols='3' align='left' colsep='1' rowsep='1'>
222  *   <tbody>
223  *     <row>
224  *       <entry>3</entry>
225  *       <entry>4</entry>
226  *     </row>
227  *     <row>
228  *       <entry>14.23</entry>
229  *       <entry>4.8</entry>
230  *       <entry>-3.95</entry>
231  *     </row>
232  *     <row>
233  *       <entry>18.74</entry>
234  *       <entry>2.76</entry>
235  *       <entry>-2.62</entry>
236  *     </row>
237  *     <row>
238  *       <entry>23.46</entry>
239  *       <entry>1.4</entry>
240  *       <entry>-1.95</entry>
241  *     </row>
242  *     <row>
243  *       <entry>27.53</entry>
244  *       <entry>1.76</entry>
245  *       <entry>-2.01</entry>
246  *     </row>
247  *   </tbody>
248  * </tgroup>
249  *
250  * Interpolation from this makes cast corrector. The top and tail are
251  * interpolated towards [0, 0, 0] and [100, 0, 0], intermediate values are
252  * interpolated along straight lines fitted between the specified points.
253  * Rows may be in any order (ie. they need not be sorted on L*).
254  *
255  * Each pixel is displaced in a/b by the amount specified for that L in the
256  * table.
257  *     </para>
258  *   </listitem>
259  *   <listitem>
260  *     <para>
261  *       <emphasis>L*</emphasis>
262  *
263  * Pass in scale and offset for L. L' = (L + offset) * scale.
264  *     </para>
265  *   </listitem>
266  *   <listitem>
267  *     <para>
268  *       <emphasis>saturation</emphasis>
269  *
270  * scale a and b by these amounts, eg. 1.5 increases saturation.
271  *     </para>
272  *   </listitem>
273  * </itemizedlist>
274  *
275  * Find the top two by generating and printing a greyscale. Find the bottom
276  * by printing a Macbeth and looking at a/b spread
277  *
278  * Returns: 0 on success, -1 on error.
279  */
280 int
im_lab_morph(IMAGE * in,IMAGE * out,DOUBLEMASK * mask,double L_offset,double L_scale,double a_scale,double b_scale)281 im_lab_morph( IMAGE *in, IMAGE *out,
282 	DOUBLEMASK *mask,
283 	double L_offset, double L_scale,
284 	double a_scale, double b_scale )
285 {
286 	Params *parm;
287 
288         /* Recurse for coded images.
289          */
290 	if( in->Coding == IM_CODING_LABQ ) {
291 		IMAGE *t[2];
292 
293 		if( im_open_local_array( out, t, 2, "im_lab_morph", "p" ) ||
294 			im_LabQ2Lab( in, t[0] ) ||
295 			im_lab_morph( t[0], t[1],
296 				mask, L_offset, L_scale, a_scale, b_scale ) ||
297 			im_Lab2LabQ( t[1], out ) )
298 			return( -1 );
299 
300 		return( 0 );
301 	}
302 
303 	if( !(parm = IM_NEW( out, Params )) ||
304 		morph_init( parm,
305 			in, out, L_scale, L_offset, mask, a_scale, b_scale ) )
306 		return( -1 );
307 
308 	return( im__colour_unary( "im_lab_morph", in, out, IM_TYPE_LAB,
309 		(im_wrapone_fn) morph_buffer, parm, NULL ) );
310 }
311