1 /*
2 */
3
4 /*
5
6 Copyright (C) 2014 Ferrero Andrea
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21
22 */
23
24 /*
25
26 These files are distributed with PhotoFlow - http://aferrero2707.github.io/PhotoFlow/
27
28 */
29
30 #include "scale.hh"
31
32 /*
33 * Post-rotation auto-cropping
34 *
35 * Source code adapted from Darktable v1.6.8
36 */
37
38 /** region of interest */
39 typedef struct dt_iop_roi_t
40 {
41 int x, y, width, height;
42 float scale;
43 } dt_iop_roi_t;
44
45
46 typedef struct dt_iop_clipping_data_t
47 {
48 float angle; // rotation angle
49 float aspect; // forced aspect ratio
50 float m[4]; // rot matrix
51 float ki_h, k_h; // keystone correction, ki and corrected k
52 float ki_v, k_v; // keystone correction, ki and corrected k
53 float tx, ty; // rotation center
54 float cx, cy, cw, ch; // crop window
55 float cix, ciy, ciw, cih; // crop window on roi_out 1.0 scale
56 uint32_t all_off; // 1: v and h off, else one of them is used
57 uint32_t flags; // flipping flags
58 uint32_t flip; // flipped output buffer so more area would fit.
59
60 float k_space[4]; // space for the "destination" rectangle of the keystone quadrilatere
61 float kxa, kya, kxb, kyb, kxc, kyc, kxd,
62 kyd; // point of the "source" quadrilatere (modified if keystone is not "full")
63 float a, b, d, e, g, h; // value of the transformation matrix (c=f=0 && i=1)
64 int k_apply;
65 int crop_auto;
66 float enlarge_x, enlarge_y;
67 } dt_iop_clipping_data_t;
68
69
70
71 // helper to count corners in for loops:
get_corner(const float * aabb,const int i,float * p)72 static void get_corner(const float *aabb, const int i, float *p)
73 {
74 for(int k = 0; k < 2; k++) p[k] = aabb[2 * ((i >> k) & 1) + k];
75 }
76
77
78
mul_mat_vec_2(const float * m,const float * p,float * o)79 static void mul_mat_vec_2(const float *m, const float *p, float *o)
80 {
81 o[0] = p[0] * m[0] + p[1] * m[1];
82 o[1] = p[0] * m[2] + p[1] * m[3];
83 }
84
85
transform(float * x,float * o,const float * m,const float t_h,const float t_v)86 static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
87 {
88 float rt[] = { m[0], -m[1], -m[2], m[3] };
89 mul_mat_vec_2(rt, x, o);
90 o[1] *= (1.0f + o[0] * t_h);
91 o[0] *= (1.0f + o[1] * t_v);
92 }
93
94
95
96 // 1st pass: how large would the output be, given this input roi?
97 // this is always called with the full buffer before processing.
modify_roi_out(dt_iop_clipping_data_t * d,dt_iop_roi_t * roi_in_orig,dt_iop_roi_t * roi_out)98 static void modify_roi_out(dt_iop_clipping_data_t* d, dt_iop_roi_t* roi_in_orig, dt_iop_roi_t* roi_out)
99 {
100 dt_iop_roi_t roi_in_d = *roi_in_orig;
101 dt_iop_roi_t *roi_in = &roi_in_d;
102
103 // use whole-buffer roi information to create matrix and inverse.
104 float rt[] = { cosf(d->angle), sinf(d->angle), -sinf(d->angle), cosf(d->angle) };
105 if(d->angle == 0.0f)
106 {
107 rt[0] = rt[3] = 1.0;
108 rt[1] = rt[2] = 0.0f;
109 }
110
111 for(int k = 0; k < 4; k++) d->m[k] = rt[k];
112
113 d->ki_h = d->ki_v = d->k_h = d->k_v = 0.0f;
114
115 *roi_out = *roi_in;
116
117 // correct keystone correction factors by resolution of this buffer
118 const float kc = 1.0f / fminf(roi_in->width, roi_in->height);
119 d->k_h = d->ki_h * kc;
120 d->k_v = d->ki_v * kc;
121
122 d->cx = 0.0f;
123 d->cy = 0.0f;
124 d->cw = 1.0f;
125 d->ch = 1.0f;
126
127 float cropscale = -1.0f;
128 // check portrait/landscape orientation, whichever fits more area:
129 const float oaabb[4]
130 = { -.5f * roi_in->width, -.5f * roi_in->height, .5f * roi_in->width, .5f * roi_in->height };
131 for(int flip = 0; flip < 2; flip++)
132 {
133 const float roi_in_width = flip ? roi_in->height : roi_in->width;
134 const float roi_in_height = flip ? roi_in->width : roi_in->height;
135 float newcropscale = 1.0f;
136 // fwd transform rotated points on corners and scale back inside roi_in bounds.
137 float p[2], o[2],
138 aabb[4] = { -.5f * roi_in_width, -.5f * roi_in_height, .5f * roi_in_width, .5f * roi_in_height };
139 for(int c = 0; c < 4; c++)
140 {
141 get_corner(oaabb, c, p);
142 transform(p, o, rt, d->k_h, d->k_v);
143 for(int k = 0; k < 2; k++)
144 if(fabsf(o[k]) > 0.001f) newcropscale = fminf(newcropscale, aabb[(o[k] > 0 ? 2 : 0) + k] / o[k]);
145 }
146 if(newcropscale >= cropscale)
147 {
148 cropscale = newcropscale;
149 // remember rotation center in whole-buffer coordinates:
150 d->tx = roi_in->width * .5f;
151 d->ty = roi_in->height * .5f;
152 d->flip = flip;
153
154 float ach = d->ch - d->cy, acw = d->cw - d->cx;
155 // rotate and clip to max extent
156 if(flip)
157 {
158 roi_out->y = d->tx - (.5f - d->cy) * cropscale * roi_in->width;
159 roi_out->x = d->ty - (.5f - d->cx) * cropscale * roi_in->height;
160 roi_out->height = ach * cropscale * roi_in->width;
161 roi_out->width = acw * cropscale * roi_in->height;
162 }
163 else
164 {
165 roi_out->x = d->tx - (.5f - d->cx) * cropscale * roi_in->width;
166 roi_out->y = d->ty - (.5f - d->cy) * cropscale * roi_in->height;
167 roi_out->width = acw * cropscale * roi_in->width;
168 roi_out->height = ach * cropscale * roi_in->height;
169 }
170 //std::cout<<"acw="<<acw<<" ach="<<ach<<" cropscale="<<cropscale<<std::endl;
171 //std::cout<<"roi_in: "<<roi_in->width<<"x"<<roi_in->height<<"+"<<roi_in->x<<","<<roi_in->y<<std::endl;
172 //std::cout<<"roi_out: "<<roi_out->width<<"x"<<roi_out->height<<"+"<<roi_out->x<<","<<roi_out->y<<std::endl;
173 }
174 }
175
176 // sanity check.
177 if(roi_out->x < 0) roi_out->x = 0;
178 if(roi_out->y < 0) roi_out->y = 0;
179 if(roi_out->width < 1) roi_out->width = 1;
180 if(roi_out->height < 1) roi_out->height = 1;
181
182 // save rotation crop on output buffer in world scale:
183 d->cix = roi_out->x;
184 d->ciy = roi_out->y;
185 d->ciw = roi_out->width;
186 d->cih = roi_out->height;
187 }
188
189
190
191
ScalePar()192 PF::ScalePar::ScalePar():
193 OpParBase(),
194 vflip("vflip",this,false),
195 hflip("hflip",this,false),
196 rotate_angle("rotate_angle",this,0),
197 autocrop("autocrop",this,true),
198 scale_mode("scale_mode",this, PF::SCALE_MODE_FIT, "SCALE_MODE_FIT", _("Fit")),
199 scale_unit("scale_unit", this, PF::SCALE_UNIT_PERCENT, "SCALE_UNIT_PERCENT", _("percent")),
200 scale_interp("scale_interp", this, PF::SCALE_INTERP_LANCZOS3, "SCALE_INTERP_LANCZOS3", _("lanczos3")),
201 scale_width_pixels("scale_width_pixels",this,0),
202 scale_height_pixels("scale_height_pixels",this,0),
203 scale_width_percent("scale_width_percent",this,100),
204 scale_height_percent("scale_height_percent",this,100),
205 scale_width_mm("scale_width_mm",this,0),
206 scale_height_mm("scale_height_mm",this,0),
207 scale_width_cm("scale_width_cm",this,0),
208 scale_height_cm("scale_height_cm",this,0),
209 scale_width_inches("scale_width_inches",this,0),
210 scale_height_inches("scale_height_inches",this,0),
211 scale_resolution("scale_resolution",this,300),
212 rotation_points("rotation_points",this),
213 sin_angle(0), cos_angle(1), scale_mult(1)
214 {
215 //scale_mode.add_enum_value( SCALE_MODE_FILL, "SCALE_MODE_FILL", "Fill" );
216 //scale_mode.add_enum_value( SCALE_MODE_RESIZE, "SCALE_MODE_RESIZE", "Resize" );
217
218 scale_unit.add_enum_value( SCALE_UNIT_PX, "SCALE_UNIT_PX", _("pixels") );
219 scale_unit.add_enum_value( SCALE_UNIT_MM, "SCALE_UNIT_MM", _("mm") );
220 scale_unit.add_enum_value( SCALE_UNIT_CM, "SCALE_UNIT_CM", _("cm") );
221 scale_unit.add_enum_value( SCALE_UNIT_INCHES, "SCALE_UNIT_INCHES", _("inches") );
222
223 scale_interp.add_enum_value( SCALE_INTERP_NEAREST, "SCALE_INTERP_NEAREST", _("nearest") );
224 scale_interp.add_enum_value( SCALE_INTERP_BILINEAR, "SCALE_INTERP_BILINEAR", _("bilinear") );
225 scale_interp.add_enum_value( SCALE_INTERP_BICUBIC, "SCALE_INTERP_BICUBIC", _("bicubic") );
226 scale_interp.add_enum_value( SCALE_INTERP_NOHALO, "SCALE_INTERP_NOHALO", _("nohalo") );
227 scale_interp.add_enum_value( SCALE_INTERP_LANCZOS2, "SCALE_INTERP_LANCZOS2", _("lanczos2") );
228
229 set_type( "scale" );
230
231 set_default_name( _("scale and rotate") );
232 }
233
234
235
build(std::vector<VipsImage * > & in,int first,VipsImage * imap,VipsImage * omap,unsigned int & level)236 VipsImage* PF::ScalePar::build(std::vector<VipsImage*>& in, int first,
237 VipsImage* imap, VipsImage* omap,
238 unsigned int& level)
239 {
240 VipsImage* srcimg = NULL;
241 if( in.size() > 0 ) srcimg = in[0];
242 if( srcimg == NULL ) return NULL;
243 VipsImage* out, *rotated;
244
245 PF_REF( srcimg, "ScalePar::build(): initial srcimg ref" );
246 bool do_autocrop = autocrop.get();
247
248 if( is_editing() ) {
249 //std::cout<<"ScalePar::build(): editing, returning source image"<<std::endl;
250 //PF_REF( srcimg, "ScalePar::build(): srcimg ref (editing mode)" );
251 //return srcimg;
252 do_autocrop = false;
253 }
254
255
256 in_width = srcimg->Xsize;
257 in_height = srcimg->Ysize;
258 out_width = in_width;
259 out_height = in_height;
260 sin_angle = 0;
261 cos_angle = 1;
262
263 crop.left = crop.top = 0;
264 crop.width = out_width;
265 crop.height = out_height;
266
267 if( vflip.get() ) {
268 VipsImage* flipped;
269 if( vips_flip( srcimg, &flipped, VIPS_DIRECTION_VERTICAL, NULL ) ) {
270 PF_UNREF( srcimg, "ScalePar::build(): image unref after vips_flip() failed." );
271 return NULL;
272 }
273 PF_UNREF( srcimg, "ScalePar::build(): image unref after vips_flip()." );
274 srcimg = flipped;
275 }
276
277 if( hflip.get() ) {
278 VipsImage* flipped;
279 if( vips_flip( srcimg, &flipped, VIPS_DIRECTION_HORIZONTAL, NULL ) ) {
280 PF_UNREF( srcimg, "ScalePar::build(): image unref after vips_flip() failed." );
281 return NULL;
282 }
283 PF_UNREF( srcimg, "ScalePar::build(): image unref after vips_flip()." );
284 srcimg = flipped;
285 }
286
287 sin_angle = 0;
288 cos_angle = 1;
289
290 if( rotate_angle.get() != 0 ) {
291 sin_angle = sin( rotate_angle.get() * 3.141592653589793 / 180.0 );
292 cos_angle = cos( rotate_angle.get() * 3.141592653589793 / 180.0 );
293 if( vips_similarity(srcimg, &rotated, "angle", rotate_angle.get(),
294 //"idx", (double)-1*srcimg->Xsize/4,
295 //"idy", (double)-1*srcimg->Ysize/2,
296 //"odx", (double)srcimg->Xsize/4,
297 //"ody", (double)srcimg->Ysize/2,
298 NULL) ) {
299 return NULL;
300 }
301 PF_UNREF( srcimg, "srcimg unref after rotate" );
302
303 out_width = rotated->Xsize;
304 out_height = rotated->Ysize;
305 crop.left = crop.top = 0;
306 crop.width = out_width;
307 crop.height = out_height;
308
309 if( do_autocrop == true ) {
310 dt_iop_clipping_data_t data;
311 data.angle = rotate_angle.get() * 3.141592653589793 / 180.0;
312 dt_iop_roi_t roi_in, roi_out;
313 roi_in.x = roi_in.y = 0;
314 roi_in.width = srcimg->Xsize;
315 roi_in.height = srcimg->Ysize;
316 modify_roi_out(&data, &roi_in, &roi_out);
317 /*
318 int dw1 = static_cast<int>(fabs(sin_angle)*out_height);
319 int dw2 = static_cast<int>(fabs(cos_angle)*out_width);
320 int dh1 = static_cast<int>(fabs(sin_angle)*out_width);
321 int dh2 = static_cast<int>(fabs(cos_angle)*out_height);
322 int dw = MIN( dw1, dw2 );
323 int dh = MIN( dh1, dh2 );
324 std::cout<<"rotation: sin_angle="<<sin_angle<<" cos_angle="<<cos_angle<<std::endl;
325 std::cout<<"rotation: out_width="<<out_width<<" dw1="<<dw1<<" dw2="<<dw2<<" dw="<<dw<<std::endl;
326 //std::cout<<"rotation: width="<<srcimg->Xsize<<"->"<<cropped_width<<" height="<<srcimg->Ysize<<"->"<<cropped_height<<std::endl;
327 if( vips_crop( srcimg, &out, dw, dh, out_width-2*dw, out_height-2*dh, NULL ) ) {
328 std::cout<<"WARNIG: ScalePar::build(): vips_crop() failed."<<std::endl;
329 //std::cout<<"srcimg->Xsize="<<srcimg->Xsize<<" srcimg->Ysize="<<srcimg->Ysize<<std::endl;
330 //std::cout<<"vips_crop( srcimg, &out, "<<crop_left.get()/scale_factor<<", "<<crop_top.get()/scale_factor<<", "
331 // <<crop_width.get()/scale_factor<<", "<<crop_height.get()/scale_factor<<", NULL )"<<std::endl;
332 return NULL;
333 }
334 */
335 roi_out.width -= 2;
336 roi_out.height -= 2;
337 int dw = rotated->Xsize - roi_out.width;
338 int dh = rotated->Ysize - roi_out.height;
339 if( vips_crop( rotated, &out, dw/2, dh/2, roi_out.width, roi_out.height, NULL ) ) {
340 std::cout<<"WARNIG: ScalePar::build(): vips_crop() failed."<<std::endl;
341 //std::cout<<"srcimg->Xsize="<<srcimg->Xsize<<" srcimg->Ysize="<<srcimg->Ysize<<std::endl;
342 //std::cout<<"vips_crop( srcimg, &out, "<<crop_left.get()/scale_factor<<", "<<crop_top.get()/scale_factor<<", "
343 // <<crop_width.get()/scale_factor<<", "<<crop_height.get()/scale_factor<<", NULL )"<<std::endl;
344 return NULL;
345 }
346 PF_UNREF( rotated, "" );
347 crop.left = dw/2;
348 crop.top = dh/2;
349 crop.width = roi_out.width;
350 crop.height = roi_out.height;
351 } else {
352 out = rotated;
353 }
354 set_image_hints( out );
355 srcimg = out;
356 } /*else {
357 PF_REF( srcimg, "ScalePar::build(): srcimg ref for angle=0" );
358 }*/
359
360 scale_mult = 1;
361 int scale_factor = 1;
362 for(unsigned int l = 0; l < level; l++ ) {
363 scale_factor *= 2;
364 }
365 int full_width = srcimg->Xsize * scale_factor;
366 int full_height = srcimg->Ysize * scale_factor;
367 float scale_width = 0;
368 float scale_height = 0;
369
370 if( scale_unit.get_enum_value().first == PF::SCALE_UNIT_PX ) {
371 scale_width = ((float)scale_width_pixels.get())/full_width;
372 scale_height = ((float)scale_height_pixels.get())/full_height;
373 }
374 if( scale_unit.get_enum_value().first == PF::SCALE_UNIT_PERCENT ) {
375 if( scale_mode.get_enum_value().first == PF::SCALE_MODE_FIT ) {
376 scale_width = scale_width_percent.get()/100.0f;
377 scale_height = scale_width_percent.get()/100.0f;
378 } else {
379 scale_width = scale_width_percent.get()/100.0f;
380 scale_height = scale_height_percent.get()/100.0f;
381 }
382 }
383 if( scale_unit.get_enum_value().first == PF::SCALE_UNIT_MM ) {
384 float new_width = ((float)scale_width_mm.get())*scale_resolution.get()/25.4f;
385 float new_height = ((float)scale_height_mm.get())*scale_resolution.get()/25.4f;
386 scale_width = new_width/full_width;
387 scale_height = new_height/full_height;
388 }
389 if( scale_unit.get_enum_value().first == PF::SCALE_UNIT_CM ) {
390 float new_width = ((float)scale_width_cm.get())*scale_resolution.get()/2.54f;
391 float new_height = ((float)scale_height_cm.get())*scale_resolution.get()/2.54f;
392 scale_width = new_width/full_width;
393 scale_height = new_height/full_height;
394 }
395 if( scale_unit.get_enum_value().first == PF::SCALE_UNIT_INCHES ) {
396 int new_width = scale_width_inches.get()*scale_resolution.get();
397 int new_height = scale_height_inches.get()*scale_resolution.get();
398 scale_width = ((float)new_width)/full_width;
399 scale_height = ((float)new_height)/full_height;
400 }
401
402 if( scale_mode.get_enum_value().first == PF::SCALE_MODE_FIT ) {
403 if( /*level!=0 ||*/ (scale_width==1 && scale_height==1) ||
404 (scale_width==0 && scale_height==0) ) {
405 //PF_REF( srcimg, "ScalePar::build(): srcimg ref (editing mode)" );
406 return srcimg;
407 }
408 float scale = 0;
409 if( scale_width == 0 ) scale = scale_height;
410 else if( scale_height == 0 ) scale = scale_width;
411 else scale = MIN( scale_width, scale_height );
412 scale_mult = scale;
413
414 /*
415 VipsInterpolate* interpolate = NULL;
416 if( scale < 1 ) {
417 interpolate = vips_interpolate_new( "nohalo" );
418 if( !interpolate )
419 interpolate = vips_interpolate_new( "bicubic" );
420 if( !interpolate )
421 interpolate = vips_interpolate_new( "bilinear" );
422 } else {
423 interpolate = vips_interpolate_new( "nearest" );
424 }
425 std::cout<<"ScalePar::build(): scale="<<scale<<" interpolate="<<interpolate<<std::endl;
426 */
427 VipsKernel kernel = VIPS_KERNEL_NEAREST;
428 if(level > 0) {
429 if( vips_resize(srcimg, &out, scale, "kernel", VIPS_KERNEL_CUBIC, NULL) ) {
430 std::cout<<"ScalePar::build(): vips_resize() failed."<<std::endl;
431 return srcimg;
432 }
433 } else {
434 if( scale_interp.get_enum_value().first == PF::SCALE_INTERP_NOHALO ) {
435 VipsInterpolate* interpolate = vips_interpolate_new( "nohalo" );
436 if( interpolate ) {
437 if( vips_affine( srcimg, &out,
438 scale, 0, 0, scale,
439 "interpolate", interpolate, NULL ) ) {
440 PF_UNREF( interpolate, "ScalePar::build(): interpolate unref" );
441 return srcimg;
442 }
443 PF_UNREF( interpolate, "ScalePar::build(): interpolate unref" );
444 std::cout<<"ScalePar::build(): scale="<<scale<<" interpolate=nohalo"<<std::endl;
445 } else {
446 std::cout<<"ScalePar::build(): vips_affine() failed, reverting to cubic interpolation"<<std::endl;
447 if( vips_resize(srcimg, &out, scale, "kernel", VIPS_KERNEL_CUBIC, NULL) ) {
448 std::cout<<"ScalePar::build(): vips_resize() failed."<<std::endl;
449 return srcimg;
450 }
451 std::cout<<"ScalePar::build(): scale="<<scale<<" kernel="<<kernel<<std::endl;
452 }
453 } else {
454 switch(scale_interp.get_enum_value().first) {
455 case PF::SCALE_INTERP_NEAREST: kernel = VIPS_KERNEL_NEAREST; break;
456 case PF::SCALE_INTERP_BILINEAR: kernel = VIPS_KERNEL_LINEAR; break;
457 case PF::SCALE_INTERP_BICUBIC: kernel = VIPS_KERNEL_CUBIC; break;
458 case PF::SCALE_INTERP_LANCZOS2: kernel = VIPS_KERNEL_LANCZOS2; break;
459 case PF::SCALE_INTERP_LANCZOS3: kernel = VIPS_KERNEL_LANCZOS3; break;
460 //case PF::SCALE_INTERP_NOHALO: kernel = VIPS_KERNEL_XXX; break;
461 default: kernel = VIPS_KERNEL_NEAREST; break;
462 }
463 if( vips_resize(srcimg, &out, scale, "kernel", kernel, NULL) ) {
464 std::cout<<"ScalePar::build(): vips_resize() failed."<<std::endl;
465 //PF_UNREF( interpolate, "ScalePar::build(): interpolate unref" );
466 return srcimg;
467 }
468 std::cout<<"ScalePar::build(): scale="<<scale<<" kernel="<<kernel<<std::endl;
469 }
470 }
471 //PF_UNREF( interpolate, "ScalePar::build(): interpolate unref" );
472 PF_UNREF( srcimg, "ScalePar::build(): srcimg unref after vips_resize()" );
473 //if( vips_resize(srcimg, &out, scale, "interpolate", interpolate, NULL) ) {
474 } else {
475 //PF_REF( srcimg, "ScalePar::build(): srcimg ref (editing mode)" );
476 //return srcimg;
477
478 out = srcimg;
479 }
480
481 set_image_hints( out );
482 return out;
483 }
484