mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 09:18:18 +00:00
Add option to stretch or crop
This commit is contained in:
@@ -55,46 +55,64 @@ class ResizeImage(ImageProcessor):
|
||||
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)
|
||||
return same_size and same_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()
|
||||
target_width = image.info.width
|
||||
target_height = image.info.height
|
||||
if target == ProcessingTarget.TAGS:
|
||||
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']
|
||||
if use_width:
|
||||
target_width = config.setting['cover_tags_resize_target_width']
|
||||
target_width = config.setting['cover_tags_resize_target_width']
|
||||
use_height = config.setting['cover_tags_resize_use_height']
|
||||
if use_height:
|
||||
target_height = config.setting['cover_tags_resize_target_height']
|
||||
target_height = config.setting['cover_tags_resize_target_height']
|
||||
stretch = config.setting["cover_tags_stretch"]
|
||||
crop = config.setting["cover_tags_crop"]
|
||||
else:
|
||||
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']
|
||||
if use_width:
|
||||
target_width = config.setting['cover_file_resize_target_width']
|
||||
target_width = config.setting['cover_file_resize_target_width']
|
||||
use_height = config.setting['cover_file_resize_use_height']
|
||||
if use_height:
|
||||
target_height = config.setting['cover_file_resize_target_height']
|
||||
target_height = config.setting['cover_file_resize_target_height']
|
||||
stretch = config.setting["cover_file_stretch"]
|
||||
crop = config.setting["cover_file_crop"]
|
||||
|
||||
if scale_down and (image.info.width > target_width or image.info.height > target_height):
|
||||
aspect_ratio = Qt.AspectRatioMode.KeepAspectRatio
|
||||
elif scale_up and (image.info.width < target_width or image.info.height < target_height):
|
||||
aspect_ratio = Qt.AspectRatioMode.KeepAspectRatioByExpanding
|
||||
else:
|
||||
width_scale_factor = 1
|
||||
width_resize = image.info.width
|
||||
if use_width:
|
||||
width_scale_factor = target_width / image.info.width
|
||||
width_resize = target_width
|
||||
height_scale_factor = 1
|
||||
height_resize = image.info.height
|
||||
if use_height:
|
||||
height_scale_factor = target_height / image.info.height
|
||||
height_resize = target_height
|
||||
if (width_scale_factor == 1 and height_scale_factor == 1
|
||||
or ((width_scale_factor > 1 and 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()
|
||||
if use_width and use_height:
|
||||
scaled_image = qimage.scaled(target_width, target_height, aspect_ratio)
|
||||
elif use_width:
|
||||
scaled_image = qimage.scaledToWidth(target_width)
|
||||
else:
|
||||
scaled_image = qimage.scaledToHeight(target_height)
|
||||
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_width and use_height:
|
||||
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",
|
||||
|
||||
@@ -181,12 +181,16 @@ 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
|
||||
|
||||
@@ -17,7 +17,7 @@ from picard.i18n import gettext as _
|
||||
class Ui_CoverProcessingOptionsPage(object):
|
||||
def setupUi(self, CoverProcessingOptionsPage):
|
||||
CoverProcessingOptionsPage.setObjectName("CoverProcessingOptionsPage")
|
||||
CoverProcessingOptionsPage.resize(478, 361)
|
||||
CoverProcessingOptionsPage.resize(478, 423)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(CoverProcessingOptionsPage)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.filtering = QtWidgets.QGroupBox(parent=CoverProcessingOptionsPage)
|
||||
@@ -161,6 +161,23 @@ 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.verticalLayout_5 = QtWidgets.QVBoxLayout(self.tags_resize_mode)
|
||||
self.verticalLayout_5.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout_5.setSpacing(2)
|
||||
self.verticalLayout_5.setObjectName("verticalLayout_5")
|
||||
self.tags_keep = QtWidgets.QRadioButton(parent=self.tags_resize_mode)
|
||||
self.tags_keep.setChecked(True)
|
||||
self.tags_keep.setObjectName("tags_keep")
|
||||
self.verticalLayout_5.addWidget(self.tags_keep)
|
||||
self.tags_crop = QtWidgets.QRadioButton(parent=self.tags_resize_mode)
|
||||
self.tags_crop.setObjectName("tags_crop")
|
||||
self.verticalLayout_5.addWidget(self.tags_crop)
|
||||
self.tags_stretch = QtWidgets.QRadioButton(parent=self.tags_resize_mode)
|
||||
self.tags_stretch.setObjectName("tags_stretch")
|
||||
self.verticalLayout_5.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(False)
|
||||
@@ -236,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.verticalLayout_6 = QtWidgets.QVBoxLayout(self.file_resize_mode)
|
||||
self.verticalLayout_6.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout_6.setSpacing(2)
|
||||
self.verticalLayout_6.setObjectName("verticalLayout_6")
|
||||
self.file_keep = QtWidgets.QRadioButton(parent=self.file_resize_mode)
|
||||
self.file_keep.setChecked(True)
|
||||
self.file_keep.setObjectName("file_keep")
|
||||
self.verticalLayout_6.addWidget(self.file_keep)
|
||||
self.file_crop = QtWidgets.QRadioButton(parent=self.file_resize_mode)
|
||||
self.file_crop.setObjectName("file_crop")
|
||||
self.verticalLayout_6.addWidget(self.file_crop)
|
||||
self.file_stretch = QtWidgets.QRadioButton(parent=self.file_resize_mode)
|
||||
self.file_stretch.setObjectName("file_stretch")
|
||||
self.verticalLayout_6.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)
|
||||
@@ -259,6 +293,9 @@ class Ui_CoverProcessingOptionsPage(object):
|
||||
self.px_label5.setText(_("px"))
|
||||
self.tags_resize_height_label.setText(_("Height:"))
|
||||
self.px_label6.setText(_("px"))
|
||||
self.tags_keep.setText(_("Keep aspect ratio"))
|
||||
self.tags_crop.setText(_("Crop scaled overflow"))
|
||||
self.tags_stretch.setText(_("Stretch/shrink to target"))
|
||||
self.save_to_file.setTitle(_("Resize images saved to files"))
|
||||
self.file_scale_up.setText(_("Scale up"))
|
||||
self.file_scale_down.setText(_("Scale down"))
|
||||
@@ -266,3 +303,6 @@ class Ui_CoverProcessingOptionsPage(object):
|
||||
self.px_label3.setText(_("px"))
|
||||
self.file_resize_height_label.setText(_("Height:"))
|
||||
self.px_label4.setText(_("px"))
|
||||
self.file_keep.setText(_("Keep aspect ratio"))
|
||||
self.file_crop.setText(_("Crop scaled overflow"))
|
||||
self.file_stretch.setText(_("Stretch/shrink to target"))
|
||||
|
||||
@@ -50,12 +50,16 @@ class CoverProcessingOptionsPage(OptionsPage):
|
||||
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')
|
||||
|
||||
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)
|
||||
@@ -96,12 +100,16 @@ class CoverProcessingOptionsPage(OptionsPage):
|
||||
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()
|
||||
@@ -114,12 +122,16 @@ class CoverProcessingOptionsPage(OptionsPage):
|
||||
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)
|
||||
|
||||
@@ -86,12 +86,16 @@ class ImageProcessorsTest(PicardTestCase):
|
||||
'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,
|
||||
}
|
||||
@@ -182,8 +186,8 @@ class ImageProcessorsTest(PicardTestCase):
|
||||
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), (500, 625))
|
||||
self._check_resize_image((500, 250), (1000, 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)
|
||||
@@ -211,6 +215,64 @@ class ImageProcessorsTest(PicardTestCase):
|
||||
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):
|
||||
image = create_fake_image(0, 0, "jpg")
|
||||
coverartimage = CoverArtImage()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>478</width>
|
||||
<height>361</height>
|
||||
<height>423</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -307,6 +307,51 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="tags_resize_mode" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<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>Keep aspect ratio</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="tags_crop">
|
||||
<property name="text">
|
||||
<string>Crop scaled overflow</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="tags_stretch">
|
||||
<property name="text">
|
||||
<string>Stretch/shrink to target</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -468,6 +513,51 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="file_resize_mode" native="true">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<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>Keep aspect ratio</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="file_crop">
|
||||
<property name="text">
|
||||
<string>Crop scaled overflow</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="file_stretch">
|
||||
<property name="text">
|
||||
<string>Stretch/shrink to target</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
Reference in New Issue
Block a user