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