Simplify vflist_get_formatted()
[geeqie.git] / src / similar.c
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22
23 #include "main.h"
24 #include "similar.h"
25
26 /**
27  * @file
28  * 
29  * These functions are intended to find images with similar color content. For
30  * example when an image was saved at different compression levels or dimensions
31  * (scaled down/up) the contents are similar, but these files do not match by file
32  * size, dimensions, or checksum.
33  *
34  * These functions create a 32 x 32 array for each color channel (red, green, blue).
35  * The array represents the average color of each corresponding part of the
36  * image. (imagine the image cut into 1024 rectangles, or a 32 x 32 grid.
37  * Each grid is then processed for the average color value, this is what
38  * is stored in the array)
39  *
40  * To compare two images, generate a ImageSimilarityData for each image, then
41  * pass them to the compare function. The return value is the percent match
42  * of the two images. (for this, simple comparisons are used, basically the return
43  * is an average of the corresponding array differences)
44  *
45  * for image_sim_compare(), the return is 0.0 to 1.0: \n
46  *  1.0 for exact matches (an image is compared to itself) \n
47  *  0.0 for exact opposite images (compare an all black to an all white image) \n
48  * generally only a match of > 0.85 are significant at all, and >.95 is useful to
49  * find images that have been re-saved to other formats, dimensions, or compression.
50  */
51
52
53 /*
54  * The experimental (alternate) algorithm is only for testing of new techniques to
55  * improve the result, and hopes to reduce false positives.
56  */
57
58 static gboolean alternate_enabled = FALSE;
59
60 void image_sim_alternate_set(gboolean enable)
61 {
62         alternate_enabled = enable;
63 }
64
65 gboolean image_sim_alternate_enabled(void)
66 {
67         return alternate_enabled;
68 }
69
70 ImageSimilarityData *image_sim_new(void)
71 {
72         ImageSimilarityData *sd = g_new0(ImageSimilarityData, 1);
73
74         return sd;
75 }
76
77 void image_sim_free(ImageSimilarityData *sd)
78 {
79         g_free(sd);
80 }
81
82 static gint image_sim_channel_eq_sort_cb(gconstpointer a, gconstpointer b)
83 {
84         gint *pa = (gpointer)a;
85         gint *pb = (gpointer)b;
86         if (pa[1] < pb[1]) return -1;
87         if (pa[1] > pb[1]) return 1;
88         return 0;
89 }
90
91 static void image_sim_channel_equal(guint8 *pix, gint len)
92 {
93         gint *buf;
94         gint i;
95         gint p;
96
97         buf = g_new0(gint, len * 2);
98
99         p = 0;
100         for (i = 0; i < len; i++)
101                 {
102                 buf[p] = i;
103                 p++;
104                 buf[p] = (gint)pix[i];
105                 p++;
106                 }
107
108         qsort(buf, len, sizeof(gint) * 2, image_sim_channel_eq_sort_cb);
109
110         p = 0;
111         for (i = 0; i < len; i++)
112                 {
113                 gint n;
114
115                 n = buf[p];
116                 p += 2;
117                 pix[n] = (guint8)(255 * i / len);
118                 }
119
120         g_free(buf);
121 }
122
123 static void image_sim_channel_norm(guint8 *pix, gint len)
124 {
125         guint8 l, h, delta;
126         gint i;
127         gdouble scale;
128
129         l = h = pix[0];
130
131         for (i = 0; i < len; i++)
132                 {
133                 if (pix[i] < l) l = pix[i];
134                 if (pix[i] > h) h = pix[i];
135                 }
136
137         delta = h - l;
138         scale = (delta != 0) ? 255.0 / (gdouble)(delta) : 1.0;
139
140         for (i = 0; i < len; i++)
141                 {
142                 pix[i] = (guint8)((gdouble)(pix[i] - l) * scale);
143                 }
144 }
145
146 /*
147  * define these to enable various components of the experimental compare functions
148  *
149  * Convert the thumbprint to greyscale (ignore all color information when comparing)
150  *  #define ALTERNATE_USES_GREYSCALE 1
151  *
152  * Take into account the difference in change from one pixel to the next
153  *  #define ALTERNATE_INCLUDE_COMPARE_CHANGE 1
154  */
155
156 void image_sim_alternate_processing(ImageSimilarityData *sd)
157 {
158 #ifdef ALTERNATE_USES_GREYSCALE
159         gint i;
160 #endif
161
162         if (!alternate_enabled) return;
163
164         image_sim_channel_norm(sd->avg_r, sizeof(sd->avg_r));
165         image_sim_channel_norm(sd->avg_g, sizeof(sd->avg_g));
166         image_sim_channel_norm(sd->avg_b, sizeof(sd->avg_b));
167
168         image_sim_channel_equal(sd->avg_r, sizeof(sd->avg_r));
169         image_sim_channel_equal(sd->avg_g, sizeof(sd->avg_g));
170         image_sim_channel_equal(sd->avg_b, sizeof(sd->avg_b));
171
172 #ifdef ALTERNATE_USES_GREYSCALE
173         for (i = 0; i < sizeof(sd->avg_r); i++)
174                 {
175                 guint8 n;
176
177                 n = (guint8)((gint)(sd->avg_r[i] + sd->avg_g[i] + sd->avg_b[i]) / 3);
178                 sd->avg_r[i] = sd->avg_g[i] = sd->avg_b[i] = n;
179                 }
180 #endif
181 }
182
183 gint mround(gdouble x)
184 {
185         gint ipart = x;
186         gdouble fpart = x-ipart;
187         return (fpart < 0.5 ? ipart : ipart+1);
188 }
189
190 void image_sim_fill_data(ImageSimilarityData *sd, GdkPixbuf *pixbuf)
191 {
192         gint w, h;
193         gint rs;
194         guchar *pix;
195         gboolean has_alpha;
196         gint p_step;
197
198         guchar *p;
199         gint i;
200         gint j;
201         gint x_inc, y_inc, xy_inc;
202         gint xs, ys;
203         gint w_left, h_left;
204
205         gboolean x_small = FALSE;       /* if less than 32 w or h, set TRUE */
206         gboolean y_small = FALSE;
207         if (!sd || !pixbuf) return;
208
209         w = gdk_pixbuf_get_width(pixbuf);
210         h = gdk_pixbuf_get_height(pixbuf);
211         rs = gdk_pixbuf_get_rowstride(pixbuf);
212         pix = gdk_pixbuf_get_pixels(pixbuf);
213         has_alpha = gdk_pixbuf_get_has_alpha(pixbuf);
214
215         p_step = has_alpha ? 4 : 3;
216         x_inc = w / 32;
217         y_inc = h / 32;
218         w_left = w;
219         h_left = h;
220
221         if (x_inc < 1)
222                 {
223                 x_inc = 1;
224                 x_small = TRUE;
225                 }
226         if (y_inc < 1)
227                 {
228                 y_inc = 1;
229                 y_small = TRUE;
230                 }
231
232         j = 0;
233
234         h_left = h;
235         for (ys = 0; ys < 32; ys++)
236                 {
237                 if (y_small) j = (gdouble)h / 32 * ys;
238                         else y_inc = mround((gdouble)h_left/(32-ys));
239                 i = 0;
240
241                 w_left = w;
242                 for (xs = 0; xs < 32; xs++)
243                         {
244                         gint x, y;
245                         gint r, g, b;
246                         gint t;
247                         guchar *xpos;
248
249                         if (x_small) i = (gdouble)w / 32 * xs;
250                                 else x_inc = mround((gdouble)w_left/(32-xs));
251                         xy_inc = x_inc * y_inc;
252                         r = g = b = 0;
253                         xpos = pix + (i * p_step);
254
255                         for (y = j; y < j + y_inc; y++)
256                                 {
257                                 p = xpos + (y * rs);
258                                 for (x = i; x < i + x_inc; x++)
259                                         {
260                                         r += *p; p++;
261                                         g += *p; p++;
262                                         b += *p; p++;
263                                         if (has_alpha) p++;
264                                         }
265                                 }
266
267                         r /= xy_inc;
268                         g /= xy_inc;
269                         b /= xy_inc;
270
271                         t = ys * 32 + xs;
272                         sd->avg_r[t] = r;
273                         sd->avg_g[t] = g;
274                         sd->avg_b[t] = b;
275
276                         i += x_inc;
277                         w_left -= x_inc;
278                         }
279
280                 j += y_inc;
281                 h_left -= y_inc;
282                 }
283
284         sd->filled = TRUE;
285 }
286
287 ImageSimilarityData *image_sim_new_from_pixbuf(GdkPixbuf *pixbuf)
288 {
289         ImageSimilarityData *sd;
290
291         sd = image_sim_new();
292         image_sim_fill_data(sd, pixbuf);
293
294         return sd;
295 }
296
297 #ifdef ALTERNATE_INCLUDE_COMPARE_CHANGE
298 static gdouble alternate_image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, gdouble min)
299 {
300         gint sim;
301         gint i;
302         gint j;
303         gint ld;
304
305         if (!a || !b || !a->filled || !b->filled) return 0.0;
306
307         min = 1.0 - min;
308         sim = 0.0;
309         ld = 0;
310
311         for (j = 0; j < 1024; j += 32)
312                 {
313                 for (i = j; i < j + 32; i++)
314                         {
315                         gint cr, cg, cb;
316                         gint cd;
317
318                         cr = abs(a->avg_r[i] - b->avg_r[i]);
319                         cg = abs(a->avg_g[i] - b->avg_g[i]);
320                         cb = abs(a->avg_b[i] - b->avg_b[i]);
321
322                         cd = cr + cg + cb;
323                         sim += cd + abs(cd - ld);
324                         ld = cd / 3;
325                         }
326                 /* check for abort, if so return 0.0 */
327                 if ((gdouble)sim / (255.0 * 1024.0 * 4.0) > min) return 0.0;
328                 }
329
330         return (1.0 - ((gdouble)sim / (255.0 * 1024.0 * 4.0)) );
331 }
332 #endif
333
334 gdouble image_sim_compare_transfo(ImageSimilarityData *a, ImageSimilarityData *b, gchar transfo)
335 {
336         gint sim;
337         gint i1, i2, *i;
338         gint j1, j2, *j;
339
340         if (!a || !b || !a->filled || !b->filled) return 0.0;
341
342         sim = 0.0;
343
344         if (transfo & 1) { i = &j2; j = &i2; } else { i = &i2; j = &j2; }
345         for (j1 = 0; j1 < 32; j1++)
346                 {
347                 if (transfo & 2) *j = 31-j1; else *j = j1;
348                 for (i1 = 0; i1 < 32; i1++)
349                         {
350                         if (transfo & 4) *i = 31-i1; else *i = i1;
351                         sim += abs(a->avg_r[i1*32+j1] - b->avg_r[i2*32+j2]);
352                         sim += abs(a->avg_g[i1*32+j1] - b->avg_g[i2*32+j2]);
353                         sim += abs(a->avg_b[i1*32+j1] - b->avg_b[i2*32+j2]);
354                         }
355                 }
356
357         return 1.0 - ((gdouble)sim / (255.0 * 1024.0 * 3.0));
358 }
359
360 gdouble image_sim_compare(ImageSimilarityData *a, ImageSimilarityData *b)
361 {
362         gint max_t = (options->rot_invariant_sim ? 8 : 1);
363
364         gint t;
365         gdouble score, max_score = 0;
366
367         for(t = 0; t < max_t; t++)
368         {
369                 score = image_sim_compare_transfo(a, b, t);
370                 if (score > max_score) max_score = score;
371         }
372         return max_score;
373 }
374
375
376 /*
377 4 rotations (0, 90, 180, 270) combined with two mirrors (0, H)
378 generate all possible isometric transformations
379 = 8 tests
380 = change dir of x, change dir of y, exchange x and y = 2^3 = 8
381 */
382 gdouble image_sim_compare_fast_transfo(ImageSimilarityData *a, ImageSimilarityData *b, gdouble min, gchar transfo)
383 {
384         gint sim;
385         gint i1, i2, *i;
386         gint j1, j2, *j;
387
388 #ifdef ALTERNATE_INCLUDE_COMPARE_CHANGE
389         if (alternate_enabled) return alternate_image_sim_compare_fast(a, b, min);
390 #endif
391
392         if (!a || !b || !a->filled || !b->filled) return 0.0;
393
394         min = 1.0 - min;
395         sim = 0.0;
396
397         if (transfo & 1) { i = &j2; j = &i2; } else { i = &i2; j = &j2; }
398         for (j1 = 0; j1 < 32; j1++)
399                 {
400                 if (transfo & 2) *j = 31-j1; else *j = j1;
401                 for (i1 = 0; i1 < 32; i1++)
402                         {
403                         if (transfo & 4) *i = 31-i1; else *i = i1;
404                         sim += abs(a->avg_r[i1*32+j1] - b->avg_r[i2*32+j2]);
405                         sim += abs(a->avg_g[i1*32+j1] - b->avg_g[i2*32+j2]);
406                         sim += abs(a->avg_b[i1*32+j1] - b->avg_b[i2*32+j2]);
407                         }
408                 /* check for abort, if so return 0.0 */
409                 if ((gdouble)sim / (255.0 * 1024.0 * 3.0) > min) return 0.0;
410                 }
411
412         return (1.0 - ((gdouble)sim / (255.0 * 1024.0 * 3.0)) );
413 }
414
415 /* this uses a cutoff point so that it can abort early when it gets to
416  * a point that can simply no longer make the cut-off point.
417  */
418 gdouble image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, gdouble min)
419 {
420         gint max_t = (options->rot_invariant_sim ? 8 : 1);
421
422         gint t;
423         gdouble score, max_score = 0;
424
425         for(t = 0; t < max_t; t++)
426         {
427                 score = image_sim_compare_fast_transfo(a, b, min, t);
428                 if (score > max_score) max_score = score;
429         }
430         return max_score;
431 }
432 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */