add fix whitebalance
[booh] / bin / booh
index fa6c35413e41ba5de94cea9964888fada3fa4d82..d42b71b5197abc37ddfed247881731ceaaefd621 100755 (executable)
--- a/bin/booh
+++ b/bin/booh
@@ -1,4 +1,4 @@
-#!/usr/bin/ruby
+#! /usr/bin/ruby
 #
 #                         *  BOOH  *
 #
@@ -23,6 +23,7 @@ require 'getoptlong'
 require 'tempfile'
 
 require 'gtk2'
+require 'booh/gtkadds'
 require 'booh/GtkAutoTable'
 
 require 'gettext'
@@ -299,31 +300,46 @@ def create_editzone(scrolledwindow, pagenum, image)
     return [ frame, textview ]
 end
 
+def update_shown_pixbuf(thumbnail_img, img, desired_x, desired_y)
+
+    if !$modified_pixbufs[thumbnail_img]
+        $modified_pixbufs[thumbnail_img] = { :orig => img.pixbuf }
+    elsif !$modified_pixbufs[thumbnail_img][:orig]
+        $modified_pixbufs[thumbnail_img][:orig] = img.pixbuf
+    end
+
+    pixbuf = $modified_pixbufs[thumbnail_img][:orig].dup
+
+    #- rotate
+    if $modified_pixbufs[thumbnail_img][:angle_to_orig] && $modified_pixbufs[thumbnail_img][:angle_to_orig] != 0
+        pixbuf = rotate_pixbuf(pixbuf, $modified_pixbufs[thumbnail_img][:angle_to_orig])
+        msg 3, "sizes: #{pixbuf.width} #{pixbuf.height} - desired #{desired_x}x#{desired_x}"
+        if pixbuf.height > desired_y
+            pixbuf = pixbuf.scale(pixbuf.width * (desired_y.to_f/pixbuf.height), desired_y, Gdk::Pixbuf::INTERP_BILINEAR)
+        elsif pixbuf.width < desired_x && pixbuf.height < desired_y
+            pixbuf = pixbuf.scale(desired_x, pixbuf.height * (desired_x.to_f/pixbuf.width), Gdk::Pixbuf::INTERP_BILINEAR)
+        end
+    end
+
+    #- fix white balance
+    if $modified_pixbufs[thumbnail_img][:whitebalance]
+        pixbuf.whitebalance!($modified_pixbufs[thumbnail_img][:whitebalance])
+    end
+
+    img.pixbuf = $modified_pixbufs[thumbnail_img][:pixbuf] = pixbuf
+end
+
 def rotate_real(angle, thumbnail_img, img, xmlelem, attributes_prefix, desired_x, desired_y)
     $modified = true
 
     #- update rotate attribute
-    xmlelem.add_attribute("#{attributes_prefix}rotate", current_angle = (xmlelem.attributes["#{attributes_prefix}rotate"].to_i + angle) % 360)
+    xmlelem.add_attribute("#{attributes_prefix}rotate", (xmlelem.attributes["#{attributes_prefix}rotate"].to_i + angle) % 360)
 
-    if !$rotated_pixbufs[thumbnail_img]
-        $rotated_pixbufs[thumbnail_img] = { :orig => img.pixbuf, :angle_to_orig => angle % 360 }
-    else
-        $rotated_pixbufs[thumbnail_img][:angle_to_orig] = ($rotated_pixbufs[thumbnail_img][:angle_to_orig] + angle) % 360
-    end
-    msg 3, "angle: #{angle}, angle to orig: #{$rotated_pixbufs[thumbnail_img][:angle_to_orig]}"
-
-    #- rotate shown thumbnail
-    pixbuf = rotate_pixbuf($rotated_pixbufs[thumbnail_img][:orig], $rotated_pixbufs[thumbnail_img][:angle_to_orig])
-    msg 3, "sizes: #{pixbuf.width} #{pixbuf.height} - desired #{desired_x}x#{desired_x}"
-    if pixbuf.height > desired_y
-        img.pixbuf = $rotated_pixbufs[thumbnail_img][:pixbuf] = pixbuf.scale(pixbuf.width * (desired_y.to_f/pixbuf.height), desired_y,
-                                                                             Gdk::Pixbuf::INTERP_BILINEAR)
-    elsif pixbuf.width < desired_x && pixbuf.height < desired_y
-        img.pixbuf = $rotated_pixbufs[thumbnail_img][:pixbuf] = pixbuf.scale(desired_x, pixbuf.height * (desired_x.to_f/pixbuf.width),
-                                                                             Gdk::Pixbuf::INTERP_BILINEAR)
-    else
-        img.pixbuf = $rotated_pixbufs[thumbnail_img][:pixbuf] = pixbuf
-    end
+    $modified_pixbufs[thumbnail_img] ||= {}
+    $modified_pixbufs[thumbnail_img][:angle_to_orig] = (($modified_pixbufs[thumbnail_img][:angle_to_orig] || 0) + angle) % 360
+    msg 3, "angle: #{angle}, angle to orig: #{$modified_pixbufs[thumbnail_img][:angle_to_orig]}"
+
+    update_shown_pixbuf(thumbnail_img, img, desired_x, desired_y)
 end
 
 def rotate(angle, thumbnail_img, img, xmlelem, attributes_prefix, desired_x, desired_y)
@@ -398,10 +414,10 @@ from. There are approximately 25 frames per second in a video.
     dialog.show_all
 
     dialog.run { |response|
-        $modified = true
         newval = entry.text
         dialog.destroy
         if response == Gtk::Dialog::RESPONSE_OK
+            $modified = true
             msg 3, "changing frame offset top #{newval}"
             return { :old => value, :new => newval }
         else
@@ -410,18 +426,110 @@ from. There are approximately 25 frames per second in a video.
     }
 end
 
-def gen_real_thumbnail(type, origfile, destfile, xmldir, size, img)
-    Thread.new {
-        push_mousecursor_wait
+def change_whitebalance(xmlelem, attributes_prefix, value)
+    $modified = true
+    xmlelem.add_attribute("#{attributes_prefix}white-balance", value)
+end
+
+def recalc_whitebalance(level, orig, thumbnail_img, img, xmlelem, attributes_prefix, desired_x, desired_y, infotype)
+
+    #- in case the white balance was already modified in the config file and thumbnail, we cannot just revert, we need to use original file
+    if (!$modified_pixbufs[thumbnail_img] || !$modified_pixbufs[thumbnail_img][:whitebalance]) && xmlelem.attributes["#{attributes_prefix}white-balance"]
+        save_whitebalance = xmlelem.attributes["#{attributes_prefix}white-balance"]
+        xmlelem.delete_attribute("#{attributes_prefix}white-balance")
+        destfile = "#{thumbnail_img}-orig-whitebalance.jpg"
+        gen_real_thumbnail_core(attributes_prefix == '' ? 'element' : 'subdir', orig, destfile,
+                                xmlelem, attributes_prefix == '' ? $default_size['thumbnails'] : $albums_thumbnail_size, infotype)
+        $modified_pixbufs[thumbnail_img] ||= {}
+        $modified_pixbufs[thumbnail_img][:orig] = pixbuf_or_nil(destfile)
         system("rm -f '#{destfile}'")
-        #- type can be 'element' or 'subdir'
-        if type == 'element'
-            gen_thumbnails_element(origfile, xmldir, false, [ { 'filename' => destfile, 'size' => size } ])
+        xmlelem.add_attribute("#{attributes_prefix}white-balance", save_whitebalance)
+        $modified_pixbufs[thumbnail_img][:angle_to_orig] = 0
+        if entry2type(orig) == 'video'
+            #- cleanup temp for videos
+            system("rm -f #{current_dest_dir}/screenshot.jpg000000.jpg")
+        end
+    end
+
+    $modified_pixbufs[thumbnail_img] ||= {}
+    $modified_pixbufs[thumbnail_img][:whitebalance] = level.to_f
+
+    update_shown_pixbuf(thumbnail_img, img, desired_x, desired_y)
+end
+
+def ask_whitebalance(orig, thumbnail_img, img_, xmlelem, attributes_prefix, desired_x, desired_y, infotype)
+    #- init $modified_pixbufs correctly
+#    update_shown_pixbuf(thumbnail_img, img_, desired_x, desired_y)
+
+    value = xmlelem.attributes["#{attributes_prefix}white-balance"] || "0"
+
+    dialog = Gtk::Dialog.new(utf8(_("Fix white balance")),
+                             $main_window,
+                             Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT,
+                             [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK],
+                             [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL])
+
+    lbl = Gtk::Label.new
+    lbl.markup = utf8(
+_("You can fix the <b>white balance</b> of the image, if your image is too blue
+or too yellow because your camera didn't detect the light correctly. Drag the
+slider below the image to the left for more blue, to the right for more yellow.
+"))
+    dialog.vbox.add(lbl)
+    dialog.vbox.add(evt = Gtk::EventBox.new.add(img = Gtk::Image.new(img_.pixbuf)))
+    dialog.vbox.add(hs = Gtk::HScale.new(-200, 200, 1).set_value(value.to_i))
+    
+    dialog.window_position = Gtk::Window::POS_MOUSE
+    dialog.show_all
+
+    lastval = nil
+    thread = nil
+    timeout = Gtk.timeout_add(100) {
+        if hs.value != lastval
+            lastval = hs.value
+            if thread
+                thread.kill
+            end
+            thread = Thread.new {
+                recalc_whitebalance(lastval, orig, thumbnail_img, img, xmlelem, attributes_prefix, desired_x, desired_y, infotype)
+            }
+        end
+        true
+    }
+
+    dialog.run { |response|
+        Gtk.timeout_remove(timeout)
+        if response == Gtk::Dialog::RESPONSE_OK
+            $modified = true
+            newval = hs.value.to_s
+            msg 3, "changing white balance to #{newval}"
+            dialog.destroy
+            return { :old => value, :new => newval }
         else
-            gen_thumbnails_subdir(origfile, xmldir, false, [ { 'filename' => destfile, 'size' => size } ], find_subalbum_info_type(xmldir))
+            $modified_pixbufs[thumbnail_img] ||= {}
+            $modified_pixbufs[thumbnail_img][:whitebalance] = value.to_f
+            dialog.destroy
+            return nil
         end
+    }
+end
+
+def gen_real_thumbnail_core(type, origfile, destfile, xmldir, size, infotype)
+    system("rm -f '#{destfile}'")
+    #- type can be 'element' or 'subdir'
+    if type == 'element'
+        gen_thumbnails_element(origfile, xmldir, false, [ { 'filename' => destfile, 'size' => size } ])
+    else
+        gen_thumbnails_subdir(origfile, xmldir, false, [ { 'filename' => destfile, 'size' => size } ], infotype)
+    end
+end
+
+def gen_real_thumbnail(type, origfile, destfile, xmldir, size, img, infotype)
+    Thread.new {
+        push_mousecursor_wait
+        gen_real_thumbnail_core(type, origfile, destfile, xmldir, size, infotype)
         img.set(destfile)
-        $rotated_pixbufs[destfile] = { :orig => img.pixbuf, :pixbuf => img.pixbuf, :angle_to_orig => 0 }
+        $modified_pixbufs[destfile] = { :orig => img.pixbuf, :pixbuf => img.pixbuf, :angle_to_orig => 0 }
         if entry2type(origfile) == 'video'
             #- cleanup temp for videos
             system("rm -f #{current_dest_dir}/screenshot.jpg000000.jpg")
@@ -438,12 +546,15 @@ def popup_thumbnail_menu(event, optionals, type, xmldir, attributes_prefix, clos
         changeimg.signal_connect('activate') { closures[:change].call }
         menu.append(            Gtk::SeparatorMenuItem.new)
     end
-    menu.append(    r90 = Gtk::ImageMenuItem.new(utf8(_("Rotate clockwise"))))
+    menu.append(r90 = Gtk::ImageMenuItem.new(utf8(_("Rotate clockwise"))))
     r90.image = Gtk::Image.new("#{$FPATH}/images/stock-rotate-90-16.png")
     r90.signal_connect('activate') { closures[:rotate].call(90) }
-    menu.append(   r270 = Gtk::ImageMenuItem.new(utf8(_("Rotate counter-clockwise"))))
+    menu.append(r270 = Gtk::ImageMenuItem.new(utf8(_("Rotate counter-clockwise"))))
     r270.image = Gtk::Image.new("#{$FPATH}/images/stock-rotate-270-16.png")
     r270.signal_connect('activate') { closures[:rotate].call(-90) }
+    menu.append(whitebalance = Gtk::ImageMenuItem.new(utf8(_("Fix white-balance"))))
+    whitebalance.image = Gtk::Image.new("#{$FPATH}/images/stock-tool-color-balance-16.png")
+    whitebalance.signal_connect('activate') { closures[:whitebalance].call }
     if type == 'video'
         menu.append(               Gtk::SeparatorMenuItem.new)
         menu.append(  color_swap = Gtk::ImageMenuItem.new(utf8(_("Red/blue color swap"))))
@@ -476,15 +587,15 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
     frame1 = Gtk::Frame.new
 
     my_gen_real_thumbnail = proc {
-        gen_real_thumbnail('element', from_utf8("#{$current_path}/#{filename}"), thumbnail_img, $xmldir, $default_size['thumbnails'], img)
+        gen_real_thumbnail('element', from_utf8("#{$current_path}/#{filename}"), thumbnail_img, $xmldir, $default_size['thumbnails'], img, '')
     }
 
     #- generate the thumbnail if missing (if image was rotated but booh was not relaunched)
-    if !$rotated_pixbufs[thumbnail_img] && !File.exists?(thumbnail_img)
+    if !$modified_pixbufs[thumbnail_img] && !File.exists?(thumbnail_img)
         frame1.add(img = Gtk::Image.new)
         my_gen_real_thumbnail.call
     else
-        frame1.add(img = Gtk::Image.new($rotated_pixbufs[thumbnail_img] ? $rotated_pixbufs[thumbnail_img][:pixbuf] : thumbnail_img))
+        frame1.add(img = Gtk::Image.new($modified_pixbufs[thumbnail_img] ? $modified_pixbufs[thumbnail_img][:pixbuf] : thumbnail_img))
     end
     evtbox = Gtk::EventBox.new.add(Gtk::Alignment.new(0.5, 0.5, 0, 0).add(frame1.set_shadow_type(Gtk::SHADOW_ETCHED_OUT)))
 
@@ -561,6 +672,29 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         end
     }
 
+    whitebalance_and_cleanup = Proc.new {
+        if values = ask_whitebalance(from_utf8("#{$current_path}/#{filename}"), thumbnail_img, img,
+                                     $xmldir.elements["[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+            perform_change_whitebalance_and_cleanup = Proc.new { |val|
+                change_whitebalance($xmldir.elements["[@filename='#{filename}']"], '', val)
+                recalc_whitebalance(val, from_utf8("#{$current_path}/#{filename}"), thumbnail_img, img,
+                                    $xmldir.elements["[@filename='#{filename}']"], '', $default_thumbnails[:x], $default_thumbnails[:y], '')
+                cleanup_all_thumbnails.call
+            }
+            perform_change_whitebalance_and_cleanup.call(values[:new])
+
+            save_undo(_("fix white balance"),
+                      Proc.new {
+                          perform_change_whitebalance_and_cleanup.call(values[:old])
+                          $notebook.set_page(1)
+                          Proc.new {
+                              perform_change_whitebalance_and_cleanup.call(values[:new])
+                              $notebook.set_page(1)
+                          }
+                      })
+        end
+    }
+
     enhance_and_cleanup = Proc.new {
         perform_enhance_and_cleanup = Proc.new {
             enhance($xmldir.elements["[@filename='#{filename}']"], '')
@@ -706,7 +840,7 @@ def add_thumbnail(autotable, filename, type, thumbnail_img, caption)
         if event.event_type == Gdk::Event::BUTTON_PRESS && event.button == 3
             popup_thumbnail_menu(event, ['delete'], type, $xmldir.elements["[@filename='#{filename}']"], '',
                                  { :rotate => rotate_and_cleanup, :color_swap => color_swap_and_cleanup, :enhance => enhance_and_cleanup,
-                                   :frame_offset => change_frame_offset_and_cleanup, :delete => delete })
+                                   :frame_offset => change_frame_offset_and_cleanup, :delete => delete, :whitebalance => whitebalance_and_cleanup })
         end
         if event.event_type == Gdk::Event::BUTTON2_PRESS && event.button == 1
             view_element(filename)
@@ -989,14 +1123,14 @@ def change_dir
 
         img = nil
         my_gen_real_thumbnail = proc {
-            gen_real_thumbnail('subdir', captionfile, thumbnail_file, xmldir, $albums_thumbnail_size, img)
+            gen_real_thumbnail('subdir', captionfile, thumbnail_file, xmldir, $albums_thumbnail_size, img, infotype)
         }
 
-        if !$rotated_pixbufs[thumbnail_file] && !File.exists?(thumbnail_file)
+        if !$modified_pixbufs[thumbnail_file] && !File.exists?(thumbnail_file)
             f.add(img = Gtk::Image.new)
             my_gen_real_thumbnail.call
         else
-            f.add(img = Gtk::Image.new($rotated_pixbufs[thumbnail_file] ? $rotated_pixbufs[thumbnail_file][:pixbuf] : thumbnail_file))
+            f.add(img = Gtk::Image.new($modified_pixbufs[thumbnail_file] ? $modified_pixbufs[thumbnail_file][:pixbuf] : thumbnail_file))
         end
         hbox.pack_end(Gtk::Alignment.new(0.5, 0.5, 0, 0).add(evtbox = Gtk::EventBox.new.add(f)), false, false)
         $subalbums.attach(hbox,
@@ -1035,7 +1169,7 @@ def change_dir
                 msg 3, "new captionfile is: #{fc.filename}"
                 perform_changefile = Proc.new {
                     $subalbums_edits[xmldir.attributes['path']][:captionfile] = captionfile = new_file
-                    $rotated_pixbufs.delete(thumbnail_file)
+                    $modified_pixbufs.delete(thumbnail_file)
                     xmldir.delete_attribute("#{infotype}-rotate")
                     xmldir.delete_attribute("#{infotype}-color-swap")
                     xmldir.delete_attribute("#{infotype}-enhance")
@@ -1105,6 +1239,29 @@ def change_dir
             end
         }
 
+        whitebalance_and_cleanup = Proc.new {
+            if values = ask_whitebalance(captionfile, thumbnail_file, img, xmldir, "#{infotype}-",
+                                         $default_albums_thumbnails[:x], $default_albums_thumbnails[:y], infotype)
+                perform_change_whitebalance_and_cleanup = Proc.new { |val|
+                    change_whitebalance(xmldir, "#{infotype}-", val)
+                    recalc_whitebalance(val, captionfile, thumbnail_file, img, xmldir, "#{infotype}-",
+                                        $default_albums_thumbnails[:x], $default_albums_thumbnails[:y], infotype)
+                    system("rm -f '#{thumbnail_file}'")
+                }
+                perform_change_whitebalance_and_cleanup.call(values[:new])
+                
+                save_undo(_("fix white balance"),
+                          Proc.new {
+                              perform_change_whitebalance_and_cleanup.call(values[:old])
+                              $notebook.set_page(0)
+                              Proc.new {
+                                  perform_change_whitebalance_and_cleanup.call(values[:new])
+                                  $notebook.set_page(0)
+                              }
+                          })
+            end
+        }
+
         enhance_and_cleanup = Proc.new {
             perform_enhance_and_cleanup = Proc.new {
                 enhance(xmldir, "#{infotype}-")
@@ -1137,7 +1294,7 @@ def change_dir
             if event.event_type == Gdk::Event::BUTTON_PRESS && event.button == 3
                 popup_thumbnail_menu(event, ['change_image'], entry2type(captionfile), xmldir, "#{infotype}-",
                                      { :change => change_image, :rotate => rotate_and_cleanup, :enhance => enhance_and_cleanup,
-                                       :color_swap => color_swap_and_cleanup, :frame_offset => change_frame_offset_and_cleanup })
+                                       :color_swap => color_swap_and_cleanup, :frame_offset => change_frame_offset_and_cleanup, :whitebalance => whitebalance_and_cleanup })
             end
             if event.event_type == Gdk::Event::BUTTON2_PRESS && event.button == 1
                 change_image.call
@@ -1301,7 +1458,7 @@ def open_file(filename)
     $filename = nil
     $modified = false
     $current_path = nil   #- invalidate
-    $rotated_pixbufs = {}
+    $modified_pixbufs = {}
     $albums_ts.clear
     $autotable.clear
     $subalbums_vb.children.each { |chld|