interrupt loading of current image in case user is moving fast, in order to display...
[booh] / ext / rbbooh.cc
1 /*
2  *                         *  BOOH  *
3  *
4  * A.k.a `Best web-album Of the world, Or your money back, Humerus'.
5  *
6  * The acronyn sucks, however this is a tribute to Dragon Ball by
7  * Akira Toriyama, where the last enemy beaten by heroes of Dragon
8  * Ball is named "Boo". But there was already a free software project
9  * called Boo, so this one will be it "Booh". Or whatever.
10  *
11  *
12  * Copyright (c) 2005-2008 Guillaume Cottenceau
13  *
14  * This software may be freely redistributed under the terms of the GNU
15  * public license version 2.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  *
21  */
22
23 #include <math.h>
24
25 #include <exiv2/image.hpp>
26 #include <exiv2/exif.hpp>
27
28 #define GDK_PIXBUF_ENABLE_BACKEND
29 #include <gtk/gtk.h>
30 #include "rbgobject.h"
31
32 #define _SELF(s) GDK_PIXBUF(RVAL2GOBJ(s)) 
33
34 static VALUE whitebalance(VALUE self, VALUE level) {
35         double red_filter[256], blue_filter[256];
36         int i, x, y;
37         guchar* pixels = gdk_pixbuf_get_pixels(_SELF(self));
38         int rowstride = gdk_pixbuf_get_rowstride(_SELF(self));
39
40         double factor = 1 + fabs(NUM2DBL(level))/100;
41         if (NUM2DBL(level) < 0) {
42                 factor = 1/factor;
43         }
44
45         for (i = 0; i < 256; i++) {
46                 red_filter[i]  = pow(((double)i)/255, 1/factor) * 255;
47                 blue_filter[i] = pow(((double)i)/255, factor) * 255;
48         }
49     
50         for (y = 0; y < gdk_pixbuf_get_height(_SELF(self)); y++) {
51                 guchar* pixline = &(pixels[rowstride*y]);
52                 for (x = 0; x < gdk_pixbuf_get_width(_SELF(self)); x++) {
53                         pixline[x*3]   = (guchar) red_filter[pixline[x*3]];
54                         pixline[x*3+2] = (guchar) blue_filter[pixline[x*3+2]];
55                 }
56         }
57
58         return self;
59 }
60
61 static VALUE gammacorrect(VALUE self, VALUE level) {
62         double filter[256];
63         int i, x, y;
64         guchar* pixels = gdk_pixbuf_get_pixels(_SELF(self));
65         int rowstride = gdk_pixbuf_get_rowstride(_SELF(self));
66
67         double factor = 1 + fabs(NUM2DBL(level))/100;
68         if (NUM2DBL(level) > 0) {
69                 factor = 1/factor;
70         }
71
72         for (i = 0; i < 256; i++) {
73                 filter[i] = pow(((double)i)/255, factor) * 255;
74         }
75     
76         for (y = 0; y < gdk_pixbuf_get_height(_SELF(self)); y++) {
77                 guchar* pixline = &(pixels[rowstride*y]);
78                 for (x = 0; x < gdk_pixbuf_get_width(_SELF(self)); x++) {
79                         pixline[x*3]   = (guchar) filter[pixline[x*3]];
80                         pixline[x*3+1] = (guchar) filter[pixline[x*3+1]];
81                         pixline[x*3+2] = (guchar) filter[pixline[x*3+2]];
82                 }
83         }
84
85         return self;
86 }
87
88 static VALUE exif_orientation(VALUE module, VALUE filename) {
89         try {
90                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
91                 image->readMetadata();
92                 Exiv2::ExifData &exifData = image->exifData();
93                 if (exifData.empty()) {
94                         return Qnil;
95                 }
96                 Exiv2::ExifData::const_iterator i = exifData.findKey(Exiv2::ExifKey("Exif.Image.Orientation"));
97                 if (i != exifData.end()) {
98                         return INT2NUM(i->value().toLong());
99                 }
100                 return Qnil;
101         } catch (Exiv2::AnyError& e) {
102                 // actually, I don't care about exceptions because I will try non JPG images
103                 // std::cerr << "Caught Exiv2 exception: " << e << "\n";
104                 return Qnil;
105         }
106 }
107
108 static VALUE exif_set_orientation(VALUE module, VALUE filename, VALUE val) {
109         try {
110                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
111                 image->readMetadata();
112                 Exiv2::ExifData &exifData = image->exifData();
113                 exifData["Exif.Image.Orientation"] = uint16_t(NUM2INT(val));
114                 image->writeMetadata();
115         } catch (Exiv2::AnyError& e) {
116                 // actually, I don't care about exceptions because I will try non JPG images
117                 // std::cout << "Caught Exiv2 exception: " << e << "\n";
118         }
119         return Qnil;
120 }
121
122 static VALUE exif_datetimeoriginal(VALUE module, VALUE filename) {
123         try {
124                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
125                 image->readMetadata();
126                 Exiv2::ExifData &exifData = image->exifData();
127                 if (exifData.empty()) {
128                         return Qnil;
129                 }
130                 Exiv2::ExifData::const_iterator i = exifData.findKey(Exiv2::ExifKey("Exif.Photo.DateTimeOriginal"));
131                 if (i != exifData.end()) {
132                         return rb_str_new2(i->value().toString().c_str());
133                 }
134                 return Qnil;
135         } catch (Exiv2::AnyError& e) {
136                 std::cout << "Caught Exiv2 exception: " << e << "\n";
137                 return Qnil;
138         }
139 }
140
141 // internalize drawing "video" borders, it is too slow in ruby (0.12 secs on my p4 2.8 GHz, whereas it's barely measurable with this implementation)
142 static VALUE draw_borders(VALUE self, VALUE pixbuf, VALUE x1, VALUE x2, VALUE ystart, VALUE yend) {
143         GdkDrawable* drawable = GDK_DRAWABLE(RVAL2GOBJ(self));
144         int y = NUM2INT(ystart);
145         int yend_ = NUM2INT(yend);
146         GdkPixbuf* pb = GDK_PIXBUF(RVAL2GOBJ(pixbuf));
147         int height = gdk_pixbuf_get_height(pb);
148         while (y < yend_) {
149                 int render_height = MIN(height, yend_ - y);
150                 gdk_draw_pixbuf(drawable, NULL, pb, 0, 0, NUM2INT(x1), y, -1, render_height, GDK_RGB_DITHER_NONE, -1, -1);
151                 gdk_draw_pixbuf(drawable, NULL, pb, 0, 0, NUM2INT(x2), y, -1, render_height, GDK_RGB_DITHER_NONE, -1, -1);
152                 y += height;
153         }
154         return self;
155 }
156
157 // internalize memory leak fix for GdkPixbuf.rotate
158 // (bugged as of rg2 0.16.0)
159 static VALUE rotate_noleak(VALUE self, VALUE angle) {
160         VALUE ret;
161         GdkPixbuf* dest = gdk_pixbuf_rotate_simple(_SELF(self), (GdkPixbufRotation) RVAL2GENUM(angle, GDK_TYPE_PIXBUF_ROTATION));
162         if (dest == NULL)
163                 return Qnil;
164         ret = GOBJ2RVAL(dest);
165         g_object_unref(dest);
166         return ret;
167 }
168
169 // internalize allowing to pass Qnil to RVAL2BOXED to have NULL passed to Gtk
170 // (bugged as of rg2 0.16.0)
171 static VALUE modify_bg(VALUE self, VALUE state, VALUE color) {
172         gtk_widget_modify_bg(GTK_WIDGET(RVAL2GOBJ(self)), (GtkStateType) RVAL2GENUM(state, GTK_TYPE_STATE_TYPE),
173                              NIL_P(color) ? NULL : (GdkColor*) RVAL2BOXED(color, GDK_TYPE_COLOR));
174         return self;
175 }
176
177 // internalize pixbuf loading for 30% more speedup
178 static VALUE load_not_freezing_ui(VALUE self, VALUE path, VALUE interruptable, VALUE area_prepared_signal_id) {
179         int interruptable_ = RTEST(interruptable);
180         char buf[65536];
181         size_t amount;
182         GdkPixbufLoader* loader = GDK_PIXBUF_LOADER(RVAL2GOBJ(self));
183         GError* error = NULL;
184         FILE* f = fopen(RVAL2CSTR(path), "r");
185         while ((amount = fread(buf, 1, 65536, f)) > 0) {
186                 if (!gdk_pixbuf_loader_write(loader, (const guchar*) buf, amount, &error)) {
187                         fclose(f);
188                         RAISE_GERROR(error);
189                 }
190                 while (gtk_events_pending()) {
191                         gtk_main_iteration();
192                 }
193                 if (interruptable_ && RTEST(rb_eval_string("$interrupt_loading"))) {
194                         // interrupted, case when the user clicked/keyboarded too quickly for this image to
195                         // display; we cancel this loading, but before we disconnect the area-prepared
196                         // signal handler, else the call to gdk_pixbuf_loader_get_pixbuf will take ages
197                         // (between 100 and 400 ms on my p4 for a large photo!)
198                         g_signal_handler_disconnect(loader, NUM2INT(area_prepared_signal_id));
199                         gdk_pixbuf_loader_close(loader, NULL);
200                         fclose(f);
201                         return Qfalse;
202                 }
203         }
204         fclose(f);
205         return Qtrue;
206 }
207
208 extern "C" {
209 void 
210 Init_libadds()
211 {
212     RGObjClassInfo* cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF, Qnil);
213     rb_define_method(cinfo->klass, "whitebalance!", (VALUE (*)(...)) whitebalance, 1); 
214     rb_define_method(cinfo->klass, "gammacorrect!", (VALUE (*)(...)) gammacorrect, 1); 
215     rb_define_method(cinfo->klass, "rotate", (VALUE (*)(...)) rotate_noleak, 1); 
216
217     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_DRAWABLE, Qnil);
218     rb_define_method(cinfo->klass, "draw_borders", (VALUE (*)(...)) draw_borders, 5);
219
220     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GTK_TYPE_WIDGET, Qnil);
221     rb_define_method(cinfo->klass, "modify_bg", (VALUE (*)(...)) modify_bg, 2);
222
223     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF_LOADER, Qnil);
224     rb_define_method(cinfo->klass, "load_not_freezing_ui", (VALUE (*)(...)) load_not_freezing_ui, 3);
225
226     VALUE exif = rb_define_module("Exif");
227     rb_define_module_function(exif, "orientation", (VALUE (*)(...)) exif_orientation, 1);
228     rb_define_module_function(exif, "set_orientation", (VALUE (*)(...)) exif_set_orientation, 2);
229     rb_define_module_function(exif, "datetimeoriginal", (VALUE (*)(...)) exif_datetimeoriginal, 1);
230 }
231 }