File indexing completed on 2025-02-02 04:14:51
0001 /* 0002 * SPDX-FileCopyrightText: 2018 Anna Medonosova <anna.medonosova@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "KoGamutMask.h" 0008 0009 #include <cstring> 0010 0011 #include <QVector> 0012 #include <QString> 0013 #include <QFile> 0014 #include <QList> 0015 #include <QDomDocument> 0016 #include <QDomElement> 0017 #include <QByteArray> 0018 #include <QBuffer> 0019 #include <QScopedPointer> 0020 0021 #include <FlakeDebug.h> 0022 0023 #include <KoStore.h> 0024 #include <KoStoreDevice.h> 0025 #include <KoDocumentResourceManager.h> 0026 #include <SvgParser.h> 0027 #include <SvgWriter.h> 0028 #include <KoShape.h> 0029 #include <kis_assert.h> 0030 #include <QTransform> 0031 #include <KoMarker.h> 0032 0033 //#include <kis_debug.h> 0034 0035 KoGamutMaskShape::KoGamutMaskShape(KoShape* shape) 0036 : m_maskShape(shape) 0037 { 0038 } 0039 0040 KoGamutMaskShape::KoGamutMaskShape() 0041 { 0042 }; 0043 0044 KoGamutMaskShape::~KoGamutMaskShape() 0045 { 0046 delete m_maskShape; 0047 }; 0048 0049 KoShape* KoGamutMaskShape::koShape() 0050 { 0051 return m_maskShape; 0052 } 0053 0054 bool KoGamutMaskShape::coordIsClear(const QPointF& coord) const 0055 { 0056 bool isClear = m_maskShape->hitTest(coord); 0057 0058 return isClear; 0059 } 0060 0061 void KoGamutMaskShape::paint(QPainter &painter) 0062 { 0063 painter.save(); 0064 painter.setTransform(m_maskShape->absoluteTransformation(), true); 0065 m_maskShape->paint(painter); 0066 painter.restore(); 0067 } 0068 0069 void KoGamutMaskShape::paintStroke(QPainter &painter) 0070 { 0071 painter.save(); 0072 painter.setTransform(m_maskShape->absoluteTransformation(), true); 0073 m_maskShape->paintStroke(painter); 0074 painter.restore(); 0075 } 0076 0077 struct KoGamutMask::Private { 0078 QString name; 0079 QString title; 0080 QByteArray data; 0081 QVector<KoGamutMaskShape*> maskShapes; 0082 QVector<KoGamutMaskShape*> previewShapes; 0083 QSizeF maskSize; 0084 int rotation {0}; 0085 }; 0086 0087 KoGamutMask::KoGamutMask(const QString& filename) 0088 : KoResource(filename) 0089 , d(new Private) 0090 { 0091 d->maskSize = QSizeF(144.0,144.0); 0092 setRotation(0); 0093 } 0094 0095 KoGamutMask::KoGamutMask() 0096 : KoResource(QString()) 0097 , d(new Private) 0098 { 0099 d->maskSize = QSizeF(144.0,144.0); 0100 setRotation(0); 0101 } 0102 0103 KoGamutMask::KoGamutMask(KoGamutMask* rhs) 0104 : KoGamutMask(*rhs) 0105 { 0106 } 0107 0108 KoGamutMask::KoGamutMask(const KoGamutMask &rhs) 0109 : QObject(0) 0110 , KoResource(rhs) 0111 , d(new Private) 0112 { 0113 setTitle(rhs.title()); 0114 setDescription(rhs.description()); 0115 d->maskSize = rhs.d->maskSize; 0116 0117 QList<KoShape*> newShapes; 0118 for(KoShape* sh: rhs.koShapes()) { 0119 newShapes.append(sh->cloneShape()); 0120 } 0121 setMaskShapes(newShapes); 0122 } 0123 0124 KoResourceSP KoGamutMask::clone() const 0125 { 0126 return KoResourceSP(new KoGamutMask(*this)); 0127 } 0128 0129 KoGamutMask::~KoGamutMask() 0130 { 0131 qDeleteAll(d->maskShapes); 0132 qDeleteAll(d->previewShapes); 0133 delete d; 0134 } 0135 0136 bool KoGamutMask::coordIsClear(const QPointF& coord, bool preview) 0137 { 0138 QVector<KoGamutMaskShape*>* shapeVector; 0139 0140 if (preview && !d->previewShapes.isEmpty()) { 0141 shapeVector = &d->previewShapes; 0142 } else { 0143 shapeVector = &d->maskShapes; 0144 } 0145 0146 for(KoGamutMaskShape* shape: *shapeVector) { 0147 if (shape->coordIsClear(coord) == true) { 0148 return true; 0149 } 0150 } 0151 0152 return false; 0153 } 0154 0155 void KoGamutMask::paint(QPainter &painter, bool preview) 0156 { 0157 QVector<KoGamutMaskShape*>* shapeVector; 0158 0159 if (preview && !d->previewShapes.isEmpty()) { 0160 shapeVector = &d->previewShapes; 0161 } else { 0162 shapeVector = &d->maskShapes; 0163 } 0164 0165 for(KoGamutMaskShape* shape: *shapeVector) { 0166 shape->paint(painter); 0167 } 0168 } 0169 0170 void KoGamutMask::paintStroke(QPainter &painter, bool preview) 0171 { 0172 QVector<KoGamutMaskShape*>* shapeVector; 0173 0174 if (preview && !d->previewShapes.isEmpty()) { 0175 shapeVector = &d->previewShapes; 0176 } else { 0177 shapeVector = &d->maskShapes; 0178 } 0179 0180 for(KoGamutMaskShape* shape: *shapeVector) { 0181 shape->paintStroke(painter); 0182 } 0183 } 0184 0185 QTransform KoGamutMask::maskToViewTransform(qreal viewSize) 0186 { 0187 // apply mask rotation before drawing 0188 QPointF centerPoint(viewSize*0.5, viewSize*0.5); 0189 0190 QTransform transform; 0191 transform.translate(centerPoint.x(), centerPoint.y()); 0192 transform.rotate(rotation()); 0193 transform.translate(-centerPoint.x(), -centerPoint.y()); 0194 0195 qreal scale = viewSize/(maskSize().width()); 0196 transform.scale(scale, scale); 0197 0198 return transform; 0199 } 0200 0201 QTransform KoGamutMask::viewToMaskTransform(qreal viewSize) 0202 { 0203 QPointF centerPoint(viewSize*0.5, viewSize*0.5); 0204 0205 QTransform transform; 0206 qreal scale = viewSize/(maskSize().width()); 0207 transform.scale(1/scale, 1/scale); 0208 0209 transform.translate(centerPoint.x(), centerPoint.y()); 0210 transform.rotate(-rotation()); 0211 transform.translate(-centerPoint.x(), -centerPoint.y()); 0212 0213 return transform; 0214 } 0215 0216 bool KoGamutMask::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) 0217 { 0218 Q_UNUSED(resourcesInterface); 0219 0220 if (!dev->isOpen()) dev->open(QIODevice::ReadOnly); 0221 0222 d->data = dev->readAll(); 0223 0224 // TODO: test 0225 KIS_ASSERT_RECOVER_RETURN_VALUE(d->data.size() != 0, false); 0226 0227 if (filename().isNull()) { 0228 warnFlake << "Cannot load gamut mask" << name() << "there is no filename set"; 0229 return false; 0230 } 0231 0232 if (d->data.isNull()) { 0233 QFile file(filename()); 0234 if (file.size() == 0) { 0235 warnFlake << "Cannot load gamut mask" << name() << "there is no data available"; 0236 return false; 0237 } 0238 0239 file.open(QIODevice::ReadOnly); 0240 d->data = file.readAll(); 0241 file.close(); 0242 } 0243 0244 QBuffer buf(&d->data); 0245 buf.open(QBuffer::ReadOnly); 0246 0247 QScopedPointer<KoStore> store(KoStore::createStore(&buf, KoStore::Read, "application/x-krita-gamutmask", KoStore::Zip)); 0248 if (!store || store->bad()) return false; 0249 0250 bool storeOpened = store->open("gamutmask.svg"); 0251 if (!storeOpened) { return false; } 0252 0253 QByteArray data; 0254 data.resize(store->size()); 0255 QByteArray ba = store->read(store->size()); 0256 store->close(); 0257 0258 if (ba.size() == 0) { // empty gamutmask.svg is possible when the first temporary resource is saved 0259 setMaskShapes(QList<KoShape*>()); 0260 d->maskSize = QSizeF(0, 0); 0261 d->title = ""; 0262 } else { 0263 0264 QString errorMsg; 0265 int errorLine = 0; 0266 int errorColumn = 0; 0267 0268 QDomDocument xmlDocument = SvgParser::createDocumentFromSvg(ba, &errorMsg, &errorLine, &errorColumn); 0269 if (xmlDocument.isNull()) { 0270 0271 errorFlake << "Parsing error in " << filename() << "! Aborting!" << endl 0272 << " In line: " << errorLine << ", column: " << errorColumn << endl 0273 << " Error message: " << errorMsg << endl; 0274 errorFlake << "Parsing error in the main document at line" << errorLine 0275 << ", column" << errorColumn << endl 0276 << "Error message: " << errorMsg; 0277 0278 return false; 0279 } 0280 0281 KoDocumentResourceManager manager; 0282 SvgParser parser(&manager); 0283 parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values 0284 QSizeF fragmentSize; 0285 0286 QList<KoShape*> shapes = parser.parseSvg(xmlDocument.documentElement(), &fragmentSize); 0287 0288 d->maskSize = fragmentSize; 0289 0290 d->title = parser.documentTitle(); 0291 setName(d->title); 0292 setDescription(parser.documentDescription()); 0293 0294 setMaskShapes(shapes); 0295 0296 } 0297 0298 0299 0300 if (store->open("preview.png")) { 0301 KoStoreDevice previewDev(store.data()); 0302 previewDev.open(QIODevice::ReadOnly); 0303 0304 QImage preview = QImage(); 0305 preview.load(&previewDev, "PNG"); 0306 setImage(preview); 0307 0308 (void)store->close(); 0309 } 0310 0311 buf.close(); 0312 0313 setValid(true); 0314 0315 return true; 0316 } 0317 0318 void KoGamutMask::setMaskShapes(QList<KoShape*> shapes) 0319 { 0320 setMaskShapesToVector(shapes, d->maskShapes); 0321 } 0322 0323 QList<KoShape*> KoGamutMask::koShapes() const 0324 { 0325 QList<KoShape*> shapes; 0326 for(KoGamutMaskShape* maskShape: d->maskShapes) { 0327 shapes.append(maskShape->koShape()); 0328 } 0329 0330 return shapes; 0331 } 0332 0333 bool KoGamutMask::saveToDevice(QIODevice *dev) const 0334 { 0335 KoStore* store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-gamutmask", KoStore::Zip)); 0336 if (!store || store->bad()) return false; 0337 0338 QList<KoShape*> shapes = koShapes(); 0339 0340 std::sort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex); 0341 0342 if (!store->open("gamutmask.svg")) { 0343 return false; 0344 } 0345 0346 KoStoreDevice storeDev(store); 0347 storeDev.open(QIODevice::WriteOnly); 0348 0349 SvgWriter writer(shapes); 0350 writer.setDocumentTitle(d->title); 0351 writer.setDocumentDescription(description()); 0352 0353 writer.save(storeDev, d->maskSize); 0354 0355 if (!store->close()) { return false; } 0356 0357 0358 if (!store->open("preview.png")) { 0359 return false; 0360 } 0361 0362 KoStoreDevice previewDev(store); 0363 previewDev.open(QIODevice::WriteOnly); 0364 0365 image().save(&previewDev, "PNG"); 0366 if (!store->close()) { return false; } 0367 0368 return store->finalize(); 0369 } 0370 0371 QString KoGamutMask::title() const 0372 { 0373 return d->title; 0374 } 0375 0376 void KoGamutMask::setTitle(QString title) 0377 { 0378 d->title = title; 0379 setName(title); 0380 } 0381 0382 QString KoGamutMask::description() const 0383 { 0384 QMap<QString, QVariant> m = metadata(); 0385 return m["description"].toString(); 0386 } 0387 0388 void KoGamutMask::setDescription(QString description) 0389 { 0390 addMetaData("description", description); 0391 } 0392 0393 QString KoGamutMask::defaultFileExtension() const 0394 { 0395 return ".kgm"; 0396 } 0397 0398 int KoGamutMask::rotation() 0399 { 0400 return d->rotation; 0401 } 0402 0403 void KoGamutMask::setRotation(int rotation) 0404 { 0405 d->rotation = rotation; 0406 } 0407 0408 QSizeF KoGamutMask::maskSize() 0409 { 0410 return d->maskSize; 0411 } 0412 0413 void KoGamutMask::setPreviewMaskShapes(QList<KoShape*> shapes) 0414 { 0415 setMaskShapesToVector(shapes, d->previewShapes); 0416 } 0417 0418 void KoGamutMask::setMaskShapesToVector(QList<KoShape *> shapes, QVector<KoGamutMaskShape *> &targetVector) 0419 { 0420 targetVector.clear(); 0421 0422 for(KoShape* sh: shapes) { 0423 KoGamutMaskShape* maskShape = new KoGamutMaskShape(sh); 0424 targetVector.append(maskShape); 0425 } 0426 } 0427 0428 // clean up when ending mask preview 0429 void KoGamutMask::clearPreview() 0430 { 0431 d->previewShapes.clear(); 0432 }