diff --git a/imgsrc/resize.svg b/imgsrc/resize.svg
new file mode 100644
index 0000000000..1d33cd86c5
--- /dev/null
+++ b/imgsrc/resize.svg
@@ -0,0 +1,5299 @@
+
+
+
diff --git a/resources/images/resize.png b/resources/images/resize.png
new file mode 100644
index 0000000000..f2ae6afa65
Binary files /dev/null and b/resources/images/resize.png differ
diff --git a/src/calibre/gui2/tweak_book/editor/canvas.py b/src/calibre/gui2/tweak_book/editor/canvas.py
index de1d974e76..435920782f 100644
--- a/src/calibre/gui2/tweak_book/editor/canvas.py
+++ b/src/calibre/gui2/tweak_book/editor/canvas.py
@@ -10,7 +10,7 @@ import sys, string, weakref
from functools import wraps
from PyQt4.Qt import (
- QWidget, QPainter, QColor, QApplication, Qt, QPixmap, QRectF,
+ QWidget, QPainter, QColor, QApplication, Qt, QPixmap, QRectF, QMatrix,
QPointF, QPen, pyqtSignal, QUndoCommand, QUndoStack, QIcon, QImage)
from calibre import fit_image
@@ -89,6 +89,27 @@ class Trim(Command):
sr = canvas.selection_state.rect
return img.copy(*get_selection_rect(img, sr, target))
+class Rotate(Command):
+
+ def __init__(self, canvas):
+ Command.__init__(self, _('Rotate image'), canvas)
+
+ def __call__(self, canvas):
+ img = canvas.current_image
+ m = QMatrix()
+ m.rotate(90)
+ return img.transformed(m, Qt.SmoothTransformation)
+
+class Scale(Command):
+
+ def __init__(self, width, height, canvas):
+ self.width, self.height = width, height
+ Command.__init__(self, _('Resize image'), canvas)
+
+ def __call__(self, canvas):
+ img = canvas.current_image
+ return img.scaled(self.width, self.height, transformMode=Qt.SmoothTransformation)
+
class Replace(Command):
''' Replace the current image with another image. If there is a selection,
@@ -226,6 +247,16 @@ class Canvas(QWidget):
self.undo_stack.push(Trim(self))
return True
+ @imageop
+ def rotate_image(self):
+ self.undo_stack.push(Rotate(self))
+ return True
+
+ @imageop
+ def resize_image(self, width, height):
+ self.undo_stack.push(Scale(width, height, self))
+ return True
+
# The selection rectangle {{{
@property
def dc_size(self):
@@ -378,7 +409,6 @@ class Canvas(QWidget):
elif self.selection_state.current_mode == 'selected' and self.selection_state.rect is not None and self.selection_state.rect.contains(ev.pos()):
self.setCursor(self.get_cursor())
self.update()
- # }}}
def keyPressEvent(self, ev):
k = ev.key()
@@ -394,6 +424,7 @@ class Canvas(QWidget):
self.update()
else:
return QWidget.keyPressEvent(self, ev)
+ # }}}
# Painting {{{
@painter
diff --git a/src/calibre/gui2/tweak_book/editor/image.py b/src/calibre/gui2/tweak_book/editor/image.py
index 656f59bd45..c35143851b 100644
--- a/src/calibre/gui2/tweak_book/editor/image.py
+++ b/src/calibre/gui2/tweak_book/editor/image.py
@@ -7,14 +7,77 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal '
import sys
+from functools import partial
from PyQt4.Qt import (
- QMainWindow, Qt, QApplication, pyqtSignal)
+ QMainWindow, Qt, QApplication, pyqtSignal, QLabel, QIcon, QFormLayout,
+ QDialog, QSpinBox, QCheckBox, QDialogButtonBox)
from calibre.gui2 import error_dialog
from calibre.gui2.tweak_book import actions
from calibre.gui2.tweak_book.editor.canvas import Canvas
+class ResizeDialog(QDialog): # {{{
+
+ def __init__(self, width, height, parent=None):
+ QDialog.__init__(self, parent)
+ self.l = l = QFormLayout(self)
+ self.setLayout(l)
+ self.aspect_ratio = width / float(height)
+ l.addRow(QLabel(_('Choose the new width and height')))
+
+ self._width = w = QSpinBox(self)
+ w.setMinimum(1)
+ w.setMaximum(10 * width)
+ w.setValue(width)
+ w.setSuffix(' px')
+ l.addRow(_('&Width:'), w)
+
+ self._height = h = QSpinBox(self)
+ h.setMinimum(1)
+ h.setMaximum(10 * height)
+ h.setValue(height)
+ h.setSuffix(' px')
+ l.addRow(_('&Height:'), h)
+ w.valueChanged.connect(partial(self.keep_ar, 'width'))
+ h.valueChanged.connect(partial(self.keep_ar, 'height'))
+
+ self.ar = ar = QCheckBox(_('Keep &aspect ratio'))
+ ar.setChecked(True)
+ l.addRow(ar)
+ self.resize(self.sizeHint())
+
+ self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ bb.accepted.connect(self.accept)
+ bb.rejected.connect(self.reject)
+ l.addRow(bb)
+
+ def keep_ar(self, which):
+ if self.ar.isChecked():
+ val = getattr(self, which)
+ oval = val / self.aspect_ratio if which == 'width' else val * self.aspect_ratio
+ other = getattr(self, '_height' if which == 'width' else '_width')
+ other.blockSignals(True)
+ other.setValue(oval)
+ other.blockSignals(False)
+
+ @dynamic_property
+ def width(self):
+ def fget(self):
+ return self._width.value()
+ def fset(self, val):
+ self._width.setValue(val)
+ return property(fget=fget, fset=fset)
+
+ @dynamic_property
+ def height(self):
+ def fget(self):
+ return self._height.value()
+ def fset(self, val):
+ self._height.setValue(val)
+ return property(fget=fget, fset=fset)
+# }}}
+
class Editor(QMainWindow):
has_line_numbers = False
@@ -137,6 +200,9 @@ class Editor(QMainWindow):
self.copy_available_state_changed.emit(self.copy_available)
self.data_changed.emit(self)
self.modification_state_changed.emit(True)
+ self.fmt_label.setText((self.canvas.original_image_format or '').upper())
+ im = self.canvas.current_image
+ self.size_label.setText('{0} x {1}{2}'.format(im.width(), im.height(), 'px'))
def break_cycles(self):
self.canvas.break_cycles()
@@ -168,6 +234,18 @@ class Editor(QMainWindow):
setattr(self, 'action_' + x, b.addAction(ac.icon(), x, getattr(self, x)))
self.update_clipboard_actions()
+ b.addSeparator()
+ self.action_trim = ac = b.addAction(QIcon(I('trim.png')), _('Trim image'), self.canvas.trim_image)
+ self.action_rotate = ac = b.addAction(QIcon(I('rotate-right.png')), _('Rotate image'), self.canvas.rotate_image)
+ self.action_resize = ac = b.addAction(QIcon(I('resize.png')), _('Resize image'), self.resize_image)
+
+ self.info_bar = b = self.addToolBar(_('Image information bar'))
+ self.fmt_label = QLabel('')
+ b.addWidget(self.fmt_label)
+ b.addSeparator()
+ self.size_label = QLabel('')
+ b.addWidget(self.size_label)
+
def update_clipboard_actions(self, *args):
if self.canvas.has_selection:
self.action_copy.setText(_('Copy selected region'))
@@ -176,6 +254,12 @@ class Editor(QMainWindow):
self.action_copy.setText(_('Copy image'))
self.action_paste.setText(_('Paste image'))
+ def resize_image(self):
+ im = self.canvas.current_image
+ d = ResizeDialog(im.width(), im.height(), self)
+ if d.exec_() == d.Accepted:
+ self.canvas.resize_image(d.width, d.height)
+
def launch_editor(path_to_edit, path_is_raw=False):
app = QApplication([])
if path_is_raw: