Allow scaling and converting assigned covers

This commit is contained in:
Martchus 2019-07-21 23:44:37 +02:00
parent 955c497a52
commit 865b3501c4
4 changed files with 308 additions and 52 deletions

View File

@ -125,7 +125,8 @@ set(WIDGETS_UI_FILES
gui/editortempoptionpage.ui
gui/filelayout.ui
gui/tageditorwidget.ui
gui/dbquerywidget.ui)
gui/dbquerywidget.ui
gui/imageconversiondialog.ui)
set(TEST_HEADER_FILES)
set(TEST_SRC_FILES tests/cli.cpp)
@ -187,7 +188,8 @@ set(REQUIRED_ICONS
tag-delete
system-file-manager
document-save
view-media-lyrics)
view-media-lyrics
image-resize-symbolic)
# find c++utilities
set(CONFIGURATION_PACKAGE_SUFFIX

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QtGui::ImageConversionDialog</class>
<widget class="QDialog" name="QtGui::ImageConversionDialog">
<property name="windowTitle">
<string>Convert image</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<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="QWidget" name="mainWidget" native="true">
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QWidget" name="levelWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<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="QSpinBox" name="widthSpinBox">
<property name="accelerated">
<bool>true</bool>
</property>
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>2048</number>
</property>
<property name="value">
<number>512</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timesLabel">
<property name="text">
<string>x</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="heightSpinBox">
<property name="suffix">
<string> px</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>2048</number>
</property>
<property name="value">
<number>512</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="formatLabel">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="formatComboBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="aspectRatioLabel">
<property name="text">
<string>Aspect ratio</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="aspectRatioComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="bottomWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>168</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="abortPushButton">
<property name="styleSheet">
<string notr="true">background: none;</string>
</property>
<property name="text">
<string>Abort</string>
</property>
<property name="icon">
<iconset theme="window-close">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="confirmPushButton">
<property name="styleSheet">
<string notr="true">background: none;</string>
</property>
<property name="text">
<string>Confirm</string>
</property>
<property name="icon">
<iconset theme="go-next">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -3,6 +3,7 @@
#include "../application/settings.h"
#include "../misc/utility.h"
#include "ui_imageconversiondialog.h"
#include "ui_picturepreviewselection.h"
#include <tagparser/diagnostics.h>
@ -14,6 +15,7 @@
#include <tagparser/vorbis/vorbiscommentfield.h>
#include <qtutilities/misc/conversion.h>
#include <qtutilities/misc/dialogutils.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/misc/traits.h>
@ -59,6 +61,7 @@ namespace QtGui {
PicturePreviewSelection::PicturePreviewSelection(Tag *tag, KnownField field, QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::PicturePreviewSelection)
, m_imageConversionDialog(nullptr)
, m_scene(nullptr)
, m_textItem(nullptr)
, m_pixmapItem(nullptr)
@ -249,6 +252,71 @@ void PicturePreviewSelection::updateSizeAndMimeType(size_t fileSize, const QSize
setToolTip(info.join(QChar('\n')));
}
/*!
* \brief Assigns the specified \a image to the specified \a tagValue using the specified \a format.
* \remarks Shows a message box if an error occurs and returns a "null" QImage.
*/
QImage PicturePreviewSelection::convertTagValueToImage(const TagValue &value)
{
QImage img;
if (value.mimeType() == "-->") {
const auto fileName(Utility::stringToQString(value.toString(), value.dataEncoding()));
QFile file(fileName);
if (file.open(QFile::ReadOnly)) {
img = QImage::fromData(file.readAll());
} else {
QMessageBox::warning(this, QCoreApplication::applicationName(),
tr("The attached image can't be found. It is supposed to be stored as external file \"%1\".").arg(fileName));
return img;
}
} else if (value.dataSize() < numeric_limits<int>::max()) {
img = QImage::fromData(reinterpret_cast<const uchar *>(value.dataPointer()), static_cast<int>(value.dataSize()));
}
if (img.isNull()) {
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The attached image format is not supported."));
}
return img;
}
/*!
* \brief Assigns the specified \a image to the specified \a tagValue using the specified \a format.
* \remarks Shows a message box if an error occurs.
*/
void PicturePreviewSelection::assignImageToTagValue(const QImage &image, TagValue &tagValue, const char *format)
{
// set default MIME type
QString mimeType;
if (strcmp(format, "JPEG") == 0) {
mimeType = QStringLiteral("image/jpeg");
} else if (strcmp(format, "PNG") == 0) {
mimeType = QStringLiteral("image/png");
}
// save image to buffer
QByteArray imageData;
QBuffer buffer(&imageData);
buffer.open(QIODevice::WriteOnly);
if (!image.save(&buffer, format)) {
QMessageBox::critical(this, QCoreApplication::applicationName(), tr("Unable to save image from clipboard."));
return;
}
// ask for MIME type
if (mimeType.isEmpty()) {
bool ok;
mimeType
= QInputDialog::getText(this, tr("Enter MIME type"), tr("Enter the MIME type for the pasted image."), QLineEdit::Normal, mimeType, &ok);
if (!ok) {
return;
}
}
// assign image
const auto mimeTypeUtf8(mimeType.toUtf8());
tagValue.assignData(imageData.data(), static_cast<size_t>(imageData.size()), TagDataType::Picture);
tagValue.setMimeType(mimeTypeUtf8.constData());
}
/*!
* \brief Pushes the ID3v2 cover values to the specified \a tag.
* \param tag Specifies a tag to push the values to.
@ -405,39 +473,9 @@ void PicturePreviewSelection::pasteOfSelectedType(const char *format)
return;
}
// set default MIME type
QString mimeType;
if (strcmp(format, "JPEG") == 0) {
mimeType = QStringLiteral("image/jpeg");
} else if (strcmp(format, "PNG") == 0) {
mimeType = QStringLiteral("image/png");
}
// save image to buffer
QByteArray imageData;
QBuffer buffer(&imageData);
buffer.open(QIODevice::WriteOnly);
if (!image.save(&buffer, format)) {
QMessageBox::critical(this, QCoreApplication::applicationName(), tr("Unable to save image from clipboard."));
return;
}
// ask for MIME type
if (mimeType.isEmpty()) {
bool ok;
mimeType
= QInputDialog::getText(this, tr("Enter MIME type"), tr("Enter the MIME type for the pasted image."), QLineEdit::Normal, mimeType, &ok);
if (!ok) {
return;
}
}
// assign image
assert(m_currentTypeIndex < m_values.size());
TagValue &selectedCover = m_values[m_currentTypeIndex];
const auto mimeTypeUtf8(mimeType.toUtf8());
selectedCover.assignData(imageData.data(), static_cast<size_t>(imageData.size()), TagDataType::Picture);
selectedCover.setMimeType(mimeTypeUtf8.constData());
assignImageToTagValue(image, m_values[m_currentTypeIndex], format);
updatePreview(m_currentTypeIndex);
}
@ -503,22 +541,8 @@ void PicturePreviewSelection::displaySelected()
}
// load image
QImage img;
if (value.mimeType() == "-->") {
const auto fileName(Utility::stringToQString(value.toString(), value.dataEncoding()));
QFile file(fileName);
if (file.open(QFile::ReadOnly)) {
img = QImage::fromData(file.readAll());
} else {
QMessageBox::warning(this, QCoreApplication::applicationName(),
tr("The attached image can't be found. It is supposed to be stored as external file \"%1\".").arg(fileName));
return;
}
} else if (value.dataSize() < numeric_limits<int>::max()) {
img = QImage::fromData(reinterpret_cast<const uchar *>(value.dataPointer()), static_cast<int>(value.dataSize()));
}
const auto img = convertTagValueToImage(value);
if (img.isNull()) {
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The attached image can't be displayed."));
return;
}
@ -550,8 +574,10 @@ void PicturePreviewSelection::changeMimeTypeOfSelected()
auto &selectedCover = m_values[m_currentTypeIndex];
auto mimeType = QString::fromUtf8(selectedCover.mimeType().data());
bool ok;
mimeType = QInputDialog::getText(
this, tr("Enter/confirm mime type"), tr("Confirm or enter the mime type of the selected file."), QLineEdit::Normal, mimeType, &ok);
mimeType = QInputDialog::getText(this, tr("Enter/confirm MIME type"),
tr("Confirm or enter the MIME type of the selected file. This merely changes the <i>assumed</i> format. <i>No</i> image format conversion "
"done."),
QLineEdit::Normal, mimeType, &ok);
if (!ok) {
return;
}
@ -559,6 +585,51 @@ void PicturePreviewSelection::changeMimeTypeOfSelected()
updateSizeAndMimeType(m_currentFileSize, m_currentResolution, mimeType);
}
void PicturePreviewSelection::convertSelected()
{
assert(m_currentTypeIndex < m_values.size());
auto &selectedCover = m_values[m_currentTypeIndex];
// load image
const auto img = convertTagValueToImage(selectedCover);
if (img.isNull()) {
return;
}
// show image conversion dialog
if (!m_imageConversionDialog) {
m_imageConversionDialog = new QDialog(this);
m_imageConversionUI = make_unique<Ui::ImageConversionDialog>();
m_imageConversionUI->setupUi(m_imageConversionDialog);
#ifdef Q_OS_WIN32
m_imageConversionDialog->setStyleSheet(dialogStyle());
#endif
m_imageConversionUI->formatComboBox->addItems({ tr("JPEG"), tr("PNG") });
m_imageConversionUI->aspectRatioComboBox->addItems({ tr("Ignore"), tr("Keep"), tr("Keep by expanding") });
m_imageConversionUI->aspectRatioComboBox->setCurrentIndex(1);
connect(m_imageConversionUI->confirmPushButton, &QPushButton::clicked, m_imageConversionDialog, &QDialog::accept);
connect(m_imageConversionUI->abortPushButton, &QPushButton::clicked, m_imageConversionDialog, &QDialog::reject);
}
m_imageConversionUI->widthSpinBox->setValue(img.width());
m_imageConversionUI->heightSpinBox->setValue(img.height());
if (m_imageConversionDialog->exec() != QDialog::Accepted) {
return;
}
// scale image
const auto scaledImg = img.scaled(m_imageConversionUI->widthSpinBox->value(), m_imageConversionUI->heightSpinBox->value(),
static_cast<Qt::AspectRatioMode>(m_imageConversionUI->aspectRatioComboBox->currentIndex()), Qt::SmoothTransformation);
if (scaledImg.isNull()) {
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("Unable to scale image."));
return;
}
// assign image
assignImageToTagValue(scaledImg, selectedCover, m_imageConversionUI->formatComboBox->currentIndex() == 0 ? "JPEG" : "PNG");
updatePreview(m_currentTypeIndex);
}
/*!
* \brief Sets whether cover buttons are hidden.
*/
@ -760,9 +831,12 @@ void PicturePreviewSelection::showContextMenu(const QPoint &position)
}
#endif
if (m_ui->extractButton->isEnabled()) {
QAction *mimeAction = menu.addAction(tr("Change MIME-type"));
auto *const mimeAction = menu.addAction(tr("Change MIME-type"));
mimeAction->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
connect(mimeAction, &QAction::triggered, this, &PicturePreviewSelection::changeMimeTypeOfSelected);
auto *const convertAction = menu.addAction(tr("Resize/convert assigned image"));
convertAction->setIcon(QIcon::fromTheme(QStringLiteral("image-resize-symbolic")));
connect(convertAction, &QAction::triggered, this, &PicturePreviewSelection::convertSelected);
}
menu.addSeparator();
if (m_ui->removeButton->isEnabled()) {

View File

@ -30,7 +30,8 @@ TAGEDITOR_ENUM_CLASS PreviousValueHandling : int;
namespace Ui {
class PicturePreviewSelection;
}
class ImageConversionDialog;
} // namespace Ui
class PicturePreviewSelection : public QWidget {
Q_OBJECT
@ -60,6 +61,7 @@ public slots:
void extractSelected();
void displaySelected();
void changeMimeTypeOfSelected();
void convertSelected();
void setCoverButtonsHidden(bool hideCoverButtons);
signals:
@ -81,8 +83,12 @@ private slots:
private:
bool setup(PreviousValueHandling previousValueHandling = PreviousValueHandling::Clear);
void updateSizeAndMimeType(std::size_t fileSize, const QSize &resolution, const QString &mimeType);
QImage convertTagValueToImage(const TagParser::TagValue &value);
void assignImageToTagValue(const QImage &image, TagParser::TagValue &tagValue, const char *format);
std::unique_ptr<Ui::PicturePreviewSelection> m_ui;
std::unique_ptr<Ui::ImageConversionDialog> m_imageConversionUI;
QDialog *m_imageConversionDialog;
QGraphicsScene *m_scene;
QGraphicsTextItem *m_textItem;
QPixmap m_pixmap;