3
# Copyright (C) 2011 Tim Marston <edam@waxworlds.org>
5
# This file is part of add-copyright-to-images (hereafter referred to as "this
7
# See http://www.waxworlds.org/edam/software/add-copyright-to-images for more
10
# This program is free software: you can redistribute it and/or modify
11
# it under the terms of the GNU Lesser General Public License as published
12
# by the Free Software Foundation, either version 3 of the License, or
13
# (at your option) any later version.
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
# GNU Lesser General Public License for more details.
20
# You should have received a copy of the GNU Lesser General Public License
21
# along with this program. If not, see <http://www.gnu.org/licenses/>.
28
pygtk.require( '2.0' );
30
from subprocess import call
31
from .preferences import *
32
from .prefs_dialog import *
37
DRAWING_AREA_INIT_WIDTH = 550
38
DRAWING_AREA_INIT_HEIGHT = 500
39
IMAGEMAGICK_CONVERT_BIN = '/usr/bin/convert'
41
def __init__( self, filename ):
42
self.filename = filename
45
self.prefs = Preferences()
47
# load main image and reset copyright image state to unloaded
48
self.load_main_image()
49
self.copyright_pic = None
52
self.pos = "bottom-left";
54
# work out the initial size of the drawing area so that the greatest of
55
# the two dimensions is 450px
57
float( self.DRAWING_AREA_INIT_WIDTH ) / self.pic.get_width(), \
58
float( self.DRAWING_AREA_INIT_HEIGHT ) / self.pic.get_height() )
59
init_width = int( round( scale * self.pic.get_width() ) )
60
init_height = int( round( scale * self.pic.get_height() ) )
63
self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )
64
self.window.connect( "delete_event", self.delete_event )
65
self.window.connect( "destroy", self.destroy )
66
self.window.connect( "key-press-event", self.key_press_event )
67
self.window.set_title( "Add Copyright to Images" );
68
self.window.set_border_width( 10 )
69
self.window.set_position( gtk.WIN_POS_CENTER )
70
vbox = gtk.VBox( False, 10 )
71
self.window.add( vbox )
74
self.drawing_area = gtk.DrawingArea()
75
self.drawing_area.set_size_request( init_width, init_height )
76
self.drawing_area.add_events( gtk.gdk.POINTER_MOTION_MASK | \
77
gtk.gdk.POINTER_MOTION_HINT_MASK | \
78
gtk.gdk.BUTTON_PRESS_MASK )
79
self.drawing_area.connect( "configure_event", \
80
self.drawing_area_configure_event )
81
self.drawing_area.connect( "expose_event", \
82
self.drawing_area_expose_event )
83
self.drawing_area.connect( "motion_notify_event", \
84
self.motion_notify_event )
85
self.drawing_area.connect( "button_press_event", \
86
self.drawing_area_button_press_event )
87
vbox.pack_start( self.drawing_area, True, True, 0 )
90
hbox = gtk.HBox( False, 10 )
91
vbox.pack_end( hbox, False, False, 0 )
92
left_button_box = gtk.HButtonBox()
93
left_button_box.set_layout( gtk.BUTTONBOX_START )
94
hbox.pack_start( left_button_box, True, True, 0 )
95
button_box = gtk.HButtonBox()
96
button_box.set_layout( gtk.BUTTONBOX_END )
97
button_box.set_spacing( 10 )
98
hbox.pack_end( button_box, True, True, 0 )
99
prefs_button = gtk.Button( stock = gtk.STOCK_PREFERENCES )
100
prefs_button.connect( "clicked", self.prefs_button_pressed )
101
left_button_box.pack_start( prefs_button, True, True, 0 )
102
cancel_button = gtk.Button( stock = gtk.STOCK_CANCEL )
103
cancel_button.connect_object( "clicked", gtk.Widget.destroy, \
105
button_box.pack_end( cancel_button, True, True, 0 )
106
self.ok_button = gtk.Button( stock = gtk.STOCK_APPLY )
107
self.ok_button.connect( "clicked", self.ok_button_pressed )
108
button_box.pack_end( self.ok_button, True, True, 0 )
109
self.ok_button.set_flags( gtk.CAN_DEFAULT )
110
self.ok_button.grab_default()
111
self.ok_button.grab_focus()
114
self.window.show_all()
116
# load copyright image
117
self.load_copyright_image()
119
def load_main_image( self ):
122
self.pic = gtk.gdk.pixbuf_new_from_file( self.filename )
123
except gobject.GError as e:
124
dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
125
gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, e.message )
131
if self.prefs.resize:
133
float( self.prefs.resize_width ) / self.pic.get_width(), \
134
float( self.prefs.resize_height ) / self.pic.get_height() )
135
self.pic = self.pic.scale_simple( \
136
int( round( scale * self.pic.get_width() ) ), \
137
int( round( scale * self.pic.get_height() ) ), \
138
gtk.gdk.INTERP_BILINEAR )
140
def load_copyright_image( self ):
143
self.copyright_pic = None
144
self.ok_button.set_sensitive( False )
146
# check if we've got a copyright filename set
147
if self.prefs.copyright_filename == '':
148
dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL, \
149
gtk.MESSAGE_INFO, gtk.BUTTONS_OK, \
150
"No copyright image has been set. " + \
151
"Please set one in the Preferences." )
152
dialog.format_secondary_text( "While this issue persists, you " + \
153
"can not proceed with this operation" )
158
# load copyright overlay image file
160
self.copyright_pic = gtk.gdk.pixbuf_new_from_file( \
161
os.path.expanduser( self.prefs.copyright_filename ) )
162
except gobject.GError as e:
163
dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
164
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, \
165
"Unable to load copyright overlay image: " + \
167
dialog.format_secondary_text( "While this issue persists, you " + \
168
"can not proceed with this operation" )
173
# check the copyright overlay pic is smaller than the main pic
174
if self.copyright_pic.get_width() > self.pic.get_width() or \
175
self.copyright_pic.get_height() > self.pic.get_height():
176
dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
177
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, \
178
"The copyright overlay image is larger than the photo!" )
179
dialog.format_secondary_text( "While this issue persists, you " + \
180
"can not proceed with this operation" )
183
self.copyright_pic = None
186
# enable the "ok" button and reconfigure the drawing area to generate
188
self.ok_button.set_sensitive( True )
189
self.drawing_area.queue_resize()
194
def ok_button_pressed( self, widget ):
196
# calculate output filename
197
pos = self.filename.rindex( '.' )
198
if self.prefs.export_inplace:
201
append = self.prefs.export_append
202
out_filename = self.filename[ : self.filename.rindex( '.' ) ] + \
203
append + self.filename[ self.filename.rindex( '.' ) : ]
206
top = 0 if self.pos.find( 'top' ) != -1 else \
207
self.pic.get_height() - self.copyright_pic.get_height()
208
left = 0 if self.pos.find( 'left' ) != -1 else \
209
self.pic.get_width() - self.copyright_pic.get_width()
211
infile = self.filename
213
# caluclate program args
214
resize_geometry = str( self.prefs.resize_width ) + "x" + \
215
str( self.prefs.resize_height )
216
copyright_geometry = str( self.copyright_pic.get_width() ) + \
217
"x" + str( self.copyright_pic.get_height() ) + "+" + \
218
str( left ) + "+" + str( top )
219
args = [ self.IMAGEMAGICK_CONVERT_BIN, \
220
os.path.expanduser( self.prefs.copyright_filename ), \
221
"-geometry", copyright_geometry, \
222
"-compose", "screen", "-composite", \
224
if( self.prefs.resize ):
225
args[ 1 : 1 ] = [ "(", "-resize", resize_geometry, \
228
args[ 1 : 1 ] = [ self.filename ]
230
# call ImageMagick to do our dirtywork
234
print >>sys.stderr, "ImageMagick failed, returned ", ret
237
print >>sys.stderr, "Failed to execute ImageMagick: ", e
240
if self.prefs.export_inplace:
241
# move original file out of the way
243
os.rename( self.filename, self.filename + ".old" )
245
print >>sys.stderr, "Could not rename original image file: ", \
249
# move new file in-place
251
os.rename( out_filename, self.filename )
253
# try and put the original file back again
255
os.rename( self.filename + ".old", self.filename )
258
print >>sys.stderr, e.strerror
263
os.unlink( self.filename + ".old" )
265
print >>sys.stderr, "Unable to remove original image file (it ", \
266
"now has '.old' appended to it's name): ", e.strerror
270
self.window.destroy()
272
def prefs_button_pressed( self, widget ):
273
PreferencesDialog( self.window ).main()
274
self.prefs = Preferences()
275
self.load_main_image()
276
self.load_copyright_image()
277
self.drawing_area.queue_resize()
278
self.drawing_area.queue_draw()
280
def key_press_event( self, widget, event ):
281
if event.keyval == gtk.keysyms.Escape:
282
self.window.destroy()
283
elif event.keyval == gtk.keysyms.Up:
284
self.move_pos( "bottom", "top" )
285
elif event.keyval == gtk.keysyms.Down:
286
self.move_pos( "top", "bottom" )
287
elif event.keyval == gtk.keysyms.Left:
288
self.move_pos( "right", "left" )
289
elif event.keyval == gtk.keysyms.Right:
290
self.move_pos( "left", "right" )
296
def delete_event( self, widget, event, data = None ):
299
def destroy( self, widget, data = None ):
302
def motion_notify_event( self, widget, event ):
303
x, y, width, height = widget.get_allocation()
307
x, y, state = event.window.get_pointer()
313
# calculate copyright overlay pos
314
pos = ( "top" if y <= height / 2 else "bottom" ) + "-" + \
315
( "left" if x <= width / 2 else "right" )
317
# redraw if necessary
318
if getattr( self, "pos", None ) != pos:
320
self.generate_pixmap()
325
def drawing_area_button_press_event( self, widget, event ):
326
if self.ok_button.get_property( "sensitive" ):
327
self.ok_button.clicked()
329
def drawing_area_configure_event( self, widget, event ):
330
x, y, width, height = widget.get_allocation()
332
# work out minimum scale required to fit pic_orig in to the widget area
333
scale = min( float( width ) / self.pic.get_width(),
334
float( height ) / self.pic.get_height() )
337
self.pic_scale = self.pic.scale_simple( \
338
int( round( scale * self.pic.get_width() ) ), \
339
int( round( scale * self.pic.get_height() ) ), \
340
gtk.gdk.INTERP_BILINEAR )
341
if self.copyright_pic != None:
342
self.copyright_pic_scale = self.copyright_pic.scale_simple( \
343
int( round( scale * self.copyright_pic.get_width() ) ), \
344
int( round( scale * self.copyright_pic.get_height() ) ), \
345
gtk.gdk.INTERP_BILINEAR )
347
# recreate the pixmap to render to the screen
348
self.generate_pixmap()
350
def generate_pixmap( self ):
351
widget = self.drawing_area
352
x, y, width, height = widget.get_allocation()
354
# calculate offsets to center pic in drawing area
355
pre_width = ( width - self.pic_scale.get_width() ) / 2
356
pre_height = ( height - self.pic_scale.get_height() ) / 2
357
post_width = width - self.pic_scale.get_width() - pre_width
358
post_height = height - self.pic_scale.get_height() - pre_height
361
self.pixmap = gtk.gdk.Pixmap( widget.window, width, height )
363
# create composite pixbuf containing the main image
364
pixbuf = self.pic_scale.copy()
366
if self.copyright_pic != None and \
367
getattr( self, "copyright_pic_scale", None ) != None:
369
# work out position of copyright overlay
370
if self.pos == "top-left" or self.pos == "bottom-left":
372
if self.pos == "top-right" or self.pos == "bottom-right":
373
x = self.pic_scale.get_width() - \
374
self.copyright_pic_scale.get_width()
375
if self.pos == "top-left" or self.pos == "top-right":
377
if self.pos == "bottom-left" or self.pos == "bottom-right":
378
y = self.pic_scale.get_height() - \
379
self.copyright_pic_scale.get_height()
382
self.copyright_pic_scale.composite( pixbuf, x, y, \
383
self.copyright_pic_scale.get_width(), \
384
self.copyright_pic_scale.get_height(), \
385
x, y, 1, 1, gtk.gdk.INTERP_NEAREST, 255 )
387
# draw composit pixbuf to the off-screen pixmap
388
self.pixmap.draw_pixbuf( widget.get_style().white_gc, \
389
pixbuf, 0, 0, pre_width, pre_height, \
390
pixbuf.get_width(), pixbuf.get_height() )
392
# draw black rectangles if the pic is being centered
393
if pre_width > 0 or pre_height > 0:
394
self.pixmap.draw_rectangle( widget.get_style().black_gc, True, \
396
pre_width if pre_width > 0 else width, \
397
pre_height if pre_height > 0 else height )
398
if post_width > 0 or post_height > 0:
399
self.pixmap.draw_rectangle( widget.get_style().black_gc, True, \
400
width - post_width if post_width > 0 else 0, \
401
height - post_height if post_height > 0 else 0, \
402
post_width if post_width > 0 else width, \
403
post_height if post_height > 0 else height )
405
def move_pos( self, tok_from, tok_to ):
406
# calculate copyright overlay pos
407
pos = self.pos.replace( tok_from, tok_to, 1 )
409
# redraw if necessary
412
self.generate_pixmap()
413
self.drawing_area.queue_draw()
415
def drawing_area_expose_event( self, widget, event ):
416
x, y, width, height = event.area
417
widget.window.draw_drawable( \
418
widget.get_style().fg_gc[ gtk.STATE_NORMAL ], \
419
self.pixmap, x, y, x, y, width, height )