File indexing completed on 2024-05-12 15:59:32

0001 /*
0002  *  SPDX-FileCopyrightText: 2007-2008 Cyrille Berger <cberger@cberger.net>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #ifndef KOCOLORCONVERSIONSYSTEM_P_H
0008 #define KOCOLORCONVERSIONSYSTEM_P_H
0009 
0010 #include "DebugPigment.h"
0011 #include "KoColorSpaceRegistry.h"
0012 #include "KoColorModelStandardIds.h"
0013 #include "KoColorConversionTransformationFactory.h"
0014 #include "KoColorSpaceEngine.h"
0015 #include "KoColorConversionSystem.h"
0016 #include <boost/operators.hpp>
0017 
0018 #include <QList>
0019 
0020 enum NodeCapability {
0021     None = 0x0,
0022     HasColor = 0x1,
0023     HasAlpha = 0x2,
0024     HasHdr = 0x4
0025 };
0026 
0027 Q_DECLARE_FLAGS(NodeCapabilities, NodeCapability)
0028 Q_DECLARE_OPERATORS_FOR_FLAGS(NodeCapabilities)
0029 
0030 struct KoColorConversionSystem::Node : boost::equality_comparable<KoColorConversionSystem::Node>
0031 {
0032 
0033     Node()
0034         : isInitialized(false)
0035         , referenceDepth(0)
0036         , crossingCost(1)
0037         , colorSpaceFactory(0)
0038         , isEngine(false)
0039         , engine(0) {}
0040 
0041     void init(const KoColorSpaceFactory* _colorSpaceFactory) {
0042         dbgPigment << "Initialise " << modelId << " " << depthId << " " << profileName;
0043 
0044         if (isInitialized) {
0045             dbgPigment << "Re-initializing node. Old factory" << colorSpaceFactory << "new factory" << _colorSpaceFactory;
0046         }
0047         isInitialized = true;
0048 
0049         if (_colorSpaceFactory) {
0050             {
0051                 const bool isGray = modelId == GrayColorModelID.id() ||
0052                         modelId == GrayAColorModelID.id();
0053 
0054                 const bool isAlpha = modelId == AlphaColorModelID.id();
0055 
0056                 if (_colorSpaceFactory->isHdr()) {
0057                     m_capabilities |= HasHdr;
0058                 }
0059 
0060                 if (!isGray && !isAlpha) {
0061                     m_capabilities |= HasColor;
0062                 }
0063 
0064                 if (!isAlpha && modelId != GrayColorModelID.id()) {
0065                     m_capabilities |= HasAlpha;
0066                 }
0067             }
0068 
0069             colorSpaceFactory = _colorSpaceFactory;
0070             referenceDepth = _colorSpaceFactory->referenceDepth();
0071             crossingCost = _colorSpaceFactory->crossingCost();
0072         }
0073     }
0074 
0075     void init(const KoColorSpaceEngine* _engine) {
0076         Q_ASSERT(!isInitialized);
0077         isEngine = true;
0078         isInitialized = true;
0079         engine = _engine;
0080         m_capabilities = HasAlpha | HasColor | HasHdr;
0081     }
0082 
0083     QString id() const {
0084         return modelId + " " + depthId + " " + profileName;
0085     }
0086 
0087     NodeKey key() const;
0088 
0089     friend bool operator==(const Node &lhs, const Node &rhs) {
0090         return lhs.modelId == rhs.modelId &&
0091             lhs.depthId == rhs.depthId &&
0092             lhs.profileName == rhs.profileName;
0093     }
0094 
0095     NodeCapabilities capabilities() const {
0096         return m_capabilities;
0097     }
0098 
0099     QString modelId;
0100     QString depthId;
0101     QString profileName;
0102     bool isInitialized;
0103     int referenceDepth;
0104     QList<Vertex*> outputVertexes;
0105     int crossingCost;
0106     const KoColorSpaceFactory* colorSpaceFactory;
0107     bool isEngine;
0108     const KoColorSpaceEngine* engine;
0109     NodeCapabilities m_capabilities = None;
0110 };
0111 
0112 Q_DECLARE_TYPEINFO(KoColorConversionSystem::Node, Q_MOVABLE_TYPE);
0113 
0114 QDebug operator<<(QDebug dbg, const KoColorConversionSystem::Node &node)
0115 {
0116     dbg.nospace() << "Node(" << node.modelId << ", " << node.depthId << ", " << node.profileName << ")";
0117     return dbg.space();
0118 }
0119 
0120 struct KoColorConversionSystem::Vertex {
0121 
0122     Vertex(Node* _srcNode, Node* _dstNode)
0123         : srcNode(_srcNode)
0124         , dstNode(_dstNode)
0125         , factoryFromSrc(0)
0126         , factoryFromDst(0) {
0127     }
0128 
0129     ~Vertex() {
0130         if (factoryFromSrc == factoryFromDst) {
0131             delete factoryFromSrc;
0132         } else {
0133             delete factoryFromSrc;
0134             delete factoryFromDst;
0135         }
0136     }
0137 
0138     void setFactoryFromSrc(KoColorConversionTransformationFactory* factory) {
0139         factoryFromSrc = factory;
0140     }
0141 
0142     void setFactoryFromDst(KoColorConversionTransformationFactory* factory) {
0143         factoryFromDst = factory;
0144     }
0145 
0146     KoColorConversionTransformationFactory* factory() {
0147         if (factoryFromSrc) return factoryFromSrc;
0148         return factoryFromDst;
0149     }
0150 
0151     Node* srcNode;
0152     Node* dstNode;
0153 
0154 private:
0155 
0156     KoColorConversionTransformationFactory* factoryFromSrc; // Factory provided by the destination node
0157     KoColorConversionTransformationFactory* factoryFromDst; // Factory provided by the destination node
0158 
0159 };
0160 
0161 struct KoColorConversionSystem::NodeKey
0162         : public boost::equality_comparable<NodeKey>
0163 {
0164 
0165     NodeKey(const QString &_modelId, const QString &_depthId, const QString &_profileName)
0166         : modelId(_modelId)
0167         , depthId(_depthId)
0168         , profileName(_profileName) {}
0169 
0170     bool operator==(const KoColorConversionSystem::NodeKey& rhs) const {
0171         return modelId == rhs.modelId && depthId == rhs.depthId && profileName == rhs.profileName;
0172     }
0173 
0174     QString modelId;
0175     QString depthId;
0176     QString profileName;
0177 };
0178 Q_DECLARE_TYPEINFO(KoColorConversionSystem::NodeKey, Q_MOVABLE_TYPE);
0179 
0180 QDebug operator<<(QDebug dbg, const KoColorConversionSystem::NodeKey &key)
0181 {
0182     dbg.nospace() << "NodeKey(" << key.modelId << ", " << key.depthId << ", " << key.profileName << ")";
0183     return dbg.space();
0184 }
0185 
0186 
0187 inline KoColorConversionSystem::NodeKey KoColorConversionSystem::Node::key() const
0188 {
0189     return NodeKey(modelId, depthId, profileName);
0190 }
0191 
0192 struct KoColorConversionSystem::Path {
0193 
0194     Path()
0195         : referenceDepth(0)
0196         , isGood(false)
0197         , cost(0) {}
0198 
0199     Node* startNode() {
0200         return vertexes.size() > 0 ?
0201                     (vertexes.first())->srcNode
0202                   : 0;
0203     }
0204 
0205     bool operator==(const Path &other) const {
0206         return other.vertexes == vertexes;
0207     }
0208 
0209 
0210     const Node* startNode() const {
0211         return vertexes.size() > 0 ?
0212                     (vertexes.first())->srcNode
0213                   : 0;
0214     }
0215 
0216     Node* endNode() {
0217         return vertexes.size() > 0 ?
0218                     (vertexes.last())->dstNode
0219                   : 0;
0220     }
0221 
0222     const Node* endNode() const {
0223         return vertexes.size() > 0 ?
0224                     (vertexes.last())->dstNode
0225                   : 0;
0226     }
0227 
0228     bool isEmpty() const {
0229         return vertexes.isEmpty();
0230     }
0231 
0232     void appendVertex(Vertex* v) {
0233         if (vertexes.empty()) {
0234             referenceDepth = v->srcNode->referenceDepth;
0235             commonNodeCapabilities = v->srcNode->capabilities();
0236         }
0237 
0238         commonNodeCapabilities &= v->dstNode->capabilities();
0239 
0240         vertexes.append(v);
0241 
0242         referenceDepth = qMin(referenceDepth, v->dstNode->referenceDepth);
0243         cost += v->dstNode->crossingCost;
0244     }
0245 
0246     NodeCapabilities unsupportedCapabilities() const {
0247         NodeCapabilities minimalCaps =
0248             !vertexes.isEmpty() ?
0249             vertexes.first()->srcNode->capabilities() &
0250                 vertexes.last()->dstNode->capabilities() :
0251             None;
0252 
0253         return (minimalCaps & commonNodeCapabilities) ^ minimalCaps;
0254     }
0255 
0256     // Compress path to hide the Engine node and correctly select the factory
0257     typedef QPair<Node*, const KoColorConversionTransformationAbstractFactory* > node2factory;
0258     QList< node2factory > compressedPath() const {
0259         QList< node2factory > nodes;
0260         nodes.push_back(node2factory(vertexes.first()->srcNode , vertexes.first()->factory()));
0261         const KoColorConversionTransformationAbstractFactory* previousFactory = 0;
0262         Q_FOREACH (Vertex* vertex, vertexes) { // Unless the node is the icc node, add it to the path
0263             Node* n = vertex->dstNode;
0264             if (n->isEngine) {
0265                 previousFactory = n->engine;
0266             } else {
0267                 nodes.push_back(
0268                             node2factory(n,
0269                                          previousFactory ? previousFactory : vertex->factory()));
0270                 previousFactory = 0;
0271             }
0272         }
0273         return nodes;
0274     }
0275 
0276     int length() const {
0277         return vertexes.size();
0278     }
0279 
0280     bool contains(Node* n) const {
0281         Q_FOREACH (Vertex* v, vertexes) {
0282             if (v->srcNode == n || v->dstNode == n) {
0283                 return true;
0284             }
0285         }
0286         return false;
0287     }
0288 
0289     QList<Vertex*> vertexes;
0290     int referenceDepth;
0291     bool isGood;
0292     int cost;
0293     NodeCapabilities commonNodeCapabilities = None;
0294 
0295 };
0296 Q_DECLARE_TYPEINFO(KoColorConversionSystem::Path, Q_MOVABLE_TYPE);
0297 
0298 
0299 inline QDebug operator<<(QDebug dbg, const KoColorConversionSystem::Path &path)
0300 {
0301     bool havePrintedFirst = false;
0302 
0303     Q_FOREACH (const KoColorConversionSystem::Vertex *v, path.vertexes) {
0304         if (!havePrintedFirst) {
0305             dbg.nospace() << v->srcNode->id();
0306             havePrintedFirst = true;
0307         }
0308 
0309         dbg.nospace() << "->" << v->dstNode->id();
0310     }
0311 
0312     return dbg.space();
0313 }
0314 
0315 
0316 typedef QHash<KoColorConversionSystem::Node*, KoColorConversionSystem::Path > Node2PathHash;
0317 
0318 
0319 uint qHash(const KoColorConversionSystem::NodeKey &key)
0320 {
0321     return qHash(key.modelId) + qHash(key.depthId);
0322 }
0323 
0324 struct Q_DECL_HIDDEN KoColorConversionSystem::Private {
0325 
0326     Private(RegistryInterface *_registryInterface) : registryInterface(_registryInterface) {}
0327 
0328     QHash<NodeKey, Node*> graph;
0329     QList<Vertex*> vertexes;
0330     RegistryInterface *registryInterface;
0331 };
0332 
0333 struct PathQualityChecker {
0334 
0335     PathQualityChecker(int _referenceDepth)
0336         : referenceDepth(_referenceDepth)
0337     {}
0338 
0339     /// @return true if the path maximize all the criteria (except length)
0340     inline bool isGoodPath(const KoColorConversionSystem::Path & path) const {
0341         return path.unsupportedCapabilities() == None &&
0342             path.referenceDepth >= referenceDepth;
0343     }
0344 
0345     /**
0346      * Compare two paths.
0347      */
0348     inline bool lessWorseThan(const KoColorConversionSystem::Path &path1, const KoColorConversionSystem::Path &path2) const {
0349         // There is no point in comparing two paths which doesn't start from the same node or doesn't end at the same node
0350 
0351         NodeCapabilities unsupported1 = path1.unsupportedCapabilities();
0352         NodeCapabilities unsupported2 = path2.unsupportedCapabilities();
0353 
0354         if (!unsupported1.testFlag(HasHdr) && unsupported2.testFlag(HasHdr)) {
0355             return true;
0356         }
0357 
0358         if (!unsupported1.testFlag(HasColor) && unsupported2.testFlag(HasColor)) {
0359             return true;
0360         }
0361 
0362         if (!unsupported1.testFlag(HasAlpha) && unsupported2.testFlag(HasAlpha)) {
0363             return true;
0364         }
0365 
0366         if (path1.referenceDepth == path2.referenceDepth) {
0367             return path1.cost < path2.cost; // if they have the same cost, well anyway you have to choose one, and there is no point in keeping one and not the other
0368         }
0369         return path1.referenceDepth > path2.referenceDepth;
0370     }
0371     int referenceDepth;
0372 };
0373 
0374 #undef CHECK_ONE_AND_NOT_THE_OTHER
0375 
0376 #endif