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:
Kovid Goyal 2024-03-13 21:33:49 +05:30
parent 361eb92708
commit 5b53a49ba1
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 91 additions and 13 deletions

View File

@ -7,14 +7,68 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import os
import sys
from qt.core import (
QDialog, QDialogButtonBox, QHBoxLayout, QIcon, QKeySequence,
QLabel, QSize, Qt, QToolBar, QVBoxLayout
QCheckBox, QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout, QIcon, QKeySequence,
QLabel, QSize, QSpinBox, Qt, QToolBar, QVBoxLayout,
)
from calibre.gui2 import gprefs
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):
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.shortcut().toString(QKeySequence.SequenceFormat.NativeText)))
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_area_changed.connect(self.selection_area_changed)
l.addWidget(c)
@ -73,6 +129,13 @@ class TrimImage(QDialog):
def sizeHint(self):
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):
self.canvas.trim_image()
self.selection_changed(False)

View File

@ -4,25 +4,26 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, weakref
import sys
import weakref
from functools import wraps
from io import BytesIO
from qt.core import (
QWidget, QPainter, QColor, QApplication, Qt, QPixmap, QRectF, QTransform,
QPointF, QPen, pyqtSignal, QUndoCommand, QUndoStack, QIcon, QImage,
QImageWriter)
QApplication, QColor, QIcon, QImage, QImageWriter, QPainter, QPen, QPixmap, QPointF,
QRect, QRectF, Qt, QTransform, QUndoCommand, QUndoStack, QWidget, pyqtSignal,
)
from calibre import fit_image
from calibre.gui2 import error_dialog, pixmap_to_data
from calibre.gui2.dnd import (
image_extensions, dnd_has_extension, dnd_has_image, dnd_get_image, DownloadDialog)
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
DownloadDialog, dnd_get_image, dnd_has_extension, dnd_has_image, image_extensions,
)
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):
@ -575,6 +576,20 @@ class Canvas(QWidget):
self.selection_state.current_mode = 'select'
self.selection_state.rect = None
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):
changed = False