Fix #314: Remote commands for thumbnail maintenance
[geeqie.git] / src / similar.c
index f709d55..56aae23 100644 (file)
@@ -1,13 +1,22 @@
 /*
- * Geeqie
- * (C) 2004 John Ellis
- * Copyright (C) 2008 The Geeqie Team
+ * Copyright (C) 2004 John Ellis
+ * Copyright (C) 2008 - 2016 The Geeqie Team
  *
  * Author: John Ellis
  *
- * This software is released under the GNU General Public License (GNU GPL).
- * Please read the included file COPYING for more information.
- * This software comes with no warranty of any kind, use at your own risk!
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  */
 
 
  * improve the result, and hopes to reduce false positives.
  */
 
-static gint alternate_enabled = FALSE;
+static gboolean alternate_enabled = FALSE;
 
-void image_sim_alternate_set(gint enable)
+void image_sim_alternate_set(gboolean enable)
 {
        alternate_enabled = enable;
 }
 
-gint image_sim_alternate_enabled(void)
+gboolean image_sim_alternate_enabled(void)
 {
        return alternate_enabled;
 }
@@ -68,44 +77,10 @@ void image_sim_free(ImageSimilarityData *sd)
        g_free(sd);
 }
 
-#if 0
-static void image_sim_channel_expand(guint8 *pix, gint len)
-{
-       guint8 l, h;
-       gint i;
-       gdouble scale;
-
-       /* set the start values */
-       l = h = pix[0];
-
-       /* find min/max */
-       for (i = 0; i < len; i++)
-               {
-               if (pix[i] < l) l = pix[i];
-               if (pix[i] > h) h = pix[i];
-               }
-
-       /* calc scale from range */
-       if (l != h)
-               {
-               scale = 255.0 / (gdouble)(h - l);
-               }
-       else
-               {
-               scale = 1.0;
-               }
-
-       for (i = 0; i < len; i++)
-               {
-               pix[i] = (guint8)((gdouble)pix[i] - l * scale);
-               }
-}
-#endif
-
-static int image_sim_channel_eq_sort_cb(const void *a, const void *b)
+static gint image_sim_channel_eq_sort_cb(gconstpointer a, gconstpointer b)
 {
-       gint *pa = (void *)a;
-       gint *pb = (void *)b;
+       gint *pa = (gpointer)a;
+       gint *pb = (gpointer)b;
        if (pa[1] < pb[1]) return -1;
        if (pa[1] > pb[1]) return 1;
        return 0;
@@ -136,7 +111,7 @@ static void image_sim_channel_equal(guint8 *pix, gint len)
                gint n;
 
                n = buf[p];
-               p+= 2;
+               p += 2;
                pix[n] = (guint8)(255 * i / len);
                }
 
@@ -145,7 +120,7 @@ static void image_sim_channel_equal(guint8 *pix, gint len)
 
 static void image_sim_channel_norm(guint8 *pix, gint len)
 {
-       guint8 l, h;
+       guint8 l, h, delta;
        gint i;
        gdouble scale;
 
@@ -157,7 +132,8 @@ static void image_sim_channel_norm(guint8 *pix, gint len)
                if (pix[i] > h) h = pix[i];
                }
 
-       scale = (h-l !=0) ? 255.0 / (gdouble)(h - l) : 1.0;
+       delta = h - l;
+       scale = (delta != 0) ? 255.0 / (gdouble)(delta) : 1.0;
 
        for (i = 0; i < len; i++)
                {
@@ -202,23 +178,30 @@ void image_sim_alternate_processing(ImageSimilarityData *sd)
 #endif
 }
 
+gint mround(gdouble x)
+{
+       gint ipart = x;
+       gdouble fpart = x-ipart;
+       return (fpart < 0.5 ? ipart : ipart+1);
+}
+
 void image_sim_fill_data(ImageSimilarityData *sd, GdkPixbuf *pixbuf)
 {
        gint w, h;
        gint rs;
        guchar *pix;
-       gint has_alpha;
+       gboolean has_alpha;
        gint p_step;
 
        guchar *p;
        gint i;
        gint j;
-       gint x_inc, y_inc;
+       gint x_inc, y_inc, xy_inc;
        gint xs, ys;
+       gint w_left, h_left;
 
-       gint x_small = FALSE;   /* if less than 32 w or h, set TRUE */
-       gint y_small = FALSE;
-
+       gboolean x_small = FALSE;       /* if less than 32 w or h, set TRUE */
+       gboolean y_small = FALSE;
        if (!sd || !pixbuf) return;
 
        w = gdk_pixbuf_get_width(pixbuf);
@@ -230,6 +213,8 @@ void image_sim_fill_data(ImageSimilarityData *sd, GdkPixbuf *pixbuf)
        p_step = has_alpha ? 4 : 3;
        x_inc = w / 32;
        y_inc = h / 32;
+       w_left = w;
+       h_left = h;
 
        if (x_inc < 1)
                {
@@ -244,25 +229,30 @@ void image_sim_fill_data(ImageSimilarityData *sd, GdkPixbuf *pixbuf)
 
        j = 0;
 
+       h_left = h;
        for (ys = 0; ys < 32; ys++)
                {
                if (y_small) j = (gdouble)h / 32 * ys;
-
+                       else y_inc = mround((gdouble)h_left/(32-ys));
                i = 0;
 
+               w_left = w;
                for (xs = 0; xs < 32; xs++)
                        {
                        gint x, y;
                        gint r, g, b;
                        gint t;
+                       guchar *xpos;
 
                        if (x_small) i = (gdouble)w / 32 * xs;
-
+                               else x_inc = mround((gdouble)w_left/(32-xs));
+                       xy_inc = x_inc * y_inc;
                        r = g = b = 0;
+                       xpos = pix + (i * p_step);
 
                        for (y = j; y < j + y_inc; y++)
                                {
-                               p = pix + (y * rs) + (i * p_step);
+                               p = xpos + (y * rs);
                                for (x = i; x < i + x_inc; x++)
                                        {
                                        r += *p; p++;
@@ -272,10 +262,9 @@ void image_sim_fill_data(ImageSimilarityData *sd, GdkPixbuf *pixbuf)
                                        }
                                }
 
-                       t = x_inc * y_inc;
-                       r /= t;
-                       g /= t;
-                       b /= t;
+                       r /= xy_inc;
+                       g /= xy_inc;
+                       b /= xy_inc;
 
                        t = ys * 32 + xs;
                        sd->avg_r[t] = r;
@@ -283,9 +272,11 @@ void image_sim_fill_data(ImageSimilarityData *sd, GdkPixbuf *pixbuf)
                        sd->avg_b[t] = b;
 
                        i += x_inc;
+                       w_left -= x_inc;
                        }
 
                j += y_inc;
+               h_left -= y_inc;
                }
 
        sd->filled = TRUE;
@@ -311,12 +302,11 @@ static gdouble alternate_image_sim_compare_fast(ImageSimilarityData *a, ImageSim
 
        if (!a || !b || !a->filled || !b->filled) return 0.0;
 
-
        min = 1.0 - min;
        sim = 0.0;
        ld = 0;
 
-       for (j = 0; j < 1024; j+= 32)
+       for (j = 0; j < 1024; j += 32)
                {
                for (i = j; i < j + 32; i++)
                        {
@@ -339,33 +329,59 @@ static gdouble alternate_image_sim_compare_fast(ImageSimilarityData *a, ImageSim
 }
 #endif
 
-gdouble image_sim_compare(ImageSimilarityData *a, ImageSimilarityData *b)
+gdouble image_sim_compare_transfo(ImageSimilarityData *a, ImageSimilarityData *b, gchar transfo)
 {
        gint sim;
-       gint i;
+       gint i1, i2, *i;
+       gint j1, j2, *j;
 
        if (!a || !b || !a->filled || !b->filled) return 0.0;
 
        sim = 0.0;
 
-       for (i = 0; i < 1024; i++)
+       if (transfo & 1) { i = &j2; j = &i2; } else { i = &i2; j = &j2; }
+       for (j1 = 0; j1 < 32; j1++)
                {
-               sim += abs(a->avg_r[i] - b->avg_r[i]);
-               sim += abs(a->avg_g[i] - b->avg_g[i]);
-               sim += abs(a->avg_b[i] - b->avg_b[i]);
+               if (transfo & 2) *j = 31-j1; else *j = j1;
+               for (i1 = 0; i1 < 32; i1++)
+                       {
+                       if (transfo & 4) *i = 31-i1; else *i = i1;
+                       sim += abs(a->avg_r[i1*32+j1] - b->avg_r[i2*32+j2]);
+                       sim += abs(a->avg_g[i1*32+j1] - b->avg_g[i2*32+j2]);
+                       sim += abs(a->avg_b[i1*32+j1] - b->avg_b[i2*32+j2]);
+                       }
                }
 
        return 1.0 - ((gdouble)sim / (255.0 * 1024.0 * 3.0));
 }
 
-/* this uses a cutoff point so that it can abort early when it gets to
- * a point that can simply no longer make the cut-off point.
- */
-gdouble image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, gdouble min)
+gdouble image_sim_compare(ImageSimilarityData *a, ImageSimilarityData *b)
+{
+       gint max_t = (options->rot_invariant_sim ? 8 : 1);
+
+       gint t;
+       gdouble score, max_score = 0;
+
+       for(t = 0; t < max_t; t++)
+       {
+               score = image_sim_compare_transfo(a, b, t);
+               if (score > max_score) max_score = score;
+       }
+       return max_score;
+}
+
+
+/*
+4 rotations (0, 90, 180, 270) combined with two mirrors (0, H)
+generate all possible isometric transformations
+= 8 tests
+= change dir of x, change dir of y, exchange x and y = 2^3 = 8
+*/
+gdouble image_sim_compare_fast_transfo(ImageSimilarityData *a, ImageSimilarityData *b, gdouble min, gchar transfo)
 {
        gint sim;
-       gint i;
-       gint j;
+       gint i1, i2, *i;
+       gint j1, j2, *j;
 
 #ifdef ALTERNATE_INCLUDE_COMPARE_CHANGE
        if (alternate_enabled) return alternate_image_sim_compare_fast(a, b, min);
@@ -376,13 +392,16 @@ gdouble image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, g
        min = 1.0 - min;
        sim = 0.0;
 
-       for (j = 0; j < 1024; j+= 32)
+       if (transfo & 1) { i = &j2; j = &i2; } else { i = &i2; j = &j2; }
+       for (j1 = 0; j1 < 32; j1++)
                {
-               for (i = j; i < j + 32; i++)
+               if (transfo & 2) *j = 31-j1; else *j = j1;
+               for (i1 = 0; i1 < 32; i1++)
                        {
-                       sim += abs(a->avg_r[i] - b->avg_r[i]);
-                       sim += abs(a->avg_g[i] - b->avg_g[i]);
-                       sim += abs(a->avg_b[i] - b->avg_b[i]);
+                       if (transfo & 4) *i = 31-i1; else *i = i1;
+                       sim += abs(a->avg_r[i1*32+j1] - b->avg_r[i2*32+j2]);
+                       sim += abs(a->avg_g[i1*32+j1] - b->avg_g[i2*32+j2]);
+                       sim += abs(a->avg_b[i1*32+j1] - b->avg_b[i2*32+j2]);
                        }
                /* check for abort, if so return 0.0 */
                if ((gdouble)sim / (255.0 * 1024.0 * 3.0) > min) return 0.0;
@@ -390,3 +409,22 @@ gdouble image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, g
 
        return (1.0 - ((gdouble)sim / (255.0 * 1024.0 * 3.0)) );
 }
+
+/* this uses a cutoff point so that it can abort early when it gets to
+ * a point that can simply no longer make the cut-off point.
+ */
+gdouble image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, gdouble min)
+{
+       gint max_t = (options->rot_invariant_sim ? 8 : 1);
+
+       gint t;
+       gdouble score, max_score = 0;
+
+       for(t = 0; t < max_t; t++)
+       {
+               score = image_sim_compare_fast_transfo(a, b, min, t);
+               if (score > max_score) max_score = score;
+       }
+       return max_score;
+}
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */