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 }