/gtk/prep-images

To get this branch, use:
bzr branch http://bzr.ed.am/gtk/prep-images
1 by edam
- initial commit, includes project and build setup
1
# main_window.py
2
#
3
# Copyright (C) 2011 Tim Marston <edam@waxworlds.org>
4
#
3 by edam
- renamed project from add-copyright-to-images to prep-images
5
# This file is part of prep-images (hereafter referred to as "this program").
6
# See http://www.waxworlds.org/edam/software/gtk/prep-images for more
1 by edam
- initial commit, includes project and build setup
7
# information.
8
#
9
# This program is free software: you can redistribute it and/or modify
3 by edam
- renamed project from add-copyright-to-images to prep-images
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation, either version 3 of the License, or
1 by edam
- initial commit, includes project and build setup
12
# (at your option) any later version.
13
#
14
# This program is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
3 by edam
- renamed project from add-copyright-to-images to prep-images
17
# GNU General Public License for more details.
1 by edam
- initial commit, includes project and build setup
18
#
3 by edam
- renamed project from add-copyright-to-images to prep-images
19
# You should have received a copy of the GNU General Public License
1 by edam
- initial commit, includes project and build setup
20
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
22
23
import sys
24
import os
25
import gobject
26
import pygtk
27
pygtk.require( '2.0' );
28
import gtk
29
from subprocess import call
30
from .preferences import *
31
from .prefs_dialog import *
32
33
34
class MainWindow:
35
36
	DRAWING_AREA_INIT_WIDTH = 550
37
	DRAWING_AREA_INIT_HEIGHT = 500
38
	IMAGEMAGICK_CONVERT_BIN = '/usr/bin/convert'
39
40
	def __init__( self, filename ):
41
		self.filename = filename
42
43
		# load preferences
44
		self.prefs = Preferences()
45
46
		# load main image and reset copyright image state to unloaded
47
		self.load_main_image()
48
		self.copyright_pic = None
49
50
		# init pos
51
		self.pos = "bottom-left";
52
53
		# work out the initial size of the drawing area so that the greatest of
54
		# the two dimensions is 450px
55
		scale = min( \
3 by edam
- renamed project from add-copyright-to-images to prep-images
56
			float( self.DRAWING_AREA_INIT_WIDTH ) / self.pic.get_width(), \
57
			float( self.DRAWING_AREA_INIT_HEIGHT ) / self.pic.get_height() )
1 by edam
- initial commit, includes project and build setup
58
		init_width = int( round( scale * self.pic.get_width() ) )
59
		init_height = int( round( scale * self.pic.get_height() ) )
60
61
		# create window
62
		self.window = gtk.Window( gtk.WINDOW_TOPLEVEL )
63
		self.window.connect( "delete_event", self.delete_event )
64
		self.window.connect( "destroy", self.destroy )
65
		self.window.connect( "key-press-event", self.key_press_event )
66
		self.window.set_title( "Add Copyright to Images" );
67
		self.window.set_border_width( 10 )
68
		self.window.set_position( gtk.WIN_POS_CENTER )
69
		vbox = gtk.VBox( False, 10 )
70
		self.window.add( vbox )
71
72
		# create drawing area
73
		self.drawing_area = gtk.DrawingArea()
74
		self.drawing_area.set_size_request( init_width, init_height )
75
		self.drawing_area.add_events( gtk.gdk.POINTER_MOTION_MASK | \
3 by edam
- renamed project from add-copyright-to-images to prep-images
76
			gtk.gdk.POINTER_MOTION_HINT_MASK | \
77
			gtk.gdk.BUTTON_PRESS_MASK )
1 by edam
- initial commit, includes project and build setup
78
		self.drawing_area.connect( "configure_event", \
3 by edam
- renamed project from add-copyright-to-images to prep-images
79
			self.drawing_area_configure_event )
1 by edam
- initial commit, includes project and build setup
80
		self.drawing_area.connect( "expose_event", \
3 by edam
- renamed project from add-copyright-to-images to prep-images
81
			self.drawing_area_expose_event )
1 by edam
- initial commit, includes project and build setup
82
		self.drawing_area.connect( "motion_notify_event", \
3 by edam
- renamed project from add-copyright-to-images to prep-images
83
			self.motion_notify_event )
1 by edam
- initial commit, includes project and build setup
84
		self.drawing_area.connect( "button_press_event", \
3 by edam
- renamed project from add-copyright-to-images to prep-images
85
			self.drawing_area_button_press_event )
1 by edam
- initial commit, includes project and build setup
86
		vbox.pack_start( self.drawing_area, True, True, 0 )
87
88
		# create buttons
89
		hbox = gtk.HBox( False, 10 )
90
		vbox.pack_end( hbox, False, False, 0 )
91
		left_button_box = gtk.HButtonBox()
92
		left_button_box.set_layout( gtk.BUTTONBOX_START )
93
		hbox.pack_start( left_button_box, True, True, 0 )
94
		button_box = gtk.HButtonBox()
95
		button_box.set_layout( gtk.BUTTONBOX_END )
96
		button_box.set_spacing( 10 )
97
		hbox.pack_end( button_box, True, True, 0 )
98
		prefs_button = gtk.Button( stock = gtk.STOCK_PREFERENCES )
99
		prefs_button.connect( "clicked", self.prefs_button_pressed )
100
		left_button_box.pack_start( prefs_button, True, True, 0 )
101
		cancel_button = gtk.Button( stock = gtk.STOCK_CANCEL )
102
		cancel_button.connect_object( "clicked", gtk.Widget.destroy, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
103
			self.window )
1 by edam
- initial commit, includes project and build setup
104
		button_box.pack_end( cancel_button, True, True, 0 )
105
		self.ok_button = gtk.Button( stock = gtk.STOCK_APPLY )
106
		self.ok_button.connect( "clicked", self.ok_button_pressed )
107
		button_box.pack_end( self.ok_button, True, True, 0 )
108
		self.ok_button.set_flags( gtk.CAN_DEFAULT )
109
		self.ok_button.grab_default()
110
		self.ok_button.grab_focus()
111
112
		# show it all
113
		self.window.show_all()
114
115
		# load copyright image
116
		self.load_copyright_image()
117
118
	def load_main_image( self ):
3 by edam
- renamed project from add-copyright-to-images to prep-images
119
1 by edam
- initial commit, includes project and build setup
120
		# load image file
121
		try:
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, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
125
				gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, e.message )
1 by edam
- initial commit, includes project and build setup
126
			dialog.run()
127
			dialog.destroy()
128
			exit( 1 )
129
130
		# scale it
131
		if self.prefs.resize:
132
			scale = min( \
3 by edam
- renamed project from add-copyright-to-images to prep-images
133
				float( self.prefs.resize_width ) / self.pic.get_width(), \
134
				float( self.prefs.resize_height ) / self.pic.get_height() )
1 by edam
- initial commit, includes project and build setup
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 )
139
140
	def load_copyright_image( self ):
141
142
		# reset state
143
		self.copyright_pic = None
144
		self.ok_button.set_sensitive( False )
145
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, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
149
				gtk.MESSAGE_INFO, gtk.BUTTONS_OK, \
150
				"No copyright image has been set. " + \
151
				"Please set one in the Preferences." )
1 by edam
- initial commit, includes project and build setup
152
			dialog.format_secondary_text( "While this issue persists, you " + \
3 by edam
- renamed project from add-copyright-to-images to prep-images
153
				"can not proceed with this operation" )
1 by edam
- initial commit, includes project and build setup
154
			dialog.run()
155
			dialog.destroy()
156
			return
157
158
		# load copyright overlay image file
159
		try:
160
			self.copyright_pic = gtk.gdk.pixbuf_new_from_file( \
3 by edam
- renamed project from add-copyright-to-images to prep-images
161
				os.path.expanduser( self.prefs.copyright_filename ) )
1 by edam
- initial commit, includes project and build setup
162
		except gobject.GError as e:
163
			dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
164
				gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, \
165
				"Unable to load copyright overlay image: " + \
166
				e.message )
1 by edam
- initial commit, includes project and build setup
167
			dialog.format_secondary_text( "While this issue persists, you " + \
3 by edam
- renamed project from add-copyright-to-images to prep-images
168
				"can not proceed with this operation" )
1 by edam
- initial commit, includes project and build setup
169
			dialog.run()
170
			dialog.destroy()
171
			return
172
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, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
177
				gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, \
178
				"The copyright overlay image is larger than the photo!" )
1 by edam
- initial commit, includes project and build setup
179
			dialog.format_secondary_text( "While this issue persists, you " + \
180
					"can not proceed with this operation" )
181
			dialog.run()
182
			dialog.destroy()
183
			self.copyright_pic = None
184
			return
185
186
		# enable the "ok" button and reconfigure the drawing area to generate
187
		# scaled puixbufs
188
		self.ok_button.set_sensitive( True )
189
		self.drawing_area.queue_resize()
190
191
	def main( self ):
192
		gtk.main()
193
194
	def ok_button_pressed( self, widget ):
195
196
		# calculate output filename
197
		pos = self.filename.rindex( '.' )
198
		if self.prefs.export_inplace:
199
			append = ".tmp"
200
		else:
201
			append = self.prefs.export_append
202
		out_filename = self.filename[ : self.filename.rindex( '.' ) ] + \
3 by edam
- renamed project from add-copyright-to-images to prep-images
203
			append + self.filename[ self.filename.rindex( '.' ) : ]
1 by edam
- initial commit, includes project and build setup
204
205
		# calculate geometry
206
		top = 0 if self.pos.find( 'top' ) != -1 else \
3 by edam
- renamed project from add-copyright-to-images to prep-images
207
			self.pic.get_height() - self.copyright_pic.get_height()
1 by edam
- initial commit, includes project and build setup
208
		left = 0 if self.pos.find( 'left' ) != -1 else \
3 by edam
- renamed project from add-copyright-to-images to prep-images
209
			self.pic.get_width() - self.copyright_pic.get_width()
1 by edam
- initial commit, includes project and build setup
210
211
		infile = self.filename
212
3 by edam
- renamed project from add-copyright-to-images to prep-images
213
		# calculate program args
1 by edam
- initial commit, includes project and build setup
214
		resize_geometry = str( self.prefs.resize_width ) + "x" + \
3 by edam
- renamed project from add-copyright-to-images to prep-images
215
			str( self.prefs.resize_height )
1 by edam
- initial commit, includes project and build setup
216
		copyright_geometry = str( self.copyright_pic.get_width() ) + \
3 by edam
- renamed project from add-copyright-to-images to prep-images
217
			"x" + str( self.copyright_pic.get_height() ) + "+" + \
218
			str( left ) + "+" + str( top )
1 by edam
- initial commit, includes project and build setup
219
		args = [ self.IMAGEMAGICK_CONVERT_BIN, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
220
			os.path.expanduser( self.prefs.copyright_filename ), \
221
			"-geometry", copyright_geometry, \
6 by Tim Marston
fixed composit operator to handle light and dark images
222
			"-compose", "src-over", "-composite", \
3 by edam
- renamed project from add-copyright-to-images to prep-images
223
			out_filename ]
1 by edam
- initial commit, includes project and build setup
224
		if( self.prefs.resize ):
225
			args[ 1 : 1 ] = [ "(", "-resize", resize_geometry, \
4 by edam
- removed '.0' from the end of the version no.
226
				self.filename, "-unsharp", "1x3+.6+.09", ")" ]
1 by edam
- initial commit, includes project and build setup
227
		else:
228
			args[ 1 : 1 ] = [ self.filename ]
229
230
		# call ImageMagick to do our dirtywork
231
		try:
4 by edam
- removed '.0' from the end of the version no.
232
			print "calling: " + ' '.join( args )
1 by edam
- initial commit, includes project and build setup
233
			ret = call( args )
234
			if ret != 0:
235
				print >>sys.stderr, "ImageMagick failed, returned ", ret
236
				return
237
		except OSError as e:
238
			print >>sys.stderr, "Failed to execute ImageMagick: ", e
239
			return
240
241
		if self.prefs.export_inplace:
242
			# move original file out of the way
243
			try:
244
				os.rename( self.filename, self.filename + ".old" )
245
			except OSError as e:
246
				print >>sys.stderr, "Could not rename original image file: ", \
3 by edam
- renamed project from add-copyright-to-images to prep-images
247
					e.strerror
1 by edam
- initial commit, includes project and build setup
248
				exit( 1 )
249
250
			# move new file in-place
251
			try:
252
				os.rename( out_filename, self.filename )
253
			except OSError as e:
254
				# try and put the original file back again
255
				try:
256
					os.rename( self.filename + ".old", self.filename )
257
				except OSError:
258
					pass
259
				print >>sys.stderr, e.strerror
260
				exit( 1 )
261
262
			# delete original
263
			try:
264
				os.unlink( self.filename + ".old" )
265
			except OSError as e:
266
				print >>sys.stderr, "Unable to remove original image file (it ", \
267
					"now has '.old' appended to it's name): ", e.strerror
268
				exit( 1 )
269
270
		# close this window
271
		self.window.destroy()
272
273
	def prefs_button_pressed( self, widget ):
274
		PreferencesDialog( self.window ).main()
275
		self.prefs = Preferences()
276
		self.load_main_image()
277
		self.load_copyright_image()
278
		self.drawing_area.queue_resize()
279
		self.drawing_area.queue_draw()
280
281
	def key_press_event( self, widget, event ):
282
		if event.keyval == gtk.keysyms.Escape:
283
			self.window.destroy()
284
		elif event.keyval == gtk.keysyms.Up:
285
			self.move_pos( "bottom", "top" )
286
		elif event.keyval == gtk.keysyms.Down:
287
			self.move_pos( "top", "bottom" )
288
		elif event.keyval == gtk.keysyms.Left:
289
			self.move_pos( "right", "left" )
290
		elif event.keyval == gtk.keysyms.Right:
291
			self.move_pos( "left", "right" )
292
		else:
293
			return False
294
295
		return True
296
297
	def delete_event( self, widget, event, data = None ):
298
		return False
299
300
	def destroy( self, widget, data = None ):
301
		gtk.main_quit()
302
303
	def motion_notify_event( self, widget, event ):
304
		x, y, width, height = widget.get_allocation()
305
306
		# get mouse position
307
		if event.is_hint:
308
			x, y, state = event.window.get_pointer()
309
		else:
310
			x = event.x
311
			y = event.y
312
			state = event.state
313
314
		# calculate copyright overlay pos
3 by edam
- renamed project from add-copyright-to-images to prep-images
315
		pos = ( "top" if y <= height / 2 else "bottom" ) + "-" + \
316
			( "left" if x <= width / 2 else "right" )
1 by edam
- initial commit, includes project and build setup
317
318
		# redraw if necessary
319
		if getattr( self, "pos", None ) != pos:
320
			self.pos = pos
321
			self.generate_pixmap()
322
			widget.queue_draw()
323
324
		return True
325
326
	def drawing_area_button_press_event( self, widget, event ):
327
		if self.ok_button.get_property( "sensitive" ):
328
			self.ok_button.clicked()
329
330
	def drawing_area_configure_event( self, widget, event ):
331
		x, y, width, height = widget.get_allocation()
332
333
		# work out minimum scale required to fit pic_orig in to the widget area
334
		scale = min( float( width ) / self.pic.get_width(),
3 by edam
- renamed project from add-copyright-to-images to prep-images
335
			float( height ) / self.pic.get_height() )
1 by edam
- initial commit, includes project and build setup
336
337
		# scale the images
338
		self.pic_scale = self.pic.scale_simple( \
3 by edam
- renamed project from add-copyright-to-images to prep-images
339
			int( round( scale * self.pic.get_width() ) ), \
340
			int( round( scale * self.pic.get_height() ) ), \
341
			gtk.gdk.INTERP_BILINEAR )
1 by edam
- initial commit, includes project and build setup
342
		if self.copyright_pic != None:
343
			self.copyright_pic_scale = self.copyright_pic.scale_simple( \
3 by edam
- renamed project from add-copyright-to-images to prep-images
344
				int( round( scale * self.copyright_pic.get_width() ) ), \
345
				int( round( scale * self.copyright_pic.get_height() ) ), \
346
				gtk.gdk.INTERP_BILINEAR )
1 by edam
- initial commit, includes project and build setup
347
348
		# recreate the pixmap to render to the screen
349
		self.generate_pixmap()
350
351
	def generate_pixmap( self ):
352
		widget = self.drawing_area
353
		x, y, width, height = widget.get_allocation()
354
3 by edam
- renamed project from add-copyright-to-images to prep-images
355
		# calculate offsets to centre pic in drawing area
1 by edam
- initial commit, includes project and build setup
356
		pre_width = ( width - self.pic_scale.get_width() ) / 2
357
		pre_height = ( height - self.pic_scale.get_height() ) / 2
358
		post_width = width - self.pic_scale.get_width() - pre_width
359
		post_height = height - self.pic_scale.get_height() - pre_height
360
361
		# create pixmap
362
		self.pixmap = gtk.gdk.Pixmap( widget.window, width, height )
363
364
		# create composite pixbuf containing the main image
365
		pixbuf = self.pic_scale.copy()
366
367
		if self.copyright_pic != None and \
3 by edam
- renamed project from add-copyright-to-images to prep-images
368
			getattr( self, "copyright_pic_scale", None ) != None:
1 by edam
- initial commit, includes project and build setup
369
370
			# work out position of copyright overlay
371
			if self.pos == "top-left" or self.pos == "bottom-left":
372
				x = 0
373
			if self.pos == "top-right" or self.pos == "bottom-right":
374
				x = self.pic_scale.get_width() - \
3 by edam
- renamed project from add-copyright-to-images to prep-images
375
					self.copyright_pic_scale.get_width()
1 by edam
- initial commit, includes project and build setup
376
			if self.pos == "top-left" or self.pos == "top-right":
377
				y = 0
378
			if self.pos == "bottom-left" or self.pos == "bottom-right":
379
				y = self.pic_scale.get_height() - \
3 by edam
- renamed project from add-copyright-to-images to prep-images
380
					self.copyright_pic_scale.get_height()
1 by edam
- initial commit, includes project and build setup
381
382
			# draw copyright
383
			self.copyright_pic_scale.composite( pixbuf, x, y, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
384
				self.copyright_pic_scale.get_width(), \
385
				self.copyright_pic_scale.get_height(), \
386
				x, y, 1, 1, gtk.gdk.INTERP_NEAREST, 255 )
1 by edam
- initial commit, includes project and build setup
387
3 by edam
- renamed project from add-copyright-to-images to prep-images
388
		# draw composite pixbuf to the off-screen pixmap
1 by edam
- initial commit, includes project and build setup
389
		self.pixmap.draw_pixbuf( widget.get_style().white_gc, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
390
			pixbuf, 0, 0, pre_width, pre_height, \
391
			pixbuf.get_width(), pixbuf.get_height() )
1 by edam
- initial commit, includes project and build setup
392
3 by edam
- renamed project from add-copyright-to-images to prep-images
393
		# draw black rectangles if the pic is being centred
1 by edam
- initial commit, includes project and build setup
394
		if pre_width > 0 or pre_height > 0:
395
			self.pixmap.draw_rectangle( widget.get_style().black_gc, True, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
396
				0, 0, \
397
				pre_width if pre_width > 0 else width, \
398
				pre_height if pre_height > 0 else height )
1 by edam
- initial commit, includes project and build setup
399
		if post_width > 0 or post_height > 0:
400
			self.pixmap.draw_rectangle( widget.get_style().black_gc, True, \
3 by edam
- renamed project from add-copyright-to-images to prep-images
401
				width - post_width if post_width > 0 else 0, \
402
				height - post_height if post_height > 0 else 0, \
403
				post_width if post_width > 0 else width, \
404
				post_height if post_height > 0 else height )
1 by edam
- initial commit, includes project and build setup
405
406
	def move_pos( self, tok_from, tok_to ):
3 by edam
- renamed project from add-copyright-to-images to prep-images
407
1 by edam
- initial commit, includes project and build setup
408
		# calculate copyright overlay pos
409
		pos = self.pos.replace( tok_from, tok_to, 1 )
410
411
		# redraw if necessary
412
		if self.pos != pos:
413
			self.pos = pos
414
			self.generate_pixmap()
415
			self.drawing_area.queue_draw()
416
417
	def drawing_area_expose_event( self, widget, event ):
418
		x, y, width, height = event.area
419
		widget.window.draw_drawable( \
3 by edam
- renamed project from add-copyright-to-images to prep-images
420
			widget.get_style().fg_gc[ gtk.STATE_NORMAL ], \
421
			self.pixmap, x, y, x, y, width, height )
1 by edam
- initial commit, includes project and build setup
422
		return False