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