1 /* LiquidRescaling Library
2 * Copyright (C) 2007-2009 Carlo Baldassi (the "Author") <carlobaldassi@gmail.com>.
5 * This library implements the algorithm described in the paper
6 * "Seam Carving for Content-Aware Image Resizing"
7 * by Shai Avidan and Ariel Shamir
8 * which can be found at http://www.faculty.idc.ac.il/arik/imret.pdf
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; version 3 dated June, 2007.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, see <http://www.gnu.org/licenses/>
25 #include <lqr/lqr_base.h>
26 #include <lqr/lqr_gradient.h>
27 #include <lqr/lqr_rwindow.h>
28 #include <lqr/lqr_energy.h>
29 #include <lqr/lqr_progress_pub.h>
30 #include <lqr/lqr_cursor_pub.h>
31 #include <lqr/lqr_vmap.h>
32 #include <lqr/lqr_vmap_list.h>
33 #include <lqr/lqr_carver_list.h>
34 #include <lqr/lqr_carver.h>
39 #endif /* __LQR_DEBUG__ */
41 /* read normalised pixel value from
42 * rgb buffer at the given index */
44 lqr_pixel_get_norm (void * rgb
, gint rgb_ind
, LqrColDepth col_depth
)
49 return (gdouble
) AS_8I(rgb
)[rgb_ind
] / 0xFF;
50 case LQR_COLDEPTH_16I
:
51 return (gdouble
) AS_16I(rgb
)[rgb_ind
] / 0xFFFF;
52 case LQR_COLDEPTH_32F
:
53 return (gdouble
) AS_32F(rgb
)[rgb_ind
];
54 case LQR_COLDEPTH_64F
:
55 return (gdouble
) AS_64F(rgb
)[rgb_ind
];
59 #endif /* __LQR_DEBUG__ */
65 lqr_pixel_get_rgbcol (void *rgb
, gint rgb_ind
, LqrColDepth col_depth
, LqrImageType image_type
, gint channel
)
67 gdouble black_fact
= 0;
73 return lqr_pixel_get_norm (rgb
, rgb_ind
+ channel
, col_depth
);
75 return 1. - lqr_pixel_get_norm (rgb
, rgb_ind
+ channel
, col_depth
);
78 black_fact
= 1 - lqr_pixel_get_norm(rgb
, rgb_ind
+ 3, col_depth
);
79 return black_fact
* (1. - (lqr_pixel_get_norm (rgb
, rgb_ind
+ channel
, col_depth
)));
83 #endif /* __LQR_DEBUG__ */
89 lqr_carver_read_brightness_grey (LqrCarver
* r
, gint x
, gint y
)
91 gint now
= r
->raw
[y
][x
];
92 gint rgb_ind
= now
* r
->channels
;
93 return lqr_pixel_get_norm (r
->rgb
, rgb_ind
, r
->col_depth
);
97 lqr_carver_read_brightness_std (LqrCarver
* r
, gint x
, gint y
)
99 gdouble red
, green
, blue
;
100 gint now
= r
->raw
[y
][x
];
101 gint rgb_ind
= now
* r
->channels
;
103 red
= lqr_pixel_get_rgbcol (r
->rgb
, rgb_ind
, r
->col_depth
, r
->image_type
, 0);
104 green
= lqr_pixel_get_rgbcol (r
->rgb
, rgb_ind
, r
->col_depth
, r
->image_type
, 1);
105 blue
= lqr_pixel_get_rgbcol (r
->rgb
, rgb_ind
, r
->col_depth
, r
->image_type
, 2);
106 return (red
+ green
+ blue
) / 3;
110 lqr_carver_read_brightness_custom (LqrCarver
* r
, gint x
, gint y
)
114 gchar has_alpha
= (r
->alpha_channel
>= 0 ? 1 : 0);
115 gchar has_black
= (r
->black_channel
>= 0 ? 1 : 0);
116 guint col_channels
= r
->channels
- has_alpha
- has_black
;
118 gdouble black_fact
= 0;
120 gint now
= r
->raw
[y
][x
];
124 black_fact
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ r
->black_channel
, r
->col_depth
);
127 for (k
= 0; k
< r
->channels
; k
++) if ((k
!= r
->alpha_channel
) && (k
!= r
->black_channel
))
129 gdouble col
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ k
, r
->col_depth
);
130 sum
+= 1. - (1. - col
) * (1. - black_fact
);
143 /* read average pixel value at x, y
144 * for energy computation */
146 lqr_carver_read_brightness (LqrCarver
* r
, gint x
, gint y
)
148 gchar has_alpha
= (r
->alpha_channel
>= 0 ? 1 : 0);
149 gdouble alpha_fact
= 1;
151 gint now
= r
->raw
[y
][x
];
155 switch (r
->image_type
)
158 case LQR_GREYA_IMAGE
:
159 bright
= lqr_carver_read_brightness_grey (r
, x
, y
);
165 case LQR_CMYKA_IMAGE
:
166 bright
= lqr_carver_read_brightness_std (r
, x
, y
);
168 case LQR_CUSTOM_IMAGE
:
169 bright
= lqr_carver_read_brightness_custom (r
, x
, y
);
175 alpha_fact
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ r
->alpha_channel
, r
->col_depth
);
178 return bright
* alpha_fact
;
182 lqr_carver_read_luma_std (LqrCarver
* r
, gint x
, gint y
)
184 gdouble red
, green
, blue
;
185 gint now
= r
->raw
[y
][x
];
186 gint rgb_ind
= now
* r
->channels
;
188 red
= lqr_pixel_get_rgbcol (r
->rgb
, rgb_ind
, r
->col_depth
, r
->image_type
, 0);
189 green
= lqr_pixel_get_rgbcol (r
->rgb
, rgb_ind
, r
->col_depth
, r
->image_type
, 1);
190 blue
= lqr_pixel_get_rgbcol (r
->rgb
, rgb_ind
, r
->col_depth
, r
->image_type
, 2);
191 return 0.2126 * red
+ 0.7152 * green
+ 0.0722 * blue
;
195 lqr_carver_read_luma (LqrCarver
* r
, gint x
, gint y
)
197 gchar has_alpha
= (r
->alpha_channel
>= 0 ? 1 : 0);
198 gdouble alpha_fact
= 1;
200 gint now
= r
->raw
[y
][x
];
204 switch (r
->image_type
)
207 case LQR_GREYA_IMAGE
:
208 bright
= lqr_carver_read_brightness_grey (r
, x
, y
);
214 case LQR_CMYKA_IMAGE
:
215 bright
= lqr_carver_read_luma_std (r
, x
, y
);
217 case LQR_CUSTOM_IMAGE
:
218 bright
= lqr_carver_read_brightness_custom (r
, x
, y
);
224 alpha_fact
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ r
->alpha_channel
, r
->col_depth
);
227 return bright
* alpha_fact
;
231 lqr_carver_read_rgba (LqrCarver
* r
, gint x
, gint y
, gint channel
)
233 gchar has_alpha
= (r
->alpha_channel
>= 0 ? 1 : 0);
235 gint now
= r
->raw
[y
][x
];
238 assert(channel
>= 0 && channel
< 4);
239 #endif /* __LQR_DEBUG__ */
243 switch (r
->image_type
)
246 case LQR_GREYA_IMAGE
:
247 return lqr_carver_read_brightness_grey (r
, x
, y
);
252 case LQR_CMYKA_IMAGE
:
253 return lqr_pixel_get_rgbcol (r
->rgb
, now
* r
->channels
, r
->col_depth
, r
->image_type
, channel
);
254 case LQR_CUSTOM_IMAGE
:
258 #endif /* __LQR_DEBUG__ */
264 return lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ r
->alpha_channel
, r
->col_depth
);
273 lqr_carver_read_custom (LqrCarver
* r
, gint x
, gint y
, gint channel
)
275 gint now
= r
->raw
[y
][x
];
277 return lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ channel
, r
->col_depth
);
284 /* read average pixel value at x, y
285 * for energy computation */
287 lqr_carver_read_brightness (LqrCarver
* r
, gint x
, gint y
)
291 gchar has_alpha
= (r
->alpha_channel
>= 0 ? 1 : 0);
292 gchar has_black
= (r
->black_channel
>= 0 ? 1 : 0);
293 guint col_channels
= r
->channels
- has_alpha
- has_black
;
295 gdouble alpha_fact
= 1;
296 gdouble black_fact
= 0;
298 gint now
= r
->raw
[y
][x
];
302 alpha_fact
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ r
->alpha_channel
, r
->col_depth
);
307 black_fact
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ r
->black_channel
, r
->col_depth
);
310 for (k
= 0; k
< r
->channels
; k
++) if ((k
!= r
->alpha_channel
) && (k
!= r
->black_channel
))
312 gdouble col
= lqr_pixel_get_norm(r
->rgb
, now
* r
->channels
+ k
, r
->col_depth
);
313 sum
+= 1. - (1. - col
) * (1. - black_fact
);
318 switch (r
->image_type
)
323 case LQR_GREYA_IMAGE
:
327 case LQR_CMYKA_IMAGE
:
330 case LQR_CUSTOM_IMAGE
:
338 #endif /* __LQR_DEBUG__ */
352 lqr_carver_read_brightness_abs (LqrCarver
* r
, gint x1
, gint y1
, gint x2
, gint y2
)
354 gchar has_alpha
= (r
->alpha_channel
> 0 ? 1 : 0);
363 a1
= R_RGB(r
->rgb
, p1
* r
->channels
+ r
->alpha_channel
) / R_RGB_MAX
;
364 a2
= R_RGB(r
->rgb
, p2
* r
->channels
+ r
->alpha_channel
) / R_RGB_MAX
;
370 for (k
= 0; k
< r
->channels
; k
++) if (k
!= r
->alpha_channel
)
372 sum
+= fabs(R_RGB(r
->rgb
, p1
* r
->channels
+ 0) * a1
- R_RGB(r
->rgb
, p2
* r
->channels
+ 0) * a2
);
374 return sum
/ (R_RGB_MAX
* (r
->channels
- has_alpha
));
378 lqr_carver_read_luma_abs (LqrCarver
* r
, gint x1
, gint y1
, gint x2
, gint y2
)
384 if (r
->image_type
== LQR_RGBA_IMAGE
)
386 a1
= R_RGB(r
->rgb
, p1
* r
->channels
+ 3) / R_RGB_MAX
;
387 a2
= R_RGB(r
->rgb
, p2
* r
->channels
+ 3) / R_RGB_MAX
;
393 return (0.2126 * fabs(R_RGB(r
->rgb
, p1
* r
->channels
+ 0) * a1
- R_RGB(r
->rgb
, p2
* r
->channels
+ 0) * a2
) +
394 0.7152 * fabs(R_RGB(r
->rgb
, p1
* r
->channels
+ 1) * a1
- R_RGB(r
->rgb
, p2
* r
->channels
+ 1) * a2
) +
395 0.0722 * fabs(R_RGB(r
->rgb
, p1
* r
->channels
+ 2) * a1
- R_RGB(r
->rgb
, p2
* r
->channels
+ 2) * a2
)) /
401 lqr_carver_read_cached_std (LqrCarver
* r
, gint x
, gint y
)
403 gint z0
= r
->raw
[y
][x
];
405 return r
->rcache
[z0
];
409 lqr_carver_read_cached_rgba (LqrCarver
* r
, gint x
, gint y
, gint channel
)
411 gint z0
= r
->raw
[y
][x
];
413 return r
->rcache
[z0
* 4 + channel
];
417 lqr_carver_read_cached_custom (LqrCarver
* r
, gint x
, gint y
, gint channel
)
419 gint z0
= r
->raw
[y
][x
];
421 return r
->rcache
[z0
* r
->channels
+ channel
];
425 lqr_energy_builtin_grad_all (gint x
, gint y
, gint img_width
, gint img_height
, LqrReaderWindow
* rwindow
, LqrGradFunc gf
)
429 gdouble (*bread_func
) (LqrReaderWindow
*, gint
, gint
);
431 switch (lqr_rwindow_get_read_t(rwindow
))
434 bread_func
= lqr_rwindow_read_bright
;
437 bread_func
= lqr_rwindow_read_luma
;
442 #endif /* __LQR_DEBUG__ */
448 gy
= bread_func(rwindow
, 0, 1) - bread_func(rwindow
, 0, 0);
450 else if (y
< img_height
- 1)
452 gy
= (bread_func(rwindow
, 0, 1) - bread_func(rwindow
, 0, -1)) / 2;
456 gy
= bread_func(rwindow
, 0, 0) - bread_func(rwindow
, 0, -1);
461 gx
= bread_func(rwindow
, 1, 0) - bread_func(rwindow
, 0, 0);
463 else if (x
< img_width
- 1)
465 gx
= (bread_func(rwindow
, 1, 0) - bread_func(rwindow
, -1, 0)) / 2;
469 gx
= bread_func(rwindow
, 0, 0) - bread_func(rwindow
, -1, 0);
476 lqr_energy_builtin_grad_norm (gint x
, gint y
, gint img_width
, gint img_height
, LqrReaderWindow
* rwindow
, gpointer extra_data
)
478 return lqr_energy_builtin_grad_all(x
, y
, img_width
, img_height
, rwindow
, lqr_grad_norm
);
482 lqr_energy_builtin_grad_sumabs (gint x
, gint y
, gint img_width
, gint img_height
, LqrReaderWindow
* rwindow
, gpointer extra_data
)
484 return lqr_energy_builtin_grad_all(x
, y
, img_width
, img_height
, rwindow
, lqr_grad_sumabs
);
488 lqr_energy_builtin_grad_xabs (gint x
, gint y
, gint img_width
, gint img_height
, LqrReaderWindow
* rwindow
, gpointer extra_data
)
490 return lqr_energy_builtin_grad_all(x
, y
, img_width
, img_height
, rwindow
, lqr_grad_xabs
);
494 lqr_energy_builtin_null (gint x
, gint y
, gint img_width
, gint img_height
, LqrReaderWindow
* rwindow
, gpointer extra_data
)
501 /* compute energy at x, y */
503 lqr_energy_abs (LqrCarver
* r
, gint x
, gint y
)
509 gy
= (*(r
->nrg_builtin
->rfabs
))(r
, x
, y
+ 1, x
, y
);
511 else if (y
< r
->h
- 1)
513 gy
= 0.5 * (*(r
->nrg_builtin
->rfabs
))(r
, x
, y
+ 1, x
, y
- 1);
517 gy
= (*(r
->nrg_builtin
->rfabs
))(r
, x
, y
, x
, y
- 1);
522 gx
= (*(r
->nrg_builtin
->rfabs
))(r
, x
+ 1, y
, x
, y
);
524 else if (x
< r
->w
- 1)
526 gx
= 0.5 * (*(r
->nrg_builtin
->rfabs
))(r
, x
+ 1, y
, x
- 1, y
);
530 gx
= (*(r
->nrg_builtin
->rfabs
))(r
, x
, y
, x
- 1, y
);
532 return (*(r
->nrg_builtin
->gf
))(gx
, gy
);
536 /* gradient function for energy computation */
539 lqr_carver_set_energy_function_builtin (LqrCarver
* r
, LqrEnergyFuncBuiltinType ef_ind
)
543 case LQR_EF_GRAD_NORM
:
544 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_grad_norm
, 1, LQR_ER_BRIGHT
, NULL
));
546 case LQR_EF_GRAD_SUMABS
:
547 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_grad_sumabs
, 1, LQR_ER_BRIGHT
, NULL
));
549 case LQR_EF_GRAD_XABS
:
550 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_grad_xabs
, 1, LQR_ER_BRIGHT
, NULL
));
552 case LQR_EF_LUMA_GRAD_NORM
:
553 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_grad_norm
, 1, LQR_ER_LUMA
, NULL
));
555 case LQR_EF_LUMA_GRAD_SUMABS
:
556 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_grad_sumabs
, 1, LQR_ER_LUMA
, NULL
));
558 case LQR_EF_LUMA_GRAD_XABS
:
559 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_grad_xabs
, 1, LQR_ER_LUMA
, NULL
));
562 CATCH (lqr_carver_set_energy_function (r
, lqr_energy_builtin_null
, 0, LQR_ER_BRIGHT
, NULL
));
573 lqr_carver_set_energy_function (LqrCarver
* r
, LqrEnergyFunc en_func
, gint radius
,
574 LqrEnergyReaderType reader_type
, gpointer extra_data
)
576 CATCH_F (r
->root
== NULL
);
579 r
->nrg_radius
= radius
;
580 r
->nrg_read_t
= reader_type
;
581 r
->nrg_extra_data
= extra_data
;
583 lqr_rwindow_destroy (r
->rwindow
);
585 if (reader_type
== LQR_ER_CUSTOM
)
587 CATCH_MEM (r
->rwindow
= lqr_rwindow_new_custom (radius
, r
->use_rcache
, r
->channels
));
591 CATCH_MEM (r
->rwindow
= lqr_rwindow_new (radius
, reader_type
, r
->use_rcache
));
598 lqr_carver_generate_rcache_bright (LqrCarver
* r
)
604 TRY_N_N (buffer
= g_try_new (gdouble
, r
->w0
* r
->h0
));
606 for (y
= 0; y
< r
->h
; y
++)
608 for (x
= 0; x
< r
->w
; x
++)
611 buffer
[z0
] = lqr_carver_read_brightness (r
, x
, y
);
619 lqr_carver_generate_rcache_luma (LqrCarver
* r
)
625 TRY_N_N (buffer
= g_try_new (gdouble
, r
->w0
* r
->h0
));
627 for (y
= 0; y
< r
->h
; y
++)
629 for (x
= 0; x
< r
->w
; x
++)
632 buffer
[z0
] = lqr_carver_read_luma (r
, x
, y
);
640 lqr_carver_generate_rcache_rgba (LqrCarver
* r
)
646 TRY_N_N (buffer
= g_try_new (gdouble
, r
->w0
* r
->h0
* 4));
648 for (y
= 0; y
< r
->h
; y
++)
650 for (x
= 0; x
< r
->w
; x
++)
653 for (k
= 0; k
< 4; k
++)
655 buffer
[z0
* 4 + k
] = lqr_carver_read_rgba (r
, x
, y
, k
);
664 lqr_carver_generate_rcache_custom (LqrCarver
* r
)
670 TRY_N_N (buffer
= g_try_new (gdouble
, r
->w0
* r
->h0
* r
->channels
));
672 for (y
= 0; y
< r
->h
; y
++)
674 for (x
= 0; x
< r
->w
; x
++)
677 for (k
= 0; k
< r
->channels
; k
++)
679 buffer
[z0
* r
->channels
+ k
] = lqr_carver_read_custom (r
, x
, y
, k
);
688 lqr_carver_generate_rcache (LqrCarver
* r
)
691 assert (r
->w
== r
->w_start
- r
->max_level
+ 1);
692 #endif /* __LQR_DEBUG__ */
694 switch (r
->nrg_read_t
)
697 return lqr_carver_generate_rcache_bright(r
);
699 return lqr_carver_generate_rcache_luma(r
);
701 return lqr_carver_generate_rcache_rgba(r
);
703 return lqr_carver_generate_rcache_custom(r
);
707 #endif /* __LQR_DEBUG__ */
714 lqr_energy_preview_new (void * buffer
, gint width
, gint height
, gint channels
, LqrColDepth colour_depth
)
717 TRY_N_N (r
= lqr_carver_new_ext (buffer
, width
, height
, channels
, colour_depth
));
718 lqr_carver_set_preserve_input_image (r
);
719 TRY_E_N (lqr_carver_init_energy_related (r
));
720 lqr_carver_set_use_cache (r
, TRUE
);
726 lqr_energy_preview_get_energy(LqrCarver
* r
, gint orientation
)
735 gfloat nrg_min
= G_MAXFLOAT
;
738 TRY_E_N (orientation
== 0 || orientation
== 1);
739 TRY_F_N (r
->nrg_active
);
742 buf_size
= r
->w
* r
->h
;
743 TRY_N_N (nrg_buffer
= g_try_new (gfloat
, buf_size
));
745 if (orientation
!= lqr_carver_get_orientation(r
))
747 TRY_E_N (lqr_carver_transpose(r
));
749 TRY_E_N (lqr_carver_build_emap (r
));
751 w
= lqr_carver_get_width(r
);
752 h
= lqr_carver_get_height(r
);
754 for (y
= 0; y
< h
; y
++)
756 for (x
= 0; x
< w
; x
++)
758 data
= orientation
== 0 ? r
->raw
[y
][x
] : r
->raw
[x
][y
];
760 nrg_max
= MAX (nrg_max
, nrg
);
761 nrg_min
= MIN (nrg_min
, nrg
);
762 nrg_buffer
[z0
++] = r
->en
[data
];
768 for (z0
= 0; z0
< buf_size
; z0
++)
770 nrg_buffer
[z0
] = (nrg_buffer
[z0
] - nrg_min
) / (nrg_max
- nrg_min
);