summaryrefslogtreecommitdiff
path: root/wpcreator/cropper.py
diff options
context:
space:
mode:
Diffstat (limited to 'wpcreator/cropper.py')
-rw-r--r--wpcreator/cropper.py229
1 files changed, 229 insertions, 0 deletions
diff --git a/wpcreator/cropper.py b/wpcreator/cropper.py
new file mode 100644
index 0000000..5a54d5e
--- /dev/null
+++ b/wpcreator/cropper.py
@@ -0,0 +1,229 @@
+import pygtk
+pygtk.require ("2.0")
+import gtk
+import cairo
+
+from PIL import Image
+
+CROP_MOVE = 0
+CROP_RESIZE_TL = 1
+CROP_RESIZE_TR = 2
+CROP_RESIZE_BL = 3
+CROP_RESIZE_BR = 4
+
+class CropperUi (gtk.DrawingArea):
+
+ path = None
+ pixbuf = None
+
+ crop_x = 0
+ crop_y = 0
+ crop_width = 0
+ crop_height = 0
+ ratio = 1.
+
+ prev_x = 0
+ prev_y = 0
+
+ full_region = None
+ crop_region = None
+ tl_region = None
+ tr_region = None
+ bl_region = None
+ br_region = None
+
+ move_cursor = None
+ resize_tl_cursor = None
+ resize_tr_cursor = None
+ resize_bl_cursor = None
+ resize_br_cursor = None
+
+ tl_br_vector = None
+ tr_bl_vector = None
+
+ target_width = None
+ target_height = None
+
+ mode = None
+
+ def __init__ (self, path, target_width, target_height):
+ super (CropperUi, self).__init__ ()
+ self.path = path
+ self.target_width = target_width
+ self.target_height = target_height
+ self.target_ratio = float (target_width) / target_height
+ self.pixbuf = gtk.gdk.pixbuf_new_from_file (path)
+ self.add_events (gtk.gdk.BUTTON_PRESS_MASK \
+ | gtk.gdk.BUTTON_RELEASE_MASK
+ | gtk.gdk.POINTER_MOTION_MASK)
+ self.connect ("expose-event", self.on_expose)
+ self.connect ("motion-notify-event", self.on_motion_notify)
+ self.connect ("button-press-event", self.on_button_press)
+ self.connect ("button-release-event", self.on_button_release)
+ self.connect_after ("size-allocate", self.on_size_allocate)
+ self.ratio = 1.
+ self.crop_x = 0
+ self.crop_y = 0
+ pixbuf_w, pixbuf_h = self.pixbuf.get_width (), self.pixbuf.get_height ()
+ pixbuf_ratio = float (pixbuf_w) / pixbuf_h
+ # If there is more width than needed, use full height
+ if pixbuf_ratio > self.target_ratio:
+ self.crop_width = int (self.target_ratio * pixbuf_h)
+ self.crop_height = pixbuf_h
+ self.crop_x = (pixbuf_w - self.crop_width) / 2
+ else: # Otherwise, use full width
+ self.crop_width = pixbuf_w
+ self.crop_height = int (float (pixbuf_w) / self.target_ratio)
+ self.crop_y = (pixbuf_h - self.crop_height) / 2
+ self.recompute_vectors ()
+ self.mode = None
+ self.move_cursor = gtk.gdk.Cursor (gtk.gdk.FLEUR)
+ self.resize_tl_cursor = gtk.gdk.Cursor (gtk.gdk.TOP_LEFT_CORNER)
+ self.resize_tr_cursor = gtk.gdk.Cursor (gtk.gdk.TOP_RIGHT_CORNER)
+ self.resize_bl_cursor = gtk.gdk.Cursor (gtk.gdk.BOTTOM_LEFT_CORNER)
+ self.resize_br_cursor = gtk.gdk.Cursor (gtk.gdk.BOTTOM_RIGHT_CORNER)
+
+ def recompute_regions (self):
+ pixbuf_w, pixbuf_h = self.pixbuf.get_width (), self.pixbuf.get_height ()
+ full_rectangle = gtk.gdk.Rectangle (0, 0, pixbuf_w, pixbuf_h)
+ self.full_region = gtk.gdk.region_rectangle (full_rectangle)
+ crop_rectangle = gtk.gdk.Rectangle (self.crop_x, self.crop_y,
+ self.crop_width, self.crop_height)
+ self.crop_region = gtk.gdk.region_rectangle (crop_rectangle)
+ corner_side = int (float (20) / self.ratio)
+ base_corner_rectangle = gtk.gdk.Rectangle (0, 0,
+ corner_side, corner_side)
+ base_corner = gtk.gdk.region_rectangle (base_corner_rectangle)
+ crop_x2 = self.crop_x + self.crop_width - corner_side
+ crop_y2 = self.crop_y + self.crop_height - corner_side
+ self.tl_region = base_corner.copy ()
+ self.tl_region.offset (self.crop_x, self.crop_y)
+ self.tr_region = base_corner.copy ()
+ self.tr_region.offset (crop_x2, self.crop_y)
+ self.bl_region = base_corner.copy ()
+ self.bl_region.offset (self.crop_x, crop_y2)
+ self.br_region = base_corner.copy ()
+ self.br_region.offset (crop_x2, crop_y2)
+
+ def recompute_vectors (self):
+ x0, y0 = self.crop_x, self.crop_y
+ x1, y1 = self.crop_x + self.crop_width, self.crop_y + self.crop_height
+ self.tl_br_vector = (x1 - x0, y1 - y0, x0, y0)
+ self.tr_bl_vector = (x0 - x1, y1 - y0, x1, y0)
+
+ def get_proj (self, (p_x, p_y), (v_x, v_y, v0_x, v0_y)):
+ k = (p_x - v0_x) * v_x + (p_y - v0_y) * v_y
+ n_v_2 = v_x * v_x + v_y * v_y
+ k2 = float (k) / n_v_2
+ return int (v0_x + k2 * v_x), int (v0_y + k2 * v_y)
+
+ def on_size_allocate (self, widget, alloc):
+ pixbuf_w, pixbuf_h = self.pixbuf.get_width (), self.pixbuf.get_height ()
+ _, _, w, h = self.get_allocation ()
+ w_ratio = float (w) / float (pixbuf_w)
+ h_ratio = float (h) / float (pixbuf_h)
+ self.ratio = min (w_ratio, h_ratio)
+ self.recompute_regions ()
+ self.queue_draw ()
+
+ def on_expose (self, widget, event):
+ cr = self.window.cairo_create ()
+ cr.set_operator (cairo.OPERATOR_OVER)
+ cr.save ()
+ cr.scale (self.ratio, self.ratio)
+ bg_color = self.get_style ().fg[self.get_state ()]
+ cr.set_source_rgb (bg_color.red_float,
+ bg_color.green_float,
+ bg_color.blue_float)
+ cr.paint ()
+ cr.set_source_pixbuf (self.pixbuf, 0, 0)
+ cr.paint ()
+ cr.set_source_rgba (1, 1, 1, 0.5)
+ cr.paint ()
+ cr.rectangle (self.crop_x, self.crop_y,
+ self.crop_width, self.crop_height)
+ cr.save ()
+ cr.clip_preserve ()
+ cr.set_source_pixbuf (self.pixbuf, 0, 0)
+ cr.paint ()
+ cr.restore ()
+ cr.set_source_rgb (0.2, 0.2, 0.2)
+ cr.set_dash ([3, 3])
+ cr.set_line_width (int (float (1) / self.ratio))
+ cr.stroke_preserve ()
+ cr.restore ()
+
+ def scale_event_coords (self, event):
+ x, y = float (event.x) / self.ratio, float (event.y) / self.ratio
+ return int (x), int (y)
+
+ def on_button_press (self, widget, event):
+ x, y = self.scale_event_coords (event)
+ if self.tl_region.point_in (x, y):
+ self.mode = CROP_RESIZE_TL
+ self.window.set_cursor (self.resize_tl_cursor)
+ elif self.tr_region.point_in (x, y):
+ self.mode = CROP_RESIZE_TR
+ self.window.set_cursor (self.resize_tr_cursor)
+ elif self.bl_region.point_in (x, y):
+ self.mode = CROP_RESIZE_BL
+ self.window.set_cursor (self.resize_bl_cursor)
+ elif self.br_region.point_in (x, y):
+ self.mode = CROP_RESIZE_BR
+ self.window.set_cursor (self.resize_br_cursor)
+ elif self.crop_region.point_in (x, y):
+ self.mode = CROP_MOVE
+ self.window.set_cursor (self.move_cursor)
+ self.prev_x = x
+ self.prev_y = y
+
+ def on_button_release (self, widget, event):
+ if self.window:
+ self.window.set_cursor (None)
+ self.recompute_regions ()
+ self.recompute_vectors ()
+ self.mode = None
+
+ def on_motion_notify (self, widget, event):
+ if self.mode == None:
+ return
+ x, y = self.scale_event_coords (event)
+ dx = x - self.prev_x
+ dy = y - self.prev_y
+ self.prev_x = x
+ self.prev_y = y
+ x0, y0 = self.crop_x, self.crop_y
+ x1, y1 = x0 + self.crop_width, y0 + self.crop_height
+ if self.mode == CROP_MOVE:
+ rect = gtk.gdk.Rectangle (self.crop_x + dx, self.crop_y + dy,
+ self.crop_width, self.crop_height)
+ if self.full_region.rect_in (rect) == gtk.gdk.OVERLAP_RECTANGLE_IN:
+ self.crop_x += dx
+ self.crop_y += dy
+ elif self.mode == CROP_RESIZE_TL:
+ x0, y0 = self.get_proj ((x, y), self.tl_br_vector)
+ elif self.mode == CROP_RESIZE_TR:
+ x1, y0 = self.get_proj ((x, y), self.tr_bl_vector)
+ elif self.mode == CROP_RESIZE_BL:
+ x0, y1 = self.get_proj ((x, y), self.tr_bl_vector)
+ elif self.mode == CROP_RESIZE_BR:
+ x1, y1 = self.get_proj ((x, y), self.tl_br_vector)
+ if self.mode != CROP_MOVE:
+ rect = gtk.gdk.Rectangle (x0, y0, x1 - x0, y1 - y0)
+ if self.full_region.rect_in (rect) == gtk.gdk.OVERLAP_RECTANGLE_IN:
+ self.crop_x = x0
+ self.crop_y = y0
+ self.crop_width = x1 - x0
+ self.crop_height = y1 - y0
+
+ self.queue_draw ()
+
+ def get_cropped_image (self):
+ image = Image.open (open (self.path))
+ x0, y0 = self.crop_x, self.crop_y
+ x1 = self.crop_x + self.crop_width
+ y1 = int (self.crop_y + float (self.crop_width) / self.target_ratio)
+ image = image.crop ((x0, y0, x1, y1))
+ image = image.resize ((self.target_width, self.target_height),
+ Image.ANTIALIAS)
+ return image