# Copyright (C) 2008, One Laptop per Child # Author: Sayamindu Dasgupta # # 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 St, Fifth Floor, Boston, MA 02110-1301 USA import logging import cairo import math from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GObject ZOOM_STEP = 0.05 ZOOM_MAX = 4 ZOOM_MIN = 0.05 def _surface_from_file(file_location, ctx): pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_location) surface = ctx.get_target().create_similar( cairo.CONTENT_COLOR_ALPHA, pixbuf.get_width(), pixbuf.get_height()) ctx_surface = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(ctx_surface, pixbuf, 0, 0) ctx_surface.paint() return surface class ImageViewer(Gtk.DrawingArea): def __init__(self): Gtk.DrawingArea.__init__(self) self._file_location = None self._surface = None self._zoom = None self._angle = 0 self._target_point = None self._anchor_point = None self._zoomtouch_scale = 1 self.connect('draw', self.__draw_cb) def set_file_location(self, file_location): self._file_location = file_location self.queue_draw() def _center_surface(self): # Setting target point to None, the draw callback will center # the image surface. self._target_point = None def set_zoom(self, zoom): if zoom < ZOOM_MIN or zoom > ZOOM_MAX: return self._zoom = zoom self.queue_draw() def get_zoom(self): return self._zoom def can_zoom_in(self): return self._zoom + ZOOM_STEP < ZOOM_MAX def can_zoom_out(self): return self._zoom - ZOOM_STEP > ZOOM_MIN def zoom_in(self): if not self.can_zoom_in(): return self._zoom += ZOOM_STEP self.queue_draw() def zoom_out(self): if not self.can_zoom_out(): return self._zoom -= ZOOM_MIN self.queue_draw() def zoom_to_fit(self): # This tries to figure out a best fit model # If the image can fit in, we show it in 1:1, # in any other case we show it in a fit to screen way alloc = self.get_allocation() surface_width = self._surface.get_width() surface_height = self._surface.get_height() if alloc.width < surface_width or alloc.height < surface_height: # Image is larger than allocated size self._zoom = min(alloc.width * 1.0 / surface_width, alloc.height * 1.0 / surface_height) else: self._zoom = 1.0 self._center_surface() self.queue_draw() def zoom_original(self): self._zoom = 1 self.queue_draw() def start_zoomtouch(self, center): self._zoomtouch_scale = 1 # Set target point to the relative coordinates of this view. alloc = self.get_allocation() self._target_point = (center[1] - alloc.x, center[2] - alloc.y) self.queue_draw() def update_zoomtouch(self, center, scale): self._zoomtouch_scale = scale # Set target point to the relative coordinates of this view. alloc = self.get_allocation() self._target_point = (center[1] - alloc.x, center[2] - alloc.y) self.queue_draw() def finish_zoomtouch(self): # Apply zoom self._zoom = self._zoom * self._zoomtouch_scale self._zoomtouch_scale = 1 # Restrict zoom values if self._zoom < ZOOM_MIN: self._zoom = ZOOM_MIN elif self._zoom > ZOOM_MAX: self._zoom = ZOOM_MAX # If at the current size the image surface is smaller than the # available space, center it on the canvas. alloc = self.get_allocation() scaled_width = self._surface.get_width() * self._zoom scaled_height = self._surface.get_height() * self._zoom if alloc.width >= scaled_width and alloc.height >= scaled_height: self._center_surface() self.queue_draw() def rotate_anticlockwise(self): self._angle = self._angle - math.pi / 2 self.queue_draw() def rotate_clockwise(self): self._angle = self._angle + math.pi / 2 self.queue_draw() def __draw_cb(self, widget, ctx): alloc = self.get_allocation() # If the image surface is not set, it reads it from the file # location. If the file location is not set yet, it just # returns. if self._surface is None: if self._file_location is None: return self._surface = _surface_from_file(self._file_location, ctx) if self._zoom is None: self.zoom_to_fit() # FIXME investigate ctx.set_antialias(cairo.ANTIALIAS_NONE) # If no target point was set via pinch-to-zoom, default to the # center of the screen. if self._target_point is None: self._target_point = (alloc.width / 2, alloc.height / 2) # Scale and center the image according to the current zoom. zoom_absolute = self._zoom * self._zoomtouch_scale scaled_width = int(self._surface.get_width() * zoom_absolute) scaled_height = int(self._surface.get_height() * zoom_absolute) # If no anchor point was set via pinch-to-zoom, default to the # center of the surface. if self._anchor_point is None: self._anchor_point = (self._surface.get_width() / 2, self._surface.get_height() / 2) ctx.translate(*self._target_point) ctx.scale(zoom_absolute, zoom_absolute) if self._angle != 0: ctx.rotate(self._angle) ctx.translate(self._anchor_point[0] * -1, self._anchor_point[1] * -1) ctx.set_source_surface(self._surface, 0, 0) # FIXME investigate ctx.get_source().set_filter(cairo.FILTER_NEAREST) ctx.paint() if __name__ == '__main__': import sys window = Gtk.Window() window.connect("destroy", Gtk.main_quit) view = ImageViewer() view.set_file_location(sys.argv[1]) window.add(view) view.show() window.set_size_request(800, 600) window.show() Gtk.main()