Merge pull request #2515 from twodoorcoupe/more_processing_options

Add more options to image resizing
This commit is contained in:
Laurent Monin
2024-06-25 11:41:31 +02:00
committed by GitHub
7 changed files with 679 additions and 164 deletions

View File

@@ -35,31 +35,87 @@ class ResizeImage(ImageProcessor):
def save_to_file(self):
config = get_config()
return config.setting['resize_images_saved_to_file']
return config.setting['cover_file_scale_down'] or config.setting['cover_file_scale_up']
def save_to_tags(self):
config = get_config()
return config.setting['resize_images_saved_to_tags']
return config.setting['cover_tags_scale_down'] or config.setting['cover_tags_scale_up']
def same_processing(self):
config = get_config()
same_width = config.setting['cover_tags_maximum_width'] == config.setting['cover_file_maximum_width']
same_height = config.setting['cover_tags_maximum_height'] == config.setting['cover_file_maximum_height']
return self.save_to_tags and self.save_to_file and same_width and same_height
setting = get_config().setting
tags_size = (
setting['cover_tags_resize_target_width'] if setting['cover_tags_resize_use_width'] else 0,
setting['cover_tags_resize_target_height'] if setting['cover_tags_resize_use_height'] else 0
)
file_size = (
setting['cover_file_resize_target_width'] if setting['cover_file_resize_use_width'] else 0,
setting['cover_file_resize_target_height'] if setting['cover_file_resize_use_height'] else 0
)
same_size = tags_size == file_size
tags_direction = (setting['cover_tags_scale_up'], setting['cover_tags_scale_down'])
file_direction = (setting['cover_file_scale_up'], setting['cover_file_scale_down'])
same_direction = tags_direction == file_direction and any(tags_direction)
tags_resize_mode = (setting['cover_tags_stretch'], setting['cover_tags_crop'])
file_resize_mode = (setting['cover_file_stretch'], setting['cover_file_crop'])
same_resize_mode = tags_resize_mode == file_resize_mode
return same_size and same_direction and same_resize_mode
def run(self, image, target):
start_time = time.time()
config = get_config()
if target == ProcessingTarget.TAGS:
max_width = config.setting['cover_tags_maximum_width']
max_height = config.setting['cover_tags_maximum_height']
scale_up = config.setting['cover_tags_scale_up']
scale_down = config.setting['cover_tags_scale_down']
use_width = config.setting['cover_tags_resize_use_width']
target_width = config.setting['cover_tags_resize_target_width']
use_height = config.setting['cover_tags_resize_use_height']
target_height = config.setting['cover_tags_resize_target_height']
stretch = config.setting["cover_tags_stretch"]
crop = config.setting["cover_tags_crop"]
else:
max_width = config.setting['cover_file_maximum_width']
max_height = config.setting['cover_file_maximum_height']
if image.info.width <= max_width and image.info.height <= max_height:
scale_up = config.setting['cover_file_scale_up']
scale_down = config.setting['cover_file_scale_down']
use_width = config.setting['cover_file_resize_use_width']
target_width = config.setting['cover_file_resize_target_width']
use_height = config.setting['cover_file_resize_use_height']
target_height = config.setting['cover_file_resize_target_height']
stretch = config.setting["cover_file_stretch"]
crop = config.setting["cover_file_crop"]
width_resize = target_width if use_width else image.info.width
height_resize = target_height if use_height else image.info.height
width_scale_factor = width_resize / image.info.width
height_scale_factor = height_resize / image.info.height
use_both_dimensions = use_height and use_width
if use_both_dimensions and not stretch:
if crop:
scale_factor = max(width_scale_factor, height_scale_factor)
else:
scale_factor = min(width_scale_factor, height_scale_factor)
width_scale_factor = scale_factor
height_scale_factor = scale_factor
if (width_scale_factor == 1 and height_scale_factor == 1
or ((width_scale_factor > 1 or height_scale_factor > 1) and not scale_up)
or ((width_scale_factor < 1 or height_scale_factor < 1) and not scale_down)):
# no resizing needed
return
qimage = image.get_result()
scaled_image = qimage.scaled(max_width, max_height, Qt.AspectRatioMode.KeepAspectRatio)
if stretch:
scaled_image = qimage.scaled(width_resize, height_resize, Qt.AspectRatioMode.IgnoreAspectRatio)
elif crop:
scaled_image = qimage.scaled(width_resize, height_resize, Qt.AspectRatioMode.KeepAspectRatioByExpanding)
cutoff_width = (scaled_image.width() - width_resize) // 2
cutoff_height = (scaled_image.height() - height_resize) // 2
scaled_image = scaled_image.copy(cutoff_width, cutoff_height, width_resize, height_resize)
else: # keep aspect ratio
if use_both_dimensions:
scaled_image = qimage.scaled(width_resize, height_resize, Qt.AspectRatioMode.KeepAspectRatio)
elif use_width:
scaled_image = qimage.scaledToWidth(width_resize)
else:
scaled_image = qimage.scaledToHeight(height_resize)
log.debug(
"Resized cover art from %d x %d to %d x %d in %.2f ms",
image.info.width,

View File

@@ -86,7 +86,7 @@ class ImageProcessor:
def same_processing(self):
return False
def run(self, data, target):
def run(self, image, target):
pass

View File

@@ -175,12 +175,22 @@ BoolOption('setting', 'save_only_one_front_image', False, title=N_("Save only a
BoolOption('setting', 'filter_cover_by_size', False)
IntOption('setting', 'cover_minimum_width', DEFAULT_COVER_MIN_SIZE)
IntOption('setting', 'cover_minimum_height', DEFAULT_COVER_MIN_SIZE)
BoolOption('setting', 'resize_images_saved_to_tags', False)
IntOption('setting', 'cover_tags_maximum_width', DEFAULT_COVER_MAX_SIZE)
IntOption('setting', 'cover_tags_maximum_height', DEFAULT_COVER_MAX_SIZE)
BoolOption('setting', 'resize_images_saved_to_file', False)
IntOption('setting', 'cover_file_maximum_width', DEFAULT_COVER_MAX_SIZE)
IntOption('setting', 'cover_file_maximum_height', DEFAULT_COVER_MAX_SIZE)
BoolOption('setting', 'cover_tags_scale_up', False)
BoolOption('setting', 'cover_tags_scale_down', False)
BoolOption('setting', 'cover_tags_resize_use_width', True)
IntOption('setting', 'cover_tags_resize_target_width', DEFAULT_COVER_MAX_SIZE)
BoolOption('setting', 'cover_tags_resize_use_height', True)
IntOption('setting', 'cover_tags_resize_target_height', DEFAULT_COVER_MAX_SIZE)
BoolOption('setting', 'cover_tags_stretch', False)
BoolOption('setting', 'cover_tags_crop', False)
BoolOption('setting', 'cover_file_scale_up', False)
BoolOption('setting', 'cover_file_scale_down', False)
BoolOption('setting', 'cover_file_resize_use_width', True)
IntOption('setting', 'cover_file_resize_target_width', DEFAULT_COVER_MAX_SIZE)
BoolOption('setting', 'cover_file_resize_use_height', True)
IntOption('setting', 'cover_file_resize_target_height', DEFAULT_COVER_MAX_SIZE)
BoolOption('setting', 'cover_file_stretch', False)
BoolOption('setting', 'cover_file_crop', False)
# picard/ui/options/dialog.py
# Attached Profiles

View File

@@ -17,7 +17,7 @@ from picard.i18n import gettext as _
class Ui_CoverProcessingOptionsPage(object):
def setupUi(self, CoverProcessingOptionsPage):
CoverProcessingOptionsPage.setObjectName("CoverProcessingOptionsPage")
CoverProcessingOptionsPage.resize(518, 285)
CoverProcessingOptionsPage.resize(478, 423)
self.verticalLayout = QtWidgets.QVBoxLayout(CoverProcessingOptionsPage)
self.verticalLayout.setObjectName("verticalLayout")
self.filtering = QtWidgets.QGroupBox(parent=CoverProcessingOptionsPage)
@@ -88,30 +88,42 @@ class Ui_CoverProcessingOptionsPage(object):
self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.resizing)
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.save_to_tags = QtWidgets.QGroupBox(parent=self.resizing)
self.save_to_tags.setCheckable(True)
self.save_to_tags.setCheckable(False)
self.save_to_tags.setChecked(False)
self.save_to_tags.setObjectName("save_to_tags")
self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.save_to_tags)
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.tags_scale_widget = QtWidgets.QWidget(parent=self.save_to_tags)
self.tags_scale_widget.setObjectName("tags_scale_widget")
self.horizontalLayout_8 = QtWidgets.QHBoxLayout(self.tags_scale_widget)
self.horizontalLayout_8.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_8.setObjectName("horizontalLayout_8")
self.tags_scale_up = QtWidgets.QCheckBox(parent=self.tags_scale_widget)
self.tags_scale_up.setObjectName("tags_scale_up")
self.horizontalLayout_8.addWidget(self.tags_scale_up)
self.tags_scale_down = QtWidgets.QCheckBox(parent=self.tags_scale_widget)
self.tags_scale_down.setObjectName("tags_scale_down")
self.horizontalLayout_8.addWidget(self.tags_scale_down)
self.verticalLayout_3.addWidget(self.tags_scale_widget)
self.tags_resize_width_widget = QtWidgets.QWidget(parent=self.save_to_tags)
self.tags_resize_width_widget.setObjectName("tags_resize_width_widget")
self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.tags_resize_width_widget)
self.horizontalLayout_5.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_5.setSpacing(4)
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.tags_resized_width_label = QtWidgets.QLabel(parent=self.tags_resize_width_widget)
self.tags_resized_width_label.setObjectName("tags_resized_width_label")
self.horizontalLayout_5.addWidget(self.tags_resized_width_label)
self.tags_resized_width_value = QtWidgets.QSpinBox(parent=self.tags_resize_width_widget)
self.tags_resize_width_label = QtWidgets.QCheckBox(parent=self.tags_resize_width_widget)
self.tags_resize_width_label.setObjectName("tags_resize_width_label")
self.horizontalLayout_5.addWidget(self.tags_resize_width_label)
self.tags_resize_width_value = QtWidgets.QSpinBox(parent=self.tags_resize_width_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tags_resized_width_value.sizePolicy().hasHeightForWidth())
self.tags_resized_width_value.setSizePolicy(sizePolicy)
self.tags_resized_width_value.setMaximum(9999)
self.tags_resized_width_value.setProperty("value", 1000)
self.tags_resized_width_value.setObjectName("tags_resized_width_value")
self.horizontalLayout_5.addWidget(self.tags_resized_width_value)
sizePolicy.setHeightForWidth(self.tags_resize_width_value.sizePolicy().hasHeightForWidth())
self.tags_resize_width_value.setSizePolicy(sizePolicy)
self.tags_resize_width_value.setMaximum(9999)
self.tags_resize_width_value.setProperty("value", 1000)
self.tags_resize_width_value.setObjectName("tags_resize_width_value")
self.horizontalLayout_5.addWidget(self.tags_resize_width_value)
self.px_label5 = QtWidgets.QLabel(parent=self.tags_resize_width_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -127,19 +139,19 @@ class Ui_CoverProcessingOptionsPage(object):
self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_3.setSpacing(4)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.tags_resized_height_label = QtWidgets.QLabel(parent=self.tags_resize_height_widget)
self.tags_resized_height_label.setObjectName("tags_resized_height_label")
self.horizontalLayout_3.addWidget(self.tags_resized_height_label)
self.tags_resized_height_value = QtWidgets.QSpinBox(parent=self.tags_resize_height_widget)
self.tags_resize_height_label = QtWidgets.QCheckBox(parent=self.tags_resize_height_widget)
self.tags_resize_height_label.setObjectName("tags_resize_height_label")
self.horizontalLayout_3.addWidget(self.tags_resize_height_label)
self.tags_resize_height_value = QtWidgets.QSpinBox(parent=self.tags_resize_height_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.tags_resized_height_value.sizePolicy().hasHeightForWidth())
self.tags_resized_height_value.setSizePolicy(sizePolicy)
self.tags_resized_height_value.setMaximum(9999)
self.tags_resized_height_value.setProperty("value", 1000)
self.tags_resized_height_value.setObjectName("tags_resized_height_value")
self.horizontalLayout_3.addWidget(self.tags_resized_height_value)
sizePolicy.setHeightForWidth(self.tags_resize_height_value.sizePolicy().hasHeightForWidth())
self.tags_resize_height_value.setSizePolicy(sizePolicy)
self.tags_resize_height_value.setMaximum(9999)
self.tags_resize_height_value.setProperty("value", 1000)
self.tags_resize_height_value.setObjectName("tags_resize_height_value")
self.horizontalLayout_3.addWidget(self.tags_resize_height_value)
self.px_label6 = QtWidgets.QLabel(parent=self.tags_resize_height_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -149,32 +161,61 @@ class Ui_CoverProcessingOptionsPage(object):
self.px_label6.setObjectName("px_label6")
self.horizontalLayout_3.addWidget(self.px_label6)
self.verticalLayout_3.addWidget(self.tags_resize_height_widget)
self.tags_resize_mode = QtWidgets.QWidget(parent=self.save_to_tags)
self.tags_resize_mode.setObjectName("tags_resize_mode")
self.horizontalLayout_10 = QtWidgets.QHBoxLayout(self.tags_resize_mode)
self.horizontalLayout_10.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_10.setSpacing(2)
self.horizontalLayout_10.setObjectName("horizontalLayout_10")
self.tags_keep = QtWidgets.QRadioButton(parent=self.tags_resize_mode)
self.tags_keep.setChecked(True)
self.tags_keep.setObjectName("tags_keep")
self.horizontalLayout_10.addWidget(self.tags_keep)
self.tags_crop = QtWidgets.QRadioButton(parent=self.tags_resize_mode)
self.tags_crop.setObjectName("tags_crop")
self.horizontalLayout_10.addWidget(self.tags_crop)
self.tags_stretch = QtWidgets.QRadioButton(parent=self.tags_resize_mode)
self.tags_stretch.setObjectName("tags_stretch")
self.horizontalLayout_10.addWidget(self.tags_stretch)
self.verticalLayout_3.addWidget(self.tags_resize_mode)
self.horizontalLayout_7.addWidget(self.save_to_tags)
self.save_to_file = QtWidgets.QGroupBox(parent=self.resizing)
self.save_to_file.setCheckable(True)
self.save_to_file.setCheckable(False)
self.save_to_file.setChecked(False)
self.save_to_file.setObjectName("save_to_file")
self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.save_to_file)
self.verticalLayout_4.setObjectName("verticalLayout_4")
self.file_scale_widget = QtWidgets.QWidget(parent=self.save_to_file)
self.file_scale_widget.setObjectName("file_scale_widget")
self.horizontalLayout_9 = QtWidgets.QHBoxLayout(self.file_scale_widget)
self.horizontalLayout_9.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_9.setObjectName("horizontalLayout_9")
self.file_scale_up = QtWidgets.QCheckBox(parent=self.file_scale_widget)
self.file_scale_up.setObjectName("file_scale_up")
self.horizontalLayout_9.addWidget(self.file_scale_up)
self.file_scale_down = QtWidgets.QCheckBox(parent=self.file_scale_widget)
self.file_scale_down.setObjectName("file_scale_down")
self.horizontalLayout_9.addWidget(self.file_scale_down)
self.verticalLayout_4.addWidget(self.file_scale_widget)
self.file_resize_width_widget = QtWidgets.QWidget(parent=self.save_to_file)
self.file_resize_width_widget.setObjectName("file_resize_width_widget")
self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.file_resize_width_widget)
self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_6.setSpacing(4)
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.file_resized_width_label = QtWidgets.QLabel(parent=self.file_resize_width_widget)
self.file_resized_width_label.setObjectName("file_resized_width_label")
self.horizontalLayout_6.addWidget(self.file_resized_width_label)
self.file_resized_width_value = QtWidgets.QSpinBox(parent=self.file_resize_width_widget)
self.file_resize_width_label = QtWidgets.QCheckBox(parent=self.file_resize_width_widget)
self.file_resize_width_label.setObjectName("file_resize_width_label")
self.horizontalLayout_6.addWidget(self.file_resize_width_label)
self.file_resize_width_value = QtWidgets.QSpinBox(parent=self.file_resize_width_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.file_resized_width_value.sizePolicy().hasHeightForWidth())
self.file_resized_width_value.setSizePolicy(sizePolicy)
self.file_resized_width_value.setMaximum(9999)
self.file_resized_width_value.setProperty("value", 1000)
self.file_resized_width_value.setObjectName("file_resized_width_value")
self.horizontalLayout_6.addWidget(self.file_resized_width_value)
sizePolicy.setHeightForWidth(self.file_resize_width_value.sizePolicy().hasHeightForWidth())
self.file_resize_width_value.setSizePolicy(sizePolicy)
self.file_resize_width_value.setMaximum(9999)
self.file_resize_width_value.setProperty("value", 1000)
self.file_resize_width_value.setObjectName("file_resize_width_value")
self.horizontalLayout_6.addWidget(self.file_resize_width_value)
self.px_label3 = QtWidgets.QLabel(parent=self.file_resize_width_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -190,19 +231,19 @@ class Ui_CoverProcessingOptionsPage(object):
self.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_4.setSpacing(4)
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.file_resized_height_label = QtWidgets.QLabel(parent=self.file_resize_height_widget)
self.file_resized_height_label.setObjectName("file_resized_height_label")
self.horizontalLayout_4.addWidget(self.file_resized_height_label)
self.file_resized_height_value = QtWidgets.QSpinBox(parent=self.file_resize_height_widget)
self.file_resize_height_label = QtWidgets.QCheckBox(parent=self.file_resize_height_widget)
self.file_resize_height_label.setObjectName("file_resize_height_label")
self.horizontalLayout_4.addWidget(self.file_resize_height_label)
self.file_resize_height_value = QtWidgets.QSpinBox(parent=self.file_resize_height_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.file_resized_height_value.sizePolicy().hasHeightForWidth())
self.file_resized_height_value.setSizePolicy(sizePolicy)
self.file_resized_height_value.setMaximum(9999)
self.file_resized_height_value.setProperty("value", 1000)
self.file_resized_height_value.setObjectName("file_resized_height_value")
self.horizontalLayout_4.addWidget(self.file_resized_height_value)
sizePolicy.setHeightForWidth(self.file_resize_height_value.sizePolicy().hasHeightForWidth())
self.file_resize_height_value.setSizePolicy(sizePolicy)
self.file_resize_height_value.setMaximum(9999)
self.file_resize_height_value.setProperty("value", 1000)
self.file_resize_height_value.setObjectName("file_resize_height_value")
self.horizontalLayout_4.addWidget(self.file_resize_height_value)
self.px_label4 = QtWidgets.QLabel(parent=self.file_resize_height_widget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -212,6 +253,23 @@ class Ui_CoverProcessingOptionsPage(object):
self.px_label4.setObjectName("px_label4")
self.horizontalLayout_4.addWidget(self.px_label4)
self.verticalLayout_4.addWidget(self.file_resize_height_widget)
self.file_resize_mode = QtWidgets.QWidget(parent=self.save_to_file)
self.file_resize_mode.setObjectName("file_resize_mode")
self.horizontalLayout_11 = QtWidgets.QHBoxLayout(self.file_resize_mode)
self.horizontalLayout_11.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_11.setSpacing(2)
self.horizontalLayout_11.setObjectName("horizontalLayout_11")
self.file_keep = QtWidgets.QRadioButton(parent=self.file_resize_mode)
self.file_keep.setChecked(True)
self.file_keep.setObjectName("file_keep")
self.horizontalLayout_11.addWidget(self.file_keep)
self.file_crop = QtWidgets.QRadioButton(parent=self.file_resize_mode)
self.file_crop.setObjectName("file_crop")
self.horizontalLayout_11.addWidget(self.file_crop)
self.file_stretch = QtWidgets.QRadioButton(parent=self.file_resize_mode)
self.file_stretch.setObjectName("file_stretch")
self.horizontalLayout_11.addWidget(self.file_stretch)
self.verticalLayout_4.addWidget(self.file_resize_mode)
self.horizontalLayout_7.addWidget(self.save_to_file)
self.verticalLayout.addWidget(self.resizing)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding)
@@ -229,12 +287,22 @@ class Ui_CoverProcessingOptionsPage(object):
self.px_label2.setText(_("px"))
self.resizing.setTitle(_("Resize images if above the given size"))
self.save_to_tags.setTitle(_("Resize images saved to tags "))
self.tags_resized_width_label.setText(_("Maximum width:"))
self.tags_scale_up.setText(_("Scale up"))
self.tags_scale_down.setText(_("Scale down"))
self.tags_resize_width_label.setText(_("Width:"))
self.px_label5.setText(_("px"))
self.tags_resized_height_label.setText(_("Maximum height:"))
self.tags_resize_height_label.setText(_("Height:"))
self.px_label6.setText(_("px"))
self.tags_keep.setText(_("Fit"))
self.tags_crop.setText(_("Fill"))
self.tags_stretch.setText(_("Stretch"))
self.save_to_file.setTitle(_("Resize images saved to files"))
self.file_resized_width_label.setText(_("Maximum width:"))
self.file_scale_up.setText(_("Scale up"))
self.file_scale_down.setText(_("Scale down"))
self.file_resize_width_label.setText(_("Width:"))
self.px_label3.setText(_("px"))
self.file_resized_height_label.setText(_("Maximum height:"))
self.file_resize_height_label.setText(_("Height:"))
self.px_label4.setText(_("px"))
self.file_keep.setText(_("Fit"))
self.file_crop.setText(_("Fill"))
self.file_stretch.setText(_("Stretch"))

View File

@@ -18,9 +18,14 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from functools import partial
from picard.config import get_config
from picard.extension_points.options_pages import register_options_page
from picard.i18n import N_
from picard.i18n import (
N_,
_,
)
from picard.ui.forms.ui_options_cover_processing import (
Ui_CoverProcessingOptionsPage,
@@ -42,36 +47,135 @@ class CoverProcessingOptionsPage(OptionsPage):
self.register_setting('filter_cover_by_size')
self.register_setting('cover_minimum_width')
self.register_setting('cover_minimum_height')
self.register_setting('resize_images_saved_to_tags')
self.register_setting('cover_tags_maximum_width')
self.register_setting('cover_tags_maximum_height')
self.register_setting('resize_images_saved_to_file')
self.register_setting('cover_file_maximum_width')
self.register_setting('cover_file_maximum_height')
self.register_setting('cover_tags_scale_up')
self.register_setting('cover_tags_scale_down')
self.register_setting('cover_tags_resize_use_width')
self.register_setting('cover_tags_resize_target_width')
self.register_setting('cover_tags_resize_use_height')
self.register_setting('cover_tags_resize_target_height')
self.register_setting('cover_tags_stretch')
self.register_setting('cover_tags_crop')
self.register_setting('cover_file_scale_up')
self.register_setting('cover_file_scale_down')
self.register_setting('cover_file_resize_use_width')
self.register_setting('cover_file_resize_target_width')
self.register_setting('cover_file_resize_use_height')
self.register_setting('cover_file_resize_target_height')
self.register_setting('cover_file_stretch')
self.register_setting('cover_file_crop')
tooltip_keep = N_(
"<p>"
"Scale the source image so that it fits within the target dimensions."
"</p><p>"
"One of the final image dimensions may be less than the target dimension if "
"the source image and target dimensions have different aspect ratios."
"</p><p>"
"For example, a 2000x1000 image resized to target dimensions of "
"1000x1000 would result in a final image size of 1000x500."
"</p>"
)
self.ui.tags_keep.setToolTip(_(tooltip_keep))
self.ui.file_keep.setToolTip(_(tooltip_keep))
tooltip_crop = (
"<p>"
"Scale the source image so that it completely fills the target dimensions "
"in both directions."
"</p><p>"
"If the source image and target dimensions have different aspect ratios"
"then there will be overflow in one direction which will be (center) cropped."
"</p><p>"
"For example, a 500x1000 image resized to target dimensions of "
"1000x1000 would first scale up to 1000x2000, then the excess height "
"would be center cropped resulting in the final image size of 1000x1000."
"</p>"
)
self.ui.tags_crop.setToolTip(_(tooltip_crop))
self.ui.file_crop.setToolTip(_(tooltip_crop))
tooltip_stretch = (
"<p>"
"Stretch the image to exactly fit the specified dimensions, "
"distorting it if necessary."
"</p><p>"
"For example, a 500x1000 image with target dimension of 1000x1000 "
"would be stretched horizontally resulting in the final image "
"size of 1000x1000."
"</p>"
)
self.ui.tags_stretch.setToolTip(_(tooltip_stretch))
self.ui.file_stretch.setToolTip(_(tooltip_stretch))
tags_checkboxes = (self.ui.tags_resize_width_label, self.ui.tags_resize_height_label)
tags_at_least_one_checked = partial(self._ensure_at_least_one_checked, tags_checkboxes)
for checkbox in tags_checkboxes:
checkbox.clicked.connect(tags_at_least_one_checked)
file_checkboxes = (self.ui.file_resize_width_label, self.ui.file_resize_height_label)
file_at_least_one_checked = partial(self._ensure_at_least_one_checked, file_checkboxes)
for checkbox in file_checkboxes:
checkbox.clicked.connect(file_at_least_one_checked)
self._spinboxes = {
self.ui.tags_resize_width_label: self.ui.tags_resize_width_value,
self.ui.tags_resize_height_label: self.ui.tags_resize_height_value,
self.ui.file_resize_width_label: self.ui.file_resize_width_value,
self.ui.file_resize_height_label: self.ui.file_resize_height_value,
}
for checkbox, spinbox in self._spinboxes.items():
spinbox.setEnabled(checkbox.isChecked())
checkbox.stateChanged.connect(self._update_resize_spinboxes)
def _update_resize_spinboxes(self):
spinbox = self._spinboxes[self.sender()]
spinbox.setEnabled(self.sender().isChecked())
def _ensure_at_least_one_checked(self, checkboxes, clicked):
if not clicked and not any(checkbox.isChecked() for checkbox in checkboxes):
sender = self.sender()
sender.setChecked(True)
def load(self):
config = get_config()
self.ui.filtering.setChecked(config.setting['filter_cover_by_size'])
self.ui.filtering_width_value.setValue(config.setting['cover_minimum_width'])
self.ui.filtering_height_value.setValue(config.setting['cover_minimum_height'])
self.ui.save_to_tags.setChecked(config.setting['resize_images_saved_to_tags'])
self.ui.tags_resized_width_value.setValue(config.setting['cover_tags_maximum_width'])
self.ui.tags_resized_height_value.setValue(config.setting['cover_tags_maximum_height'])
self.ui.save_to_file.setChecked(config.setting['resize_images_saved_to_file'])
self.ui.file_resized_width_value.setValue(config.setting['cover_file_maximum_width'])
self.ui.file_resized_height_value.setValue(config.setting['cover_file_maximum_height'])
self.ui.tags_scale_up.setChecked(config.setting['cover_tags_scale_up'])
self.ui.tags_scale_down.setChecked(config.setting['cover_tags_scale_down'])
self.ui.tags_resize_width_label.setChecked(config.setting['cover_tags_resize_use_width'])
self.ui.tags_resize_width_value.setValue(config.setting['cover_tags_resize_target_width'])
self.ui.tags_resize_height_label.setChecked(config.setting['cover_tags_resize_use_height'])
self.ui.tags_resize_height_value.setValue(config.setting['cover_tags_resize_target_height'])
self.ui.tags_stretch.setChecked(config.setting['cover_tags_stretch'])
self.ui.tags_crop.setChecked(config.setting['cover_tags_crop'])
self.ui.file_scale_up.setChecked(config.setting['cover_file_scale_up'])
self.ui.file_scale_down.setChecked(config.setting['cover_file_scale_down'])
self.ui.file_resize_width_label.setChecked(config.setting['cover_file_resize_use_width'])
self.ui.file_resize_width_value.setValue(config.setting['cover_file_resize_target_width'])
self.ui.file_resize_height_label.setChecked(config.setting['cover_file_resize_use_height'])
self.ui.file_resize_height_value.setValue(config.setting['cover_file_resize_target_height'])
self.ui.file_stretch.setChecked(config.setting['cover_file_stretch'])
self.ui.file_crop.setChecked(config.setting['cover_file_crop'])
def save(self):
config = get_config()
config.setting['filter_cover_by_size'] = self.ui.filtering.isChecked()
config.setting['cover_minimum_width'] = self.ui.filtering_width_value.value()
config.setting['cover_minimum_height'] = self.ui.filtering_height_value.value()
config.setting['resize_images_saved_to_tags'] = self.ui.save_to_tags.isChecked()
config.setting['cover_tags_maximum_width'] = self.ui.tags_resized_width_value.value()
config.setting['cover_tags_maximum_height'] = self.ui.tags_resized_height_value.value()
config.setting['resize_images_saved_to_file'] = self.ui.save_to_file.isChecked()
config.setting['cover_file_maximum_width'] = self.ui.file_resized_width_value.value()
config.setting['cover_file_maximum_height'] = self.ui.file_resized_height_value.value()
config.setting['cover_tags_scale_up'] = self.ui.tags_scale_up.isChecked()
config.setting['cover_tags_scale_down'] = self.ui.tags_scale_down.isChecked()
config.setting['cover_tags_resize_use_width'] = self.ui.tags_resize_width_label.isChecked()
config.setting['cover_tags_resize_target_width'] = self.ui.tags_resize_width_value.value()
config.setting['cover_tags_resize_use_height'] = self.ui.tags_resize_height_label.isChecked()
config.setting['cover_tags_resize_target_height'] = self.ui.tags_resize_height_value.value()
config.setting['cover_tags_stretch'] = self.ui.tags_stretch.isChecked()
config.setting['cover_tags_crop'] = self.ui.tags_crop.isChecked()
config.setting['cover_file_scale_up'] = self.ui.file_scale_up.isChecked()
config.setting['cover_file_scale_down'] = self.ui.file_scale_down.isChecked()
config.setting['cover_file_resize_use_width'] = self.ui.file_resize_width_label.isChecked()
config.setting['cover_file_resize_target_width'] = self.ui.file_resize_width_value.value()
config.setting['cover_file_resize_use_height'] = self.ui.file_resize_height_label.isChecked()
config.setting['cover_file_resize_target_height'] = self.ui.file_resize_height_value.value()
config.setting['cover_file_stretch'] = self.ui.file_stretch.isChecked()
config.setting['cover_file_crop'] = self.ui.file_crop.isChecked()
register_options_page(CoverProcessingOptionsPage)

View File

@@ -19,13 +19,13 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from copy import copy
import itertools
from PyQt6.QtCore import QBuffer
from PyQt6.QtGui import QImage
from test.picardtestcase import PicardTestCase
from picard import config
from picard.coverart.image import CoverArtImage
from picard.coverart.processing import run_image_processors
from picard.coverart.processing.filters import (
@@ -80,74 +80,197 @@ class ImageProcessorsTest(PicardTestCase):
super().setUp()
self.settings = {
'enabled_plugins': [],
'resize_images_saved_to_tags': True,
'cover_tags_maximum_width': 500,
'cover_tags_maximum_height': 500,
'resize_images_saved_to_file': True,
'cover_file_maximum_width': 750,
'cover_file_maximum_height': 750,
'cover_tags_scale_up': True,
'cover_tags_scale_down': True,
'cover_tags_resize_use_width': True,
'cover_tags_resize_target_width': 500,
'cover_tags_resize_use_height': True,
'cover_tags_resize_target_height': 500,
'cover_tags_stretch': False,
'cover_tags_crop': False,
'cover_file_scale_up': True,
'cover_file_scale_down': True,
'cover_file_resize_use_width': True,
'cover_file_resize_target_width': 750,
'cover_file_resize_use_height': True,
'cover_file_resize_target_height': 750,
'cover_file_stretch': False,
'cover_file_crop': False,
'save_images_to_tags': True,
'save_images_to_files': True,
}
def _check_image_processors(self, size, expected_tags_size, expected_file_size=None):
coverartimage = CoverArtImage()
image = create_fake_image(size[0], size[1], "jpg")
run_image_processors(image, coverartimage)
tags_size = (coverartimage.width, coverartimage.height)
if config.setting['save_images_to_tags']:
self.assertEqual(tags_size, expected_tags_size)
else:
self.assertEqual(tags_size, size)
if config.setting['save_images_to_files']:
external_cover = coverartimage.external_file_coverart
file_size = (external_cover.width, external_cover.height)
self.assertEqual(file_size, expected_file_size)
else:
self.assertIsNone(coverartimage.external_file_coverart)
extension = coverartimage.extension[1:]
self.assertEqual(extension, "jpg")
def test_image_processors_save_to_both(self):
self.set_config_values(self.settings)
self._check_image_processors((1000, 1000), (500, 500), (750, 750))
self._check_image_processors((600, 600), (500, 500), (750, 750))
self._check_image_processors((400, 400), (500, 500), (750, 750))
def test_image_processors_save_to_tags(self):
settings = copy(self.settings)
settings['save_images_to_files'] = False
self.set_config_values(settings)
self._check_image_processors((1000, 1000), (500, 500))
self._check_image_processors((600, 600), (500, 500))
self._check_image_processors((400, 400), (500, 500))
self.set_config_values(self.settings)
def test_resize(self):
sizes = [
(500, 500),
(1000, 500),
(600, 1000),
(1000, 1000),
(400, 400)
]
expected_sizes = [
(500, 500),
(500, 250),
(300, 500),
(500, 500),
(400, 400)
]
processor = ResizeImage()
for size, expected_size in zip(sizes, expected_sizes):
image = ProcessingImage(create_fake_image(size[0], size[1], "jpg"))
processor.run(image, ProcessingTarget.TAGS)
data = image.get_result("jpg")
new_image = QImage.fromData(data)
new_size = (new_image.width(), new_image.height())
self.assertEqual(new_size, expected_size)
self.assertEqual(new_size, (image.info.width, image.info.height))
def test_image_processors(self):
sizes = [
(1000, 1000),
(1000, 500),
(600, 600),
]
expected_sizes = [
((500, 500), (750, 750)),
((500, 250), (750, 375)),
((500, 500), (600, 600)),
]
def test_image_processors_save_to_file(self):
settings = copy(self.settings)
self.target_combinations = itertools.product([True, False], repeat=2)
for save_to_tags, save_to_file in self.target_combinations:
settings['save_images_to_tags'] = save_to_tags
settings['save_images_to_files'] = save_to_file
self.set_config_values(settings)
for size, expected_size in zip(sizes, expected_sizes):
coverartimage = CoverArtImage()
image = create_fake_image(size[0], size[1], "jpg")
run_image_processors(image, coverartimage)
tags_size = (coverartimage.width, coverartimage.height)
expected_size_tags = expected_size[0] if save_to_tags else size
self.assertEqual(tags_size, expected_size_tags)
if save_to_file:
external_cover = coverartimage.external_file_coverart
file_size = (external_cover.width, external_cover.height)
self.assertEqual(file_size, expected_size[1])
else:
self.assertIsNone(coverartimage.external_file_coverart)
extension = coverartimage.extension[1:]
self.assertEqual(extension, "jpg")
settings['save_images_to_tags'] = False
self.set_config_values(settings)
self._check_image_processors((1000, 1000), (1000, 1000), (750, 750))
self._check_image_processors((600, 600), (600, 600), (750, 750))
self._check_image_processors((400, 400), (400, 400), (750, 750))
self.set_config_values(self.settings)
def test_image_processors_save_to_none(self):
settings = copy(self.settings)
settings['save_images_to_tags'] = False
settings['save_images_to_files'] = False
self.set_config_values(settings)
self._check_image_processors((1000, 1000), (1000, 1000), (1000, 1000))
self.set_config_values(self.settings)
def _check_resize_image(self, size, expected_size):
image = ProcessingImage(create_fake_image(size[0], size[1], "jpg"))
processor = ResizeImage()
processor.run(image, ProcessingTarget.TAGS)
new_size = (image.get_result().width(), image.get_result().height())
new_info_size = (image.info.width, image.info.height)
self.assertEqual(new_size, expected_size)
self.assertEqual(new_info_size, expected_size)
def test_scale_down_both_dimensions(self):
self.set_config_values(self.settings)
self._check_resize_image((1000, 1000), (500, 500))
self._check_resize_image((1000, 500), (500, 250))
self._check_resize_image((600, 1200), (250, 500))
def test_scale_down_only_width(self):
settings = copy(self.settings)
settings["cover_tags_resize_use_height"] = False
self.set_config_values(settings)
self._check_resize_image((1000, 1000), (500, 500))
self._check_resize_image((1000, 500), (500, 250))
self._check_resize_image((600, 1200), (500, 1000))
self.set_config_values(self.settings)
def test_scale_down_only_height(self):
settings = copy(self.settings)
settings["cover_tags_resize_use_width"] = False
self.set_config_values(settings)
self._check_resize_image((1000, 1000), (500, 500))
self._check_resize_image((1000, 500), (1000, 500))
self._check_resize_image((600, 1200), (250, 500))
self.set_config_values(self.settings)
def test_scale_up_both_dimensions(self):
self.set_config_values(self.settings)
self._check_resize_image((250, 250), (500, 500))
self._check_resize_image((400, 500), (400, 500))
self._check_resize_image((250, 150), (500, 300))
def test_scale_up_only_width(self):
settings = copy(self.settings)
settings["cover_tags_resize_use_height"] = False
self.set_config_values(settings)
self._check_resize_image((250, 250), (500, 500))
self._check_resize_image((400, 500), (500, 625))
self._check_resize_image((500, 250), (500, 250))
self.set_config_values(self.settings)
def test_scale_up_only_height(self):
settings = copy(self.settings)
settings["cover_tags_resize_use_width"] = False
self.set_config_values(settings)
self._check_resize_image((250, 250), (500, 500))
self._check_resize_image((400, 500), (400, 500))
self._check_resize_image((500, 250), (1000, 500))
self.set_config_values(self.settings)
def test_scale_priority(self):
settings = copy(self.settings)
settings["cover_tags_resize_target_width"] = 500
settings["cover_tags_resize_target_height"] = 1000
self.set_config_values(settings)
self._check_resize_image((750, 750), (500, 500))
self.set_config_values(self.settings)
def test_stretch_both_dimensions(self):
settings = copy(self.settings)
settings["cover_tags_stretch"] = True
self.set_config_values(settings)
self._check_resize_image((1000, 100), (500, 500))
self._check_resize_image((200, 500), (500, 500))
self._check_resize_image((200, 2000), (500, 500))
self.set_config_values(self.settings)
def test_stretch_only_width(self):
settings = copy(self.settings)
settings["cover_tags_stretch"] = True
settings["cover_tags_resize_use_height"] = False
self.set_config_values(settings)
self._check_resize_image((1000, 100), (500, 100))
self._check_resize_image((200, 500), (500, 500))
self._check_resize_image((200, 2000), (500, 2000))
self.set_config_values(self.settings)
def test_stretch_only_height(self):
settings = copy(self.settings)
settings["cover_tags_stretch"] = True
settings["cover_tags_resize_use_width"] = False
self.set_config_values(settings)
self._check_resize_image((1000, 100), (1000, 500))
self._check_resize_image((200, 500), (200, 500))
self._check_resize_image((200, 2000), (200, 500))
self.set_config_values(self.settings)
def test_crop_both_dimensions(self):
settings = copy(self.settings)
settings["cover_tags_crop"] = True
self.set_config_values(settings)
self._check_resize_image((1000, 100), (500, 500))
self._check_resize_image((750, 1000), (500, 500))
self._check_resize_image((250, 1000), (500, 500))
self.set_config_values(self.settings)
def test_crop_only_width(self):
settings = copy(self.settings)
settings["cover_tags_crop"] = True
settings["cover_tags_resize_use_height"] = False
self.set_config_values(settings)
self._check_resize_image((1000, 100), (500, 100))
self._check_resize_image((750, 1000), (500, 1000))
self._check_resize_image((250, 1000), (500, 1000))
self.set_config_values(self.settings)
def test_crop_only_height(self):
settings = copy(self.settings)
settings["cover_tags_crop"] = True
settings["cover_tags_resize_use_width"] = False
self.set_config_values(settings)
self._check_resize_image((1000, 100), (1000, 500))
self._check_resize_image((750, 1000), (750, 500))
self._check_resize_image((250, 1000), (250, 500))
self.set_config_values(self.settings)
def test_identification_error(self):

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>518</width>
<height>285</height>
<width>478</width>
<height>423</height>
</rect>
</property>
<property name="windowTitle">
@@ -155,12 +155,44 @@
<string>Resize images saved to tags </string>
</property>
<property name="checkable">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="tags_scale_widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="tags_scale_up">
<property name="text">
<string>Scale up</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="tags_scale_down">
<property name="text">
<string>Scale down</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="tags_resize_width_widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
@@ -180,14 +212,14 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="tags_resized_width_label">
<widget class="QCheckBox" name="tags_resize_width_label">
<property name="text">
<string>Maximum width:</string>
<string>Width:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="tags_resized_width_value">
<widget class="QSpinBox" name="tags_resize_width_value">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -237,14 +269,14 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="tags_resized_height_label">
<widget class="QCheckBox" name="tags_resize_height_label">
<property name="text">
<string>Maximum height:</string>
<string>Height:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="tags_resized_height_value">
<widget class="QSpinBox" name="tags_resize_height_value">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -275,6 +307,51 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="tags_resize_mode" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="tags_keep">
<property name="text">
<string>Fit</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="tags_crop">
<property name="text">
<string>Fill</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="tags_stretch">
<property name="text">
<string>Stretch</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
@@ -284,12 +361,44 @@
<string>Resize images saved to files</string>
</property>
<property name="checkable">
<bool>true</bool>
<bool>false</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QWidget" name="file_scale_widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="file_scale_up">
<property name="text">
<string>Scale up</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="file_scale_down">
<property name="text">
<string>Scale down</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="file_resize_width_widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
@@ -309,14 +418,14 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="file_resized_width_label">
<widget class="QCheckBox" name="file_resize_width_label">
<property name="text">
<string>Maximum width:</string>
<string>Width:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="file_resized_width_value">
<widget class="QSpinBox" name="file_resize_width_value">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -366,14 +475,14 @@
<number>0</number>
</property>
<item>
<widget class="QLabel" name="file_resized_height_label">
<widget class="QCheckBox" name="file_resize_height_label">
<property name="text">
<string>Maximum height:</string>
<string>Height:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="file_resized_height_value">
<widget class="QSpinBox" name="file_resize_height_value">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -404,6 +513,51 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="file_resize_mode" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="file_keep">
<property name="text">
<string>Fit</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="file_crop">
<property name="text">
<string>Fill</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="file_stretch">
<property name="text">
<string>Stretch</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>