1 /* ============================================================
2  *
3  * This file is a part of digiKam project
4  * https://www.digikam.org
5  *
6  * Date        : 2005-05-25
7  * Description : threaded image filter class.
8  *
9  * Copyright (C) 2005-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10  * Copyright (C) 2007-2012 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 "dimgthreadedfilter.h"
26 
27 // Qt includes
28 
29 #include <QObject>
30 #include <QDateTime>
31 #include <QThreadPool>
32 
33 // Local includes
34 
35 #include "digikam_debug.h"
36 
37 namespace Digikam
38 {
39 
DImgThreadedFilter(QObject * const parent,const QString & name)40 DImgThreadedFilter::DImgThreadedFilter(QObject* const parent, const QString& name)
41     : DynamicThread(parent),
42       m_version(1),
43       m_wasCancelled(false)
44 {
45     setOriginalImage(DImg());
46     setFilterName(name);
47 
48     initMaster();
49 }
50 
DImgThreadedFilter(DImg * const orgImage,QObject * const parent,const QString & name)51 DImgThreadedFilter::DImgThreadedFilter(DImg* const orgImage, QObject* const parent,
52                                        const QString& name)
53     : DynamicThread(parent),
54       m_version(1),
55       m_wasCancelled(false)
56 {
57     // remove meta data
58 
59     setOriginalImage(orgImage->copyImageData());
60     setFilterName(name);
61 
62     initMaster();
63 }
64 
DImgThreadedFilter(DImgThreadedFilter * const master,const DImg & orgImage,const DImg & destImage,int progressBegin,int progressEnd,const QString & name)65 DImgThreadedFilter::DImgThreadedFilter(DImgThreadedFilter* const master, const DImg& orgImage,
66                                        const DImg& destImage, int progressBegin, int progressEnd,
67                                        const QString& name)
68     : m_version(1),
69       m_wasCancelled(false),
70       m_destImage(destImage)
71 {
72     setFilterName(name);
73     setOriginalImage(orgImage);
74 
75     initSlave(master, progressBegin, progressEnd);
76 }
77 
~DImgThreadedFilter()78 DImgThreadedFilter::~DImgThreadedFilter()
79 {
80     // NOTE: use dynamic binding as this virtual method can be re-implemented in derived classes.
81 
82     this->cancelFilter();
83 
84     if (m_master)
85     {
86         m_master->setSlave(nullptr);
87     }
88 }
89 
initSlave(DImgThreadedFilter * const master,int progressBegin,int progressEnd)90 void DImgThreadedFilter::initSlave(DImgThreadedFilter* const master, int progressBegin, int progressEnd)
91 {
92     m_master          = master;
93     m_slave           = nullptr;
94     m_progressBegin   = progressBegin;
95     m_progressSpan    = progressEnd - progressBegin;
96     m_progressCurrent = 0;
97 
98     if (m_master)
99     {
100         m_master->setSlave(this);
101     }
102 }
103 
initMaster()104 void DImgThreadedFilter::initMaster()
105 {
106     m_master          = nullptr;
107     m_slave           = nullptr;
108     m_progressBegin   = 0;
109     m_progressSpan    = 100;
110     m_progressCurrent = 0;
111 }
112 
setupFilter(const DImg & orgImage)113 void DImgThreadedFilter::setupFilter(const DImg& orgImage)
114 {
115     setOriginalImage(orgImage);
116 
117     // some filters may require that initFilter is called
118 
119     initFilter();
120 }
121 
setupAndStartDirectly(const DImg & orgImage,DImgThreadedFilter * const master,int progressBegin,int progressEnd)122 void DImgThreadedFilter::setupAndStartDirectly(const DImg& orgImage, DImgThreadedFilter* const master,
123                                                int progressBegin, int progressEnd)
124 {
125     initSlave(master, progressBegin, progressEnd);
126     setupFilter(orgImage);
127 }
128 
setOriginalImage(const DImg & orgImage)129 void DImgThreadedFilter::setOriginalImage(const DImg& orgImage)
130 {
131     m_orgImage = orgImage;
132 }
133 
setFilterName(const QString & name)134 void DImgThreadedFilter::setFilterName(const QString& name)
135 {
136     m_name = QString(name);
137 }
138 
supportedVersions() const139 QList<int> DImgThreadedFilter::supportedVersions() const
140 {
141     return (QList<int>() << 1);
142 }
143 
setFilterVersion(int version)144 void DImgThreadedFilter::setFilterVersion(int version)
145 {
146     if (supportedVersions().contains(version))
147     {
148         m_version = version;
149     }
150 }
151 
filterVersion() const152 int DImgThreadedFilter::filterVersion() const
153 {
154     return m_version;
155 }
156 
initFilter()157 void DImgThreadedFilter::initFilter()
158 {
159     prepareDestImage();
160 
161     if (m_master)
162     {
163         startFilterDirectly();
164     }
165 }
166 
prepareDestImage()167 void DImgThreadedFilter::prepareDestImage()
168 {
169     m_destImage.reset();
170 
171     if (!m_orgImage.isNull())
172     {
173         m_destImage = DImg(m_orgImage.width(),      m_orgImage.height(),
174                            m_orgImage.sixteenBit(), m_orgImage.hasAlpha());
175     }
176 }
177 
startFilter()178 void DImgThreadedFilter::startFilter()
179 {
180     if (m_orgImage.width() && m_orgImage.height())
181     {
182         start();
183     }
184     else  // No image data
185     {
186         emit finished(false);
187         qCDebug(DIGIKAM_DIMG_LOG) << m_name << "::No valid image data !!! ...";
188     }
189 }
190 
startFilterDirectly()191 void DImgThreadedFilter::startFilterDirectly()
192 {
193     if (m_orgImage.width() && m_orgImage.height())
194     {
195         emit started();
196 
197         m_wasCancelled = false;
198 
199         try
200         {
201             QDateTime now = QDateTime::currentDateTime();
202             filterImage();
203             //qCDebug(DIGIKAM_DIMG_LOG) << m_name << ":: excecution time : " << now.msecsTo(QDateTime::currentDateTime()) << " ms";
204         }
205         catch (std::bad_alloc& ex)
206         {
207             // TODO: User notification
208             qCCritical(DIGIKAM_DIMG_LOG) << "Caught out-of-memory exception! Aborting operation" << ex.what();
209 
210             emit finished(false);
211 
212             return;
213         }
214 
215         emit finished(!m_wasCancelled);
216     }
217     else  // No image data
218     {
219         emit finished(false);
220         qCDebug(DIGIKAM_DIMG_LOG) << m_name << "::No valid image data !!! ...";
221     }
222 }
223 
run()224 void DImgThreadedFilter::run()
225 {
226     startFilterDirectly();
227 }
228 
cancelFilter()229 void DImgThreadedFilter::cancelFilter()
230 {
231     if (isRunning())
232     {
233         m_wasCancelled = true;
234     }
235 
236     stop();
237 
238     if (m_slave)
239     {
240         m_slave->stop();
241 
242         // do not wait on slave, it is not running in its own separate thread!
243         //m_slave->cleanupFilter();
244     }
245 
246     wait();
247     cleanupFilter();
248 }
249 
postProgress(int progr)250 void DImgThreadedFilter::postProgress(int progr)
251 {
252     if (m_master)
253     {
254         progr = modulateProgress(progr);
255         m_master->postProgress(progr);
256     }
257     else if (m_progressCurrent != progr)
258     {
259         emit progress(progr);
260         m_progressCurrent = progr;
261     }
262 }
263 
setSlave(DImgThreadedFilter * const slave)264 void DImgThreadedFilter::setSlave(DImgThreadedFilter* const slave)
265 {
266     m_slave = slave;
267 }
268 
modulateProgress(int progress)269 int DImgThreadedFilter::modulateProgress(int progress)
270 {
271     return m_progressBegin + (int)((double)progress * (double)m_progressSpan / 100.0);
272 }
273 
parametersSuccessfullyRead() const274 bool DImgThreadedFilter::parametersSuccessfullyRead() const
275 {
276     return true;
277 }
278 
readParametersError(const FilterAction &) const279 QString DImgThreadedFilter::readParametersError(const FilterAction&) const
280 {
281     return QString();
282 }
283 
multithreadedSteps(int stop,int start) const284 QList<int> DImgThreadedFilter::multithreadedSteps(int stop, int start) const
285 {
286     uint  nbCore = QThreadPool::globalInstance()->maxThreadCount();
287     float step   = ((float)stop - (float)start) / (float)nbCore;
288     QList<int> vals;
289 
290     vals << start;
291 
292     for (uint i = 1 ; i < nbCore ; ++i)
293     {
294         vals << vals.last() + step;
295     }
296 
297     vals << stop;
298 
299     return vals;
300 }
301 
302 } // namespace Digikam
303