summaryrefslogtreecommitdiff
path: root/wpcreator/cropper.py
blob: 5a54d5e9111033b310650154290d5c1d4829a9e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
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