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