mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Add basic image manipulation operations
This commit is contained in:
parent
67eccb28a1
commit
0cd7b5f46f
5299
imgsrc/resize.svg
Normal file
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
BIN
resources/images/resize.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
@ -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
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user