Skip to content

Commit

Permalink
Merge pull request #81 from ConradHughes/cjch_updating
Browse files Browse the repository at this point in the history
Updates of possible interest
  • Loading branch information
jepler authored Nov 5, 2020
2 parents 150facd + c7ff104 commit fb5ff13
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 67 deletions.
87 changes: 56 additions & 31 deletions cropgtk.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,29 @@ def escape(self, *args):
self.result = 0
self.loop.quit()

def save_and_stay(self, *args):
self.result = 2
self.loop.quit()

def close(self, *args):
self.result = -1
self.loop.quit()

def key(self, w, e):
if e.keyval == gdk.KEY_Escape: self.escape()
elif e.keyval == gdk.KEY_Return: self.done()
elif e.string and e.string in ',<': self.rotate_ccw()
elif e.string and e.string in '.>': self.rotate_cw()
elif e.string:
if e.string == 'n': self.escape()
elif e.string == 'q': self.close()
elif e.string == 's': self.save_and_stay()
elif e.string in ',<': self.rotate_ccw()
elif e.string in '.>': self.rotate_cw()
# Don't know whether other event handlers need it too, but if
# this doesn't return True (True prevents further handlers from
# being invoked), a return somehow double-triggers self.done(),
# skipping the next image in a multi-file invocation. Not clear
# what's going on, but this stops it.
return True

def image_set(self):
self.render()
Expand Down Expand Up @@ -238,6 +252,7 @@ def run(self):
drag = self.drag
task = self.task

prev_name = None
for image_name in self.image_names():
self['window1'].set_title(
_("%s - CropGTK") % os.path.basename(image_name))
Expand Down Expand Up @@ -268,51 +283,61 @@ def run(self):
while drag.rotation != rotation:
drag.rotate_ccw()
drag.scale = scale
self.set_busy(0)
v = self.drag.wait()
self.set_busy()
if v == -1: break # user closed app
if v == 0:
self.log("Skipped %s" % os.path.basename(image_name))
continue # user hit "next" / escape

target = self.output_name(image_name,image_type)
if not target:
self.log("Skipped %s" % os.path.basename(image_name))
continue # user hit "cancel" on save dialog

task.add(CropRequest(
image=image,
image_name=image_name,
corners=drag.get_corners(),
rotation=drag.rotation,
target=target,
))

v = 2
while v == 2:
self.set_busy(0)
v = self.drag.wait()
self.set_busy()
if v == -1: break # user closed app
if v == 0:
self.log("Skipped %s" % os.path.basename(image_name))
continue # user hit "next" / escape
if v == 2: # save but stick with this image
target = self.output_name(image_name,image_type,True,prev_name)
prev_name = target
else:
target = self.output_name(image_name,image_type)
if not target:
self.log("Skipped %s" % os.path.basename(image_name))
continue # user hit "cancel" on save dialog
task.add(CropRequest(
image=image,
image_name=image_name,
corners=drag.get_corners(),
rotation=drag.rotation,
target=target,
))
if v == -1: break # user closed app

def image_names(self):
if len(sys.argv) > 1:
for i in sys.argv[1:]: yield i
else:
c = filechooser.Chooser(self['window1'], _("Select images to crop"))
c = filechooser.Chooser(_("Select images to crop"), self['window1'])
while 1:
files = c.run()
if not files: break
for i in files: yield i

def output_name(self, image_name, image_type):
def output_name(self, image_name, image_type, chooser=False, prev_name=None):
image_name = os.path.abspath(image_name)
d = os.path.dirname(image_name)
i = os.path.basename(image_name)
j = os.path.splitext(i)[0]
if j.endswith('-crop'): j += os.path.splitext(i)[1]
else: j += "-crop" + os.path.splitext(i)[1]
if os.access(d, os.W_OK): return os.path.join(d, j)
if chooser and prev_name is not None:
d = os.path.dirname(prev_name)
j = os.path.basename(prev_name)
else:
d = os.path.dirname(image_name)
j = os.path.splitext(i)[0]
if j.endswith('-crop'): j += os.path.splitext(i)[1]
else: j += "-crop" + os.path.splitext(i)[1]
if os.access(d, os.W_OK) and not chooser: return os.path.join(d, j)
title = _('Save cropped version of %s') % i
if self.dirchooser is None:
self.dirchooser = filechooser.DirChooser(self['window1'], title)
self.dirchooser.set_current_folder(desktop_name())
self.dirchooser = filechooser.DirChooser(title, self['window1'])
else:
self.dirchooser.set_title(title)
self.dirchooser.set_current_folder(d if os.access(d, os.W_OK) else desktop_name())
self.dirchooser.set_current_name(j)
r = self.dirchooser.run()
if not r: return ''
Expand Down
33 changes: 5 additions & 28 deletions cropgui_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,8 @@ def get_cropspec(image, corners, rotation):
t, l, r, b = corners
w = r - l
h = b - t

# The coordinates passed to jpegtran are interpreted post-rotation.
# Non-whole blocks are already imperfectly rotated by being left on the
# side, so we need to subtract them
if image.format == "JPEG":
round_x, round_y = image_round(image)
orig_w, orig_h = image.size
if rotation in (8, 6):
orig_w, orig_h = orig_h, orig_w
round_x, round_y = round_y, round_x
if rotation in (3, 8):
t -= orig_h % round_y
if rotation in (3, 6):
l -= orig_w % round_x
assert t >= 0, "t < 0 should be handled in fix(): {}".format(t)
assert l >= 0, "l < 0 should be handled in fix(): {}".format(l)

# Technically this should produce perfect crops, but jpegtran is broken
# and so mistakenly rounds out fractional crops on flipped images. Sigh.
return "%dx%d+%d+%d" % (w, h, l, t)


Expand Down Expand Up @@ -209,7 +194,6 @@ def apply_rotation(self, image):

def image_or_rotation_changed(self):
self._image = image = self.apply_rotation(self._orig_image)
self.apply_rotation(image)
self.top, self.bottom = self.fix(0, self.h, self.h, self.round_y, self.rotation in (3, 8))
self.left, self.right = self.fix(0, self.w, self.w, self.round_x, self.rotation in (3, 6))
blurred = image.copy()
Expand All @@ -227,18 +211,11 @@ def fix(self, a, b, lim, r, reverse):
r: rounding size
reverse: True to treat the upper bound as the origin
"""
a, b = sorted((int(a), int(b)))
if reverse:
offset = lim % r
a = lim - ((((lim - a) + r - 1) // r) * r)
else:
offset = 0
a, b = sorted((int(a), int(b)))
a = ((a - offset) // r) * r + offset
b = ((b - offset + r - 1) // r) * r + offset
# jpegtran handles non-whole blocks by leaving them on the edge of the
# image, away from the rotated position of their old neighbors.
# Keeping them isn't useful, so clamp them off.
a = clamp(a, offset if reverse else 0, lim)
b = clamp(b, offset if reverse else 0, lim)
a = (a // r) * r
return a, b

def get_corners(self):
Expand Down
8 changes: 6 additions & 2 deletions filechooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def update_preview_cb(file_chooser, preview):

class BaseChooser:
def __init__(self, title, parent):
# Gnome's "attach-modal-dialogs" can be set to false using
# gnome-tweak-tool in order to enable movable modal dialogs. No idea
# how to do that app-specifically though.
self.dialog = dialog = \
gtk.FileChooserDialog(title, parent, self.mode, self.buttons)

Expand All @@ -92,7 +95,7 @@ class Chooser(BaseChooser):
gtk.STOCK_OPEN, gtk.ResponseType.OK)

def __init__(self, title, parent):
BaseChooser.__init__(self, parent, title)
BaseChooser.__init__(self, title, parent)

self.dialog.set_default_response(gtk.ResponseType.OK)
self.dialog.set_select_multiple(True)
Expand Down Expand Up @@ -132,8 +135,9 @@ class DirChooser(BaseChooser):
gtk.STOCK_SAVE, gtk.ResponseType.OK)

def __init__(self, title, parent):
BaseChooser.__init__(self, parent, title)
BaseChooser.__init__(self, title, parent)
self.dialog.set_default_response(gtk.ResponseType.OK)
self.dialog.set_do_overwrite_confirmation(True)

def set_current_name(self, filename):
self.dialog.set_current_name(filename)
Expand Down
15 changes: 15 additions & 0 deletions generate_test_images.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh -e

# Generate some chessboard JPGs for testing with.

dir=test
[ -d $dir ] || mkdir $dir

convert -size 100x100 pattern:gray50 -scale 1600% -sampling-factor 2x2 \
-crop 796x396+0+0 $dir/chess-2x2.jpg
convert -size 100x100 pattern:gray50 -scale 800%x1600% -sampling-factor 1x2 \
-crop 796x396+0+0 $dir/chess-1x2.jpg
convert -size 100x100 pattern:gray50 -scale 1600%x800% -sampling-factor 2x1 \
-crop 796x396+0+0 $dir/chess-2x1.jpg
convert -size 100x100 pattern:gray50 -scale 800% -sampling-factor 1x1 \
-crop 796x396+0+0 $dir/chess-1x1.jpg
12 changes: 6 additions & 6 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,21 @@ if [ -z "$FLAVOR" ]; then FLAVOR=`default_flavor`; fi
mkdir -p $TARGET$BINDIR $TARGET$LIBDIR $TARGET$SHAREDIR/applications \
$TARGET$SHAREDIR/pixmaps

cp cropgui.desktop $TARGET$SHAREDIR/applications
cp cropgui.png $TARGET$SHAREDIR/pixmaps
install --mode=644 cropgui.desktop $TARGET$SHAREDIR/applications
install --mode=644 cropgui.png $TARGET$SHAREDIR/pixmaps

case $FLAVOR in
gtk)
echo "Installing gtk version of cropgui"
cp cropgtk.py $TARGET$BINDIR/cropgui && \
cp cropgui_common.py filechooser.py cropgui.glade \
install --mode=755 cropgtk.py $TARGET$BINDIR/cropgui && \
install --mode=644 cropgui_common.py filechooser.py cropgui.glade \
stock-rotate-90-16.png stock-rotate-270-16.png \
$TARGET$LIBDIR
;;
tk)
echo "Installing tkinter version of cropgui"
cp cropgui.py $TARGET$BINDIR/cropgui && \
cp log.py cropgui_common.py $TARGET$LIBDIR
install --mode=755 cropgui.py $TARGET$BINDIR/cropgui && \
install --mode=644 log.py cropgui_common.py $TARGET$LIBDIR
;;
*)
echo "Unknown flavor $FLAVOR"
Expand Down

0 comments on commit fb5ff13

Please sign in to comment.