File indexing completed on 2024-05-19 04:21:29

0001 // vim: set tabstop=4 shiftwidth=4 expandtab:
0002 /*
0003 Gwenview: an image viewer
0004 Copyright 2010 Aurélien Gâteau <agateau@kde.org>
0005 
0006 This program is free software; you can redistribute it and/or
0007 modify it under the terms of the GNU General Public License
0008 as published by the Free Software Foundation; either version 2
0009 of the License, or (at your option) any later version.
0010 
0011 This program is distributed in the hope that it will be useful,
0012 but WITHOUT ANY WARRANTY; without even the implied warranty of
0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014 GNU General Public License for more details.
0015 
0016 You should have received a copy of the GNU General Public License
0017 along with this program; if not, write to the Free Software
0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.
0019 
0020 */
0021 // Self
0022 #include "resizeimagedialog.h"
0023 
0024 // Qt
0025 #include <QBuffer>
0026 #include <QDialogButtonBox>
0027 #include <QFileInfo>
0028 #include <QPushButton>
0029 #include <QTimer>
0030 
0031 // KF
0032 #include <KGuiItem>
0033 #include <KIO/Global>
0034 #include <KLocalizedString>
0035 
0036 // Local
0037 #include <lib/gwenviewconfig.h>
0038 #include <ui_resizeimagewidget.h>
0039 
0040 namespace Gwenview
0041 {
0042 struct ResizeImageDialogPrivate : public Ui_ResizeImageWidget {
0043     bool mUpdateFromRatio;
0044     bool mUpdateFromSizeOrPercentage;
0045     QSize mOriginalSize;
0046 };
0047 
0048 ResizeImageDialog::ResizeImageDialog(QWidget *parent)
0049     : QDialog(parent)
0050     , d(new ResizeImageDialogPrivate)
0051 {
0052     d->mUpdateFromRatio = false;
0053     d->mUpdateFromSizeOrPercentage = false;
0054 
0055     auto mainLayout = new QVBoxLayout;
0056     setLayout(mainLayout);
0057     mainLayout->setSizeConstraint(QLayout::SetFixedSize);
0058 
0059     auto content = new QWidget(this);
0060     d->setupUi(content);
0061     mainLayout->addWidget(content);
0062     auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0063     QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0064     okButton->setDefault(true);
0065     okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0066     connect(buttonBox, &QDialogButtonBox::accepted, this, &ResizeImageDialog::accept);
0067     connect(buttonBox, &QDialogButtonBox::rejected, this, &ResizeImageDialog::reject);
0068     mainLayout->addWidget(buttonBox);
0069 
0070     content->layout()->setContentsMargins(0, 0, 0, 0);
0071     KGuiItem::assign(okButton, KGuiItem(i18n("Resize"), QStringLiteral("transform-scale")));
0072     setWindowTitle(content->windowTitle());
0073     d->mWidthSpinBox->setFocus();
0074 
0075     d->mEstimatedSizeLabel->setToolTip(i18nc("@action",
0076                                              "Assuming that the image format won't be changed and\nfor lossy images using the "
0077                                              "value set in 'Lossy image save quality'."));
0078 
0079     connect(d->mWidthSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &ResizeImageDialog::slotWidthChanged);
0080     connect(d->mHeightSpinBox, QOverload<int>::of(&QSpinBox::valueChanged), this, &ResizeImageDialog::slotHeightChanged);
0081     connect(d->mWidthPercentSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &ResizeImageDialog::slotWidthPercentChanged);
0082     connect(d->mHeightPercentSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &ResizeImageDialog::slotHeightPercentChanged);
0083     connect(d->mWidthSpinBox, &QSpinBox::editingFinished, this, &ResizeImageDialog::slotCalculateImageSize);
0084     connect(d->mHeightSpinBox, &QSpinBox::editingFinished, this, &ResizeImageDialog::slotCalculateImageSize);
0085     connect(d->mWidthPercentSpinBox, &QSpinBox::editingFinished, this, &ResizeImageDialog::slotCalculateImageSize);
0086     connect(d->mHeightPercentSpinBox, &QSpinBox::editingFinished, this, &ResizeImageDialog::slotCalculateImageSize);
0087     connect(d->mKeepAspectCheckBox, &QCheckBox::toggled, this, &ResizeImageDialog::slotKeepAspectChanged);
0088 
0089     auto timer = new QTimer(this);
0090     connect(timer, &QTimer::timeout, this, QOverload<>::of(&ResizeImageDialog::slotCalculateImageSize));
0091     timer->start(2000);
0092 }
0093 
0094 ResizeImageDialog::~ResizeImageDialog()
0095 {
0096     delete d;
0097 }
0098 
0099 void ResizeImageDialog::setOriginalSize(const QSize &size)
0100 {
0101     d->mOriginalSize = size;
0102     d->mOriginalWidthLabel->setText(QString::number(size.width()) + QStringLiteral(" px"));
0103     d->mOriginalHeightLabel->setText(QString::number(size.height()) + QStringLiteral(" px"));
0104     d->mWidthSpinBox->setValue(size.width());
0105     d->mHeightSpinBox->setValue(size.height());
0106 }
0107 
0108 void ResizeImageDialog::setCurrentImageUrl(const QUrl &imageUrl)
0109 {
0110     mCurrentImageUrl = imageUrl;
0111 
0112     // mCurrentSize->setText has to be set after the mCurrentImageUrl has been set, otherwise it's -1
0113     QFileInfo fileInfo(mCurrentImageUrl.toLocalFile());
0114     d->mCurrentSize->setText(KIO::convertSize(fileInfo.size()));
0115     mValueChanged = false;
0116 }
0117 
0118 QSize ResizeImageDialog::size() const
0119 {
0120     return QSize(d->mWidthSpinBox->value(), d->mHeightSpinBox->value());
0121 }
0122 
0123 void ResizeImageDialog::slotWidthChanged(int width)
0124 {
0125     mValueChanged = true;
0126     // Update width percentage to match width, only if this was a manual adjustment
0127     if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) {
0128         d->mUpdateFromSizeOrPercentage = true;
0129         d->mWidthPercentSpinBox->setValue((double(width) / d->mOriginalSize.width()) * 100);
0130         d->mUpdateFromSizeOrPercentage = false;
0131     }
0132 
0133     if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) {
0134         return;
0135     }
0136 
0137     // Update height to keep ratio, only if ratio locked and this was a manual adjustment
0138     d->mUpdateFromRatio = true;
0139     d->mHeightSpinBox->setValue(d->mOriginalSize.height() * width / d->mOriginalSize.width());
0140     d->mUpdateFromRatio = false;
0141 }
0142 
0143 void ResizeImageDialog::slotHeightChanged(int height)
0144 {
0145     mValueChanged = true;
0146     // Update height percentage to match height, only if this was a manual adjustment
0147     if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) {
0148         d->mUpdateFromSizeOrPercentage = true;
0149         d->mHeightPercentSpinBox->setValue((double(height) / d->mOriginalSize.height()) * 100);
0150         d->mUpdateFromSizeOrPercentage = false;
0151     }
0152 
0153     if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) {
0154         return;
0155     }
0156 
0157     // Update width to keep ratio, only if ratio locked and this was a manual adjustment
0158     d->mUpdateFromRatio = true;
0159     d->mWidthSpinBox->setValue(d->mOriginalSize.width() * height / d->mOriginalSize.height());
0160     d->mUpdateFromRatio = false;
0161 }
0162 
0163 void ResizeImageDialog::slotWidthPercentChanged(double widthPercent)
0164 {
0165     mValueChanged = true;
0166     // Update width to match width percentage, only if this was a manual adjustment
0167     if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) {
0168         d->mUpdateFromSizeOrPercentage = true;
0169         d->mWidthSpinBox->setValue((widthPercent / 100) * d->mOriginalSize.width());
0170         d->mUpdateFromSizeOrPercentage = false;
0171     }
0172 
0173     if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) {
0174         return;
0175     }
0176 
0177     // Keep height percentage in sync with width percentage, only if ratio locked and this was a manual adjustment
0178     d->mUpdateFromRatio = true;
0179     d->mHeightPercentSpinBox->setValue(d->mWidthPercentSpinBox->value());
0180     d->mUpdateFromRatio = false;
0181 }
0182 
0183 void ResizeImageDialog::slotHeightPercentChanged(double heightPercent)
0184 {
0185     mValueChanged = true;
0186     // Update height to match height percentage, only if this was a manual adjustment
0187     if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) {
0188         d->mUpdateFromSizeOrPercentage = true;
0189         d->mHeightSpinBox->setValue((heightPercent / 100) * d->mOriginalSize.height());
0190         d->mUpdateFromSizeOrPercentage = false;
0191     }
0192 
0193     if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) {
0194         return;
0195     }
0196 
0197     // Keep height percentage in sync with width percentage, only if ratio locked and this was a manual adjustment
0198     d->mUpdateFromRatio = true;
0199     d->mWidthPercentSpinBox->setValue(d->mHeightPercentSpinBox->value());
0200     d->mUpdateFromRatio = false;
0201 }
0202 
0203 void ResizeImageDialog::slotKeepAspectChanged(bool value)
0204 {
0205     if (value) {
0206         d->mUpdateFromSizeOrPercentage = true;
0207         slotWidthChanged(d->mWidthSpinBox->value());
0208         slotWidthPercentChanged(d->mWidthPercentSpinBox->value());
0209         d->mUpdateFromSizeOrPercentage = false;
0210     }
0211 }
0212 
0213 void ResizeImageDialog::slotCalculateImageSize()
0214 {
0215     qint64 size = calculateEstimatedImageSize();
0216     if (size == -1) {
0217         return;
0218     } else if (size == -2) {
0219         d->mEstimatedSize->setText(i18n("error"));
0220     } else {
0221         d->mEstimatedSize->setText(KIO::convertSize(size));
0222     }
0223 }
0224 
0225 qint64 ResizeImageDialog::calculateEstimatedImageSize()
0226 {
0227     if (mValueChanged) {
0228         QImage image(mCurrentImageUrl.toLocalFile());
0229         if (image.isNull()) {
0230             return -2;
0231         }
0232 
0233         QByteArray ba;
0234         QBuffer buffer(&ba);
0235         QFileInfo fileInfo(mCurrentImageUrl.toLocalFile());
0236         QString suffix = fileInfo.suffix();
0237 
0238         buffer.open(QIODevice::ReadWrite);
0239         image = image.scaled(size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0240 
0241         if (QString::compare(suffix, QStringLiteral("jpg"), Qt::CaseInsensitive) == 0
0242             || QString::compare(suffix, QStringLiteral("jpeg"), Qt::CaseInsensitive) == 0
0243             || QString::compare(suffix, QStringLiteral("avif"), Qt::CaseInsensitive) == 0
0244             || QString::compare(suffix, QStringLiteral("heic"), Qt::CaseInsensitive) == 0
0245             || QString::compare(suffix, QStringLiteral("webp"), Qt::CaseInsensitive) == 0) {
0246             image.save(&buffer, suffix.toStdString().c_str(), GwenviewConfig::jPEGQuality());
0247         } else {
0248             image.save(&buffer, suffix.toStdString().c_str());
0249         }
0250 
0251         qint64 size = buffer.size();
0252         buffer.close();
0253 
0254         mValueChanged = false;
0255         return size;
0256     }
0257     return -1;
0258 }
0259 
0260 } // namespace
0261 
0262 #include "moc_resizeimagedialog.cpp"