File indexing completed on 2025-01-19 03:51:13
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2004-11-28 0007 * Description : a digiKam image editor tool to process image 0008 * free rotation. 0009 * 0010 * SPDX-FileCopyrightText: 2004-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2009-2010 by Andi Clemens <andi dot clemens at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "freerotationtool.h" 0018 0019 // C++ includes 0020 0021 #include <cmath> 0022 0023 // Qt includes 0024 0025 #include <QGridLayout> 0026 #include <QImage> 0027 #include <QLabel> 0028 #include <QPainter> 0029 #include <QPushButton> 0030 #include <QIcon> 0031 #include <QStyle> 0032 #include <QApplication> 0033 0034 // KDE includes 0035 0036 #include <klocalizedstring.h> 0037 #include <ksharedconfig.h> 0038 #include <kconfiggroup.h> 0039 0040 // Local includes 0041 0042 #include "dimg.h" 0043 #include "dexpanderbox.h" 0044 #include "editortoolsettings.h" 0045 #include "freerotationfilter.h" 0046 #include "freerotationsettings.h" 0047 #include "imageiface.h" 0048 #include "imageguidewidget.h" 0049 0050 namespace DigikamEditorFreeRotationToolPlugin 0051 { 0052 0053 class Q_DECL_HIDDEN FreeRotationTool::Private 0054 { 0055 public: 0056 0057 explicit Private() 0058 : configGroupName (QLatin1String("freerotation Tool")), 0059 newHeightLabel (nullptr), 0060 newWidthLabel (nullptr), 0061 autoAdjustBtn (nullptr), 0062 autoAdjustPoint1Btn (nullptr), 0063 autoAdjustPoint2Btn (nullptr), 0064 settingsView (nullptr), 0065 expanderBox (nullptr), 0066 gboxSettings (nullptr), 0067 previewWidget (nullptr) 0068 { 0069 } 0070 0071 const QString configGroupName; 0072 0073 QLabel* newHeightLabel; 0074 QLabel* newWidthLabel; 0075 0076 QPoint autoAdjustPoint1; 0077 QPoint autoAdjustPoint2; 0078 0079 QPushButton* autoAdjustBtn; 0080 QPushButton* autoAdjustPoint1Btn; 0081 QPushButton* autoAdjustPoint2Btn; 0082 0083 FreeRotationSettings* settingsView; 0084 0085 DExpanderBox* expanderBox; 0086 EditorToolSettings* gboxSettings; 0087 ImageGuideWidget* previewWidget; 0088 }; 0089 0090 FreeRotationTool::FreeRotationTool(QObject* const parent) 0091 : EditorToolThreaded(parent), 0092 d (new Private) 0093 { 0094 setObjectName(QLatin1String("freerotation")); 0095 0096 d->previewWidget = new ImageGuideWidget(nullptr, true, ImageGuideWidget::HVGuideMode); 0097 d->previewWidget->setWhatsThis(i18n("This is the free rotation operation preview. " 0098 "If you move the mouse cursor on this preview, " 0099 "a vertical and horizontal dashed line will be drawn " 0100 "to guide you in adjusting the free rotation correction. " 0101 "Release the left mouse button to freeze the dashed " 0102 "line's position.")); 0103 0104 setToolView(d->previewWidget); 0105 setPreviewModeMask(PreviewToolBar::UnSplitPreviewModes); 0106 0107 // ------------------------------------------------------------- 0108 0109 QString temp; 0110 ImageIface iface; 0111 0112 d->gboxSettings = new EditorToolSettings(nullptr); 0113 d->gboxSettings->setTools(EditorToolSettings::ColorGuide); 0114 0115 QLabel* const label1 = new QLabel(i18n("New width:")); 0116 d->newWidthLabel = new QLabel(temp.setNum(iface.originalSize().width()) + i18n(" px")); 0117 d->newWidthLabel->setAlignment(Qt::AlignBottom | Qt::AlignRight); 0118 0119 QLabel* const label2 = new QLabel(i18n("New height:")); 0120 d->newHeightLabel = new QLabel(temp.setNum(iface.originalSize().height()) + i18n(" px")); 0121 d->newHeightLabel->setAlignment(Qt::AlignBottom | Qt::AlignRight); 0122 0123 // ------------------------------------------------------------- 0124 0125 QString btnWhatsThis = i18n("Select a point in the preview widget, " 0126 "then click this button to assign the point for auto-correction."); 0127 0128 QColor textColor = qApp->palette().color(QPalette::Active, QPalette::Text); 0129 0130 QPixmap pm1 = generateBtnPixmap(QLatin1String("1"), textColor); 0131 d->autoAdjustPoint1Btn = new QPushButton; 0132 d->autoAdjustPoint1Btn->setIcon(pm1); 0133 d->autoAdjustPoint1Btn->setSizePolicy(QSizePolicy::MinimumExpanding, 0134 QSizePolicy::MinimumExpanding); 0135 0136 QPixmap pm2 = generateBtnPixmap(QLatin1String("2"), textColor); 0137 d->autoAdjustPoint2Btn = new QPushButton; 0138 d->autoAdjustPoint2Btn->setIcon(pm2); 0139 d->autoAdjustPoint2Btn->setSizePolicy(QSizePolicy::MinimumExpanding, 0140 QSizePolicy::MinimumExpanding); 0141 0142 d->autoAdjustPoint1Btn->setToolTip(btnWhatsThis); 0143 d->autoAdjustPoint1Btn->setWhatsThis(btnWhatsThis); 0144 d->autoAdjustPoint2Btn->setToolTip(btnWhatsThis); 0145 d->autoAdjustPoint2Btn->setWhatsThis(btnWhatsThis); 0146 0147 // -------------------------------------------------------- 0148 0149 // try to determine the maximum text width, to set the button minwidth 0150 QPoint p; 0151 setPointInvalid(p); 0152 QString invalidText = generateButtonLabel(p); 0153 p.setX(1); 0154 p.setY(2); 0155 QString validText = generateButtonLabel(p); 0156 0157 QFont fnt = d->autoAdjustPoint1Btn->font(); 0158 QFontMetrics fm(fnt); 0159 0160 const int offset = (pm1.width() * 2) + 10; 0161 int minWidth1 = fm.horizontalAdvance(invalidText) + offset; 0162 int minWidth2 = fm.horizontalAdvance(validText) + offset; 0163 int minWidth = qMax<int>(minWidth1, minWidth2); 0164 0165 // set new minwidth 0166 d->autoAdjustPoint1Btn->setMinimumWidth(minWidth); 0167 d->autoAdjustPoint2Btn->setMinimumWidth(minWidth); 0168 0169 // -------------------------------------------------------- 0170 0171 d->autoAdjustPoint1Btn->setText(invalidText); 0172 d->autoAdjustPoint2Btn->setText(invalidText); 0173 0174 d->autoAdjustBtn = new QPushButton(i18nc("Automatic Adjustment", "Adjust")); 0175 d->autoAdjustBtn->setSizePolicy(QSizePolicy::MinimumExpanding, 0176 QSizePolicy::Expanding); 0177 0178 // -------------------------------------------------------- 0179 0180 QWidget* const autoAdjustContainer = new QWidget; 0181 QGridLayout* const containerLayout2 = new QGridLayout; 0182 QLabel* const autoDescr = new QLabel; 0183 autoDescr->setText(i18n("<p>Correct the rotation of your images automatically by assigning two points in the " 0184 "preview widget and clicking <i>Adjust</i>.<br/>" 0185 "You can either adjust horizontal or vertical lines.</p>")); 0186 autoDescr->setAlignment(Qt::AlignJustify); 0187 autoDescr->setWordWrap(true); 0188 0189 const int cmargin = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutLeftMargin), 0190 qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutTopMargin), 0191 qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutRightMargin), 0192 QApplication::style()->pixelMetric(QStyle::PM_LayoutBottomMargin)))); 0193 0194 containerLayout2->addWidget(autoDescr, 0, 0, 1, -1); 0195 containerLayout2->addWidget(d->autoAdjustPoint1Btn, 1, 0, 1, 1); 0196 containerLayout2->addWidget(d->autoAdjustBtn, 1, 2, 2, 1); 0197 containerLayout2->addWidget(d->autoAdjustPoint2Btn, 2, 0, 1, 1); 0198 containerLayout2->setColumnStretch(1, 10); 0199 containerLayout2->setContentsMargins(cmargin, cmargin, cmargin, cmargin); 0200 autoAdjustContainer->setLayout(containerLayout2); 0201 0202 // ------------------------------------------------------------- 0203 0204 DLineWidget* const line = new DLineWidget(Qt::Horizontal); 0205 d->settingsView = new FreeRotationSettings(d->gboxSettings->plainPage()); 0206 d->expanderBox = new DExpanderBox; 0207 d->expanderBox->setObjectName(QLatin1String("FreeRotationTool Expander")); 0208 d->expanderBox->addItem(autoAdjustContainer, QIcon::fromTheme(QLatin1String("transform-rotate")), i18n("Automatic Adjustment"), 0209 QLatin1String("AutoAdjustContainer"), true); 0210 d->expanderBox->addItem(d->settingsView, QIcon::fromTheme(QLatin1String("transform-rotate")), i18n("Settings"), 0211 QLatin1String("SettingsContainer"), true); 0212 d->expanderBox->addStretch(); 0213 0214 // ------------------------------------------------------------- 0215 0216 const int spacing = d->gboxSettings->spacingHint(); 0217 0218 QGridLayout* const grid2 = new QGridLayout; 0219 grid2->addWidget(label1, 0, 0, 1, 1); 0220 grid2->addWidget(d->newWidthLabel, 0, 1, 1, 1); 0221 grid2->addWidget(label2, 1, 0, 1, 1); 0222 grid2->addWidget(d->newHeightLabel, 1, 1, 1, 1); 0223 grid2->addWidget(line, 2, 0, 1, -1); 0224 grid2->addWidget(d->expanderBox, 3, 0, 1, -1); 0225 grid2->setRowStretch(3, 10); 0226 grid2->setContentsMargins(spacing, spacing, spacing, spacing); 0227 grid2->setSpacing(spacing); 0228 d->gboxSettings->plainPage()->setLayout(grid2); 0229 0230 setToolSettings(d->gboxSettings); 0231 0232 // ------------------------------------------------------------- 0233 0234 connect(d->settingsView, SIGNAL(signalSettingsChanged()), 0235 this, SLOT(slotTimer())); 0236 0237 connect(d->gboxSettings, SIGNAL(signalColorGuideChanged()), 0238 this, SLOT(slotColorGuideChanged())); 0239 0240 connect(d->autoAdjustPoint1Btn, SIGNAL(clicked()), 0241 this, SLOT(slotAutoAdjustP1Clicked())); 0242 0243 connect(d->autoAdjustPoint2Btn, SIGNAL(clicked()), 0244 this, SLOT(slotAutoAdjustP2Clicked())); 0245 0246 connect(d->autoAdjustBtn, SIGNAL(clicked()), 0247 this, SLOT(slotAutoAdjustClicked())); 0248 } 0249 0250 FreeRotationTool::~FreeRotationTool() 0251 { 0252 delete d; 0253 } 0254 0255 void FreeRotationTool::slotColorGuideChanged() 0256 { 0257 d->previewWidget->slotChangeGuideColor(d->gboxSettings->guideColor()); 0258 d->previewWidget->slotChangeGuideSize(d->gboxSettings->guideSize()); 0259 } 0260 0261 void FreeRotationTool::readSettings() 0262 { 0263 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0264 KConfigGroup group = config->group(d->configGroupName); 0265 0266 d->settingsView->readSettings(group); 0267 0268 d->expanderBox->readSettings(group); 0269 0270 resetPoints(); 0271 slotColorGuideChanged(); 0272 } 0273 0274 void FreeRotationTool::writeSettings() 0275 { 0276 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0277 KConfigGroup group = config->group(d->configGroupName); 0278 d->settingsView->writeSettings(group); 0279 0280 d->expanderBox->writeSettings(group); 0281 0282 group.sync(); 0283 } 0284 0285 void FreeRotationTool::slotResetSettings() 0286 { 0287 d->settingsView->resetToDefault(); 0288 resetPoints(); 0289 slotPreview(); 0290 } 0291 0292 void FreeRotationTool::preparePreview() 0293 { 0294 FreeRotationContainer settings = d->settingsView->settings(); 0295 ImageIface* const iface = d->previewWidget->imageIface(); 0296 DImg preview = iface->preview(); 0297 settings.orgW = iface->originalSize().width(); 0298 settings.orgH = iface->originalSize().height(); 0299 setFilter(new FreeRotationFilter(&preview, this, settings)); 0300 } 0301 0302 void FreeRotationTool::prepareFinal() 0303 { 0304 ImageIface iface; 0305 FreeRotationContainer settings = d->settingsView->settings(); 0306 DImg* const orgImage = iface.original(); 0307 settings.orgW = iface.originalSize().width(); 0308 settings.orgH = iface.originalSize().height(); 0309 0310 setFilter(new FreeRotationFilter(orgImage, this, settings)); 0311 } 0312 0313 void FreeRotationTool::setPreviewImage() 0314 { 0315 ImageIface* const iface = d->previewWidget->imageIface(); 0316 int w = iface->previewSize().width(); 0317 int h = iface->previewSize().height(); 0318 DImg imTemp = filter()->getTargetImage().smoothScale(w, h, Qt::KeepAspectRatio); 0319 DImg imDest(w, h, filter()->getTargetImage().sixteenBit(), filter()->getTargetImage().hasAlpha()); 0320 0321 QColor background = toolView()->backgroundRole(); 0322 imDest.fill(DColor(background, filter()->getTargetImage().sixteenBit())); 0323 imDest.bitBltImage(&imTemp, (w-imTemp.width())/2, (h-imTemp.height())/2); 0324 0325 iface->setPreview(imDest.smoothScale(iface->previewSize())); 0326 d->previewWidget->updatePreview(); 0327 0328 QString temp; 0329 0330 FreeRotationFilter* const tool = dynamic_cast<FreeRotationFilter*>(filter()); 0331 0332 if (tool) 0333 { 0334 QSize newSize = tool->getNewSize(); 0335 int new_w = (newSize.width() == -1) ? iface->originalSize().width() : newSize.width(); 0336 int new_h = (newSize.height() == -1) ? iface->originalSize().height() : newSize.height(); 0337 d->newWidthLabel->setText(temp.setNum(new_w) + i18n(" px")); 0338 d->newHeightLabel->setText(temp.setNum(new_h) + i18n(" px")); 0339 } 0340 } 0341 0342 void FreeRotationTool::setFinalImage() 0343 { 0344 ImageIface iface; 0345 DImg targetImage = filter()->getTargetImage(); 0346 iface.setOriginal(i18n("Free Rotation"), filter()->filterAction(), targetImage); 0347 } 0348 0349 QString FreeRotationTool::generateButtonLabel(const QPoint& p) const 0350 { 0351 QString clickToSet = i18n("Click to set"); 0352 QString isOk = i18nc("point has been set and is valid", "Okay"); 0353 bool clickToSetIsWider = clickToSet.count() >= isOk.count(); 0354 QString widestString = clickToSetIsWider ? clickToSet : isOk; 0355 int maxLength = widestString.count(); 0356 QString label = clickToSetIsWider ? clickToSet : centerString(clickToSet, maxLength); 0357 0358 if (pointIsValid(p)) 0359 { 0360 label = clickToSetIsWider ? centerString(isOk, maxLength) : isOk; 0361 } 0362 0363 return label; 0364 } 0365 0366 QString FreeRotationTool::centerString(const QString& str, int maxLength) const 0367 { 0368 QString tmp = str; 0369 int max = (maxLength == -1) ? tmp.count() : maxLength; 0370 0371 // fill with additional whitespace, to match the original label length and center 0372 // the text, without moving the button icon 0373 0374 int diff = qAbs<int>(max - str.count()); 0375 0376 if (diff > 0) 0377 { 0378 QString delimiter = QLatin1String(" "); 0379 int times = (diff / 2); 0380 0381 tmp.prepend(delimiter.repeated(times)); 0382 tmp.append(delimiter.repeated(times)); 0383 0384 diff = qAbs<int>(maxLength - tmp.count()); 0385 0386 if (diff != 0) 0387 { 0388 // too long? 0389 if (tmp.count() > maxLength) 0390 { 0391 tmp.chop(diff); 0392 } 0393 // too short? 0394 else if (tmp.count() < maxLength) 0395 { 0396 tmp.append(delimiter.repeated(diff)); 0397 } 0398 } 0399 } 0400 0401 return tmp; 0402 } 0403 0404 void FreeRotationTool::updatePoints() 0405 { 0406 // set labels 0407 0408 QString tmp = generateButtonLabel(d->autoAdjustPoint1); 0409 d->autoAdjustPoint1Btn->setText(tmp); 0410 0411 tmp = generateButtonLabel(d->autoAdjustPoint2); 0412 d->autoAdjustPoint2Btn->setText(tmp); 0413 0414 // set points in preview widget, don't add invalid points 0415 0416 QPolygon points; 0417 0418 if (pointIsValid(d->autoAdjustPoint1)) 0419 { 0420 points << d->autoAdjustPoint1; 0421 d->autoAdjustPoint2Btn->setEnabled(true); 0422 } 0423 else 0424 { 0425 d->autoAdjustPoint2Btn->setEnabled(false); 0426 } 0427 0428 if (pointIsValid(d->autoAdjustPoint2)) 0429 { 0430 points << d->autoAdjustPoint2; 0431 } 0432 0433 d->previewWidget->setPoints(points, true); 0434 0435 // enable / disable adjustment buttons 0436 0437 bool valid = (pointIsValid(d->autoAdjustPoint1) && 0438 pointIsValid(d->autoAdjustPoint2)) && 0439 (d->autoAdjustPoint1 != d->autoAdjustPoint2); 0440 d->autoAdjustBtn->setEnabled(valid); 0441 } 0442 0443 void FreeRotationTool::resetPoints() 0444 { 0445 setPointInvalid(d->autoAdjustPoint1); 0446 setPointInvalid(d->autoAdjustPoint2); 0447 d->previewWidget->resetPoints(); 0448 updatePoints(); 0449 } 0450 0451 void FreeRotationTool::slotAutoAdjustP1Clicked() 0452 { 0453 d->autoAdjustPoint1 = d->previewWidget->getSpotPosition(); 0454 updatePoints(); 0455 } 0456 0457 void FreeRotationTool::slotAutoAdjustP2Clicked() 0458 { 0459 d->autoAdjustPoint2 = d->previewWidget->getSpotPosition(); 0460 updatePoints(); 0461 } 0462 0463 void FreeRotationTool::slotAutoAdjustClicked() 0464 { 0465 // we need to check manually here if the button is enabled, 0466 // because this slot can be called with an action now 0467 0468 if (!d->autoAdjustBtn->isEnabled()) 0469 { 0470 return; 0471 } 0472 0473 double angle = calculateAutoAngle(); 0474 0475 if (fabs(angle) > 45.0) 0476 { 0477 if (angle < 0.0) 0478 { 0479 angle += 90.0; 0480 } 0481 else 0482 { 0483 angle -= 90.0; 0484 } 0485 } 0486 0487 // we need to add the calculated angle to the currently set angle 0488 0489 angle = d->settingsView->settings().angle + angle; 0490 0491 // convert the angle to a string so we can easily split it up 0492 0493 QString angleStr = QString::number(angle, 'f', 2); 0494 QStringList anglesList = angleStr.split(QLatin1Char('.')); 0495 0496 // try to set the angle widgets with the extracted values 0497 0498 if (anglesList.count() == 2) 0499 { 0500 bool ok = false; 0501 int mainAngle = anglesList.at(0).toInt(&ok); 0502 0503 if (!ok) 0504 { 0505 mainAngle = 0; 0506 } 0507 0508 double fineAngle = QString(QLatin1String("0.") + anglesList.at(1)).toDouble(&ok); 0509 fineAngle = (angle < 0.0) ? -fineAngle : fineAngle; 0510 0511 if (!ok) 0512 { 0513 fineAngle = 0.0; 0514 } 0515 0516 FreeRotationContainer prm = d->settingsView->settings(); 0517 prm.angle = mainAngle + fineAngle; 0518 d->settingsView->setSettings(prm); 0519 slotPreview(); 0520 } 0521 0522 resetPoints(); 0523 } 0524 0525 QPixmap FreeRotationTool::generateBtnPixmap(const QString& label, const QColor& color) const 0526 { 0527 QPixmap pm(22, 22); 0528 pm.fill(Qt::transparent); 0529 0530 QPainter p(&pm); 0531 p.setRenderHint(QPainter::Antialiasing); 0532 p.setPen(color); 0533 0534 p.drawEllipse(1, 1, 20, 20); 0535 p.drawText(pm.rect(), label, Qt::AlignHCenter | Qt::AlignVCenter); 0536 0537 p.end(); 0538 0539 return pm; 0540 } 0541 0542 double FreeRotationTool::calculateAutoAngle() const 0543 { 0544 // check if all points are valid 0545 0546 if (!pointIsValid(d->autoAdjustPoint1) && !pointIsValid(d->autoAdjustPoint2)) 0547 { 0548 return 0.0; 0549 } 0550 0551 return FreeRotationFilter::calculateAngle(d->autoAdjustPoint1, d->autoAdjustPoint2); 0552 } 0553 0554 void FreeRotationTool::setPointInvalid(QPoint& p) 0555 { 0556 p.setX(-1); 0557 p.setY(-1); 0558 } 0559 0560 bool FreeRotationTool::pointIsValid(const QPoint& p) const 0561 { 0562 bool valid = true; 0563 0564 if ((p.x() == -1) || (p.y() == -1)) 0565 { 0566 valid = false; 0567 } 0568 0569 return valid; 0570 } 0571 0572 QString FreeRotationTool::repeatString(const QString& str, int times) const 0573 { 0574 QString tmp; 0575 0576 for (int i = 0 ; i < times ; ++i) 0577 { 0578 tmp.append(str); 0579 } 0580 0581 return tmp; 0582 } 0583 0584 } // namespace DigikamEditorFreeRotationToolPlugin 0585 0586 #include "moc_freerotationtool.cpp"