1 /** -*- mode: c++ ; c-basic-offset: 2 -*-
2  *
3  *  @file FilterThread.cpp
4  *
5  *  Copyright 2017 Sebastien Fourey
6  *
7  *  This file is part of G'MIC-Qt, a generic plug-in for raster graphics
8  *  editors, offering hundreds of filters thanks to the underlying G'MIC
9  *  image processing framework.
10  *
11  *  gmic_qt is free software: you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation, either version 3 of the License, or
14  *  (at your option) any later version.
15  *
16  *  gmic_qt is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with gmic_qt.  If not, see <http://www.gnu.org/licenses/>.
23  *
24  */
25 #include "FilterThread.h"
26 #include <QDebug>
27 #include <iostream>
28 #include "FilterParameters/AbstractParameter.h"
29 #include "GmicStdlib.h"
30 #include "ImageConverter.h"
31 #include "Logger.h"
32 #include "Utils.h"
33 #include "gmic.h"
34 using namespace cimg_library;
35 
FilterThread(QObject * parent,const QString & name,const QString & command,const QString & arguments,const QString & environment,GmicQt::OutputMessageMode mode)36 FilterThread::FilterThread(QObject * parent, const QString & name, const QString & command, const QString & arguments, const QString & environment, GmicQt::OutputMessageMode mode)
37     : QThread(parent), _command(command), _arguments(arguments), _environment(environment), _images(new cimg_library::CImgList<float>), _imageNames(new cimg_library::CImgList<char>), _name(name),
38       _messageMode(mode)
39 {
40   _gmicAbort = false;
41   _failed = false;
42   _gmicProgress = 0.0f;
43   // ENTERING;
44 #ifdef _IS_MACOS_
45   setStackSize(8 * 1024 * 1024);
46 #endif
47 }
48 
~FilterThread()49 FilterThread::~FilterThread()
50 {
51   delete _images;
52   delete _imageNames;
53 }
54 
setArguments(const QString & str)55 void FilterThread::setArguments(const QString & str)
56 {
57   _arguments = str;
58 }
59 
setImageNames(const cimg_library::CImgList<char> & imageNames)60 void FilterThread::setImageNames(const cimg_library::CImgList<char> & imageNames)
61 {
62   *_imageNames = imageNames;
63 }
64 
swapImages(cimg_library::CImgList<float> & images)65 void FilterThread::swapImages(cimg_library::CImgList<float> & images)
66 {
67   _images->swap(images);
68 }
69 
setInputImages(const cimg_library::CImgList<float> & list)70 void FilterThread::setInputImages(const cimg_library::CImgList<float> & list)
71 {
72   *_images = list;
73 }
74 
images() const75 const cimg_library::CImgList<float> & FilterThread::images() const
76 {
77   return *_images;
78 }
79 
imageNames() const80 const cimg_library::CImgList<char> & FilterThread::imageNames() const
81 {
82   return *_imageNames;
83 }
84 
status2StringList(const QString & status)85 QStringList FilterThread::status2StringList(const QString & status)
86 {
87   // Check if status matches something like "{...}{...}_1{...}_0"
88   QRegExp statusRegExp(QString("^") + QChar(gmic_lbrace) + "(.*)" + QChar(gmic_rbrace) + QString("(_[012][+*-]?)?$"));
89   QRegExp statusSeparatorRegExp(QChar(gmic_rbrace) + QString("(_[012][+*-]?)?") + QChar(gmic_lbrace));
90   if (status.isEmpty()) {
91     return QStringList();
92   }
93   if (statusRegExp.indexIn(status) == -1) {
94     // TRACE << "Warning: Incorrect status syntax " << status;
95     return QStringList();
96   }
97   QList<QString> list = statusRegExp.cap(1).split(statusSeparatorRegExp);
98   if (!list.isEmpty()) {
99     QList<QString>::iterator it = list.begin();
100     while (it != list.end()) {
101       QByteArray array = it->toLocal8Bit();
102       gmic::strreplace_fw(array.data());
103       *it++ = array;
104     }
105   }
106   return list;
107 }
108 
status2Visibilities(const QString & status)109 QList<int> FilterThread::status2Visibilities(const QString & status)
110 {
111   if (status.isEmpty()) {
112     return QList<int>();
113   }
114   // Check if status matches something like "{...}{...}_1{...}_0"
115   QRegExp statusRegExp(QString("^") + QChar(gmic_lbrace) + "(.*)" + QChar(gmic_rbrace) + QString("(_[012])?$"));
116   if (!status.isEmpty() && statusRegExp.indexIn(status) == -1) {
117     // TRACE << "Incorrect status syntax " << status;
118     return QList<int>();
119   }
120   QByteArray ba = status.toLocal8Bit();
121   const char * pc = ba.constData();
122   const char * limit = pc + ba.size();
123 
124   QList<int> result;
125   while (pc < limit) {
126     if (*pc == gmic_rbrace) {
127       if ((pc < limit - 2) && pc[1] == '_' && pc[2] >= '0' && pc[2] <= '2' && (!pc[3] || pc[3] == gmic_lbrace)) {
128         auto visibilityState = static_cast<AbstractParameter::VisibilityState>(pc[2] - '0');
129         result.push_back(visibilityState);
130         pc += 3;
131       } else if (!pc[1] || (pc[1] == gmic_lbrace)) {
132         result.push_back(AbstractParameter::UnspecifiedVisibilityState);
133         ++pc;
134       } else {
135         // TRACE << "Ignoring status" << qPrintable(status);
136         return QList<int>();
137       }
138     } else {
139       ++pc;
140     }
141   }
142   return result;
143 }
144 
gmicStatus() const145 QStringList FilterThread::gmicStatus() const
146 {
147   return status2StringList(_gmicStatus);
148 }
149 
parametersVisibilityStates() const150 QList<int> FilterThread::parametersVisibilityStates() const
151 {
152   return status2Visibilities(_gmicStatus);
153 }
154 
errorMessage() const155 QString FilterThread::errorMessage() const
156 {
157   return _errorMessage;
158 }
159 
failed() const160 bool FilterThread::failed() const
161 {
162   return _failed;
163 }
164 
aborted() const165 bool FilterThread::aborted() const
166 {
167   return _gmicAbort;
168 }
169 
duration() const170 int FilterThread::duration() const
171 {
172   return _startTime.elapsed();
173 }
174 
progress() const175 float FilterThread::progress() const
176 {
177   return _gmicProgress;
178 }
179 
name() const180 QString FilterThread::name() const
181 {
182   return _name;
183 }
184 
fullCommand() const185 QString FilterThread::fullCommand() const
186 {
187   QString result = _command;
188   GmicQt::appendWithSpace(result, _arguments);
189   return result;
190 }
191 
setLogSuffix(const QString & text)192 void FilterThread::setLogSuffix(const QString & text)
193 {
194   _logSuffix = text;
195 }
196 
abortGmic()197 void FilterThread::abortGmic()
198 {
199   _gmicAbort = true;
200 }
201 
run()202 void FilterThread::run()
203 {
204   _startTime.start();
205   _errorMessage.clear();
206   _failed = false;
207   QString fullCommandLine;
208   try {
209     fullCommandLine = QString::fromLocal8Bit(GmicQt::commandFromOutputMessageMode(_messageMode));
210     GmicQt::appendWithSpace(fullCommandLine, _command);
211     GmicQt::appendWithSpace(fullCommandLine, _arguments);
212     _gmicAbort = false;
213     _gmicProgress = -1;
214     if (_messageMode > GmicQt::Quiet) {
215       Logger::log(QString("\n[%1]%2 %3\n").arg(GmicQt::pluginCodeName()).arg(_logSuffix).arg(fullCommandLine));
216     }
217     gmic gmicInstance(_environment.isEmpty() ? nullptr : QString("%1").arg(_environment).toLocal8Bit().constData(), GmicStdLib::Array.constData(), true);
218     gmicInstance.set_variable("_host", GmicQt::HostApplicationShortname, '=');
219     gmicInstance.set_variable("_tk", "qt", '=');
220     gmicInstance.run(fullCommandLine.toLocal8Bit().constData(), *_images, *_imageNames, &_gmicProgress, &_gmicAbort);
221     _gmicStatus = gmicInstance.status;
222   } catch (gmic_exception & e) {
223     _images->assign();
224     _imageNames->assign();
225     const char * message = e.what();
226     _errorMessage = message;
227     if (_messageMode > GmicQt::Quiet) {
228       Logger::log(QString("\n[%1]./error/ When running command '%2', this error occurred:\n%3\n").arg(GmicQt::pluginCodeName()).arg(fullCommandLine).arg(message));
229     }
230     _failed = true;
231   }
232 }
233