11#include < algorithm>
2- #include < tuple >
2+ #include < functional >
33#include < memory>
4+ #include < tuple>
45#include < vips/vips8>
56
67#include " common.h"
@@ -289,69 +290,104 @@ namespace sharp {
289290 }
290291 }
291292
293+ /*
294+ Calculate the Shannon entropy
295+ */
296+ double EntropyStrategy::operator ()(VImage image) {
297+ return image.hist_find ().hist_entropy ();
298+ }
299+
300+ /*
301+ Calculate the intensity of edges, skin tone and saturation
302+ */
303+ double AttentionStrategy::operator ()(VImage image) {
304+ // Convert to LAB colourspace
305+ VImage lab = image.colourspace (VIPS_INTERPRETATION_LAB );
306+ VImage l = lab[0 ];
307+ VImage a = lab[1 ];
308+ VImage b = lab[2 ];
309+ // Edge detect luminosity with the Sobel operator
310+ VImage sobel = vips::VImage::new_matrixv (3 , 3 ,
311+ -1.0 , 0.0 , 1.0 ,
312+ -2.0 , 0.0 , 2.0 ,
313+ -1.0 , 0.0 , 1.0 );
314+ VImage edges = l.conv (sobel).abs () + l.conv (sobel.rot90 ()).abs ();
315+ // Skin tone chroma thresholds trained with http://humanae.tumblr.com/
316+ VImage skin = (a >= 3 ) & (a <= 22 ) & (b >= 4 ) & (b <= 31 );
317+ // Chroma >~50% saturation
318+ VImage lch = lab.colourspace (VIPS_INTERPRETATION_LCH );
319+ VImage c = lch[1 ];
320+ VImage saturation = c > 60 ;
321+ // Find maximum in combined saliency mask
322+ VImage mask = edges + skin + saturation;
323+ return mask.max ();
324+ }
325+
292326 /*
293327 Calculate crop area based on image entropy
294328 */
295- std::tuple<int , int > EntropyCrop (VImage image, int const outWidth, int const outHeight) {
329+ std::tuple<int , int > Crop (
330+ VImage image, int const outWidth, int const outHeight, std::function<double (VImage)> strategy
331+ ) {
296332 int left = 0 ;
297333 int top = 0 ;
298334 int const inWidth = image.width ();
299335 int const inHeight = image.height ();
300336 if (inWidth > outWidth) {
301- // Reduce width by repeated removing slices from edge with lowest entropy
337+ // Reduce width by repeated removing slices from edge with lowest score
302338 int width = inWidth;
303- double leftEntropy = 0.0 ;
304- double rightEntropy = 0.0 ;
339+ double leftScore = 0.0 ;
340+ double rightScore = 0.0 ;
305341 // Max width of each slice
306342 int const maxSliceWidth = static_cast <int >(ceil ((inWidth - outWidth) / 8.0 ));
307343 while (width > outWidth) {
308344 // Width of current slice
309345 int const slice = std::min (width - outWidth, maxSliceWidth);
310- if (leftEntropy == 0.0 ) {
311- // Update entropy of left slice
312- leftEntropy = Entropy (image.extract_area (left, 0 , slice, inHeight));
346+ if (leftScore == 0.0 ) {
347+ // Update score of left slice
348+ leftScore = strategy (image.extract_area (left, 0 , slice, inHeight));
313349 }
314- if (rightEntropy == 0.0 ) {
315- // Update entropy of right slice
316- rightEntropy = Entropy (image.extract_area (width - slice - 1 , 0 , slice, inHeight));
350+ if (rightScore == 0.0 ) {
351+ // Update score of right slice
352+ rightScore = strategy (image.extract_area (width - slice - 1 , 0 , slice, inHeight));
317353 }
318- // Keep slice with highest entropy
319- if (leftEntropy >= rightEntropy ) {
354+ // Keep slice with highest score
355+ if (leftScore >= rightScore ) {
320356 // Discard right slice
321- rightEntropy = 0.0 ;
357+ rightScore = 0.0 ;
322358 } else {
323359 // Discard left slice
324- leftEntropy = 0.0 ;
360+ leftScore = 0.0 ;
325361 left = left + slice;
326362 }
327363 width = width - slice;
328364 }
329365 }
330366 if (inHeight > outHeight) {
331- // Reduce height by repeated removing slices from edge with lowest entropy
367+ // Reduce height by repeated removing slices from edge with lowest score
332368 int height = inHeight;
333- double topEntropy = 0.0 ;
334- double bottomEntropy = 0.0 ;
369+ double topScore = 0.0 ;
370+ double bottomScore = 0.0 ;
335371 // Max height of each slice
336372 int const maxSliceHeight = static_cast <int >(ceil ((inHeight - outHeight) / 8.0 ));
337373 while (height > outHeight) {
338374 // Height of current slice
339375 int const slice = std::min (height - outHeight, maxSliceHeight);
340- if (topEntropy == 0.0 ) {
341- // Update entropy of top slice
342- topEntropy = Entropy (image.extract_area (0 , top, inWidth, slice));
376+ if (topScore == 0.0 ) {
377+ // Update score of top slice
378+ topScore = strategy (image.extract_area (0 , top, inWidth, slice));
343379 }
344- if (bottomEntropy == 0.0 ) {
345- // Update entropy of bottom slice
346- bottomEntropy = Entropy (image.extract_area (0 , height - slice - 1 , inWidth, slice));
380+ if (bottomScore == 0.0 ) {
381+ // Update score of bottom slice
382+ bottomScore = strategy (image.extract_area (0 , height - slice - 1 , inWidth, slice));
347383 }
348- // Keep slice with highest entropy
349- if (topEntropy >= bottomEntropy ) {
384+ // Keep slice with highest score
385+ if (topScore >= bottomScore ) {
350386 // Discard bottom slice
351- bottomEntropy = 0.0 ;
387+ bottomScore = 0.0 ;
352388 } else {
353389 // Discard top slice
354- topEntropy = 0.0 ;
390+ topScore = 0.0 ;
355391 top = top + slice;
356392 }
357393 height = height - slice;
@@ -360,13 +396,6 @@ namespace sharp {
360396 return std::make_tuple (left, top);
361397 }
362398
363- /*
364- Calculate the Shannon entropy for an image
365- */
366- double Entropy (VImage image) {
367- return image.hist_find ().hist_entropy ();
368- }
369-
370399 /*
371400 Insert a tile cache to prevent over-computation of any previous operations in the pipeline
372401 */
0 commit comments