4 * A.k.a `Best web-album Of the world, Or your money back, Humerus'.
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.
12 * Copyright (c) 2005-2010 Guillaume Cottenceau
14 * This software may be freely redistributed under the terms of the GNU
15 * public license version 2.
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.
25 #include <exiv2/image.hpp>
26 #include <exiv2/exif.hpp>
28 #define GDK_PIXBUF_ENABLE_BACKEND
30 #include "rbgobject.h"
32 #define _SELF(s) GDK_PIXBUF(RVAL2GOBJ(s))
34 static VALUE whitebalance(VALUE self, VALUE level) {
35 double red_filter[256], blue_filter[256];
37 int maxx = gdk_pixbuf_get_width(_SELF(self));
38 int maxy = gdk_pixbuf_get_height(_SELF(self));
39 guchar* pixels = gdk_pixbuf_get_pixels(_SELF(self));
40 int rowstride = gdk_pixbuf_get_rowstride(_SELF(self));
42 double factor = 1 + fabs(NUM2DBL(level))/100;
43 if (NUM2DBL(level) < 0) {
47 for (i = 0; i < 256; i++) {
48 red_filter[i] = pow(((double)i)/255, 1/factor) * 255;
49 blue_filter[i] = pow(((double)i)/255, factor) * 255;
52 for (y = 0; y < maxy; y++) {
53 guchar* pixline = &(pixels[rowstride*y]);
54 for (x = 0; x < maxx; x++) {
55 pixline[x*3] = (guchar) red_filter[pixline[x*3]];
56 pixline[x*3+2] = (guchar) blue_filter[pixline[x*3+2]];
63 static VALUE gammacorrect(VALUE self, VALUE level) {
66 int maxx = gdk_pixbuf_get_width(_SELF(self));
67 int maxy = gdk_pixbuf_get_height(_SELF(self));
68 guchar* pixels = gdk_pixbuf_get_pixels(_SELF(self));
69 int rowstride = gdk_pixbuf_get_rowstride(_SELF(self));
71 double factor = 1 + fabs(NUM2DBL(level))/100;
72 if (NUM2DBL(level) > 0) {
76 for (i = 0; i < 256; i++) {
77 filter[i] = pow(((double)i)/255, factor) * 255;
80 for (y = 0; y < maxy; y++) {
81 guchar* pixline = &(pixels[rowstride*y]);
82 for (x = 0; x < maxx; x++) {
83 pixline[x*3] = (guchar) filter[pixline[x*3]];
84 pixline[x*3+1] = (guchar) filter[pixline[x*3+1]];
85 pixline[x*3+2] = (guchar) filter[pixline[x*3+2]];
92 static VALUE exif_orientation(VALUE module, VALUE filename) {
94 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
95 image->readMetadata();
96 Exiv2::ExifData &exifData = image->exifData();
97 if (exifData.empty()) {
100 Exiv2::ExifData::const_iterator i = exifData.findKey(Exiv2::ExifKey("Exif.Image.Orientation"));
101 if (i != exifData.end() && i->count() > 0) {
102 return INT2NUM(i->value().toLong());
105 } catch (Exiv2::AnyError& e) {
106 // actually, I don't care about exceptions because I will try non JPG images
107 // std::cerr << "Caught Exiv2 exception: " << e << "\n";
112 static VALUE exif_set_orientation(VALUE module, VALUE filename, VALUE val) {
114 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
115 image->readMetadata();
116 Exiv2::ExifData &exifData = image->exifData();
117 exifData["Exif.Image.Orientation"] = uint16_t(NUM2INT(val));
118 image->writeMetadata();
119 } catch (Exiv2::AnyError& e) {
120 // actually, I don't care about exceptions because I will try non JPG images
121 // std::cout << "Caught Exiv2 exception: " << e << "\n";
126 static VALUE exif_datetimeoriginal(VALUE module, VALUE filename) {
128 Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(StringValuePtr(filename));
129 image->readMetadata();
130 Exiv2::ExifData &exifData = image->exifData();
131 if (exifData.empty()) {
134 Exiv2::ExifData::const_iterator i = exifData.findKey(Exiv2::ExifKey("Exif.Photo.DateTimeOriginal"));
135 if (i != exifData.end() && i->count() > 0) {
136 return rb_str_new2(i->value().toString().c_str());
139 } catch (Exiv2::AnyError& e) {
140 // actually, I don't care about exceptions because I will try non JPG images
141 // std::cout << "Caught Exiv2 exception: " << e << "\n";
146 // 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)
147 static VALUE draw_borders(VALUE self, VALUE pixbuf, VALUE x1, VALUE x2, VALUE ystart, VALUE yend) {
148 GdkDrawable* drawable = GDK_DRAWABLE(RVAL2GOBJ(self));
149 int y = NUM2INT(ystart);
150 int yend_ = NUM2INT(yend);
151 GdkPixbuf* pb = GDK_PIXBUF(RVAL2GOBJ(pixbuf));
152 int height = gdk_pixbuf_get_height(pb);
154 int render_height = MIN(height, yend_ - y);
155 gdk_draw_pixbuf(drawable, NULL, pb, 0, 0, NUM2INT(x1), y, -1, render_height, GDK_RGB_DITHER_NONE, -1, -1);
156 gdk_draw_pixbuf(drawable, NULL, pb, 0, 0, NUM2INT(x2), y, -1, render_height, GDK_RGB_DITHER_NONE, -1, -1);
162 // internalize memory leak fix for GdkPixbuf.rotate
163 // (bugged as of rg2 0.16.0)
164 static VALUE rotate_noleak(VALUE self, VALUE angle) {
166 GdkPixbuf* dest = gdk_pixbuf_rotate_simple(_SELF(self), (GdkPixbufRotation) RVAL2GENUM(angle, GDK_TYPE_PIXBUF_ROTATION));
169 ret = GOBJ2RVAL(dest);
170 g_object_unref(dest);
174 // internalize allowing to pass Qnil to RVAL2BOXED to have NULL passed to Gtk
175 // (bugged as of rg2 0.16.0)
176 static VALUE modify_bg(VALUE self, VALUE state, VALUE color) {
177 gtk_widget_modify_bg(GTK_WIDGET(RVAL2GOBJ(self)), (GtkStateType) RVAL2GENUM(state, GTK_TYPE_STATE_TYPE),
178 NIL_P(color) ? NULL : (GdkColor*) RVAL2BOXED(color, GDK_TYPE_COLOR));
182 // internalize pixbuf loading for 30% more speedup
183 static VALUE load_not_freezing_ui(VALUE self, VALUE path, VALUE offset) {
186 size_t off = NUM2INT(offset);
187 GdkPixbufLoader* loader = GDK_PIXBUF_LOADER(RVAL2GOBJ(self));
188 GError* error = NULL;
189 FILE* f = fopen(RVAL2CSTR(path), "r");
191 gdk_pixbuf_loader_close(loader, NULL);
192 rb_raise(rb_eRuntimeError, "Unable to open file %s for reading", RVAL2CSTR(path));
195 if (fseek(f, off, SEEK_SET) != 0) {
196 rb_raise(rb_eRuntimeError, "Unable to seek file %s", RVAL2CSTR(path));
201 while ((amount = fread(buf, 1, 65536, f)) > 0) {
202 if (!gdk_pixbuf_loader_write(loader, (const guchar*) buf, amount, &error)) {
203 gdk_pixbuf_loader_close(loader, NULL);
208 if (gtk_events_pending() && !feof(f)) {
209 // interrupted, case when the user clicked/keyboarded too quickly for this image to
210 // display; we temporarily interrupt this loading
215 gdk_pixbuf_loader_close(loader, NULL);
224 RGObjClassInfo* cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF, Qnil);
225 rb_define_method(cinfo->klass, "whitebalance!", (VALUE (*)(...)) whitebalance, 1);
226 rb_define_method(cinfo->klass, "gammacorrect!", (VALUE (*)(...)) gammacorrect, 1);
227 rb_define_method(cinfo->klass, "rotate", (VALUE (*)(...)) rotate_noleak, 1);
229 cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_DRAWABLE, Qnil);
230 rb_define_method(cinfo->klass, "draw_borders", (VALUE (*)(...)) draw_borders, 5);
232 cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GTK_TYPE_WIDGET, Qnil);
233 rb_define_method(cinfo->klass, "modify_bg", (VALUE (*)(...)) modify_bg, 2);
235 cinfo = (RGObjClassInfo*)rbgobj_lookup_class_by_gtype(GDK_TYPE_PIXBUF_LOADER, Qnil);
236 rb_define_method(cinfo->klass, "load_not_freezing_ui", (VALUE (*)(...)) load_not_freezing_ui, 2);
238 VALUE exif = rb_define_module("Exif");
239 rb_define_module_function(exif, "orientation", (VALUE (*)(...)) exif_orientation, 1);
240 rb_define_module_function(exif, "set_orientation", (VALUE (*)(...)) exif_set_orientation, 2);
241 rb_define_module_function(exif, "datetimeoriginal", (VALUE (*)(...)) exif_datetimeoriginal, 1);