Krita Source Code Documentation
Loading...
Searching...
No Matches
KoColorConversionSystem.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007-2008 Cyrille Berger <cberger@cberger.net>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
9
10#include <QHash>
11#include <QString>
12
15#include "KoColorProfile.h"
16#include "KoColorSpace.h"
19
20
22 : d(new Private(registryInterface))
23{
24}
25
27{
28 qDeleteAll(d->graph);
29 qDeleteAll(d->vertexes);
30 delete d;
31}
32
34{
35 Vertex* v1 = createVertex(_node, _engine);
36 Vertex* v2 = createVertex(_engine, _node);
37
38 Q_UNUSED(v1);
39 Q_UNUSED(v2);
40}
41
43{
44 NodeKey key(engine->id(), engine->id(), engine->id());
45 Node* n = new Node;
46 n->modelId = engine->id();
47 n->depthId = engine->id();
48 n->profileName = engine->id();
49 n->referenceDepth = 64; // engine don't have reference depth,
50 d->graph.insert(key, n);
51 n->init(engine);
52 return n;
53}
54
55
57{
58 dbgPigment << "Inserting color space " << csf->name() << " (" << csf->id() << ") Model: " << csf->colorModelId() << " Depth: " << csf->colorDepthId() << " into the CCS";
59 const QList<const KoColorProfile*> profiles = d->registryInterface->profilesFor(csf);
60 QString modelId = csf->colorModelId().id();
61 QString depthId = csf->colorDepthId().id();
63 if (profiles.isEmpty()) { // There is no profile for this CS, create a node without profile name if the color engine isn't icc-based
64 if (csf->colorSpaceEngine() != "icc") {
65 Node* n = nodeFor(modelId, depthId, "default");
66 n->init(csf);
67 }
68 else {
69 dbgPigment << "Cannot add node for " << csf->name() << ", since there are no profiles available";
70 }
71 } else {
72 // Initialise the nodes
73 Q_FOREACH (const KoColorProfile* profile, profiles) {
74 Node* n = nodeFor(modelId, depthId, profile->name());
75 cctfs.append(csf->colorConversionLinksFromProfile(profile));
76 n->init(csf);
77 if (!csf->colorSpaceEngine().isEmpty()) {
79 Q_ASSERT(engine);
80 NodeKey engineKey(engine->id(), engine->id(), engine->id());
81 Node* engineNode = 0;
82 QHash<NodeKey, Node*>::ConstIterator it = d->graph.constFind(engineKey);
83 if (it != d->graph.constEnd()) {
84 engineNode = it.value();
85 } else {
86 engineNode = insertEngine(engine);
87 }
88
89 if (engine->supportsColorSpace(modelId, depthId, profile)) {
90 connectToEngine(n, engineNode);
91 }
92 }
93 }
94 }
95 // Construct a link for "custom" transformation
96 cctfs.append(csf->colorConversionLinks());
97 Q_FOREACH (KoColorConversionTransformationFactory* cctf, cctfs) {
98 Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile());
99 Q_ASSERT(srcNode);
100 Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile());
101 Q_ASSERT(dstNode);
102 // Check if the two nodes are already connected
103 Vertex* v = vertexBetween(srcNode, dstNode);
104 // If the vertex doesn't already exist, then create it
105 if (!v) {
106 v = createVertex(srcNode, dstNode);
107 }
108 Q_ASSERT(v); // we should have one now
109 if (dstNode->modelId == modelId && dstNode->depthId == depthId) {
110 v->setFactoryFromDst(cctf);
111 }
112 if (srcNode->modelId == modelId && srcNode->depthId == depthId) {
113 v->setFactoryFromSrc(cctf);
114 }
115 }
116}
117
119{
120 dbgPigmentCCS << _profile->name();
121 const QList< const KoColorSpaceFactory* >& factories = d->registryInterface->colorSpacesFor(_profile);
122 Q_FOREACH (const KoColorSpaceFactory* factory, factories) {
123 QString modelId = factory->colorModelId().id();
124 QString depthId = factory->colorDepthId().id();
125 Node* n = nodeFor(modelId, depthId, _profile->name());
126 n->init(factory);
127 if (!factory->colorSpaceEngine().isEmpty()) {
129 Q_ASSERT(engine);
130 Node* engineNode = d->graph[ NodeKey(engine->id(), engine->id(), engine->id())];
131 Q_ASSERT(engineNode);
132
133 if (engine->supportsColorSpace(modelId, depthId, _profile)) {
134 connectToEngine(n, engineNode);
135 }
136 }
138 cctfs.append(factory->colorConversionLinksFromProfile(_profile));
139 Q_FOREACH (KoColorConversionTransformationFactory* cctf, cctfs) {
140 Node* srcNode = nodeFor(cctf->srcColorModelId(), cctf->srcColorDepthId(), cctf->srcProfile());
141 Q_ASSERT(srcNode);
142 Node* dstNode = nodeFor(cctf->dstColorModelId(), cctf->dstColorDepthId(), cctf->dstProfile());
143 Q_ASSERT(dstNode);
144 if (srcNode == n || dstNode == n) {
145 // Check if the two nodes are already connected
146 Vertex* v = vertexBetween(srcNode, dstNode);
147 // If the vertex doesn't already exist, then create it
148 if (!v) {
149 v = createVertex(srcNode, dstNode);
150 }
151 Q_ASSERT(v); // we should have one now
152 if (dstNode->modelId == modelId && dstNode->depthId == depthId) {
153 v->setFactoryFromDst(cctf);
154 }
155 if (srcNode->modelId == modelId && srcNode->depthId == depthId) {
156 v->setFactoryFromSrc(cctf);
157 }
158 }
159 }
160 }
161}
162
164{
165 return d->registryInterface->colorSpace(node->modelId, node->depthId, node->profileName);
166}
167
168KoColorConversionSystem::Node* KoColorConversionSystem::createNode(const QString& _modelId, const QString& _depthId, const QString& _profileName)
169{
170 Node* n = new Node;
171 n->modelId = _modelId;
172 n->depthId = _depthId;
173 n->profileName = _profileName;
174 d->graph.insert(NodeKey(_modelId, _depthId, _profileName), n);
175 return n;
176}
177
179{
180 const KoColorProfile* profile = _colorSpace->profile();
181 return nodeFor(_colorSpace->colorModelId().id(), _colorSpace->colorDepthId().id(),
182 profile ? profile->name() : "default");
183}
184
185const KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName) const
186{
187 dbgPigmentCCS << "Look for node: " << _colorModelId << " " << _colorDepthId << " " << _profileName;
188 return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName));
189}
190
192{
193 dbgPigmentCCS << "Look for node: " << key.modelId << " " << key.depthId << " " << key.profileName << " " << d->graph.value(key);
194 return d->graph.value(key);
195}
196
197KoColorConversionSystem::Node* KoColorConversionSystem::nodeFor(const QString& _colorModelId, const QString& _colorDepthId, const QString& _profileName)
198{
199 return nodeFor(NodeKey(_colorModelId, _colorDepthId, _profileName));
200}
201
203{
204 QHash<NodeKey, Node*>::ConstIterator it = d->graph.constFind(key);
205 if (it != d->graph.constEnd()) {
206 return it.value();
207 } else {
208 return createNode(key.modelId, key.depthId, key.profileName);
209 }
210}
211
212QList<KoColorConversionSystem::Node*> KoColorConversionSystem::nodesFor(const QString& _modelId, const QString& _depthId)
213{
214 QList<Node*> nodes;
215 Q_FOREACH (Node* node, d->graph) {
216 if (node->modelId == _modelId && node->depthId == _depthId) {
217 nodes << node;
218 }
219 }
220 return nodes;
221}
222
223KoColorConversionTransformation* KoColorConversionSystem::createColorConverter(const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
224{
225 Q_ASSERT(srcColorSpace);
226 Q_ASSERT(dstColorSpace);
227 if (*srcColorSpace == *dstColorSpace) {
228 return new KoCopyColorConversionTransformation(srcColorSpace);
229 }
230 dbgPigmentCCS << srcColorSpace->id() << (srcColorSpace->profile() ? srcColorSpace->profile()->name() : "default");
231 dbgPigmentCCS << dstColorSpace->id() << (dstColorSpace->profile() ? dstColorSpace->profile()->name() : "default");
232 Path path = findBestPath(
233 nodeFor(srcColorSpace),
234 nodeFor(dstColorSpace));
235 Q_ASSERT(path.length() > 0);
236 KoColorConversionTransformation* transfo = createTransformationFromPath(path, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
237 Q_ASSERT(transfo);
238 Q_ASSERT(*transfo->srcColorSpace() == *srcColorSpace);
239 Q_ASSERT(*transfo->dstColorSpace() == *dstColorSpace);
240 return transfo;
241}
242
243void KoColorConversionSystem::createColorConverters(const KoColorSpace* colorSpace, const QList< QPair<KoID, KoID> >& possibilities, KoColorConversionTransformation*& fromCS, KoColorConversionTransformation*& toCS) const
244{
245 // TODO This function currently only select the best conversion only based on the transformation
246 // from colorSpace to one of the color spaces in the list, but not the other way around
247 // it might be worth to look also the return path.
248 const Node* csNode = nodeFor(colorSpace);
250 // Look for a color conversion
251 Path bestPath;
252 typedef QPair<KoID, KoID> KoID2KoID;
253 Q_FOREACH (const KoID2KoID & possibility, possibilities) {
254 const KoColorSpaceFactory* csf = d->registryInterface->colorSpaceFactory(possibility.first.id(), possibility.second.id());
255 if (csf) {
256 Path path = findBestPath(csNode, nodeFor(csf->colorModelId().id(), csf->colorDepthId().id(), csf->defaultProfile()));
257 Q_ASSERT(path.length() > 0);
258 path.isGood = pQC.isGoodPath(path);
259
260 if (bestPath.isEmpty()) {
261 bestPath = path;
262 } else if ((!bestPath.isGood && path.isGood) || pQC.lessWorseThan(path, bestPath)) {
263 bestPath = path;
264 }
265 }
266 }
267 Q_ASSERT(!bestPath.isEmpty());
268 const KoColorSpace* endColorSpace = defaultColorSpaceForNode(bestPath.endNode());
270 Path returnPath = findBestPath(bestPath.endNode(), csNode);
271 Q_ASSERT(!returnPath.isEmpty());
273 Q_ASSERT(*toCS->dstColorSpace() == *fromCS->srcColorSpace());
274 Q_ASSERT(*fromCS->dstColorSpace() == *toCS->srcColorSpace());
275}
276
277KoColorConversionTransformation* KoColorConversionSystem::createTransformationFromPath(const Path &path, const KoColorSpace * srcColorSpace, const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
278{
279 Q_ASSERT(srcColorSpace->colorModelId().id() == path.startNode()->modelId);
280 Q_ASSERT(srcColorSpace->colorDepthId().id() == path.startNode()->depthId);
281 Q_ASSERT(dstColorSpace->colorModelId().id() == path.endNode()->modelId);
282 Q_ASSERT(dstColorSpace->colorDepthId().id() == path.endNode()->depthId);
283
285
286 const QList< Path::node2factory > pathOfNode = path.compressedPath();
287
288 if (pathOfNode.size() == 2) { // Direct connection
289 transfo = pathOfNode[1].second->createColorTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
290 }
291 else {
292
293 KoMultipleColorConversionTransformation* mccTransfo = new KoMultipleColorConversionTransformation(srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
294
295 transfo = mccTransfo;
296
297 // Get the first intermediary color space
298 dbgPigmentCCS << pathOfNode[ 0 ].first->id() << " to " << pathOfNode[ 1 ].first->id();
299
300 const KoColorSpace* intermCS =
301 defaultColorSpaceForNode(pathOfNode[1].first);
302
303 mccTransfo->appendTransfo(pathOfNode[1].second->createColorTransformation(srcColorSpace, intermCS, renderingIntent, conversionFlags));
304
305 for (int i = 2; i < pathOfNode.size() - 1; i++) {
306 dbgPigmentCCS << pathOfNode[ i - 1 ].first->id() << " to " << pathOfNode[ i ].first->id();
307 const KoColorSpace* intermCS2 = defaultColorSpaceForNode(pathOfNode[i].first);
308 Q_ASSERT(intermCS2);
309 mccTransfo->appendTransfo(pathOfNode[i].second->createColorTransformation(intermCS, intermCS2, renderingIntent, conversionFlags));
310 intermCS = intermCS2;
311 }
312
313 dbgPigmentCCS << pathOfNode[ pathOfNode.size() - 2 ].first->id() << " to " << pathOfNode[ pathOfNode.size() - 1 ].first->id();
314 mccTransfo->appendTransfo(pathOfNode.last().second->createColorTransformation(intermCS, dstColorSpace, renderingIntent, conversionFlags));
315 }
316 return transfo;
317}
318
319
321{
322 Q_FOREACH (Vertex* oV, srcNode->outputVertexes) {
323 if (oV->dstNode == dstNode) {
324 return oV;
325 }
326 }
327 return 0;
328}
329
331{
332 Vertex* v = new Vertex(srcNode, dstNode);
333 srcNode->outputVertexes.append(v);
334 d->vertexes.append(v);
335 return v;
336}
337
338// -- Graph visualization functions --
339
341{
342 return QString(" \"%1\" -> \"%2\" %3\n").arg(v->srcNode->id()).arg(v->dstNode->id()).arg(options);
343}
344
346{
347 QString dot = "digraph CCS {\n";
348 Q_FOREACH (Vertex* oV, d->vertexes) {
349 dot += vertexToDot(oV, "default") ;
350 }
351 dot += "}\n";
352 return dot;
353}
354
355bool KoColorConversionSystem::existsPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const
356{
357 dbgPigmentCCS << "srcModelId = " << srcModelId << " srcDepthId = " << srcDepthId << " srcProfileName = " << srcProfileName << " dstModelId = " << dstModelId << " dstDepthId = " << dstDepthId << " dstProfileName = " << dstProfileName;
358 const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName);
359 const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName);
360 if (srcNode == dstNode) return true;
361 if (!srcNode) return false;
362 if (!dstNode) return false;
363 Path path = findBestPath(srcNode, dstNode);
364 bool exist = !path.isEmpty();
365 return exist;
366}
367
368bool KoColorConversionSystem::existsGoodPath(const QString& srcModelId, const QString& srcDepthId, const QString& srcProfileName, const QString& dstModelId, const QString& dstDepthId, const QString& dstProfileName) const
369{
370 const Node* srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName);
371 const Node* dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName);
372 if (srcNode == dstNode) return true;
373 if (!srcNode) return false;
374 if (!dstNode) return false;
375 Path path = findBestPath(srcNode, dstNode);
376 bool existAndGood = path.isGood;
377 return existAndGood;
378}
379
380KoColorConversionSystem::Path KoColorConversionSystem::findBestPath(const QString &srcModelId, const QString &srcDepthId, const QString &srcProfileName, const QString &dstModelId, const QString &dstDepthId, const QString &dstProfileName) const
381{
382 const Node *srcNode = nodeFor(srcModelId, srcDepthId, srcProfileName);
383 const Node *dstNode = nodeFor(dstModelId, dstDepthId, dstProfileName);
384
385 KIS_ASSERT(srcNode);
386 KIS_ASSERT(dstNode);
387
388 return findBestPath(srcNode, dstNode);
389}
390
392{
393 const Node *srcNode = nodeFor(src);
394 const Node *dstNode = nodeFor(dst);
395
396 KIS_ASSERT(srcNode);
397 KIS_ASSERT(dstNode);
398
399 return findBestPath(srcNode, dstNode);
400}
401
402
403QString KoColorConversionSystem::bestPathToDot(const QString& srcKey, const QString& dstKey) const
404{
405 const Node* srcNode = 0;
406 const Node* dstNode = 0;
407 Q_FOREACH (Node* node, d->graph) {
408 if (node->id() == srcKey) {
409 srcNode = node;
410 }
411 if (node->id() == dstKey) {
412 dstNode = node;
413 }
414 }
415 Path p = findBestPath(srcNode, dstNode);
416 Q_ASSERT(!p.isEmpty());
417 QString dot = "digraph CCS {\n" +
418 QString(" \"%1\" [color=red]\n").arg(srcNode->id()) +
419 QString(" \"%1\" [color=red]\n").arg(dstNode->id());
420 Q_FOREACH (Vertex* oV, d->vertexes) {
421 QString options;
422 if (p.vertexes.contains(oV)) {
423 options = "[color=red]";
424 }
425 dot += vertexToDot(oV, options) ;
426 }
427 dot += "}\n";
428 return dot;
429}
430
432{
433 KIS_ASSERT(srcNode);
434 KIS_ASSERT(dstNode);
435
436 dbgPigmentCCS << "Find best path between " << srcNode->id() << " and " << dstNode->id();
437
438 PathQualityChecker pQC(qMin(srcNode->referenceDepth, dstNode->referenceDepth));
439 Node2PathHash node2path; // current best path to reach a given node
440 QList<Path> possiblePaths; // list of all paths
441 // Generate the initial list of paths
442 Q_FOREACH (Vertex* v, srcNode->outputVertexes) {
443 if (v->dstNode->isInitialized) {
444 Path p;
446 Node* endNode = v->dstNode;
447 if (endNode == dstNode) {
448 Q_ASSERT(pQC.isGoodPath(p)); // <- it's a direct link, it has to be a good path
449 p.isGood = true;
450 return p;
451 } else {
452 //Q_ASSERT(!node2path.contains(endNode)); // That would be a total fuck up if there are two vertices between two nodes
453 node2path.insert(endNode, p);
454 possiblePaths.append(p);
455 }
456 }
457 }
458
459 Path currentBestPath;
460 // Continue while there are any possibilities remaining
461 while (possiblePaths.size() > 0) {
462
463 // Loop through all paths and explore one step further
464 const QList<Path> currentPaths = possiblePaths;
465 for (const Path &p : currentPaths) {
466 const Node* endNode = p.endNode();
467 for (Vertex* v : endNode->outputVertexes) {
468 if (v->dstNode->isInitialized && !p.contains(v->dstNode)) {
469 Path newP = p; // Candidate
470 newP.appendVertex(v);
471 Node* newEndNode = v->dstNode;
472 if (newEndNode == dstNode) {
473 if (pQC.isGoodPath(newP)) { // Victory
474 newP.isGood = true;
475 return newP;
476 } else if (pQC.lessWorseThan(newP, currentBestPath)) {
477 if (newP.startNode() && newP.endNode() && currentBestPath.startNode() && currentBestPath.endNode()) {
478 Q_ASSERT(newP.startNode()->id() == currentBestPath.startNode()->id());
479 Q_ASSERT(newP.endNode()->id() == currentBestPath.endNode()->id());
480 // Can we do better than dumping memory values???
481 // warnPigment << pQC.lessWorseThan(newP, currentBestPath) << " " << newP << " " << currentBestPath;
482 currentBestPath = newP;
483 }
484 }
485 } else {
486 // This is an incomplete path. Check if there's a better way to get to its endpoint.
487 Node2PathHash::Iterator it = node2path.find(newEndNode);
488 if (it != node2path.end()) {
489 Path &p2 = it.value();
490 if (pQC.lessWorseThan(newP, p2)) {
491 p2 = newP;
492 possiblePaths.append(newP);
493 }
494 } else {
495 node2path.insert(newEndNode, newP);
496 possiblePaths.append(newP);
497 }
498 }
499 }
500 }
501 possiblePaths.removeAll(p); // Remove from list of remaining paths
502 }
503 }
504
505 if (!currentBestPath.isEmpty()) {
506 warnPigment << "No good path from " << srcNode->id() << " to " << dstNode->id() << " found : length = " << currentBestPath.length() << " cost = " << currentBestPath.cost << " referenceDepth = " << currentBestPath.referenceDepth << " respectColorCorrectness = " << " isGood = " << currentBestPath.isGood ;
507 return currentBestPath;
508 }
509 errorPigment << "No path from " << srcNode->id() << " to " << dstNode->id() << " found not ";
510 return currentBestPath;
511}
#define dbgPigment
#define dbgPigmentCCS
#define warnPigment
#define errorPigment
const Params2D p
qreal v
QPointF p2
QHash< KoColorConversionSystem::Node *, KoColorConversionSystem::Path > Node2PathHash
KoColorConversionTransformation * createTransformationFromPath(const KoColorConversionSystem::Path &path, const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
Vertex * createVertex(Node *srcNode, Node *dstNode)
Node * insertEngine(const KoColorSpaceEngine *engine)
QList< Node * > nodesFor(const QString &_modelId, const QString &_depthId)
void connectToEngine(Node *_node, Node *_engine)
bool existsGoodPath(const QString &srcModelId, const QString &srcDepthId, const QString &srcProfileName, const QString &dstModelId, const QString &dstDepthId, const QString &dstProfileName) const
KoColorConversionTransformation * createColorConverter(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
KoColorConversionSystem(RegistryInterface *registryInterface)
Path findBestPath(const QString &srcModelId, const QString &srcDepthId, const QString &srcProfileName, const QString &dstModelId, const QString &dstDepthId, const QString &dstProfileName) const
const KoColorSpace * defaultColorSpaceForNode(const Node *node) const
Vertex * vertexBetween(Node *srcNode, Node *dstNode)
void insertColorSpace(const KoColorSpaceFactory *)
Node * createNode(const QString &_modelId, const QString &_depthId, const QString &_profileName)
bool existsPath(const QString &srcModelId, const QString &srcDepthId, const QString &srcProfileName, const QString &dstModelId, const QString &dstDepthId, const QString &dstProfileName) const
void insertColorProfile(const KoColorProfile *)
const Node * nodeFor(const KoColorSpace *) const
QString bestPathToDot(const QString &srcKey, const QString &dstKey) const
QString vertexToDot(Vertex *v, const QString &options) const
void createColorConverters(const KoColorSpace *colorSpace, const QList< QPair< KoID, KoID > > &possibilities, KoColorConversionTransformation *&fromCS, KoColorConversionTransformation *&toCS) const
static KoColorSpaceEngineRegistry * instance()
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual const KoColorProfile * profile() const =0
T get(const QString &id) const
QString id() const
Definition KoID.cpp:63
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
void init(const KoColorSpaceFactory *_colorSpaceFactory)
virtual bool supportsColorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile) const
virtual KoID colorDepthId() const =0
virtual QString name() const =0
virtual QString defaultProfile() const =0
virtual KoID colorModelId() const =0
virtual QString id() const =0
virtual QList< KoColorConversionTransformationFactory * > colorConversionLinks() const =0
virtual QList< KoColorConversionTransformationFactory * > colorConversionLinksFromProfile(const KoColorProfile *profile) const
colorConversionLinksFromProfile Sometimes, we need to generate special color conversion links based o...
virtual QString colorSpaceEngine() const =0
void appendTransfo(KoColorConversionTransformation *transfo)
bool lessWorseThan(const KoColorConversionSystem::Path &path1, const KoColorConversionSystem::Path &path2) const
bool isGoodPath(const KoColorConversionSystem::Path &path) const