1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2004-12-27
7  * Description : a tool to reduce lens distortions to an image.
8  *
9  * Copyright (C) 2004-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
11  *
12  * This program is free software; you can redistribute it
13  * and/or modify it under the terms of the GNU General
14  * Public License as published by the Free Software Foundation;
15  * either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * ============================================================ */
24 
25 #include "lensdistortiontool.h"
26 
27 // C++ includes
28 
29 #include <cmath>
30 
31 // Qt includes
32 
33 #include <QBrush>
34 #include <QGridLayout>
35 #include <QLabel>
36 #include <QPainter>
37 #include <QPen>
38 #include <QPixmap>
39 #include <QIcon>
40 
41 // KDE includes
42 
43 #include <ksharedconfig.h>
44 #include <kconfiggroup.h>
45 #include <klocalizedstring.h>
46 
47 // Local includes
48 
49 #include "dnuminput.h"
50 #include "editortoolsettings.h"
51 #include "imageiface.h"
52 #include "imageguidewidget.h"
53 #include "lensdistortionfilter.h"
54 
55 namespace DigikamEditorLensDistortionToolPlugin
56 {
57 
58 class Q_DECL_HIDDEN LensDistortionTool::Private
59 {
60 public:
61 
Private()62     explicit Private()
63       : maskPreviewLabel(nullptr),
64         mainInput       (nullptr),
65         edgeInput       (nullptr),
66         rescaleInput    (nullptr),
67         brightenInput   (nullptr),
68         previewWidget   (nullptr),
69         gboxSettings    (nullptr)
70     {
71     }
72 
73     static const QString configGroupName;
74     static const QString config2ndOrderDistortionEntry;
75     static const QString config4thOrderDistortionEntry;
76     static const QString configZoomFactorEntry;
77     static const QString configBrightenEntry;
78 
79     QLabel*              maskPreviewLabel;
80 
81     DDoubleNumInput*     mainInput;
82     DDoubleNumInput*     edgeInput;
83     DDoubleNumInput*     rescaleInput;
84     DDoubleNumInput*     brightenInput;
85 
86     DImg                 previewRasterImage;
87 
88     ImageGuideWidget*    previewWidget;
89     EditorToolSettings*  gboxSettings;
90 };
91 
92 const QString LensDistortionTool::Private::configGroupName(QLatin1String("lensdistortion Tool"));
93 const QString LensDistortionTool::Private::config2ndOrderDistortionEntry(QLatin1String("2nd Order Distortion"));
94 const QString LensDistortionTool::Private::config4thOrderDistortionEntry(QLatin1String("4th Order Distortion"));
95 const QString LensDistortionTool::Private::configZoomFactorEntry(QLatin1String("Zoom Factor"));
96 const QString LensDistortionTool::Private::configBrightenEntry(QLatin1String("Brighten"));
97 
98 // --------------------------------------------------------
99 
LensDistortionTool(QObject * const parent)100 LensDistortionTool::LensDistortionTool(QObject* const parent)
101     : EditorToolThreaded(parent),
102       d                 (new Private)
103 {
104     setObjectName(QLatin1String("lensdistortion"));
105 
106     d->previewWidget = new ImageGuideWidget(nullptr, true, ImageGuideWidget::HVGuideMode);
107     setToolView(d->previewWidget);
108 
109     // -------------------------------------------------------------
110 
111     d->gboxSettings = new EditorToolSettings(nullptr);
112     d->gboxSettings->setTools(EditorToolSettings::ColorGuide);
113 
114     QGridLayout* const gridSettings = new QGridLayout(d->gboxSettings->plainPage());
115 
116     d->maskPreviewLabel = new QLabel(d->gboxSettings->plainPage());
117     d->maskPreviewLabel->setAlignment ( Qt::AlignHCenter | Qt::AlignVCenter );
118     d->maskPreviewLabel->setWhatsThis( i18n("You can see here a thumbnail preview of the "
119                                             "distortion correction applied to a cross pattern.") );
120 
121     // -------------------------------------------------------------
122 
123     QLabel* const label1 = new QLabel(i18nc("value for amount of distortion", "Main:"), d->gboxSettings->plainPage());
124 
125     d->mainInput = new DDoubleNumInput(d->gboxSettings->plainPage());
126     d->mainInput->setDecimals(1);
127     d->mainInput->setRange(-100.0, 100.0, 0.1);
128     d->mainInput->setDefaultValue(0.0);
129     d->mainInput->setWhatsThis( i18n("This value controls the amount of distortion. Negative values "
130                                      "correct lens barrel distortion, while positive values correct lens "
131                                      "pincushion distortion."));
132 
133     // -------------------------------------------------------------
134 
135     QLabel* const label2 = new QLabel(i18n("Edge:"), d->gboxSettings->plainPage());
136 
137     d->edgeInput = new DDoubleNumInput(d->gboxSettings->plainPage());
138     d->edgeInput->setDecimals(1);
139     d->edgeInput->setRange(-100.0, 100.0, 0.1);
140     d->edgeInput->setDefaultValue(0.0);
141     d->edgeInput->setWhatsThis( i18n("This value controls in the same manner as the Main control, "
142                                      "but has more effect at the edges of the image than at the center."));
143 
144     // -------------------------------------------------------------
145 
146     QLabel* const label3 = new QLabel(i18n("Zoom:"), d->gboxSettings->plainPage());
147 
148     d->rescaleInput = new DDoubleNumInput(d->gboxSettings->plainPage());
149     d->rescaleInput->setDecimals(1);
150     d->rescaleInput->setRange(-100.0, 100.0, 0.1);
151     d->rescaleInput->setDefaultValue(0.0);
152     d->rescaleInput->setWhatsThis( i18n("This value rescales the overall image size."));
153 
154     // -------------------------------------------------------------
155 
156     QLabel* const label4 = new QLabel(i18n("Brighten:"), d->gboxSettings->plainPage());
157 
158     d->brightenInput = new DDoubleNumInput(d->gboxSettings->plainPage());
159     d->brightenInput->setDecimals(1);
160     d->brightenInput->setRange(-100.0, 100.0, 0.1);
161     d->brightenInput->setDefaultValue(0.0);
162     d->brightenInput->setWhatsThis( i18n("This value adjusts the brightness in image corners."));
163 
164     // -------------------------------------------------------------
165 
166     const int spacing = d->gboxSettings->spacingHint();
167 
168     gridSettings->addWidget(d->maskPreviewLabel, 0, 0, 1, 2);
169     gridSettings->addWidget(label1,              1, 0, 1, 2);
170     gridSettings->addWidget(d->mainInput,        2, 0, 1, 2);
171     gridSettings->addWidget(label2,              3, 0, 1, 2);
172     gridSettings->addWidget(d->edgeInput,        4, 0, 1, 2);
173     gridSettings->addWidget(label3,              5, 0, 1, 2);
174     gridSettings->addWidget(d->rescaleInput,     6, 0, 1, 2);
175     gridSettings->addWidget(label4,              7, 0, 1, 2);
176     gridSettings->addWidget(d->brightenInput,    8, 0, 1, 2);
177     gridSettings->setRowStretch(9, 10);
178     gridSettings->setContentsMargins(spacing, spacing, spacing, spacing);
179     gridSettings->setSpacing(spacing);
180 
181     setToolSettings(d->gboxSettings);
182 
183     // -------------------------------------------------------------
184 
185     connect(d->mainInput, SIGNAL(valueChanged(double)),
186             this, SLOT(slotTimer()));
187 
188     connect(d->edgeInput, SIGNAL(valueChanged(double)),
189             this, SLOT(slotTimer()));
190 
191     connect(d->rescaleInput, SIGNAL(valueChanged(double)),
192             this, SLOT(slotTimer()));
193 
194     connect(d->brightenInput, SIGNAL(valueChanged(double)),
195             this, SLOT(slotTimer()));
196 
197     connect(d->gboxSettings, SIGNAL(signalColorGuideChanged()),
198             this, SLOT(slotColorGuideChanged()));
199 
200     // -------------------------------------------------------------
201 
202     /* Calc transform preview.
203        We would like a checkered area to demonstrate the effect.
204        We do not have any drawing support in DImg, so we let Qt draw.
205        First we create a white QImage. We convert this to a QPixmap,
206        on which we can draw. Then we convert back to QImage,
207        convert the QImage to a DImg which we only need to create once, here.
208        Later, we apply the effect on a copy and convert the DImg to QPixmap.
209        Longing for Qt4 where we can paint directly on the QImage...
210     */
211 
212     QPixmap pix(120, 120);
213     pix.fill(Qt::white);
214     QPainter pt(&pix);
215     pt.setPen(QPen(Qt::black, 1));
216     pt.fillRect(0, 0, pix.width(), pix.height(), QBrush(Qt::black, Qt::CrossPattern));
217     pt.drawRect(0, 0, pix.width(), pix.height());
218     pt.end();
219     QImage preview       = pix.toImage();
220     d->previewRasterImage = DImg(preview.width(), preview.height(), false, false, preview.bits());
221 }
222 
~LensDistortionTool()223 LensDistortionTool::~LensDistortionTool()
224 {
225     delete d;
226 }
227 
slotColorGuideChanged()228 void LensDistortionTool::slotColorGuideChanged()
229 {
230     d->previewWidget->slotChangeGuideColor(d->gboxSettings->guideColor());
231     d->previewWidget->slotChangeGuideSize(d->gboxSettings->guideSize());
232 }
233 
readSettings()234 void LensDistortionTool::readSettings()
235 {
236     KSharedConfig::Ptr config = KSharedConfig::openConfig();
237     KConfigGroup group        = config->group(d->configGroupName);
238 
239     blockWidgetSignals(true);
240 
241     d->mainInput->setValue(group.readEntry(d->config2ndOrderDistortionEntry, d->mainInput->defaultValue()));
242     d->edgeInput->setValue(group.readEntry(d->config4thOrderDistortionEntry, d->edgeInput->defaultValue()));
243     d->rescaleInput->setValue(group.readEntry(d->configZoomFactorEntry,      d->rescaleInput->defaultValue()));
244     d->brightenInput->setValue(group.readEntry(d->configBrightenEntry,       d->brightenInput->defaultValue()));
245 
246     blockWidgetSignals(false);
247 
248     slotColorGuideChanged();
249     slotPreview();
250 }
251 
writeSettings()252 void LensDistortionTool::writeSettings()
253 {
254     KSharedConfig::Ptr config = KSharedConfig::openConfig();
255     KConfigGroup group        = config->group(d->configGroupName);
256 
257     group.writeEntry(d->config2ndOrderDistortionEntry, d->mainInput->value());
258     group.writeEntry(d->config4thOrderDistortionEntry, d->edgeInput->value());
259     group.writeEntry(d->configZoomFactorEntry,         d->rescaleInput->value());
260     group.writeEntry(d->configBrightenEntry,           d->brightenInput->value());
261 
262     config->sync();
263 }
264 
slotResetSettings()265 void LensDistortionTool::slotResetSettings()
266 {
267     blockWidgetSignals(true);
268 
269     d->mainInput->slotReset();
270     d->edgeInput->slotReset();
271     d->rescaleInput->slotReset();
272     d->brightenInput->slotReset();
273 
274     blockWidgetSignals(false);
275 
276     slotPreview();
277 }
278 
preparePreview()279 void LensDistortionTool::preparePreview()
280 {
281     double m = d->mainInput->value();
282     double e = d->edgeInput->value();
283     double r = d->rescaleInput->value();
284     double b = d->brightenInput->value();
285 
286     LensDistortionFilter transformPreview(&d->previewRasterImage, nullptr, m, e, r, b, 0, 0);
287     transformPreview.startFilterDirectly();
288     d->maskPreviewLabel->setPixmap(transformPreview.getTargetImage().convertToPixmap());
289 
290     ImageIface* const iface = d->previewWidget->imageIface();
291 
292     setFilter(new LensDistortionFilter(iface->original(), this, m, e, r, b, 0, 0));
293 }
294 
prepareFinal()295 void LensDistortionTool::prepareFinal()
296 {
297     double m = d->mainInput->value();
298     double e = d->edgeInput->value();
299     double r = d->rescaleInput->value();
300     double b = d->brightenInput->value();
301 
302     ImageIface iface;
303     setFilter(new LensDistortionFilter(iface.original(), this, m, e, r, b, 0, 0));
304 }
305 
setPreviewImage()306 void LensDistortionTool::setPreviewImage()
307 {
308     ImageIface* const iface = d->previewWidget->imageIface();
309     DImg imDest             = filter()->getTargetImage().smoothScale(iface->previewSize());
310     iface->setPreview(imDest);
311 
312     d->previewWidget->updatePreview();
313 }
314 
setFinalImage()315 void LensDistortionTool::setFinalImage()
316 {
317     ImageIface iface;
318     iface.setOriginal(i18n("Lens Distortion"), filter()->filterAction(), filter()->getTargetImage());
319 }
320 
blockWidgetSignals(bool b)321 void LensDistortionTool::blockWidgetSignals(bool b)
322 {
323     d->mainInput->blockSignals(b);
324     d->edgeInput->blockSignals(b);
325     d->rescaleInput->blockSignals(b);
326     d->brightenInput->blockSignals(b);
327 }
328 
329 } // namespace DigikamEditorLensDistortionToolPlugin
330