Permalink
Fetching contributors…
Cannot retrieve contributors at this time
367 lines (308 sloc) 11.4 KB
/*
* Copyright (C) 2017 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <QStandardPaths>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QApplication>
#include <QProcess>
#include <QByteArray>
#include <QDebug>
#include <QFileDialog>
#include <QSharedMemory>
#include <QFileInfo>
#include <QDesktopWidget>
#include <QLocalServer>
#include <QLocalSocket>
#include <QDataStream>
#include <QBuffer>
#include <QUuid>
#include <ImageConverter.h>
#include <algorithm>
#include "Host/host.h"
#include "gmic_qt.h"
#include "gmic.h"
/*
* Messages to Krita are built like this:
*
* command
* mode=int
* layer=key,imagename
* croprect=x,y,w,h
*
* Messages from Krita are built like this:
*
* key,imagename
*
* After a message has been received, "ack" is sent
*
*/
namespace GmicQt {
const QString HostApplicationName = QString("Krita");
const char *HostApplicationShortname = GMIC_QT_XSTRINGIFY(GMIC_HOST);
}
static QString socketKey = "gmic-krita";
static const char ack[] = "ack";
static QVector<QSharedMemory*> sharedMemorySegments;
QByteArray sendMessageSynchronously(const QByteArray ba)
{
QByteArray answer;
// Send a message to Krita to ask for the images and image with the given crop and mode
QLocalSocket socket;
socket.connectToServer(socketKey);
bool connected = socket.waitForConnected(1000);
if (!connected) {
qWarning() << "Could not connect to the Krita instance.";
return answer;
}
// Send the message to Krita
QDataStream ds(&socket);
ds.writeBytes(ba.constData(), ba.length());
socket.waitForBytesWritten();
while (socket.bytesAvailable() < static_cast<int>(sizeof(quint32))) {
if (!socket.isValid()) {
qWarning() << "Stale request";
return answer;
}
socket.waitForReadyRead(1000);
}
// Get the answer
quint32 remaining;
ds >> remaining;
answer.resize(remaining);
int got = 0;
char *answerBuf = answer.data();
do {
got = ds.readRawData(answerBuf, remaining);
remaining -= got;
answerBuf += got;
} while (remaining && got >= 0 && socket.waitForReadyRead(2000));
if (got < 0) {
qWarning() << "Could not receive the answer." << socket.errorString();
return answer;
}
// Acknowledge receipt
socket.write(ack, qstrlen(ack));
socket.waitForBytesWritten(1000);
socket.disconnectFromServer();
return answer;
}
void gmic_qt_get_layers_extent(int *width, int *height, GmicQt::InputMode mode)
{
*width = 0;
*height = 0;
QByteArray command = QString("command=gmic_qt_get_image_size\nmode=%1").arg((int)mode).toUtf8();
QString answer = QString::fromUtf8(sendMessageSynchronously(command));
if (answer.length() > 0) {
QList<QString> wh = answer.split(',', QString::SkipEmptyParts);
if (wh.length() == 2) {
*width = wh[0].toInt();
*height = wh[1].toInt();
}
}
//qDebug() << "gmic-qt: layers extent:" << *width << *height;
}
void gmic_qt_get_cropped_images(gmic_list<float> & images,
gmic_list<char> & imageNames,
double x, double y, double width, double height,
GmicQt::InputMode mode)
{
//qDebug() << "gmic-qt: get_cropped_images:" << x << y << width << height;
const bool entireImage = x < 0 && y < 0 && width < 0 && height < 0;
if (entireImage) {
x = 0.0;
y = 0.0;
width = 1.0;
height = 1.0;
}
// Create a message for Krita
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(mode);
QByteArray command = message.toUtf8();
QString answer = QString::fromUtf8(sendMessageSynchronously(command));
if (answer.isEmpty()) {
qWarning() << "\tgmic-qt: empty answer!";
return;
}
//qDebug() << "\tgmic-qt: " << answer;
QStringList imagesList = answer.split("\n", QString::SkipEmptyParts);
images.assign(imagesList.size());
imageNames.assign(imagesList.size());
//qDebug() << "\tgmic-qt: imagelist size" << imagesList.size();
// Parse the answer -- there should be no new lines in layernames
QStringList memoryKeys;
QList<QSize> sizes;
// Get the keys for the shared memory areas and the imageNames as prepared by Krita in G'Mic format
for (int i = 0; i < imagesList.length(); ++i) {
const QString &layer = imagesList[i];
QStringList parts = layer.split(',', QString::SkipEmptyParts);
if (parts.size() != 4) {
qWarning() << "\tgmic-qt: Got the wrong answer!";
}
memoryKeys << parts[0];
QByteArray ba = parts[1].toLatin1();
ba = QByteArray::fromHex(ba);
gmic_image<char>::string(ba.constData()).move_to(imageNames[i]);
sizes << QSize(parts[2].toInt(), parts[3].toInt());
}
//qDebug() << "\tgmic-qt: keys" << memoryKeys;
// Fill images from the shared memory areas
for (int i = 0; i < memoryKeys.length(); ++i) {
const QString &key = memoryKeys[i];
QSharedMemory m(key);
if (!m.attach(QSharedMemory::ReadOnly)) {
qWarning() << "\tgmic-qt: Could not attach to shared memory area." << m.error() << m.errorString();
}
if (m.isAttached()) {
if (!m.lock()) {
qWarning() << "\tgmic-qt: Could not lock memeory segment" << m.error() << m.errorString();
}
//qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data();
// convert the data to the list of float
gmic_image<float> gimg;
gimg.assign(sizes[i].width(), sizes[i].height(), 1, 4);
memcpy(gimg._data, m.constData(), sizes[i].width() * sizes[i].height() * 4 * sizeof(float));
gimg.move_to(images[i]);
if (!m.unlock()) {
qWarning() << "\tgmic-qt: Could not unlock memeory segment" << m.error() << m.errorString();
}
if (!m.detach()) {
qWarning() << "\tgmic-qt: Could not detach from memeory segment" << m.error() << m.errorString();
}
}
else {
qWarning() << "gmic-qt: Could not attach to shared memory area." << m.error() << m.errorString();
}
}
sendMessageSynchronously("command=gmic_qt_detach");
//qDebug() << "\tgmic-qt: Images size" << images.size() << ", names size" << imageNames.size();
}
void gmic_qt_output_images( gmic_list<float> & images,
const gmic_list<char> & imageNames,
GmicQt::OutputMode mode,
const char * /*verboseLayersLabel*/)
{
//qDebug() << "qmic-qt-output-images";
Q_FOREACH(QSharedMemory *sharedMemory, sharedMemorySegments) {
if (sharedMemory->isAttached()) {
sharedMemory->detach();
}
}
qDeleteAll(sharedMemorySegments);
sharedMemorySegments.clear();
//qDebug() << "\tqmic-qt: shared memory" << sharedMemorySegments.count();
// Create qsharedmemory segments for each image
// Create a message for Krita based on mode, the keys of the qsharedmemory segments and the imageNames
QString message = QString("command=gmic_qt_output_images\nmode=%1\n").arg(mode);
for (uint i = 0; i < images.size(); ++i) {
//qDebug() << "\tgmic-qt: image number" << i;
gmic_image<float> gimg = images.at(i);
QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString()));
sharedMemorySegments.append(m);
if (!m->create(gimg._width * gimg._height * gimg._spectrum * sizeof(float))) {
qWarning() << "Could not create shared memory" << m->error() << m->errorString();
return;
}
m->lock();
memcpy(m->data(), gimg._data, gimg._width * gimg._height * gimg._spectrum * sizeof(float));
m->unlock();
QString layerName((const char *const)imageNames[i]);
message += "layer=" + m->key() + ","
+ layerName.toUtf8().toHex() + ","
+ QString("%1,%2,%3").arg(gimg._spectrum).arg(gimg._width).arg(gimg._height)
+ + "\n";
}
sendMessageSynchronously(message.toUtf8());
}
void gmic_qt_show_message(const char * )
{
// May be left empty for Krita.
// Only used by launchPluginHeadless(), called in the non-interactive
// script mode of GIMP.
}
void gmic_qt_apply_color_profile(cimg_library::CImg<gmic_pixel_type> & )
{
}
#if defined Q_OS_WIN
namespace
{
void tryInitDrMingw()
{
wchar_t path[MAX_PATH];
QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll");
if (pathStr.size() > MAX_PATH - 1) {
return;
}
int pathLen = pathStr.toWCharArray(path);
path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator
HMODULE hMod = LoadLibraryW(path);
if (!hMod) {
return;
}
// No need to call ExcHndlInit since the crash handler is installed on DllMain
auto myExcHndlSetLogFileNameA = reinterpret_cast<BOOL (APIENTRY *)(const char *)>(GetProcAddress(hMod, "ExcHndlSetLogFileNameA"));
if (!myExcHndlSetLogFileNameA) {
return;
}
// Set the log file path to %LocalAppData%\kritacrash.log
QString logFile = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).replace(L'/', L'\\') + QStringLiteral("\\gmic_krita_qt_crash.log");
myExcHndlSetLogFileNameA(logFile.toLocal8Bit());
}
} // namespace
#endif
int main(int argc, char *argv[])
{
bool headless = false;
{
QCommandLineParser parser;
parser.setApplicationDescription("Krita G'Mic Plugin");
parser.addHelpOption();
parser.addPositionalArgument("socket key", "Key to find Krita's local server socket");
QCoreApplication app(argc, argv);
parser.process(app);
const QStringList args = parser.positionalArguments();
if (args.size() > 0) {
socketKey = args[0];
}
if (args.size() > 1) {
if (args[1] == "reapply") {
headless = true;
}
}
#if defined Q_OS_WIN
#if defined DRMINGW
tryInitDrMingw();
#endif
#endif
}
qWarning() << "gmic-qt: socket Key:" << socketKey;
int r = 0;
if (headless) {
r = launchPluginHeadlessUsingLastParameters();
}
else {
r = launchPlugin();
}
Q_FOREACH(QSharedMemory *sharedMemory, sharedMemorySegments) {
if (sharedMemory->isAttached()) {
sharedMemory->detach();
}
}
qDeleteAll(sharedMemorySegments);
sharedMemorySegments.clear();
return r;
}