eog plugin – Rectangle Zoom

This is a python plugin for the linux image viewer eog (eye of gnome).
The developers of eog prefer mouse wheel zooming but i am too old to change my habits.
These habits are based on the best image viewer around: JPEG VIEW
But JPEG VIEW only works under Windows, so i have to find a linux thing around.
And thats why i wrote this little python plugin.

With the plugin you can scroll through your images with the mousewheel.
Initial zooming is done with a rectangle selection.

eogRectZoom01

eogRectZoom02

The code is easy to understand, feel free to adapt it to your needs.
And you can leave a comment down on this site.
I HOPE YOU LIKE IT!

How it works:
– activate the plugin in the plugins settings page
– Wheel: next / previous image when in Fit-to-Window mode.
– DoubleClick: Switch between Fullscreen / windowed mode or fits the image in the window
– Zooming: Select a region with the mouse and its done
– its difficult to use this plugin with the provided DoubleclicktoFullscreen plugin!

I think everyone looking for eog plugins knows what to do for installing:
https://wiki.gnome.org/Apps/EyeOfGnome/Plugins

This is the plugin, save as eogRectZoom.plugin

[Plugin]
Loader=python
Module=eogRectZoom
Icon=eogRectZoom.png
IAge=2
Name=Rectangle Zoom
Description=Adds a Rectangle Zoom, Fullscreen Double Click and Mousewheel features
Authors=Fliggs
Copyright=Copyright © 2014 Fliggs
Website=https://fliggs.wordpress.com/

This is the python code, save as eogRectZoom.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  Eye of Gnome - RectangleZoom Plugin
#  
#  Version 2014.11
#
#  Copyright 2014 FLiggs
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#  Eye of GNOME Plugins
#  http://live.gnome.org/EyeOfGnome/Plugins
#
#  Eye of GNOME Reference Manual
#  http://developer.gnome.org/eog/stable/index.html
#
#  GDK-3.0 Reference 
#  https://lazka.github.io/pgi-docs/Gdk-3.0/index.html
#
#  What it does:
#   - WHEEL:
#       - When image is in Fit-To-Window-Mode mouse wheel will go to next / previous image
#   - DOUBLECLICK:
#       - When image is in Fit-To-Window-Mode DoubleClick will switch between Fullscreen and Windowmode
#       - When image is not in Fit-To-Window-Mode DoubleClick will switch into Fit-To-Window-Mode
#   - LEFT BUTTON SELECTION:
#       - When image is in Fit-To-Window-Mode you can select a rectangle which is zoomed afterwards
#
#   Not working proper with the double Click = Fullscreen plugin!
#   Have fun.

from gi.repository import GObject, Gtk, Gdk, Eog
import cairo

class Point:
    """ Point class represents and manipulates x,y coords. """

    def __init__(self, x=0, y=0):
        """ Create a new point at x, y """
        self.x = x
        self.y = y
        
    def halfway(self, target):
        """ Return the halfway point between myself and the target """
        x_new = (self.x + target.x)/2
        y_new = (self.y + target.y)/2
        return Point(x_new, y_new)
        
    def distance(self, target):
        """ Return the distance between point self and the target point """
        x_new = abs(target.x-self.x)
        y_new = abs(target.y-self.y)
        return Point(x_new, y_new)
        

class RectZoomPlugin(GObject.Object, Eog.WindowActivatable):
    window                      =   GObject.property(type=Eog.Window)
    def __init__(self):
        functionCall            =   GObject.Object.__init__(self)
 
    def do_activate(self):
        """
        Get and set the initial values and objects
        We need:
            - The thumb_view for looping through the images
            - The Scroll-View and its children for the Zoom-Thing
                - Horizontal and Vertical Scrollbar
                - Drawing Area
            - The Standard Settings for changing the Drag and Drop Threshold
        """
        self.PointStart         =   None
        self.PointEnd           =   None
        self.cairo_context      =   None
        self.drarea_window      =   None
        
        self.viewTH             =   self.window.get_thumb_view()
        self.viewSL             =   self.window.get_view()
        self.scrl_V             =   self.viewSL.get_children()[0]
        self.scrl_H             =   self.viewSL.get_children()[1]
        self.drarea             =   self.viewSL.get_children()[2]
        
        self.drarea_handler     =   self.drarea.connect("configure-event", self.GetWindowOnConfigure)
        self.dclick_handler     =   self.window.connect("button-press-event",self.OnClick)
        self.release_handler    =   self.window.connect("button-release-event",self.OnRelease)
        self.mmove_handler      =   self.window.connect('motion_notify_event', self.OnMove)
        self.scroll_handler     =   self.window.connect("scroll-event",self.OnWheel)
        
        self.settings           =   Gtk.Settings.get_default()
        self.default_threshold  =   self.settings.get_property("gtk-dnd-drag-threshold")
        self.new_threshold      =   5000
        
        window_mode             =   self.window.get_mode()
        if window_mode          ==  Eog.WindowMode(Eog.WindowMode.FULLSCREEN):
            self.FullScreen     =   True
        else:
            self.FullScreen     =   False
    
    def do_deactivate(self):
        """
        Reset everything
        """
        functionCall            =   self.drarea.disconnect(self.drarea_handler)
        functionCall            =   self.window.disconnect(self.dclick_handler)
        functionCall            =   self.window.disconnect(self.release_handler)
        functionCall            =   self.window.disconnect(self.mmove_handler)
        functionCall            =   self.window.disconnect(self.scroll_handler)
        functionCall            =   self.viewSL.set_scroll_wheel_zoom(True)
        functionCall            =   self.settings.set_long_property("gtk-dnd-drag-threshold",
                                            self.default_threshold,"XProperty")
        
    def GetWindowOnConfigure(self,widget,event):
        """
        This is the only moment we could catch the drawing area window.
        We will need it later to draw on it
        """
        if self.drarea_window   ==  None:
            self.drarea_window  =   widget.get_window()
    
    def OnWheel(self,widget,event):
        '''
        Back and Forward in the imagelist with the mousewheel
        the first time used, nothing happens, see this bug
        GTK BUG:
        https://bugzilla.gnome.org/show_bug.cgi?id=675959
        So three cases😦
        '''
        zoom_mode               =   self.viewSL.get_zoom_mode() 
        if zoom_mode            ==  Eog.ZoomMode(Eog.ZoomMode.SHRINK_TO_FIT):
            functionCall        =   self.viewSL.set_scroll_wheel_zoom(False)
            if event.delta_y    ==  1:
                functionCall    =   self.viewTH.select_single(2) # EOG_THUMB_VIEW_SELECT_RIGHT
            elif event.delta_y  ==  -1:
                functionCall    =   self.viewTH.select_single(1) # EOG_THUMB_VIEW_SELECT_LEFT
            elif event.delta_y  ==  0:
                functionCall    =   self.viewTH.select_single(2) # EOG_THUMB_VIEW_SELECT_RIGHT
        else:
            functionCall        =   self.viewSL.set_scroll_wheel_zoom(True)
    
    def OnClick(self,widget,event):
        """
        Switch between FullScreen / Windowed-Mode and Fit-to-window
        """
        zoom_mode                       =   self.viewSL.get_zoom_mode() 
        if zoom_mode                    ==  Eog.ZoomMode(Eog.ZoomMode.SHRINK_TO_FIT):
            if event.button             ==  1:
                if event.type           ==  Gdk.EventType.BUTTON_PRESS:
                    self.PointStart     =   Point(event.x,event.y)
                    functionCall        =   self.settings.set_long_property ("gtk-dnd-drag-threshold",
                                                        self.new_threshold,"XProperty")
                elif event.type         ==  Gdk.EventType._2BUTTON_PRESS:
                    if self.FullScreen  ==  True:
                        self.FullScreen =   False
                        functionCall    =   self.window.set_mode(Eog.WindowMode(Eog.WindowMode.NORMAL))
                    else:
                        self.FullScreen =   True
                        functionCall    =   self.window.set_mode(Eog.WindowMode(Eog.WindowMode.FULLSCREEN))
                    
                    functionCall        =   self.viewSL.set_zoom_mode(Eog.ZoomMode(Eog.ZoomMode.SHRINK_TO_FIT))
                    self.PointStart     =   None
                    functionCall        =   self.settings.set_long_property ("gtk-dnd-drag-threshold",
                                                        self.default_threshold,"XProperty")
        else:
            if event.type               ==  Gdk.EventType._2BUTTON_PRESS:
                functionCall            =   self.viewSL.set_zoom_mode(1) 
                    
    def OnMove(self,widget,event):
        """
        Drawing the selection rectangle using cairo
        """
        if self.PointStart:
            functionCall                =   self.viewSL.queue_draw()
            while Gtk.events_pending():
                functionCall            =   Gtk.main_iteration_do(True)
            self.cairo_context          =   self.drarea_window.cairo_create()
            functionCall                =   self.cairo_context.set_source_rgb(1, 1,1)
            functionCall                =   self.cairo_context.set_line_width(1)
            functionCall                =   self.cairo_context.set_dash([2,2], 0.0)
            x1                          =   self.PointStart.x
            y1                          =   self.PointStart.y
            x2                          =   event.x
            y2                          =   event.y
            functionCall                =   self.cairo_context.rectangle(x1,y1, x2-x1,y2-y1)
            functionCall                =   self.cairo_context.stroke()
    
    def OnRelease(self,widget,event):
        """
        Zoom the selected area.
        We have the starting point (Button Click) and now we get the end point.
        From these we calculate the target point: its the center of the rectangle
        But we have to get the "border" of the fit-to-window-mode first, this is done with GetZeroPoint.
        Then we calculate the relative position of this point.
        We zoom in and set the relative positions then on the scrollbars
        ....easy...😉
        """
        functionCall            =   self.viewSL.queue_draw()
        functionCall            =   self.settings.set_long_property ("gtk-dnd-drag-threshold",
                                                        self.default_threshold,"XProperty")
        if not self.PointStart:
            return
        
        if self.PointStart.x    ==  event.x:
            self.PointStart     =   None
            return
        
        PointEnd                =   Point(event.x,event.y)
        PointTarget             =   self.PointStart.halfway(PointEnd)
        PointZero               =   self.GetZeroPoint()
        PointTargetNew          =   PointTarget.distance(PointZero)
        PointSize               =   PointEnd.distance(self.PointStart)
        self.PointStart         =   None
        
        ad_H                    =   self.scrl_H.get_adjustment()
        ad_V                    =   self.scrl_V.get_adjustment()
        view_W                  =   self.viewSL.get_allocation().width
        view_H                  =   self.viewSL.get_allocation().height
        relativePos_X           =   PointTargetNew.x/(view_W-2*PointZero.x)
        relativePos_Y           =   PointTargetNew.y/(view_H-2*PointZero.y)
        middle_X                =   (view_W-2*PointZero.x)/2
        middle_Y                =   (view_H-2*PointZero.y)/2
        correction_X            =   (PointTargetNew.x-middle_X) / middle_X
        correction_Y            =   (PointTargetNew.y-middle_Y) / middle_Y
        
        Size_Factor_W           =   PointSize.x / (view_W-2*PointZero.x)
        Size_Factor_H           =   PointSize.y / (view_H-2*PointZero.y)
        Size_Factor             =   max(Size_Factor_W,Size_Factor_H)
        Zoom_Old                =   self.viewSL.get_zoom()
        Zoom_New                =   Zoom_Old/Size_Factor
        
        functionCall            =   self.viewSL.set_zoom(Zoom_New)
        functionCall            =   self.viewSL.set_scroll_wheel_zoom(True)
    
        max_H                   =   ad_H.get_upper()-ad_H.get_page_size()
        max_V                   =   ad_V.get_upper()-ad_V.get_page_size()
        correction_H            =   correction_X * ad_H.get_page_increment()
        correction_V            =   correction_Y * ad_V.get_page_increment()
        value_H                 =   relativePos_X * max_H + correction_H
        value_V                 =   relativePos_Y * max_V + correction_V
        
        functionCall            =   self.scrl_H.set_value(value_H)
        functionCall            =   self.scrl_V.set_value(value_V)
       
    def GetZeroPoint(self):
        '''
        In Fit-Mode the View-Window is most of the time larger
        then needed.
        So here we will calculate the ZeroPoint of the axes of the image
        '''
        imageW                  = self.window.get_image().get_pixbuf().get_width()
        imageH                  = self.window.get_image().get_pixbuf().get_height()
        viewW                   = self.viewSL.get_allocation().width
        viewH                   = self.viewSL.get_allocation().height
        zoom                    = self.viewSL.get_zoom()
        viewMinW                = zoom * imageW
        viewMinH                = zoom * imageH
        toomuchW                = (viewW-viewMinW)/2
        toomuchH                = (viewH-viewMinH)/2
        x                       = 0 + toomuchW
        y                       = 0 + toomuchH
        return Point(x,y)

8 Gedanken zu “eog plugin – Rectangle Zoom

  1. Hello,
    I like this small programm really a lot. For my purposes I would like to know the pixel size of the actual bitmap and it must be something like
    pixel_size = eog_image_get_size()
    can you help me with the correct syntax please, as I am quite new to python?

    Many thanks, Alex

  2. Hi Alex,

    i have found no solution to get the pixel size directly from the image.
    So i used:
    imageWidth = self.window.get_image().get_pixbuf().get_width()
    imageHeight = self.window.get_image().get_pixbuf().get_height()

    The pixelsize is then imageWidth x imageHeight, isn’t it?
    Was this helpful?

    Fliggs

  3. Hello!

    Thank you very much for plugin. well, not for itself (although it is great:) ) but for being best api documentation.

    When yu were writing it, what was you api docs?

    I’m now learning this python api and am pretty short to docs. So currently studying your plugin is best way….

  4. I was reading this one. And even others mentioned in the plugin even before I read the plugin. But I’m somehow missing some top level description. What is enter point, what is called when. and so on…
    What are members of window or of an image. How to access global setitngs (like slideshow delay ) and so…

    Maybe I’m searching for something not necessary/obvious…

  5. There is no python api documentation.
    You have to follow the general api link and … trial and error.

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s