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 <QImage>
12#include <QMessageBox>
13#include <QPainter>
14#include <QApplication>
15#include <QClipboard>
16#include <QSharedData>
17#include <QFileInfo>
18#include <QImageReader>
19#include <QUrl>
20
21#include <QColorSpace>
22
23#include <kundo2command.h>
24#include <KoStore.h>
25#include <KoStoreDevice.h>
26#include <krita_utils.h>
28#include <kis_dom_utils.h>
29#include <SvgUtil.h>
32
33#include <KisDocument.h>
34#include <KisPart.h>
35
36#include "kis_clipboard.h"
37
38struct KisReferenceImage::Private : public QSharedData
39{
40 // Filename within .kra (for embedding)
42
43 // File on disk (for linking)
45
46 QImage image;
49
50 qreal saturation{1.0};
51 int id{-1};
52 bool embed{true};
53
54 bool loadFromFile() {
57 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(QFileInfo(externalFilename).isReadable(), false);
58 {
59 QImageReader reader(externalFilename);
60 reader.setDecideFormatFromContent(true);
61 image = reader.read();
62
63 if (image.isNull()) {
64 reader.setAutoDetectImageFormat(true);
65 image = reader.read();
66 }
67
68 }
69
70 if (image.isNull()) {
72 }
73
74 if (image.isNull()) {
77 if (doc->image()->colorSpace()->colorModelId() == RGBAColorModelID) {
79 // Because we store as PNG, we'll need to convert to PQ here. One problem: we need to check when saving the reference images.
82 }
83 image = doc->image()->convertToQImage(doc->image()->bounds(), doc->image()->colorSpace()->profile());
84 image.setColorSpace(KoColorSpaceRegistry::instance()->QColorSpaceForProfile(doc->image()->colorSpace()->profile()));
85 } else {
86 image = doc->image()->convertToQImage(doc->image()->bounds(), 0);
87 }
88 }
90 }
91
92 // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image
93 // loaded into a qimage cannot be saved to png unless we explicitly
94 // convert the colorspace of the QImage
95#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
96 if (image.colorSpace().colorModel() != QColorSpace::ColorModel::Rgb || !image.colorSpace().isValid()) {
97 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
98 }
99#else
100 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
101#endif
102
103 return (!image.isNull());
104 }
105
106 bool loadFromQImage(const QImage &img) {
107 image = img;
108 return !image.isNull();
109 }
110
111 void updateCache() {
112 if (saturation < 1.0) {
114
115 if (saturation > 0.0) {
116 QPainter gc2(&cachedImage);
117 gc2.setOpacity(saturation);
118 gc2.drawImage(QPoint(), image);
119 }
120 } else {
122 }
123
125 }
126};
127
128
130 : KUndo2Command(kundo2_i18n("Set saturation"), parent)
131 , newSaturation(newSaturation)
132{
133 images.reserve(shapes.count());
134
135 Q_FOREACH(auto *shape, shapes) {
136 auto *reference = dynamic_cast<KisReferenceImage*>(shape);
138 images.append(reference);
139 }
140
141 Q_FOREACH(auto *image, images) {
142 oldSaturations.append(image->saturation());
143 }
144}
145
147{
148 auto saturationIterator = oldSaturations.begin();
149 Q_FOREACH(auto *image, images) {
150 image->setSaturation(*saturationIterator);
151 image->update();
152 saturationIterator++;
153 }
154}
155
157{
158 Q_FOREACH(auto *image, images) {
159 image->setSaturation(newSaturation);
160 image->update();
161 }
162}
163
169
171 : KoShape(rhs)
172 , d(rhs.d)
173{}
174
177
178KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
179{
180 KisReferenceImage *reference = new KisReferenceImage();
181 reference->d->externalFilename = filename;
182 bool ok = reference->d->loadFromFile();
183
184 if (ok) {
185 QRect r = QRect(QPoint(), reference->d->image.size());
186 QSizeF shapeSize = converter.imageToDocument(r).size();
187 reference->setSize(shapeSize);
188 } else {
189 delete reference;
190
191 if (parent) {
192 QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename));
193 }
194
195 return nullptr;
196 }
197
198 return reference;
199}
200
202{
203 const auto sz = KisClipboard::instance()->clipSize();
204 KisPaintDeviceSP clip = KisClipboard::instance()->clip({0, 0, sz.width(), sz.height()}, true);
205 return fromPaintDevice(clip, converter, nullptr);
206}
207
210{
211 if (!src) {
212 return nullptr;
213 }
214
215 auto *reference = new KisReferenceImage();
216#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
217 if (src->colorSpace()->colorModelId() == RGBAColorModelID) {
218 reference->d->image = src->convertToQImage(src->colorSpace()->profile());
219 reference->d->image.setColorSpace(KoColorSpaceRegistry::instance()->QColorSpaceForProfile(src->colorSpace()->profile()));
220 } else {
221 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile());
222 }
223#else
224 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile());
225#endif
226
227 QRect r = QRect(QPoint(), reference->d->image.size());
228 QSizeF size = converter.imageToDocument(r).size();
229 reference->setSize(size);
230
231 return reference;
232}
233
235{
236 KisReferenceImage *reference = new KisReferenceImage();
237 bool ok = reference->d->loadFromQImage(img);
238
239 if (ok) {
240 QRect r = QRect(QPoint(), reference->d->image.size());
241 QSizeF size = converter.imageToDocument(r).size();
242 reference->setSize(size);
243 } else {
244 delete reference;
245 reference = 0;
246 }
247
248 return reference;
249}
250
251void KisReferenceImage::paint(QPainter &gc) const
252{
253 if (!parent()) return;
254
255 gc.save();
256
257 QSizeF shapeSize = size();
258 // scale and rotation done by the user (excluding zoom)
259 QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height());
260
261 if (d->cachedImage.isNull()) {
262 // detach the data
263 const_cast<KisReferenceImage*>(this)->d->updateCache();
264 }
265
266 qreal scale;
267 // scale from the highDPI display
268 QTransform devicePixelRatioFTransform = QTransform::fromScale(gc.device()->devicePixelRatioF(), gc.device()->devicePixelRatioF());
269 // all three transformations: scale and rotation done by the user, scale from highDPI display, and zoom + rotation of the view
270 // order: zoom/rotation of the view; scale to high res; scale and rotation done by the user
271 QImage prescaled = d->mipmap.getClosestWithoutWorkaroundBorder(transform * devicePixelRatioFTransform * gc.transform(), &scale);
272 transform.scale(1.0 / scale, 1.0 / scale);
273
274 // Color conversion.
275 QImage *targetD = dynamic_cast<QImage*>(gc.device());
276#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
277 const bool getTargetCs = (targetD && targetD->colorSpace().colorModel() == QColorSpace::ColorModel::Rgb);
278
280 const KoColorProfile *sourceP = KoColorSpaceRegistry::instance()->profileForQColorSpace(d->image.colorSpace());
281 if (targetP != sourceP) {
283 dev->convertFromQImage(prescaled, sourceP);
285 prescaled = dev->convertToQImage(targetP);
286 if (getTargetCs) {
287 prescaled.setColorSpace(targetD->colorSpace());
288 } else {
289 prescaled.setColorSpace(QColorSpace(QColorSpace::SRgb));
290 }
291 }
292#endif
293
294 if (scale > 1.0) {
295 // enlarging should be done without smooth transformation
296 // so the user can see pixels just as they are painted
297 gc.setRenderHints(QPainter::Antialiasing);
298 } else {
299 gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
300 }
301 gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip);
302 gc.setTransform(transform, true);
303 gc.drawImage(QPoint(), prescaled);
304
305 gc.restore();
306}
307
309{
310 d->saturation = saturation;
311 d->cachedImage = QImage();
312}
313
315{
316 return d->saturation;
317}
318
320{
321 KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty());
322 d->embed = embed;
323}
324
326{
327 return d->embed;
328}
329
331{
332 return !d->externalFilename.isEmpty();
333}
334
336{
337 return d->externalFilename;
338}
339
341{
342 return d->internalFilename;
343}
344
345
346void KisReferenceImage::setFilename(const QString &filename)
347{
348 d->externalFilename = filename;
349}
350
352{
353 KoColor transparent;
354 transparent.setOpacity(0.0);
355 if (transparency() == 1.0) return transparent;
356
357 const QSizeF shapeSize = size();
358 const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height());
359
360 const QTransform transform = absoluteTransformation().inverted() * scale;
361 const QPointF localPosition = position * transform;
362
363 if (d->cachedImage.isNull()) {
364 d->updateCache();
365 }
366
367#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
368 const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileForQColorSpace(d->image.colorSpace());
370#else
372#endif
373
374 KoColor c(cs);
375 QColor pixel = d->cachedImage.pixelColor(localPosition.toPoint());
376 QVector<float> channels = {
377 static_cast<float>(pixel.blueF()),
378 static_cast<float>(pixel.greenF()),
379 static_cast<float>(pixel.redF()),
380 1.0f};
381 cs->fromNormalisedChannelsValue( c.data(),channels);
382
383 return c;
384}
385
386void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id)
387{
388 d->id = id;
389
390 QDomElement element = document.createElement("referenceimage");
391
392 if (d->embed) {
393 d->internalFilename = QString("reference_images/%1.png").arg(id);
394 }
395
396 const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename);
397 element.setAttribute("src", src);
398
399 const QSizeF &shapeSize = size();
400 element.setAttribute("width", KisDomUtils::toString(shapeSize.width()));
401 element.setAttribute("height", KisDomUtils::toString(shapeSize.height()));
402 element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false");
403 element.setAttribute("transform", SvgUtil::transformToString(transform()));
404
405 element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency()));
406 element.setAttribute("saturation", KisDomUtils::toString(d->saturation));
407
408 parentElement.appendChild(element);
409}
410
412{
413 auto *reference = new KisReferenceImage();
414
415 const QString &src = elem.attribute("src");
416
417 if (src.startsWith("file://")) {
418 reference->d->externalFilename = src.mid(7);
419 reference->d->embed = false;
420 } else {
421 reference->d->internalFilename = src;
422 reference->d->embed = true;
423 }
424
425 qreal width = KisDomUtils::toDouble(elem.attribute("width", "100"));
426 qreal height = KisDomUtils::toDouble(elem.attribute("height", "100"));
427 reference->setSize(QSizeF(width, height));
428 reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true");
429
430 auto transform = SvgTransformParser(elem.attribute("transform")).transform();
431 reference->setTransformation(transform);
432
433 qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
434 reference->setTransparency(1.0 - opacity);
435
436 qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1"));
437 reference->setSaturation(saturation);
438
439 return reference;
440}
441
443{
444 if (!d->embed) return true;
445
446 if (!store->open(d->internalFilename)) {
447 return false;
448 }
449
450 bool saved = false;
451
452 KoStoreDevice storeDev(store);
453 if (storeDev.open(QIODevice::WriteOnly)) {
454 saved = d->image.save(&storeDev, "PNG");
455 }
456
457 return store->close() && saved;
458}
459
461{
462 if (!d->embed) {
463 return d->loadFromFile();
464 }
465
466 if (!store->open(d->internalFilename)) {
467 return false;
468 }
469
470 KoStoreDevice storeDev(store);
471 if (!storeDev.open(QIODevice::ReadOnly)) {
472 return false;
473 }
474
475 if (!d->image.load(&storeDev, "PNG")) {
476 return false;
477 }
478
479 return store->close();
480}
481
483{
484 return d->image;
485}
486
488{
489 return new KisReferenceImage(*this);
490}
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:131
void removeDocument(KisDocument *document, bool deleteDocument=true)
Definition KisPart.cpp:248
KisDocument * createTemporaryDocument() const
Definition KisPart.cpp:236
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)
static KisReferenceImage * fromClipboard(const KisCoordinatesConverter &converter)
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