Add basic image manipulation operations

This commit is contained in:
Kovid Goyal 2013-12-05 12:44:38 +05:30
parent 67eccb28a1
commit 0cd7b5f46f
4 changed files with 5417 additions and 3 deletions

5299
imgsrc/resize.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 162 KiB

BIN
resources/images/resize.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -10,7 +10,7 @@ import sys, string, weakref
from functools import wraps from functools import wraps
from PyQt4.Qt import ( 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) QPointF, QPen, pyqtSignal, QUndoCommand, QUndoStack, QIcon, QImage)
from calibre import fit_image from calibre import fit_image
@ -89,6 +89,27 @@ class Trim(Command):
sr = canvas.selection_state.rect sr = canvas.selection_state.rect
return img.copy(*get_selection_rect(img, sr, target)) 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): class Replace(Command):
''' Replace the current image with another image. If there is a selection, ''' 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)) self.undo_stack.push(Trim(self))
return True 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 {{{ # The selection rectangle {{{
@property @property
def dc_size(self): 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()): 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.setCursor(self.get_cursor())
self.update() self.update()
# }}}
def keyPressEvent(self, ev): def keyPressEvent(self, ev):
k = ev.key() k = ev.key()
@ -394,6 +424,7 @@ class Canvas(QWidget):
self.update() self.update()
else: else:
return QWidget.keyPressEvent(self, ev) return QWidget.keyPressEvent(self, ev)
# }}}
# Painting {{{ # Painting {{{
@painter @painter

View File

@ -7,14 +7,77 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import sys import sys
from functools import partial
from PyQt4.Qt import ( 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 import error_dialog
from calibre.gui2.tweak_book import actions from calibre.gui2.tweak_book import actions
from calibre.gui2.tweak_book.editor.canvas import Canvas 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): class Editor(QMainWindow):
has_line_numbers = False has_line_numbers = False
@ -137,6 +200,9 @@ class Editor(QMainWindow):
self.copy_available_state_changed.emit(self.copy_available) self.copy_available_state_changed.emit(self.copy_available)
self.data_changed.emit(self) self.data_changed.emit(self)
self.modification_state_changed.emit(True) 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): def break_cycles(self):
self.canvas.break_cycles() self.canvas.break_cycles()
@ -168,6 +234,18 @@ class Editor(QMainWindow):
setattr(self, 'action_' + x, b.addAction(ac.icon(), x, getattr(self, x))) setattr(self, 'action_' + x, b.addAction(ac.icon(), x, getattr(self, x)))
self.update_clipboard_actions() 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): def update_clipboard_actions(self, *args):
if self.canvas.has_selection: if self.canvas.has_selection:
self.action_copy.setText(_('Copy selected region')) self.action_copy.setText(_('Copy selected region'))
@ -176,6 +254,12 @@ class Editor(QMainWindow):
self.action_copy.setText(_('Copy image')) self.action_copy.setText(_('Copy image'))
self.action_paste.setText(_('Paste 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): def launch_editor(path_to_edit, path_is_raw=False):
app = QApplication([]) app = QApplication([])
if path_is_raw: if path_is_raw: