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