1004933933e64af3d0c53819a44b7a95a21df467
[geeqie.git] / src / histogram.c
1 /*
2  * Geeqie
3  * Copyright (C) 2008 - 2012 The Geeqie Team
4  *
5  * Author: Vladimir Nadvornik
6  * based on a patch by Uwe Ohse
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13 #include "main.h"
14 #include "histogram.h"
15
16 #include "pixbuf_util.h"
17 #include "filedata.h"
18
19 #include <math.h>
20
21 /*
22  *----------------------------------------------------------------------------
23  * image histogram
24  *----------------------------------------------------------------------------
25  */
26
27 #define HISTMAP_SIZE 256
28
29 struct _HistMap {
30         gulong r[HISTMAP_SIZE];
31         gulong g[HISTMAP_SIZE];
32         gulong b[HISTMAP_SIZE];
33         gulong max[HISTMAP_SIZE];
34         
35         guint idle_id; /* event source id */
36         GdkPixbuf *pixbuf;
37         gint y;
38 };
39
40
41 Histogram *histogram_new(void)
42 {
43         Histogram *histogram;
44
45         histogram = g_new0(Histogram, 1);
46         histogram->histogram_channel = HCHAN_DEFAULT;
47         histogram->histogram_mode = 0;
48
49         /* grid */
50         histogram->vgrid = 5;
51         histogram->hgrid = 3;
52         histogram->grid_color.R = 160;
53         histogram->grid_color.G = 160;
54         histogram->grid_color.B = 160;
55         histogram->grid_color.A = 250;
56
57         return histogram;
58 }
59
60 void histogram_free(Histogram *histogram)
61 {
62         g_free(histogram);
63 }
64
65
66 gint histogram_set_channel(Histogram *histogram, gint chan)
67 {
68         if (!histogram) return 0;
69         histogram->histogram_channel = chan;
70         return chan;
71 }
72
73 gint histogram_get_channel(Histogram *histogram)
74 {
75         if (!histogram) return 0;
76         return histogram->histogram_channel;
77 }
78
79 gint histogram_set_mode(Histogram *histogram, gint mode)
80 {
81         if (!histogram) return 0;
82         histogram->histogram_mode = mode;
83         return mode;
84 }
85
86 gint histogram_get_mode(Histogram *histogram)
87 {
88         if (!histogram) return 0;
89         return histogram->histogram_mode;
90 }
91
92 gint histogram_toggle_channel(Histogram *histogram)
93 {
94         if (!histogram) return 0;
95         return histogram_set_channel(histogram, (histogram_get_channel(histogram)+1)%HCHAN_COUNT);
96 }
97
98 gint histogram_toggle_mode(Histogram *histogram)
99 {
100         if (!histogram) return 0;
101         return histogram_set_mode(histogram, !histogram_get_mode(histogram));
102 }
103
104 const gchar *histogram_label(Histogram *histogram)
105 {
106         const gchar *t1 = "";
107         
108         if (!histogram) return NULL;
109
110         if (histogram->histogram_mode)
111                 switch (histogram->histogram_channel)
112                         {
113                         case HCHAN_R:   t1 = _("Log Histogram on Red"); break;
114                         case HCHAN_G:   t1 = _("Log Histogram on Green"); break;
115                         case HCHAN_B:   t1 = _("Log Histogram on Blue"); break;
116                         case HCHAN_RGB: t1 = _("Log Histogram on RGB"); break;
117                         case HCHAN_MAX: t1 = _("Log Histogram on value"); break;
118                         }
119         else
120                 switch (histogram->histogram_channel)
121                         {
122                         case HCHAN_R:   t1 = _("Linear Histogram on Red"); break;
123                         case HCHAN_G:   t1 = _("Linear Histogram on Green"); break;
124                         case HCHAN_B:   t1 = _("Linear Histogram on Blue"); break;
125                         case HCHAN_RGB: t1 = _("Linear Histogram on RGB"); break;
126                         case HCHAN_MAX: t1 = _("Linear Histogram on value"); break;
127                         }
128         return t1;
129 }
130
131 static HistMap *histmap_new(void)
132 {
133         HistMap *histmap = g_new0(HistMap, 1);
134         return histmap;
135 }
136
137 void histmap_free(HistMap *histmap)
138 {
139         if (!histmap) return;
140         if (histmap->idle_id) g_source_remove(histmap->idle_id);
141         if (histmap->pixbuf) g_object_unref(histmap->pixbuf);
142         g_free(histmap);
143 }
144
145 static gboolean histmap_read(HistMap *histmap, gboolean whole)
146 {
147         gint w, h, i, j, srs, has_alpha, step, end_line;
148         guchar *s_pix;
149         GdkPixbuf *imgpixbuf = histmap->pixbuf;
150         
151         w = gdk_pixbuf_get_width(imgpixbuf);
152         h = gdk_pixbuf_get_height(imgpixbuf);
153         srs = gdk_pixbuf_get_rowstride(imgpixbuf);
154         s_pix = gdk_pixbuf_get_pixels(imgpixbuf);
155         has_alpha = gdk_pixbuf_get_has_alpha(imgpixbuf);
156         
157         if (whole)
158                 {
159                 end_line = h;
160                 }
161         else
162                 {
163                 gint lines = 1 + 16384 / w;
164                 end_line = histmap->y + lines;
165                 if (end_line > h) end_line = h;
166                 }
167
168         step = 3 + !!(has_alpha);
169         for (i = histmap->y; i < end_line; i++)
170                 {
171                 guchar *sp = s_pix + (i * srs); /* 8bit */
172                 for (j = 0; j < w; j++)
173                         {
174                         guint max = sp[0];
175                         if (sp[1] > max) max = sp[1];
176                         if (sp[2] > max) max = sp[2];
177                 
178                         histmap->r[sp[0]]++;
179                         histmap->g[sp[1]]++;
180                         histmap->b[sp[2]]++;
181                         histmap->max[max]++;
182
183                         sp += step;
184                         }
185                 }
186         histmap->y = end_line;
187         return end_line >= h;   
188 }
189
190 const HistMap *histmap_get(FileData *fd)
191 {
192         if (fd->histmap && !fd->histmap->idle_id) return fd->histmap; /* histmap exists and is finished */
193         
194         return NULL;
195 }
196
197 static gboolean histmap_idle_cb(gpointer data)
198 {
199         FileData *fd = data;
200         if (histmap_read(fd->histmap, FALSE))
201                 {
202                 /* finished */
203                 g_object_unref(fd->histmap->pixbuf); /*pixbuf is no longer needed */
204                 fd->histmap->pixbuf = NULL;
205                 fd->histmap->idle_id = 0;
206                 file_data_send_notification(fd, NOTIFY_HISTMAP);
207                 return FALSE;
208                 }
209         return TRUE;
210 }
211
212 gboolean histmap_start_idle(FileData *fd)
213 {
214         if (fd->histmap || !fd->pixbuf) return FALSE;
215
216         fd->histmap = histmap_new();
217         fd->histmap->pixbuf = fd->pixbuf;
218         g_object_ref(fd->histmap->pixbuf);
219
220         fd->histmap->idle_id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, histmap_idle_cb, fd, NULL);
221         return TRUE;
222 }
223
224
225 static void histogram_vgrid(Histogram *histogram, GdkPixbuf *pixbuf, gint x, gint y, gint width, gint height)
226 {
227         guint i;
228         float add;
229         
230         if (histogram->vgrid == 0) return;
231
232         add = width / (float)histogram->vgrid;
233
234         for (i = 1; i < histogram->vgrid; i++)
235                 {
236                 gint xpos = x + (int)(i * add + 0.5);
237
238                 pixbuf_draw_line(pixbuf, x, y, width, height, xpos, y, xpos, y + height,
239                                  histogram->grid_color.R,
240                                  histogram->grid_color.G,
241                                  histogram->grid_color.B,
242                                  histogram->grid_color.A);
243                 }
244 }
245
246 static void histogram_hgrid(Histogram *histogram, GdkPixbuf *pixbuf, gint x, gint y, gint width, gint height)
247 {
248         guint i;
249         float add;
250         
251         if (histogram->hgrid == 0) return;
252
253         add = height / (float)histogram->hgrid;
254
255         for (i = 1; i < histogram->hgrid; i++)
256                 {
257                 gint ypos = y + (int)(i * add + 0.5);
258         
259                 pixbuf_draw_line(pixbuf, x, y, width, height, x, ypos, x + width, ypos,
260                                  histogram->grid_color.R,
261                                  histogram->grid_color.G,
262                                  histogram->grid_color.B,
263                                  histogram->grid_color.A);
264                 }
265 }
266
267 gboolean histogram_draw(Histogram *histogram, const HistMap *histmap, GdkPixbuf *pixbuf, gint x, gint y, gint width, gint height)
268 {
269         /* FIXME: use the coordinates correctly */
270         gint i;
271         gulong max = 0;
272         gdouble logmax;
273         gint combine = (HISTMAP_SIZE - 1) / width + 1;
274         gint ypos = y + height;
275         
276         if (!histogram || !histmap) return FALSE;
277         
278         /* Draw the grid */
279         histogram_vgrid(histogram, pixbuf, x, y, width, height);
280         histogram_hgrid(histogram, pixbuf, x, y, width, height);
281
282         /* exclude overexposed and underexposed */
283         for (i = 1; i < HISTMAP_SIZE - 1; i++)
284                 {
285                 if (histmap->r[i] > max) max = histmap->r[i];
286                 if (histmap->g[i] > max) max = histmap->g[i];
287                 if (histmap->b[i] > max) max = histmap->b[i];
288                 if (histmap->max[i] > max) max = histmap->max[i];
289                 }
290
291         if (max > 0)
292                 logmax = log(max);
293         else
294                 logmax = 1.0;
295
296         for (i = 0; i < width; i++)
297                 {
298                 gint j;
299                 glong v[4] = {0, 0, 0, 0};
300                 gint rplus = 0;
301                 gint gplus = 0;
302                 gint bplus = 0;
303                 gint ii = i * HISTMAP_SIZE / width;
304                 gint xpos = x + i;
305                 gint num_chan;
306
307                 for (j = 0; j < combine; j++)
308                         {
309                         guint p = ii + j;
310                         v[0] += histmap->r[p];
311                         v[1] += histmap->g[p];
312                         v[2] += histmap->b[p];
313                         v[3] += histmap->max[p];
314                         }
315         
316                 for (j = 0; combine > 1 && j < 4; j++)
317                         v[j] /= combine;
318                 
319                 num_chan = (histogram->histogram_channel == HCHAN_RGB) ? 3 : 1;
320                 for (j = 0; j < num_chan; j++)
321                         {
322                         gint chanmax;
323                         if (histogram->histogram_channel == HCHAN_RGB) 
324                                 {
325                                 chanmax = HCHAN_R;
326                                 if (v[HCHAN_G] > v[HCHAN_R]) chanmax = HCHAN_G;
327                                 if (v[HCHAN_B] > v[chanmax]) chanmax = HCHAN_B;
328                                 }
329                         else
330                                 {
331                                 chanmax = histogram->histogram_channel;
332                                 }
333                         
334                                 {
335                                 gulong pt;
336                                 gint r = rplus;
337                                 gint g = gplus;
338                                 gint b = bplus;
339
340                                 switch (chanmax)
341                                         {
342                                         case HCHAN_R: rplus = r = 255; break;
343                                         case HCHAN_G: gplus = g = 255; break;
344                                         case HCHAN_B: bplus = b = 255; break;
345                                         }
346
347                                 switch (histogram->histogram_channel)
348                                         {
349                                         case HCHAN_RGB:
350                                                 if (r == 255 && g == 255 && b == 255)
351                                                         {
352                                                         r = 0;  b = 0;  g = 0;
353                                                         }
354                                                 break;
355                                         case HCHAN_R:           b = 0;  g = 0;  break;
356                                         case HCHAN_G:   r = 0;  b = 0;          break;
357                                         case HCHAN_B:   r = 0;          g = 0;  break;
358                                         case HCHAN_MAX: r = 0;  b = 0;  g = 0;  break;
359                                         }
360                                 
361                                 if (v[chanmax] == 0)
362                                         pt = 0;
363                                 else if (histogram->histogram_mode)
364                                         pt = ((gdouble)log(v[chanmax])) / logmax * (height - 1);
365                                 else
366                                         pt = ((gdouble)v[chanmax]) / max * (height - 1);
367
368                                 pixbuf_draw_line(pixbuf,
369                                         x, y, width, height,
370                                         xpos, ypos, xpos, ypos - pt,
371                                         r, g, b, 255);
372                                 }
373
374                         v[chanmax] = -1;
375                         }
376                 }
377
378         return TRUE;
379 }
380
381 void histogram_notify_cb(FileData *fd, NotifyType type, gpointer data)
382 {
383         if ((type & NOTIFY_REREAD) && fd->histmap)
384                 {
385                 DEBUG_1("Notify histogram: %s %04x", fd->path, type);
386                 histmap_free(fd->histmap);
387                 fd->histmap = NULL;
388                 }
389 }
390
391 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */