workaround exif crash
[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-2010 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 #include <exiv2/error.hpp>
28
29 #define GDK_PIXBUF_ENABLE_BACKEND
30 #include <gtk/gtk.h>
31 #include "rbgobject.h"
32
33 #define _SELF(s) GDK_PIXBUF(RVAL2GOBJ(s)) 
34
35 static VALUE whitebalance(VALUE self, VALUE level) {
36         double red_filter[256], blue_filter[256];
37         int i, x, y;
38         int maxx = gdk_pixbuf_get_width(_SELF(self));
39         int maxy = gdk_pixbuf_get_height(_SELF(self));
40         guchar* pixels = gdk_pixbuf_get_pixels(_SELF(self));
41         int rowstride = gdk_pixbuf_get_rowstride(_SELF(self));
42
43         double factor = 1 + fabs(NUM2DBL(level))/100;
44         if (NUM2DBL(level) < 0) {
45                 factor = 1/factor;
46         }
47
48         for (i = 0; i < 256; i++) {
49                 red_filter[i]  = pow(((double)i)/255, 1/factor) * 255;
50                 blue_filter[i] = pow(((double)i)/255, factor) * 255;
51         }
52     
53         for (y = 0; y < maxy; y++) {
54                 guchar* pixline = &(pixels[rowstride*y]);
55                 for (x = 0; x < maxx; x++) {
56                         pixline[x*3]   = (guchar) red_filter[pixline[x*3]];
57                         pixline[x*3+2] = (guchar) blue_filter[pixline[x*3+2]];
58                 }
59         }
60
61         return self;
62 }
63
64 static VALUE gammacorrect(VALUE self, VALUE level) {
65         double filter[256];
66         int i, x, y;
67         int maxx = gdk_pixbuf_get_width(_SELF(self));
68         int maxy = gdk_pixbuf_get_height(_SELF(self));
69         guchar* pixels = gdk_pixbuf_get_pixels(_SELF(self));
70         int rowstride = gdk_pixbuf_get_rowstride(_SELF(self));
71
72         double factor = 1 + fabs(NUM2DBL(level))/100;
73         if (NUM2DBL(level) > 0) {
74                 factor = 1/factor;
75         }
76
77         for (i = 0; i < 256; i++) {
78                 filter[i] = pow(((double)i)/255, factor) * 255;
79         }
80     
81         for (y = 0; y < maxy; y++) {
82                 guchar* pixline = &(pixels[rowstride*y]);
83                 for (x = 0; x < maxx; x++) {
84                         pixline[x*3]   = (guchar) filter[pixline[x*3]];
85                         pixline[x*3+1] = (guchar) filter[pixline[x*3+1]];
86                         pixline[x*3+2] = (guchar) filter[pixline[x*3+2]];
87                 }
88         }
89
90         return self;
91 }
92
93 static VALUE exif_orientation(VALUE module, VALUE filename) {
94         try {
95                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
96                 image->readMetadata();
97                 Exiv2::ExifData &exifData = image->exifData();
98                 if (exifData.empty()) {
99                         return Qnil;
100                 }
101                 Exiv2::ExifData::const_iterator i = exifData.findKey(Exiv2::ExifKey("Exif.Image.Orientation"));
102                 if (i != exifData.end() && i->count() > 0) {
103                         return INT2NUM(i->value().toLong());
104                 }
105                 return Qnil;
106         } catch (Exiv2::AnyError& e) {
107                 // actually, I don't care about exceptions because I will try non JPG images
108                 // std::cerr << "Caught Exiv2 exception: " << e << "\n";
109                 return Qnil;
110         }
111 }
112
113 static VALUE exif_set_orientation(VALUE module, VALUE filename, VALUE val) {
114         try {
115                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
116                 image->readMetadata();
117                 Exiv2::ExifData &exifData = image->exifData();
118                 exifData["Exif.Image.Orientation"] = uint16_t(NUM2INT(val));
119                 image->writeMetadata();
120         } catch (Exiv2::AnyError& e) {
121                 // actually, I don't care about exceptions because I will try non JPG images
122                 // std::cout << "Caught Exiv2 exception: " << e << "\n";
123         }
124         return Qnil;
125 }
126
127 static VALUE exif_datetimeoriginal(VALUE module, VALUE filename) {
128         try {
129                 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
130                 image->readMetadata();
131                 Exiv2::ExifData &exifData = image->exifData();
132                 if (exifData.empty()) {
133                         return Qnil;
134                 }
135                 Exiv2::ExifData::const_iterator i = exifData.findKey(Exiv2::ExifKey("Exif.Photo.DateTimeOriginal"));
136                 if (i != exifData.end() && i->count() > 0) {
137                         return rb_str_new2(i->value().toString().c_str());
138                 }
139                 return Qnil;
140         } catch (Exiv2::AnyError& e) {
141                 // actually, I don't care about exceptions because I will try non JPG images
142                 // std::cout << "Caught Exiv2 exception: " << e << "\n";
143                 return Qnil;
144         }
145 }
146
147 // 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)
148 static VALUE draw_borders(VALUE self, VALUE pixbuf, VALUE x1, VALUE x2, VALUE ystart, VALUE yend) {
149         GdkDrawable* drawable = GDK_DRAWABLE(RVAL2GOBJ(self));
150         int y = NUM2INT(ystart);
151         int yend_ = NUM2INT(yend);
152         GdkPixbuf* pb = GDK_PIXBUF(RVAL2GOBJ(pixbuf));
153         int height = gdk_pixbuf_get_height(pb);
154         while (y < yend_) {
155                 int render_height = MIN(height, yend_ - y);
156                 gdk_draw_pixbuf(drawable, NULL, pb, 0, 0, NUM2INT(x1), y, -1, render_height, GDK_RGB_DITHER_NONE, -1, -1);
157                 gdk_draw_pixbuf(drawable, NULL, pb, 0, 0, NUM2INT(x2), y, -1, render_height, GDK_RGB_DITHER_NONE, -1, -1);
158                 y += height;
159         }
160         return self;
161 }
162
163 // internalize memory leak fix for GdkPixbuf.rotate
164 // (bugged as of rg2 0.16.0)
165 static VALUE rotate_noleak(VALUE self, VALUE angle) {
166         VALUE ret;
167         GdkPixbuf* dest = gdk_pixbuf_rotate_simple(_SELF(self), (GdkPixbufRotation) RVAL2GENUM(angle, GDK_TYPE_PIXBUF_ROTATION));
168         if (dest == NULL)
169                 return Qnil;
170         ret = GOBJ2RVAL(dest);
171         g_object_unref(dest);
172         return ret;
173 }
174
175 // internalize allowing to pass Qnil to RVAL2BOXED to have NULL passed to Gtk
176 // (bugged as of rg2 0.16.0)
177 static VALUE modify_bg(VALUE self, VALUE state, VALUE color) {
178         gtk_widget_modify_bg(GTK_WIDGET(RVAL2GOBJ(self)), (GtkStateType) RVAL2GENUM(state, GTK_TYPE_STATE_TYPE),
179                              NIL_P(color) ? NULL : (GdkColor*) RVAL2BOXED(color, GDK_TYPE_COLOR));
180         return self;
181 }
182
183 // internalize pixbuf loading for 30% more speedup
184 static VALUE load_not_freezing_ui(VALUE self, VALUE path, VALUE offset) {
185         char buf[65536];
186         size_t amount;
187         size_t off = NUM2INT(offset);
188         GdkPixbufLoader* loader = GDK_PIXBUF_LOADER(RVAL2GOBJ(self));
189         GError* error = NULL;
190         FILE* f = fopen(RVAL2CSTR(path), "r");
191         if (!f) {
192                 gdk_pixbuf_loader_close(loader, NULL);
193                 rb_raise(rb_eRuntimeError, "Unable to open file %s for reading", RVAL2CSTR(path));
194         }
195         if (off > 0) {
196                 if (fseek(f, off, SEEK_SET) != 0) {
197                         rb_raise(rb_eRuntimeError, "Unable to seek file %s", RVAL2CSTR(path));
198                         fclose(f);
199                         return 0;
200                 }
201         }
202         while ((amount = fread(buf, 1, 65536, f)) > 0) {
203                 if (!gdk_pixbuf_loader_write(loader, (const guchar*) buf, amount, &error)) {
204                         gdk_pixbuf_loader_close(loader, NULL);
205                         fclose(f);
206                         RAISE_GERROR(error);
207                 }
208                 off += amount;
209                 if (gtk_events_pending() && !feof(f)) {
210                         // interrupted, case when the user clicked/keyboarded too quickly for this image to
211                         // display; we temporarily interrupt this loading
212                         fclose(f);
213                         return INT2NUM(off);
214                 }
215         }
216         gdk_pixbuf_loader_close(loader, NULL);
217         fclose(f);
218         return INT2NUM(0);
219 }
220
221 extern "C" {
222 void 
223 Init_libadds()
224 {
225     RGObjClassInfo* cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF, Qnil);
226     rb_define_method(cinfo->klass, "whitebalance!", (VALUE (*)(...)) whitebalance, 1); 
227     rb_define_method(cinfo->klass, "gammacorrect!", (VALUE (*)(...)) gammacorrect, 1); 
228     rb_define_method(cinfo->klass, "rotate", (VALUE (*)(...)) rotate_noleak, 1); 
229
230     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_DRAWABLE, Qnil);
231     rb_define_method(cinfo->klass, "draw_borders", (VALUE (*)(...)) draw_borders, 5);
232
233     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GTK_TYPE_WIDGET, Qnil);
234     rb_define_method(cinfo->klass, "modify_bg", (VALUE (*)(...)) modify_bg, 2);
235
236     cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF_LOADER, Qnil);
237     rb_define_method(cinfo->klass, "load_not_freezing_ui", (VALUE (*)(...)) load_not_freezing_ui, 2);
238
239     VALUE exif = rb_define_module("Exif");
240     rb_define_module_function(exif, "orientation", (VALUE (*)(...)) exif_orientation, 1);
241     rb_define_module_function(exif, "set_orientation", (VALUE (*)(...)) exif_set_orientation, 2);
242     rb_define_module_function(exif, "datetimeoriginal", (VALUE (*)(...)) exif_datetimeoriginal, 1);
243 }
244 }