1 /*
2  * Copyright (C) 2017 Boudewijn Rempt <boud@valdyas.org>
3  *
4  * @contributor Sebastien Fourey (2021) : Adapt to new plugin API
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public License
17  * along with this library; see the file COPYING.LIB.  If not, write to
18  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 #include <QApplication>
23 #include <QBuffer>
24 #include <QByteArray>
25 #include <QCommandLineOption>
26 #include <QCommandLineParser>
27 #include <QDataStream>
28 #include <QDebug>
29 #include <QDesktopWidget>
30 #include <QFileDialog>
31 #include <QFileInfo>
32 #include <QLocalServer>
33 #include <QLocalSocket>
34 #include <QProcess>
35 #include <QSharedMemory>
36 #include <QStandardPaths>
37 #include <QUuid>
38 
39 #include <algorithm>
40 #include <list>
41 #include "Common.h"
42 #include "Host/GmicQtHost.h"
43 #include "GmicQt.h"
44 #include "gmic.h"
45 
46 /*
47  * Messages to Krita are built like this:
48  *
49  * command
50  * mode=int
51  * layer=key,imagename
52  * croprect=x,y,w,h
53  *
54  * Messages from Krita are built like this:
55  *
56  * key,imagename
57  *
58  * After a message has been received, "ack" is sent
59  *
60  */
61 
62 namespace GmicQtHost
63 {
64 const QString ApplicationName = QString("Krita");
65 const char * const ApplicationShortname = GMIC_QT_XSTRINGIFY(GMIC_HOST);
66 const bool DarkThemeIsDefault = true;
67 } // namespace GmicQtHost
68 
69 static QString socketKey = "gmic-krita";
70 static const char ack[] = "ack";
71 static QVector<QSharedMemory *> sharedMemorySegments;
72 
sendMessageSynchronously(const QByteArray ba)73 QByteArray sendMessageSynchronously(const QByteArray ba)
74 {
75   QByteArray answer;
76 
77   // Send a message to Krita to ask for the images and image with the given crop and mode
78   QLocalSocket socket;
79   socket.connectToServer(socketKey);
80   bool connected = socket.waitForConnected(1000);
81   if (!connected) {
82     qWarning() << "Could not connect to the Krita instance.";
83     return answer;
84   }
85 
86   // Send the message to Krita
87   QDataStream ds(&socket);
88   ds.writeBytes(ba.constData(), ba.length());
89   socket.waitForBytesWritten();
90 
91   while (socket.bytesAvailable() < static_cast<int>(sizeof(quint32))) {
92     if (!socket.isValid()) {
93       qWarning() << "Stale request";
94       return answer;
95     }
96     socket.waitForReadyRead(1000);
97   }
98 
99   // Get the answer
100   quint32 remaining;
101   ds >> remaining;
102   answer.resize(remaining);
103   int got = 0;
104   char * answerBuf = answer.data();
105   do {
106     got = ds.readRawData(answerBuf, remaining);
107     remaining -= got;
108     answerBuf += got;
109   } while (remaining && got >= 0 && socket.waitForReadyRead(2000));
110 
111   if (got < 0) {
112     qWarning() << "Could not receive the answer." << socket.errorString();
113     return answer;
114   }
115 
116   // Acknowledge receipt
117   socket.write(ack, qstrlen(ack));
118   socket.waitForBytesWritten(1000);
119   socket.disconnectFromServer();
120 
121   return answer;
122 }
123 
124 namespace GmicQtHost
125 {
126 
getLayersExtent(int * width,int * height,GmicQt::InputMode mode)127 void getLayersExtent(int * width, int * height, GmicQt::InputMode mode)
128 {
129   *width = 0;
130   *height = 0;
131   QByteArray command = QString("command=gmic_qt_get_image_size\nmode=%1").arg((int)mode).toUtf8();
132 
133   QString answer = QString::fromUtf8(sendMessageSynchronously(command));
134   if (answer.length() > 0) {
135     QList<QString> wh = answer.split(',', QT_SKIP_EMPTY_PARTS);
136     if (wh.length() == 2) {
137       *width = wh[0].toInt();
138       *height = wh[1].toInt();
139     }
140   }
141 
142   // qDebug() << "gmic-qt: layers extent:" << *width << *height;
143 }
144 
getCroppedImages(gmic_list<float> & images,gmic_list<char> & imageNames,double x,double y,double width,double height,GmicQt::InputMode mode)145 void getCroppedImages(gmic_list<float> & images, gmic_list<char> & imageNames, double x, double y, double width, double height, GmicQt::InputMode mode)
146 {
147 
148   // qDebug() << "gmic-qt: get_cropped_images:" << x << y << width << height;
149 
150   const bool entireImage = x < 0 && y < 0 && width < 0 && height < 0;
151   if (entireImage) {
152     x = 0.0;
153     y = 0.0;
154     width = 1.0;
155     height = 1.0;
156   }
157 
158   // Create a message for Krita
159   QString message = QString("command=gmic_qt_get_cropped_images\nmode=%5\ncroprect=%1,%2,%3,%4").arg(x).arg(y).arg(width).arg(height).arg((int)mode);
160   QByteArray command = message.toUtf8();
161   QString answer = QString::fromUtf8(sendMessageSynchronously(command));
162 
163   if (answer.isEmpty()) {
164     qWarning() << "\tgmic-qt: empty answer!";
165     return;
166   }
167 
168   // qDebug() << "\tgmic-qt: " << answer;
169 
170   QStringList imagesList = answer.split("\n", QT_SKIP_EMPTY_PARTS);
171 
172   images.assign(imagesList.size());
173   imageNames.assign(imagesList.size());
174 
175   // qDebug() << "\tgmic-qt: imagelist size" << imagesList.size();
176 
177   // Parse the answer -- there should be no new lines in layernames
178   QStringList memoryKeys;
179   QList<QSize> sizes;
180   // Get the keys for the shared memory areas and the imageNames as prepared by Krita in G'Mic format
181   for (int i = 0; i < imagesList.length(); ++i) {
182     const QString & layer = imagesList[i];
183     QStringList parts = layer.split(',', QT_SKIP_EMPTY_PARTS);
184     if (parts.size() != 4) {
185       qWarning() << "\tgmic-qt: Got the wrong answer!";
186     }
187     memoryKeys << parts[0];
188     QByteArray ba = parts[1].toLatin1();
189     ba = QByteArray::fromHex(ba);
190     gmic_image<char>::string(ba.constData()).move_to(imageNames[i]);
191     sizes << QSize(parts[2].toInt(), parts[3].toInt());
192   }
193 
194   // qDebug() << "\tgmic-qt: keys" << memoryKeys;
195 
196   // Fill images from the shared memory areas
197   for (int i = 0; i < memoryKeys.length(); ++i) {
198     const QString & key = memoryKeys[i];
199     QSharedMemory m(key);
200 
201     if (!m.attach(QSharedMemory::ReadOnly)) {
202       qWarning() << "\tgmic-qt: Could not attach to shared memory area." << m.error() << m.errorString();
203     }
204     if (m.isAttached()) {
205       if (!m.lock()) {
206         qWarning() << "\tgmic-qt: Could not lock memory segment" << m.error() << m.errorString();
207       }
208       // qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data();
209 
210       // convert the data to the list of float
211       gmic_image<float> gimg;
212       gimg.assign(sizes[i].width(), sizes[i].height(), 1, 4);
213       memcpy(gimg._data, m.constData(), sizes[i].width() * sizes[i].height() * 4 * sizeof(float));
214       gimg.move_to(images[i]);
215 
216       if (!m.unlock()) {
217         qWarning() << "\tgmic-qt: Could not unlock memory segment" << m.error() << m.errorString();
218       }
219       if (!m.detach()) {
220         qWarning() << "\tgmic-qt: Could not detach from memory segment" << m.error() << m.errorString();
221       }
222     } else {
223       qWarning() << "gmic-qt: Could not attach to shared memory area." << m.error() << m.errorString();
224     }
225   }
226 
227   sendMessageSynchronously("command=gmic_qt_detach");
228 
229   // qDebug() << "\tgmic-qt:  Images size" << images.size() << ", names size" << imageNames.size();
230 }
231 
outputImages(gmic_list<float> & images,const gmic_list<char> & imageNames,GmicQt::OutputMode mode)232 void outputImages(gmic_list<float> & images, const gmic_list<char> & imageNames, GmicQt::OutputMode mode)
233 {
234 
235   // qDebug() << "qmic-qt-output-images";
236 
237   Q_FOREACH (QSharedMemory * sharedMemory, sharedMemorySegments) {
238     if (sharedMemory->isAttached()) {
239       sharedMemory->detach();
240     }
241   }
242   qDeleteAll(sharedMemorySegments);
243   sharedMemorySegments.clear();
244 
245   // qDebug() << "\tqmic-qt: shared memory" << sharedMemorySegments.count();
246 
247   // Create qsharedmemory segments for each image
248   // Create a message for Krita based on mode, the keys of the qsharedmemory segments and the imageNames
249   QString message = QString("command=gmic_qt_output_images\nmode=%1\n").arg((int)mode);
250 
251   for (uint i = 0; i < images.size(); ++i) {
252 
253     // qDebug() << "\tgmic-qt: image number" << i;
254 
255     gmic_image<float> gimg = images.at(i);
256 
257     QSharedMemory * m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString()));
258     sharedMemorySegments.append(m);
259 
260     if (!m->create(gimg._width * gimg._height * gimg._spectrum * sizeof(float))) {
261       qWarning() << "Could not create shared memory" << m->error() << m->errorString();
262       return;
263     }
264 
265     m->lock();
266     memcpy(m->data(), gimg._data, gimg._width * gimg._height * gimg._spectrum * sizeof(float));
267     m->unlock();
268 
269     QString layerName((const char *)imageNames[i]);
270 
271     message += "layer=" + m->key() + "," + layerName.toUtf8().toHex() + "," + QString("%1,%2,%3").arg(gimg._spectrum).arg(gimg._width).arg(gimg._height) + +"\n";
272   }
273   sendMessageSynchronously(message.toUtf8());
274 }
275 
showMessage(const char *)276 void showMessage(const char *)
277 {
278   // May be left empty for Krita.
279   // Only used by launchPluginHeadless(), called in the non-interactive
280   // script mode of GIMP.
281 }
282 
applyColorProfile(cimg_library::CImg<gmic_pixel_type> &)283 void applyColorProfile(cimg_library::CImg<gmic_pixel_type> &) {}
284 
285 } // namespace GmicQtHost
286 
287 #if defined Q_OS_WIN
288 #if defined DRMINGW
289 namespace
290 {
tryInitDrMingw()291 void tryInitDrMingw()
292 {
293   wchar_t path[MAX_PATH];
294   QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll");
295   if (pathStr.size() > MAX_PATH - 1) {
296     return;
297   }
298   int pathLen = pathStr.toWCharArray(path);
299   path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator
300   HMODULE hMod = LoadLibraryW(path);
301   if (!hMod) {
302     return;
303   }
304   // No need to call ExcHndlInit since the crash handler is installed on DllMain
305   auto myExcHndlSetLogFileNameA = reinterpret_cast<BOOL(APIENTRY *)(const char *)>(GetProcAddress(hMod, "ExcHndlSetLogFileNameA"));
306   if (!myExcHndlSetLogFileNameA) {
307     return;
308   }
309   // Set the log file path to %LocalAppData%\kritacrash.log
310   QString logFile = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).replace(L'/', L'\\') + QStringLiteral("\\gmic_krita_qt_crash.log");
311   myExcHndlSetLogFileNameA(logFile.toLocal8Bit());
312 }
313 } // namespace
314 #endif // DRMINGW
315 #endif // Q_OS_WIN
316 
main(int argc,char * argv[])317 int main(int argc, char * argv[])
318 {
319 
320   bool headless = false;
321   {
322     QCommandLineParser parser;
323     parser.setApplicationDescription("Krita G'Mic Plugin");
324     parser.addHelpOption();
325     parser.addPositionalArgument("socket key", "Key to find Krita's local server socket");
326     QCoreApplication app(argc, argv);
327     parser.process(app);
328     const QStringList args = parser.positionalArguments();
329     if (args.size() > 0) {
330       socketKey = args[0];
331     }
332     if (args.size() > 1) {
333       if (args[1] == "reapply") {
334         headless = true;
335       }
336     }
337 #if defined Q_OS_WIN
338 #if defined DRMINGW
339     tryInitDrMingw();
340 #endif
341 #endif
342   }
343 
344   std::list<GmicQt::InputMode> disabledInputModes;
345   disabledInputModes.push_back(GmicQt::InputMode::NoInput);
346   // disabledInputModes.push_back(GmicQt::InputMode::Active);
347   // disabledInputModes.push_back(GmicQt::InputMode::All);
348   // disabledInputModes.push_back(GmicQt::InputMode::ActiveAndBelow);
349   // disabledInputModes.push_back(GmicQt::InputMode::ActiveAndAbove);
350   disabledInputModes.push_back(GmicQt::InputMode::AllVisible);
351   disabledInputModes.push_back(GmicQt::InputMode::AllInvisible);
352 
353   std::list<GmicQt::OutputMode> disabledOutputModes;
354   // disabledOutputModes.push_back(GmicQt::OutputMode::InPlace);
355   disabledOutputModes.push_back(GmicQt::OutputMode::NewImage);
356   disabledOutputModes.push_back(GmicQt::OutputMode::NewLayers);
357   disabledOutputModes.push_back(GmicQt::OutputMode::NewActiveLayers);
358 
359   qWarning() << "gmic-qt: socket Key:" << socketKey;
360   int r = 0;
361   if (headless) {
362     GmicQt::RunParameters parameters = GmicQt::lastAppliedFilterRunParameters(GmicQt::ReturnedRunParametersFlag::AfterFilterExecution);
363     r = GmicQt::run(GmicQt::UserInterfaceMode::ProgressDialog, parameters);
364   } else {
365     r = GmicQt::run(GmicQt::UserInterfaceMode::Full, //
366                     GmicQt::RunParameters(),         //
367                     disabledInputModes,              //
368                     disabledOutputModes);
369   }
370 
371   Q_FOREACH (QSharedMemory * sharedMemory, sharedMemorySegments) {
372     if (sharedMemory->isAttached()) {
373       sharedMemory->detach();
374     }
375   }
376 
377   qDeleteAll(sharedMemorySegments);
378   sharedMemorySegments.clear();
379 
380   return r;
381 }
382