File indexing completed on 2024-05-12 16:01:53
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud@valdyas.org> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "KisReferenceImage.h" 0008 #include "KoColorSpaceRegistry.h" 0009 0010 #include <QImage> 0011 #include <QMessageBox> 0012 #include <QPainter> 0013 #include <QApplication> 0014 #include <QClipboard> 0015 #include <QSharedData> 0016 #include <QFileInfo> 0017 #include <QImageReader> 0018 #include <QUrl> 0019 0020 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0021 #include <QColorSpace> 0022 #endif 0023 0024 #include <kundo2command.h> 0025 #include <KoStore.h> 0026 #include <KoStoreDevice.h> 0027 #include <KoTosContainer_p.h> 0028 #include <krita_utils.h> 0029 #include <kis_coordinates_converter.h> 0030 #include <kis_dom_utils.h> 0031 #include <SvgUtil.h> 0032 #include <libs/flake/svg/parsers/SvgTransformParser.h> 0033 #include <libs/brush/kis_qimage_pyramid.h> 0034 0035 #include <KisDocument.h> 0036 #include <KisPart.h> 0037 0038 #include "kis_clipboard.h" 0039 0040 struct KisReferenceImage::Private : public QSharedData 0041 { 0042 // Filename within .kra (for embedding) 0043 QString internalFilename; 0044 0045 // File on disk (for linking) 0046 QString externalFilename; 0047 0048 QImage image; 0049 QImage cachedImage; 0050 KisQImagePyramid mipmap; 0051 0052 qreal saturation{1.0}; 0053 int id{-1}; 0054 bool embed{true}; 0055 0056 bool loadFromFile() { 0057 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!externalFilename.isEmpty(), false); 0058 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(QFileInfo(externalFilename).exists(), false); 0059 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(QFileInfo(externalFilename).isReadable(), false); 0060 { 0061 QImageReader reader(externalFilename); 0062 reader.setDecideFormatFromContent(true); 0063 image = reader.read(); 0064 0065 if (image.isNull()) { 0066 reader.setAutoDetectImageFormat(true); 0067 image = reader.read(); 0068 } 0069 0070 } 0071 0072 if (image.isNull()) { 0073 image.load(externalFilename); 0074 } 0075 0076 if (image.isNull()) { 0077 KisDocument * doc = KisPart::instance()->createTemporaryDocument(); 0078 if (doc->openPath(externalFilename, KisDocument::DontAddToRecent)) { 0079 image = doc->image()->convertToQImage(doc->image()->bounds(), 0); 0080 } 0081 KisPart::instance()->removeDocument(doc); 0082 } 0083 0084 // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image 0085 // loaded into a qimage cannot be saved to png unless we explicitly 0086 // convert the colorspace of the QImage 0087 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0088 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb)); 0089 #endif 0090 0091 return (!image.isNull()); 0092 } 0093 0094 void updateCache() { 0095 if (saturation < 1.0) { 0096 cachedImage = KritaUtils::convertQImageToGrayA(image); 0097 0098 if (saturation > 0.0) { 0099 QPainter gc2(&cachedImage); 0100 gc2.setOpacity(saturation); 0101 gc2.drawImage(QPoint(), image); 0102 } 0103 } else { 0104 cachedImage = image; 0105 } 0106 0107 mipmap = KisQImagePyramid(cachedImage, false); 0108 } 0109 }; 0110 0111 0112 KisReferenceImage::SetSaturationCommand::SetSaturationCommand(const QList<KoShape *> &shapes, qreal newSaturation, KUndo2Command *parent) 0113 : KUndo2Command(kundo2_i18n("Set saturation"), parent) 0114 , newSaturation(newSaturation) 0115 { 0116 images.reserve(shapes.count()); 0117 0118 Q_FOREACH(auto *shape, shapes) { 0119 auto *reference = dynamic_cast<KisReferenceImage*>(shape); 0120 KIS_SAFE_ASSERT_RECOVER_BREAK(reference); 0121 images.append(reference); 0122 } 0123 0124 Q_FOREACH(auto *image, images) { 0125 oldSaturations.append(image->saturation()); 0126 } 0127 } 0128 0129 void KisReferenceImage::SetSaturationCommand::undo() 0130 { 0131 auto saturationIterator = oldSaturations.begin(); 0132 Q_FOREACH(auto *image, images) { 0133 image->setSaturation(*saturationIterator); 0134 image->update(); 0135 saturationIterator++; 0136 } 0137 } 0138 0139 void KisReferenceImage::SetSaturationCommand::redo() 0140 { 0141 Q_FOREACH(auto *image, images) { 0142 image->setSaturation(newSaturation); 0143 image->update(); 0144 } 0145 } 0146 0147 KisReferenceImage::KisReferenceImage() 0148 : d(new Private()) 0149 { 0150 setKeepAspectRatio(true); 0151 } 0152 0153 KisReferenceImage::KisReferenceImage(const KisReferenceImage &rhs) 0154 : KoTosContainer(rhs) 0155 , d(rhs.d) 0156 {} 0157 0158 KisReferenceImage::~KisReferenceImage() 0159 {} 0160 0161 KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent) 0162 { 0163 KisReferenceImage *reference = new KisReferenceImage(); 0164 reference->d->externalFilename = filename; 0165 bool ok = reference->d->loadFromFile(); 0166 0167 if (ok) { 0168 QRect r = QRect(QPoint(), reference->d->image.size()); 0169 QSizeF shapeSize = converter.imageToDocument(r).size(); 0170 reference->setSize(shapeSize); 0171 } else { 0172 delete reference; 0173 0174 if (parent) { 0175 QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename)); 0176 } 0177 0178 return nullptr; 0179 } 0180 0181 return reference; 0182 } 0183 0184 KisReferenceImage *KisReferenceImage::fromClipboard(const KisCoordinatesConverter &converter) 0185 { 0186 const auto sz = KisClipboard::instance()->clipSize(); 0187 KisPaintDeviceSP clip = KisClipboard::instance()->clip({0, 0, sz.width(), sz.height()}, true); 0188 return fromPaintDevice(clip, converter, nullptr); 0189 } 0190 0191 KisReferenceImage * 0192 KisReferenceImage::fromPaintDevice(KisPaintDeviceSP src, const KisCoordinatesConverter &converter, QWidget *) 0193 { 0194 if (!src) { 0195 return nullptr; 0196 } 0197 0198 auto *reference = new KisReferenceImage(); 0199 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile()); 0200 0201 QRect r = QRect(QPoint(), reference->d->image.size()); 0202 QSizeF size = converter.imageToDocument(r).size(); 0203 reference->setSize(size); 0204 0205 return reference; 0206 } 0207 0208 void KisReferenceImage::paint(QPainter &gc) const 0209 { 0210 if (!parent()) return; 0211 0212 gc.save(); 0213 0214 QSizeF shapeSize = size(); 0215 // scale and rotation done by the user (excluding zoom) 0216 QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height()); 0217 0218 if (d->cachedImage.isNull()) { 0219 // detach the data 0220 const_cast<KisReferenceImage*>(this)->d->updateCache(); 0221 } 0222 0223 qreal scale; 0224 // scale from the highDPI display 0225 QTransform devicePixelRatioFTransform = QTransform::fromScale(gc.device()->devicePixelRatioF(), gc.device()->devicePixelRatioF()); 0226 // all three transformations: scale and rotation done by the user, scale from highDPI display, and zoom + rotation of the view 0227 // order: zoom/rotation of the view; scale to high res; scale and rotation done by the user 0228 QImage prescaled = d->mipmap.getClosestWithoutWorkaroundBorder(transform * devicePixelRatioFTransform * gc.transform(), &scale); 0229 transform.scale(1.0 / scale, 1.0 / scale); 0230 0231 if (scale > 1.0) { 0232 // enlarging should be done without smooth transformation 0233 // so the user can see pixels just as they are painted 0234 gc.setRenderHints(QPainter::Antialiasing); 0235 } else { 0236 gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); 0237 } 0238 gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip); 0239 gc.setTransform(transform, true); 0240 gc.drawImage(QPoint(), prescaled); 0241 0242 gc.restore(); 0243 } 0244 0245 void KisReferenceImage::setSaturation(qreal saturation) 0246 { 0247 d->saturation = saturation; 0248 d->cachedImage = QImage(); 0249 } 0250 0251 qreal KisReferenceImage::saturation() const 0252 { 0253 return d->saturation; 0254 } 0255 0256 void KisReferenceImage::setEmbed(bool embed) 0257 { 0258 KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty()); 0259 d->embed = embed; 0260 } 0261 0262 bool KisReferenceImage::embed() 0263 { 0264 return d->embed; 0265 } 0266 0267 bool KisReferenceImage::hasLocalFile() 0268 { 0269 return !d->externalFilename.isEmpty(); 0270 } 0271 0272 QString KisReferenceImage::filename() const 0273 { 0274 return d->externalFilename; 0275 } 0276 0277 QString KisReferenceImage::internalFile() const 0278 { 0279 return d->internalFilename; 0280 } 0281 0282 0283 void KisReferenceImage::setFilename(const QString &filename) 0284 { 0285 d->externalFilename = filename; 0286 d->embed = false; 0287 } 0288 0289 QColor KisReferenceImage::getPixel(QPointF position) 0290 { 0291 if (transparency() == 1.0) return Qt::transparent; 0292 0293 const QSizeF shapeSize = size(); 0294 const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height()); 0295 0296 const QTransform transform = absoluteTransformation().inverted() * scale; 0297 const QPointF localPosition = position * transform; 0298 0299 if (d->cachedImage.isNull()) { 0300 d->updateCache(); 0301 } 0302 0303 return d->cachedImage.pixelColor(localPosition.toPoint()); 0304 } 0305 0306 void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id) 0307 { 0308 d->id = id; 0309 0310 QDomElement element = document.createElement("referenceimage"); 0311 0312 if (d->embed) { 0313 d->internalFilename = QString("reference_images/%1.png").arg(id); 0314 } 0315 0316 const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename); 0317 element.setAttribute("src", src); 0318 0319 const QSizeF &shapeSize = size(); 0320 element.setAttribute("width", KisDomUtils::toString(shapeSize.width())); 0321 element.setAttribute("height", KisDomUtils::toString(shapeSize.height())); 0322 element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false"); 0323 element.setAttribute("transform", SvgUtil::transformToString(transform())); 0324 0325 element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency())); 0326 element.setAttribute("saturation", KisDomUtils::toString(d->saturation)); 0327 0328 parentElement.appendChild(element); 0329 } 0330 0331 KisReferenceImage * KisReferenceImage::fromXml(const QDomElement &elem) 0332 { 0333 auto *reference = new KisReferenceImage(); 0334 0335 const QString &src = elem.attribute("src"); 0336 0337 if (src.startsWith("file://")) { 0338 reference->d->externalFilename = src.mid(7); 0339 reference->d->embed = false; 0340 } else { 0341 reference->d->internalFilename = src; 0342 reference->d->embed = true; 0343 } 0344 0345 qreal width = KisDomUtils::toDouble(elem.attribute("width", "100")); 0346 qreal height = KisDomUtils::toDouble(elem.attribute("height", "100")); 0347 reference->setSize(QSizeF(width, height)); 0348 reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true"); 0349 0350 auto transform = SvgTransformParser(elem.attribute("transform")).transform(); 0351 reference->setTransformation(transform); 0352 0353 qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1")); 0354 reference->setTransparency(1.0 - opacity); 0355 0356 qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1")); 0357 reference->setSaturation(saturation); 0358 0359 return reference; 0360 } 0361 0362 bool KisReferenceImage::saveImage(KoStore *store) const 0363 { 0364 if (!d->embed) return true; 0365 0366 if (!store->open(d->internalFilename)) { 0367 return false; 0368 } 0369 0370 bool saved = false; 0371 0372 KoStoreDevice storeDev(store); 0373 if (storeDev.open(QIODevice::WriteOnly)) { 0374 saved = d->image.save(&storeDev, "PNG"); 0375 } 0376 0377 return store->close() && saved; 0378 } 0379 0380 bool KisReferenceImage::loadImage(KoStore *store) 0381 { 0382 if (!d->embed) { 0383 return d->loadFromFile(); 0384 } 0385 0386 if (!store->open(d->internalFilename)) { 0387 return false; 0388 } 0389 0390 KoStoreDevice storeDev(store); 0391 if (!storeDev.open(QIODevice::ReadOnly)) { 0392 return false; 0393 } 0394 0395 if (!d->image.load(&storeDev, "PNG")) { 0396 return false; 0397 } 0398 0399 return store->close(); 0400 } 0401 0402 QImage KisReferenceImage::getImage() 0403 { 0404 return d->image; 0405 } 0406 0407 KoShape *KisReferenceImage::cloneShape() const 0408 { 0409 return new KisReferenceImage(*this); 0410 }