mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Trim image: Allow specifying the size of the trim rectangle using numbers. Fixes #2056116 [Trim Cover: Trim to specified size/ratio](https://bugs.launchpad.net/calibre/+bug/2056116)
This commit is contained in:
parent
361eb92708
commit
5b53a49ba1
@ -7,14 +7,68 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QKeySequence,
|
QCheckBox, QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout, QIcon, QKeySequence,
|
||||||
QLabel, QSize, Qt, QToolBar, QVBoxLayout
|
QLabel, QSize, QSpinBox, Qt, QToolBar, QVBoxLayout,
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre.gui2 import gprefs
|
from calibre.gui2 import gprefs
|
||||||
from calibre.gui2.tweak_book.editor.canvas import Canvas
|
from calibre.gui2.tweak_book.editor.canvas import Canvas
|
||||||
|
|
||||||
|
|
||||||
|
class Region(QDialog):
|
||||||
|
|
||||||
|
ignore_value_changes = False
|
||||||
|
|
||||||
|
def __init__(self, parent, width, height, max_width, max_height):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle(_('Set size of selected area'))
|
||||||
|
self.l = l = QFormLayout(self)
|
||||||
|
l.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||||
|
self.width_input = w = QSpinBox(self)
|
||||||
|
w.setRange(20, max_width), w.setSuffix(' px'), w.setValue(width)
|
||||||
|
w.valueChanged.connect(self.value_changed)
|
||||||
|
l.addRow(_('&Width:'), w)
|
||||||
|
self.height_input = h = QSpinBox(self)
|
||||||
|
h.setRange(20, max_height), h.setSuffix(' px'), h.setValue(height)
|
||||||
|
h.valueChanged.connect(self.value_changed)
|
||||||
|
l.addRow(_('&Height:'), h)
|
||||||
|
self.const_aspect = ca = QCheckBox(_('Keep the ratio of width to height fixed'))
|
||||||
|
ca.toggled.connect(self.const_aspect_toggled)
|
||||||
|
l.addRow(ca)
|
||||||
|
k = QKeySequence('alt+1', QKeySequence.SequenceFormat.PortableText)
|
||||||
|
la = QLabel('<p>'+_('Note that holding down the {} key while dragging the selection handles'
|
||||||
|
' will resize the selection while preserving its aspect ratio.').format(
|
||||||
|
k.toString(QKeySequence.SequenceFormat.NativeText)))
|
||||||
|
la.setWordWrap(True)
|
||||||
|
la.setMinimumWidth(400)
|
||||||
|
l.addRow(la)
|
||||||
|
self.bb = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)
|
||||||
|
bb.accepted.connect(self.accept)
|
||||||
|
bb.rejected.connect(self.reject)
|
||||||
|
l.addRow(bb)
|
||||||
|
self.resize(self.sizeHint())
|
||||||
|
self.current_aspect = width / height
|
||||||
|
|
||||||
|
def const_aspect_toggled(self):
|
||||||
|
if self.const_aspect.isChecked():
|
||||||
|
self.current_aspect = self.width_input.value() / self.height_input.value()
|
||||||
|
|
||||||
|
def value_changed(self):
|
||||||
|
if self.ignore_value_changes or not self.const_aspect.isChecked():
|
||||||
|
return
|
||||||
|
src = self.sender()
|
||||||
|
self.ignore_value_changes = True
|
||||||
|
if src is self.height_input:
|
||||||
|
self.width_input.setValue(int(self.current_aspect * self.height_input.value()))
|
||||||
|
else:
|
||||||
|
self.height_input.setValue(int(self.width_input.value() / self.current_aspect))
|
||||||
|
self.ignore_value_changes = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def selection_size(self):
|
||||||
|
return self.width_input.value(), self.height_input.value()
|
||||||
|
|
||||||
|
|
||||||
class TrimImage(QDialog):
|
class TrimImage(QDialog):
|
||||||
|
|
||||||
def __init__(self, img_data, parent=None):
|
def __init__(self, img_data, parent=None):
|
||||||
@ -44,6 +98,8 @@ class TrimImage(QDialog):
|
|||||||
ac.setToolTip('{} [{}]'.format(_('Trim image by removing borders outside the selected region'),
|
ac.setToolTip('{} [{}]'.format(_('Trim image by removing borders outside the selected region'),
|
||||||
ac.shortcut().toString(QKeySequence.SequenceFormat.NativeText)))
|
ac.shortcut().toString(QKeySequence.SequenceFormat.NativeText)))
|
||||||
ac.setEnabled(False)
|
ac.setEnabled(False)
|
||||||
|
self.size_selection = ac = self.bar.addAction(QIcon.ic('resize.png'), _('&Region'), self.do_region)
|
||||||
|
ac.setToolTip(_('Specify a selection size using numbers to allow for precise control'))
|
||||||
c.selection_state_changed.connect(self.selection_changed)
|
c.selection_state_changed.connect(self.selection_changed)
|
||||||
c.selection_area_changed.connect(self.selection_area_changed)
|
c.selection_area_changed.connect(self.selection_area_changed)
|
||||||
l.addWidget(c)
|
l.addWidget(c)
|
||||||
@ -73,6 +129,13 @@ class TrimImage(QDialog):
|
|||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return QSize(900, 600)
|
return QSize(900, 600)
|
||||||
|
|
||||||
|
def do_region(self):
|
||||||
|
rect = self.canvas.selection_rect_in_image_coords
|
||||||
|
d = Region(self, int(rect.width()), int(rect.height()), self.canvas.current_image.width(), self.canvas.current_image.height())
|
||||||
|
if d.exec() == QDialog.DialogCode.Accepted:
|
||||||
|
width, height = d.selection_size
|
||||||
|
self.canvas.set_selection_size_in_image_coords(width, height)
|
||||||
|
|
||||||
def do_trim(self):
|
def do_trim(self):
|
||||||
self.canvas.trim_image()
|
self.canvas.trim_image()
|
||||||
self.selection_changed(False)
|
self.selection_changed(False)
|
||||||
|
@ -4,25 +4,26 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import sys, weakref
|
import sys
|
||||||
|
import weakref
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QWidget, QPainter, QColor, QApplication, Qt, QPixmap, QRectF, QTransform,
|
QApplication, QColor, QIcon, QImage, QImageWriter, QPainter, QPen, QPixmap, QPointF,
|
||||||
QPointF, QPen, pyqtSignal, QUndoCommand, QUndoStack, QIcon, QImage,
|
QRect, QRectF, Qt, QTransform, QUndoCommand, QUndoStack, QWidget, pyqtSignal,
|
||||||
QImageWriter)
|
)
|
||||||
|
|
||||||
from calibre import fit_image
|
from calibre import fit_image
|
||||||
from calibre.gui2 import error_dialog, pixmap_to_data
|
from calibre.gui2 import error_dialog, pixmap_to_data
|
||||||
from calibre.gui2.dnd import (
|
from calibre.gui2.dnd import (
|
||||||
image_extensions, dnd_has_extension, dnd_has_image, dnd_get_image, DownloadDialog)
|
DownloadDialog, dnd_get_image, dnd_has_extension, dnd_has_image, image_extensions,
|
||||||
from calibre.gui2.tweak_book import capitalize
|
|
||||||
from calibre.utils.imghdr import identify
|
|
||||||
from calibre.utils.img import (
|
|
||||||
remove_borders_from_image, gaussian_sharpen_image, gaussian_blur_image, image_to_data, despeckle_image,
|
|
||||||
normalize_image, oil_paint_image
|
|
||||||
)
|
)
|
||||||
|
from calibre.gui2.tweak_book import capitalize
|
||||||
|
from calibre.utils.img import (
|
||||||
|
despeckle_image, gaussian_blur_image, gaussian_sharpen_image, image_to_data,
|
||||||
|
normalize_image, oil_paint_image, remove_borders_from_image,
|
||||||
|
)
|
||||||
|
from calibre.utils.imghdr import identify
|
||||||
|
|
||||||
|
|
||||||
def painter(func):
|
def painter(func):
|
||||||
@ -575,6 +576,20 @@ class Canvas(QWidget):
|
|||||||
self.selection_state.current_mode = 'select'
|
self.selection_state.current_mode = 'select'
|
||||||
self.selection_state.rect = None
|
self.selection_state.rect = None
|
||||||
self.selection_state_changed.emit(False)
|
self.selection_state_changed.emit(False)
|
||||||
|
@property
|
||||||
|
def selection_rect_in_image_coords(self):
|
||||||
|
if self.selection_state.current_mode == 'selected':
|
||||||
|
left, top, width, height = self.rect_for_trim()
|
||||||
|
return QRect(0, 0, int(width), int(height))
|
||||||
|
return self.current_image.rect()
|
||||||
|
|
||||||
|
def set_selection_size_in_image_coords(self, width, height):
|
||||||
|
self.selection_state.reset()
|
||||||
|
i = self.current_image
|
||||||
|
self.selection_state.rect = QRectF(self.target.left(), self.target.top(),
|
||||||
|
width * self.target.width() / i.width(), height * self.target.height() / i.height())
|
||||||
|
self.selection_state.current_mode = 'selected'
|
||||||
|
self.update()
|
||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
changed = False
|
changed = False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user