Krita Source Code Documentation
Loading...
Searching...
No Matches
KisReferenceImage.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "KisReferenceImage.h"
8#include "KoColorProfile.h"
10
11#include <QtMath>
12#include <QImage>
13#include <QMessageBox>
14#include <QPainter>
15#include <QApplication>
16#include <QClipboard>
17#include <QSharedData>
18#include <QFileInfo>
19#include <QImageReader>
20#include <QUrl>
21
22#include <QColorSpace>
23
24#include <kundo2command.h>
25#include <KoStore.h>
26#include <KoStoreDevice.h>
27#include <krita_utils.h>
29#include <kis_dom_utils.h>
30#include <SvgUtil.h>
33
34#include <KisDocument.h>
35#include <KisPart.h>
36
37#include "kis_clipboard.h"
38
39struct KisReferenceImage::Private : public QSharedData
40{
41 // Filename within .kra (for embedding)
43
44 // File on disk (for linking)
46
47 QImage image;
50
51 qreal saturation{1.0};
52 int id{-1};
53 bool embed{true};
54
55 bool loadFromFile() {
58 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(QFileInfo(externalFilename).isReadable(), false);
59 {
60 QImageReader reader(externalFilename);
61 reader.setDecideFormatFromContent(true);
62 image = reader.read();
63
64 if (image.isNull()) {
65 reader.setAutoDetectImageFormat(true);
66 image = reader.read();
67 }
68
69 }
70
71 if (image.isNull()) {
73 }
74
75 if (image.isNull()) {
78 if (doc->image()->colorSpace()->colorModelId() == RGBAColorModelID) {
80 // Because we store as PNG, we'll need to convert to PQ here. One problem: we need to check when saving the reference images.
83 }
84 image = doc->image()->convertToQImage(doc->image()->bounds(), doc->image()->colorSpace()->profile());
85 image.setColorSpace(KoColorSpaceRegistry::instance()->QColorSpaceForProfile(doc->image()->colorSpace()->profile()));
86 } else {
87 image = doc->image()->convertToQImage(doc->image()->bounds(), 0);
88 }
89 }
91 }
92
93 // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image
94 // loaded into a qimage cannot be saved to png unless we explicitly
95 // convert the colorspace of the QImage
96#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
97 if (image.colorSpace().colorModel() != QColorSpace::ColorModel::Rgb || !image.colorSpace().isValid()) {
98 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
99 }
100#else
101 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
102#endif
103
104 return (!image.isNull());
105 }
106
107 bool loadFromQImage(const QImage &img) {
108 image = img;
109
110 return !image.isNull();
111 }
112
113 void updateCache() {
114 if (saturation < 1.0) {
116
117 if (saturation > 0.0) {
118 QPainter gc2(&cachedImage);
119 gc2.setOpacity(saturation);
120 gc2.drawImage(QPoint(), image);
121 }
122 } else {
123 // Need to convert ref image to ARGB to draw the zoom preview when color sampling
124 // Convert here to make sure already loaded ref images are converted
126 cachedImage.convertTo(QImage::Format_ARGB32);
127 }
128
130 }
131};
132
133
135 : KUndo2Command(kundo2_i18n("Set saturation"), parent)
136 , newSaturation(newSaturation)
137{
138 images.reserve(shapes.count());
139
140 Q_FOREACH(auto *shape, shapes) {
141 auto *reference = dynamic_cast<KisReferenceImage*>(shape);
143 images.append(reference);
144 }
145
146 Q_FOREACH(auto *image, images) {
147 oldSaturations.append(image->saturation());
148 }
149}
150
152{
153 auto saturationIterator = oldSaturations.begin();
154 Q_FOREACH(auto *image, images) {
155 image->setSaturation(*saturationIterator);
156 image->update();
157 saturationIterator++;
158 }
159}
160
162{
163 Q_FOREACH(auto *image, images) {
164 image->setSaturation(newSaturation);
165 image->update();
166 }
167}
168
174
176 : KoShape(rhs)
177 , d(rhs.d)
178{}
179
182
183KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
184{
185 KisReferenceImage *reference = new KisReferenceImage();
186 reference->d->externalFilename = filename;
187 bool ok = reference->d->loadFromFile();
188
189 if (ok) {
190 QRect r = QRect(QPoint(), reference->d->image.size());
191 QSizeF shapeSize = converter.imageToDocument(r).size();
192 reference->setSize(shapeSize);
193 } else {
194 delete reference;
195
196 if (parent) {
197 QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename));
198 }
199
200 return nullptr;
201 }
202
203 return reference;
204}
205
207{
208 const auto sz = KisClipboard::instance()->clipSize();
209 KisPaintDeviceSP clip = KisClipboard::instance()->clip({0, 0, sz.width(), sz.height()}, true);
210 return fromPaintDevice(clip, converter, nullptr);
211}
212
215{
216 if (!src) {
217 return nullptr;
218 }
219
220 auto *reference = new KisReferenceImage();
221#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
222 if (src->colorSpace()->colorModelId() == RGBAColorModelID) {
223 reference->d->image = src->convertToQImage(src->colorSpace()->profile());
224 reference->d->image.setColorSpace(KoColorSpaceRegistry::instance()->QColorSpaceForProfile(src->colorSpace()->profile()));
225 } else {
226 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile());
227 }
228#else
229 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile());
230#endif
231
232 QRect r = QRect(QPoint(), reference->d->image.size());
233 QSizeF size = converter.imageToDocument(r).size();
234 reference->setSize(size);
235
236 return reference;
237}
238
240{
241 KisReferenceImage *reference = new KisReferenceImage();
242 bool ok = reference->d->loadFromQImage(img);
243
244 if (ok) {
245 QRect r = QRect(QPoint(), reference->d->image.size());
246 QSizeF size = converter.imageToDocument(r).size();
247 reference->setSize(size);
248 } else {
249 delete reference;
250 reference = 0;
251 }
252
253 return reference;
254}
255
256void KisReferenceImage::paint(QPainter &gc) const
257{
258 if (!parent()) return;
259
260 gc.save();
261
262 QSizeF shapeSize = size();
263 // scale and rotation done by the user (excluding zoom)
264 QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height());
265
266 if (d->cachedImage.isNull()) {
267 // detach the data
268 const_cast<KisReferenceImage*>(this)->d->updateCache();
269 }
270
271 qreal scale;
272 // scale from the highDPI display
273 QTransform devicePixelRatioFTransform = QTransform::fromScale(gc.device()->devicePixelRatioF(), gc.device()->devicePixelRatioF());
274 // all three transformations: scale and rotation done by the user, scale from highDPI display, and zoom + rotation of the view
275 // order: zoom/rotation of the view; scale to high res; scale and rotation done by the user
276 QImage prescaled = d->mipmap.getClosestWithoutWorkaroundBorder(transform * devicePixelRatioFTransform * gc.transform(), &scale);
277 transform.scale(1.0 / scale, 1.0 / scale);
278
279 // Color conversion.
280 QImage *targetD = dynamic_cast<QImage*>(gc.device());
281#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
282 const bool getTargetCs = (targetD && targetD->colorSpace().colorModel() == QColorSpace::ColorModel::Rgb);
283
285 const KoColorProfile *sourceP = KoColorSpaceRegistry::instance()->profileForQColorSpace(d->image.colorSpace());
286 if (targetP != sourceP) {
288 dev->convertFromQImage(prescaled, sourceP);
290 prescaled = dev->convertToQImage(targetP);
291 if (getTargetCs) {
292 prescaled.setColorSpace(targetD->colorSpace());
293 } else {
294 prescaled.setColorSpace(QColorSpace(QColorSpace::SRgb));
295 }
296 }
297#endif
298
299 if (scale > 1.0) {
300 // enlarging should be done without smooth transformation
301 // so the user can see pixels just as they are painted
302 gc.setRenderHints(QPainter::Antialiasing);
303 } else {
304 gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
305 }
306 gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip);
307 gc.setTransform(transform, true);
308 gc.drawImage(QPoint(), prescaled);
309
310 gc.restore();
311}
312
314{
315 d->saturation = saturation;
316 d->cachedImage = QImage();
317}
318
320{
321 return d->saturation;
322}
323
325{
326 KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty());
327 d->embed = embed;
328}
329
331{
332 return d->embed;
333}
334
336{
337 return !d->externalFilename.isEmpty();
338}
339
341{
342 return d->externalFilename;
343}
344
346{
347 return d->internalFilename;
348}
349
350
351void KisReferenceImage::setFilename(const QString &filename)
352{
353 d->externalFilename = filename;
354}
355
356QPointF KisReferenceImage::documentToPixel(const QPointF &docPoint)
357{
358 QSizeF shapeSize = size();
359 QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height());
360
361 QTransform transform = absoluteTransformation().inverted() * scale;
362 QPointF localPosition = docPoint * transform;
363
364 return localPosition;
365}
366
367
368QPoint KisReferenceImage::documentToPixelFloored(const QPointF &docPoint)
369{
370 QPointF localPoint = documentToPixel(docPoint);
371 return QPoint(qFloor(localPoint.x()), qFloor(localPoint.y()));
372}
373
375{
376 KoColor transparent;
377 transparent.setOpacity(0.0);
378 if (transparency() == 1.0) return transparent;
379
380 const QPoint localPosition = documentToPixelFloored(position);
381
382 if (d->cachedImage.isNull()) {
383 d->updateCache();
384 }
385
386#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
387 const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileForQColorSpace(d->image.colorSpace());
389#else
391#endif
392
393 KoColor c(cs);
394 QColor pixel = d->cachedImage.pixelColor(localPosition);
395 QVector<float> channels = {
396 static_cast<float>(pixel.blueF()),
397 static_cast<float>(pixel.greenF()),
398 static_cast<float>(pixel.redF()),
399 1.0f};
400 cs->fromNormalisedChannelsValue( c.data(),channels);
401
402 return c;
403}
404
405void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id)
406{
407 d->id = id;
408
409 QDomElement element = document.createElement("referenceimage");
410
411 if (d->embed) {
412 d->internalFilename = QString("reference_images/%1.png").arg(id);
413 }
414
415 const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename);
416 element.setAttribute("src", src);
417
418 const QSizeF &shapeSize = size();
419 element.setAttribute("width", KisDomUtils::toString(shapeSize.width()));
420 element.setAttribute("height", KisDomUtils::toString(shapeSize.height()));
421 element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false");
422 element.setAttribute("transform", SvgUtil::transformToString(transform()));
423
424 element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency()));
425 element.setAttribute("saturation", KisDomUtils::toString(d->saturation));
426
427 parentElement.appendChild(element);
428}
429
431{
432 auto *reference = new KisReferenceImage();
433
434 const QString &src = elem.attribute("src");
435
436 if (src.startsWith("file://")) {
437 reference->d->externalFilename = src.mid(7);
438 reference->d->embed = false;
439 } else {
440 reference->d->internalFilename = src;
441 reference->d->embed = true;
442 }
443
444 qreal width = KisDomUtils::toDouble(elem.attribute("width", "100"));
445 qreal height = KisDomUtils::toDouble(elem.attribute("height", "100"));
446 reference->setSize(QSizeF(width, height));
447 reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true");
448
449 auto transform = SvgTransformParser(elem.attribute("transform")).transform();
450 reference->setTransformation(transform);
451
452 qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
453 reference->setTransparency(1.0 - opacity);
454
455 qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1"));
456 reference->setSaturation(saturation);
457
458 return reference;
459}
460
462{
463 if (!d->embed) return true;
464
465 if (!store->open(d->internalFilename)) {
466 return false;
467 }
468
469 bool saved = false;
470
471 KoStoreDevice storeDev(store);
472 if (storeDev.open(QIODevice::WriteOnly)) {
473 saved = d->image.save(&storeDev, "PNG");
474 }
475
476 return store->close() && saved;
477}
478
480{
481 if (!d->embed) {
482 return d->loadFromFile();
483 }
484
485 if (!store->open(d->internalFilename)) {
486 return false;
487 }
488
489 KoStoreDevice storeDev(store);
490 if (!storeDev.open(QIODevice::ReadOnly)) {
491 return false;
492 }
493
494 if (!d->image.load(&storeDev, "PNG")) {
495 return false;
496 }
497
498 return store->close();
499}
500
502{
503 return d->image;
504}
505
507{
508 if (d->cachedImage.isNull()) d->updateCache();
509
510 return d->cachedImage;
511}
512
514{
515 return new KisReferenceImage(*this);
516}
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
static KisClipboard * instance()
QSize clipSize() const
_Private::Traits< T >::Result imageToDocument(const T &obj) const
KisImageSP image
bool openPath(const QString &path, OpenFlags flags=None)
openPath Open a Path
QImage convertToQImage(qint32 x1, qint32 y1, qint32 width, qint32 height, const KoColorProfile *profile)
const KoColorSpace * colorSpace() const
void convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
QRect bounds() const override
QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags()) const
void convertFromQImage(const QImage &image, const KoColorProfile *profile, qint32 offsetX=0, qint32 offsetY=0)
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
static KisPart * instance()
Definition KisPart.cpp:130
void removeDocument(KisDocument *document, bool deleteDocument=true)
Definition KisPart.cpp:246
KisDocument * createTemporaryDocument() const
Definition KisPart.cpp:234
The KisReferenceImage class represents a single reference image.
bool saveImage(KoStore *store) const
bool loadImage(KoStore *store)
void paint(QPainter &gc) const override
Paint the shape fill The class extending this one is responsible for painting itself....
QString filename() const
static KisReferenceImage * fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
KoShape * cloneShape() const override
creates a deep copy of the shape or shape's subtree
void setFilename(const QString &filename)
QPointF documentToPixel(const QPointF &docPoint)
static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter)
QPoint documentToPixelFloored(const QPointF &docPoint)
void setEmbed(bool embed)
KoColor getPixel(QPointF position)
static KisReferenceImage * fromQImage(const KisCoordinatesConverter &converter, const QImage &img)
void setSaturation(qreal saturation)
void saveXml(QDomDocument &document, QDomElement &parentElement, int id)
QString internalFile() const
static KisReferenceImage * fromPaintDevice(KisPaintDeviceSP src, const KisCoordinatesConverter &converter, QWidget *parent)
QSharedDataPointer< Private > d
static KisReferenceImage * fromXml(const QDomElement &elem)
virtual KoID colorModelId() const =0
virtual KoID colorDepthId() const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
virtual const KoColorProfile * profile() const =0
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 * data()
Definition KoColor.h:144
QString id() const
Definition KoID.cpp:63
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:735
void setKeepAspectRatio(bool keepAspect)
Definition KoShape.cpp:862
KoShapeContainer * parent() const
Definition KoShape.cpp:857
QTransform absoluteTransformation() const
Definition KoShape.cpp:330
void scale(qreal sx, qreal sy)
Scale the shape using the zero-point which is the top-left corner.
Definition KoShape.cpp:210
bool keepAspectRatio() const
Definition KoShape.cpp:870
QTransform transform() const
return the current matrix that contains the rotation/scale/position of this shape
Definition KoShape.cpp:945
qreal transparency(bool recursive=false) const
Definition KoShape.cpp:645
virtual void setSize(const QSizeF &size)
Resize the shape.
Definition KoShape.cpp:249
QPointF position() const
Get the position of the shape in pt.
Definition KoShape.cpp:740
bool open(OpenMode m) override
bool close()
Definition KoStore.cpp:156
bool open(const QString &name)
Definition KoStore.cpp:109
QTransform transform() const
static QString transformToString(const QTransform &transform)
Converts specified transformation to a string.
Definition SvgUtil.cpp:104
#define KIS_SAFE_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:127
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
KUndo2MagicString kundo2_i18n(const char *text)
double toDouble(const QString &str, bool *ok=nullptr)
QString toString(const QString &value)
QImage convertQImageToGrayA(const QImage &image)
bool loadFromQImage(const QImage &img)
SetSaturationCommand(const QList< KoShape * > &images, qreal newSaturation, KUndo2Command *parent=0)
QVector< KisReferenceImage * > images
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorProfile * p709SRGBProfile() const
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorProfile * profileForQColorSpace(const QColorSpace &space)
profileForQColorSpace Find a KoColorProfile that matches a given QColorSpace. This will use the QColo...
const KoColorProfile * p2020PQProfile() const