gtk version of cropgui

This commit is contained in:
Jeff Epler 2009-07-23 10:52:05 -05:00
parent 5e1082b8f7
commit c823b5f992
4 changed files with 321 additions and 4 deletions

230
cropgtk.py Executable file
View file

@ -0,0 +1,230 @@
#!/usr/bin/python
# cropgui, a graphical front-end for lossless jpeg cropping
# Copyright (C) 2009 Jeff Epler <jepler@unpythonic.net>
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from cropgui_common import *
from cropgui_common import _
import gobject
import gtk
import gtk.glade
import filechooser
import sys
import traceback
# otherwise, on hardy the user is shown spurious "[application] closed
# unexpectedly" messages but denied the ability to actually "report [the]
# problem"
def excepthook(exc_type, exc_obj, exc_tb):
try:
w = app['window1']
except NameError:
w = None
lines = traceback.format_exception(exc_type, exc_obj, exc_tb)
m = gtk.MessageDialog(w,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
_("Stepconf encountered an error. The following "
"information may be useful in troubleshooting:\n\n")
+ "".join(lines))
m.show()
m.run()
m.destroy()
sys.excepthook = excepthook
import cropgui_common
gladefile = os.path.join(os.path.dirname(cropgui_common.__file__),
"cropgui.glade")
class DragManager(DragManagerBase):
def __init__(self, g):
self.g = g
self.idle = None
DragManagerBase.__init__(self)
w = g['window1']
i = g['eventbox1']
i.connect('button-press-event', self.press)
i.connect('motion-notify-event', self.motion)
i.connect('button-release-event', self.release)
w.connect('delete-event', self.close)
w.connect('key-press-event', self.key)
g['toolbutton1'].connect('clicked', self.done)
g['toolbutton2'].connect('clicked', self.escape)
def coords(self, event):
return event.x, event.y
def press(self, w, event):
if event.type == gtk.gdk._2BUTTON_PRESS:
return self.done()
x, y = self.coords(event)
self.drag_start(x, y, event.state & gtk.gdk.SHIFT_MASK)
def motion(self, w, event):
x, y = self.coords(event)
self.drag_continue(x, y)
def release(self, w, event):
x, y = self.coords(event)
self.drag_end(x, y)
def done(self, *args):
self.result = 1
self.loop.quit()
def escape(self, *args):
self.result = 0
self.loop.quit()
def close(self, *args):
self.result = -1
self.loop.quit()
def key(self, w, e):
if e.keyval == gtk.keysyms.Escape: self.escape()
elif e.keyval == gtk.keysyms.Return: self.done()
def image_set(self):
self.render()
def render(self):
if self.idle is None:
self.idle = gobject.idle_add(self.do_render)
def do_render(self):
if not self.idle:
return
self.idle = None
g = self.g
i = g['image1']
if not i: # app shutting down
return
if self.image is None:
pixbuf = gtk.gdk.pixbuf_new_from_data('\0\0\0',
gtk.gdk.COLORSPACE_RGB, 0, 8, 1, 1, 3)
i.set_from_pixbuf(pixbuf)
#i.set_size_request(max_w, max_h)
g['pos_left'].set_text('---')
g['pos_right'].set_text('---')
g['pos_top'].set_text('---')
g['pos_bottom'].set_text('---')
g['pos_width'].set_text('---')
g['pos_height'].set_text('---')
g['pos_ratio'].set_text('---')
else:
rendered = self.rendered()
rendered = rendered.convert('RGB')
i.set_size_request(*rendered.size)
pixbuf = gtk.gdk.pixbuf_new_from_data(rendered.tostring(),
gtk.gdk.COLORSPACE_RGB, 0, 8,
rendered.size[0], rendered.size[1], 3*rendered.size[0])
ll, tt, rr, bb = self.get_corners()
ratio = self.describe_ratio()
g['pos_left'].set_text('%d' % ll)
g['pos_right'].set_text('%d' % rr)
g['pos_top'].set_text('%d' % tt)
g['pos_bottom'].set_text('%d' % bb)
g['pos_width'].set_text('%d' % (rr-ll))
g['pos_height'].set_text('%d' % (bb-tt))
g['pos_ratio'].set_text(self.describe_ratio())
i.set_from_pixbuf(pixbuf)
return False
def wait(self):
self.loop = gobject.MainLoop()
self.result = -1
self.loop.run()
max_h = gtk.gdk.screen_height() - 64 - 64
max_w = gtk.gdk.screen_width() - 64
class App:
def __init__(self):
self.glade = gtk.glade.XML(gladefile)
self.drag = DragManager(self)
self.task = CropTask(self)
def __getitem__(self, name):
return self.glade.get_widget(name)
def log(self, msg):
s = self['statusbar1']
if s:
s.pop(0)
s.push(0, msg)
progress = log
def set_busy(self, is_busy=True):
pass
def run(self):
drag = self.drag
task = self.task
for image_name in self.image_names():
self.set_busy()
i = Image.open(image_name)
iw, ih = i.size
scale = 1
while iw > max_w or ih > max_h:
iw /= 2
ih /= 2
scale *= 2
i.thumbnail((iw, ih))
drag.image = i
drag.round = max(1, 8/scale)
drag.scale = scale
self.set_busy(0)
v = self.drag.wait()
self.set_busy()
if v == -1: break # user closed app
if v == 0: continue # user hit "next" / escape
base, ext = os.path.splitext(image_name)
t, l, r, b = drag.top, drag.left, drag.right, drag.bottom
t *= scale
l *= scale
r *= scale
b *= scale
cropspec = "%dx%d+%d+%d" % (r-l, b-t, l, t)
target = base + "-crop" + ext
task.add(['nice', 'jpegtran', '-crop', cropspec, image_name], target)
def image_names(self):
if len(sys.argv) > 1:
return sys.argv[1:]
else:
return filechooser.prompt_open()
app = App()
try:
app.run()
finally:
app.task.done()
del app.task
del app.drag

8
cropgui.gladep Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-project SYSTEM "http://glade.gnome.org/glade-project-2.0.dtd">
<glade-project>
<name>Cropgui</name>
<program_name>cropgui</program_name>
<gnome_support>FALSE</gnome_support>
</glade-project>

79
filechooser.py Normal file
View file

@ -0,0 +1,79 @@
#!/usr/bin/env python
PREVIEW_SIZE = 300
import pygtk
pygtk.require('2.0')
import gtk
import gobject
import os
import Image
HIGH_WATER, LOW_WATER = 25, 5
image_cache = {}
def update_preview_cb(file_chooser, preview):
file_chooser.set_preview_widget_active(True)
filename = file_chooser.get_preview_filename()
if os.path.isdir(filename):
preview.set_from_stock(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_LARGE_TOOLBAR)
elif filename in image_cache:
preview.set_from_pixbuf(image_cache[filename])
else:
try:
i = Image.open(filename)
w, h = i.size
i.thumbnail((PREVIEW_SIZE, PREVIEW_SIZE), Image.ANTIALIAS)
i = i.convert('RGB')
pixbuf = gtk.gdk.pixbuf_new_from_data(i.tostring(),
gtk.gdk.COLORSPACE_RGB, 0, 8, i.size[0], i.size[1],
i.size[0]*3)
preview.set_from_pixbuf(pixbuf)
if len(image_cache) > HIGH_WATER:
while len(image_cache) > LOW_WATER:
image_cache.popitem()
image_cache[filename] = pixbuf
except:
preview.set_from_stock(gtk.STOCK_MISSING_IMAGE,
gtk.ICON_SIZE_LARGE_TOOLBAR)
raise
def prompt_open():
dialog = gtk.FileChooserDialog("Open..",
None,
gtk.FILE_CHOOSER_ACTION_OPEN,
(gtk.STOCK_QUIT, gtk.RESPONSE_CANCEL,
gtk.STOCK_OPEN, gtk.RESPONSE_OK))
dialog.set_default_response(gtk.RESPONSE_OK)
dialog.set_select_multiple(True)
preview = gtk.Image()
preview.set_size_request(PREVIEW_SIZE, PREVIEW_SIZE)
dialog.set_preview_widget(preview)
dialog.connect("update-preview", update_preview_cb, preview)
filter = gtk.FileFilter()
filter.set_name("JPEG Images")
filter.add_mime_type("image/jpeg")
filter.add_pattern("*.jpg")
filter.add_pattern("*.jpeg")
filter.add_pattern("*.JPG")
filter.add_pattern("*.JPEG")
dialog.add_filter(filter)
filter = gtk.FileFilter()
filter.set_name("All files")
filter.add_pattern("*")
dialog.add_filter(filter)
response = dialog.run()
if response == gtk.RESPONSE_OK:
result = dialog.get_filenames()
else:
result = []
dialog.destroy()
return result
if __name__ == '__main__':
print prompt_open()

View file

@ -1,8 +1,8 @@
#!/bin/sh
cp cropgui.py $HOME/bin/cropgui
cp log.py cropgui_common.py $HOME/lib/python
if ! python -c 'import log' 2>&1; then
echo "Failed to import log.py: add $HOME/lib/python to PYTHONPATH"
cp cropgtk.py $HOME/bin/cropgui
cp cropgui_common.py filechooser.py $HOME/lib/python
if ! python -c 'import filechooser' 2>&1; then
echo "Failed to import filechooser.py: add $HOME/lib/python to PYTHONPATH"
fi
chmod +x $HOME/bin/cropgui
# installation script for cropgui, a graphical front-end for lossless jpeg