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"
9
10#include <QImage>
11#include <QMessageBox>
12#include <QPainter>
13#include <QApplication>
14#include <QClipboard>
15#include <QSharedData>
16#include <QFileInfo>
17#include <QImageReader>
18#include <QUrl>
19
20#include <QColorSpace>
21
22#include <kundo2command.h>
23#include <KoStore.h>
24#include <KoStoreDevice.h>
25#include <KoTosContainer_p.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 image = doc->image()->convertToQImage(doc->image()->bounds(), 0);
78 }
80 }
81
82 // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image
83 // loaded into a qimage cannot be saved to png unless we explicitly
84 // convert the colorspace of the QImage
85 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
86
87 return (!image.isNull());
88 }
89
90 bool loadFromQImage(const QImage &img) {
91 image = img;
92 return !image.isNull();
93 }
94
95 void updateCache() {
96 if (saturation < 1.0) {
98
99 if (saturation > 0.0) {
100 QPainter gc2(&cachedImage);
101 gc2.setOpacity(saturation);
102 gc2.drawImage(QPoint(), image);
103 }
104 } else {
106 }
107
109 }
110};
111
112
114 : KUndo2Command(kundo2_i18n("Set saturation"), parent)
115 , newSaturation(newSaturation)
116{
117 images.reserve(shapes.count());
118
119 Q_FOREACH(auto *shape, shapes) {
120 auto *reference = dynamic_cast<KisReferenceImage*>(shape);
122 images.append(reference);
123 }
124
125 Q_FOREACH(auto *image, images) {
126 oldSaturations.append(image->saturation());
127 }
128}
129
131{
132 auto saturationIterator = oldSaturations.begin();
133 Q_FOREACH(auto *image, images) {
134 image->setSaturation(*saturationIterator);
135 image->update();
136 saturationIterator++;
137 }
138}
139
141{
142 Q_FOREACH(auto *image, images) {
143 image->setSaturation(newSaturation);
144 image->update();
145 }
146}
147
153
158
161
162KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
163{
164 KisReferenceImage *reference = new KisReferenceImage();
165 reference->d->externalFilename = filename;
166 bool ok = reference->d->loadFromFile();
167
168 if (ok) {
169 QRect r = QRect(QPoint(), reference->d->image.size());
170 QSizeF shapeSize = converter.imageToDocument(r).size();
171 reference->setSize(shapeSize);
172 } else {
173 delete reference;
174
175 if (parent) {
176 QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename));
177 }
178
179 return nullptr;
180 }
181
182 return reference;
183}
184
186{
187 const auto sz = KisClipboard::instance()->clipSize();
188 KisPaintDeviceSP clip = KisClipboard::instance()->clip({0, 0, sz.width(), sz.height()}, true);
189 return fromPaintDevice(clip, converter, nullptr);
190}
191
194{
195 if (!src) {
196 return nullptr;
197 }
198
199 auto *reference = new KisReferenceImage();
200 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile());
201
202 QRect r = QRect(QPoint(), reference->d->image.size());
203 QSizeF size = converter.imageToDocument(r).size();
204 reference->setSize(size);
205
206 return reference;
207}
208
210{
211 KisReferenceImage *reference = new KisReferenceImage();
212 bool ok = reference->d->loadFromQImage(img);
213
214 if (ok) {
215 QRect r = QRect(QPoint(), reference->d->image.size());
216 QSizeF size = converter.imageToDocument(r).size();
217 reference->setSize(size);
218 } else {
219 delete reference;
220 reference = 0;
221 }
222
223 return reference;
224}
225
226void KisReferenceImage::paint(QPainter &gc) const
227{
228 if (!parent()) return;
229
230 gc.save();
231
232 QSizeF shapeSize = size();
233 // scale and rotation done by the user (excluding zoom)
234 QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height());
235
236 if (d->cachedImage.isNull()) {
237 // detach the data
238 const_cast<KisReferenceImage*>(this)->d->updateCache();
239 }
240
241 qreal scale;
242 // scale from the highDPI display
243 QTransform devicePixelRatioFTransform = QTransform::fromScale(gc.device()->devicePixelRatioF(), gc.device()->devicePixelRatioF());
244 // all three transformations: scale and rotation done by the user, scale from highDPI display, and zoom + rotation of the view
245 // order: zoom/rotation of the view; scale to high res; scale and rotation done by the user
246 QImage prescaled = d->mipmap.getClosestWithoutWorkaroundBorder(transform * devicePixelRatioFTransform * gc.transform(), &scale);
247 transform.scale(1.0 / scale, 1.0 / scale);
248
249 if (scale > 1.0) {
250 // enlarging should be done without smooth transformation
251 // so the user can see pixels just as they are painted
252 gc.setRenderHints(QPainter::Antialiasing);
253 } else {
254 gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
255 }
256 gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip);
257 gc.setTransform(transform, true);
258 gc.drawImage(QPoint(), prescaled);
259
260 gc.restore();
261}
262
264{
265 d->saturation = saturation;
266 d->cachedImage = QImage();
267}
268
270{
271 return d->saturation;
272}
273
275{
276 KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty());
277 d->embed = embed;
278}
279
281{
282 return d->embed;
283}
284
286{
287 return !d->externalFilename.isEmpty();
288}
289
291{
292 return d->externalFilename;
293}
294
296{
297 return d->internalFilename;
298}
299
300
301void KisReferenceImage::setFilename(const QString &filename)
302{
303 d->externalFilename = filename;
304}
305
306QColor KisReferenceImage::getPixel(QPointF position)
307{
308 if (transparency() == 1.0) return Qt::transparent;
309
310 const QSizeF shapeSize = size();
311 const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height());
312
313 const QTransform transform = absoluteTransformation().inverted() * scale;
314 const QPointF localPosition = position * transform;
315
316 if (d->cachedImage.isNull()) {
317 d->updateCache();
318 }
319
320 return d->cachedImage.pixelColor(localPosition.toPoint());
321}
322
323void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id)
324{
325 d->id = id;
326
327 QDomElement element = document.createElement("referenceimage");
328
329 if (d->embed) {
330 d->internalFilename = QString("reference_images/%1.png").arg(id);
331 }
332
333 const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename);
334 element.setAttribute("src", src);
335
336 const QSizeF &shapeSize = size();
337 element.setAttribute("width", KisDomUtils::toString(shapeSize.width()));
338 element.setAttribute("height", KisDomUtils::toString(shapeSize.height()));
339 element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false");
340 element.setAttribute("transform", SvgUtil::transformToString(transform()));
341
342 element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency()));
343 element.setAttribute("saturation", KisDomUtils::toString(d->saturation));
344
345 parentElement.appendChild(element);
346}
347
349{
350 auto *reference = new KisReferenceImage();
351
352 const QString &src = elem.attribute("src");
353
354 if (src.startsWith("file://")) {
355 reference->d->externalFilename = src.mid(7);
356 reference->d->embed = false;
357 } else {
358 reference->d->internalFilename = src;
359 reference->d->embed = true;
360 }
361
362 qreal width = KisDomUtils::toDouble(elem.attribute("width", "100"));
363 qreal height = KisDomUtils::toDouble(elem.attribute("height", "100"));
364 reference->setSize(QSizeF(width, height));
365 reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true");
366
367 auto transform = SvgTransformParser(elem.attribute("transform")).transform();
368 reference->setTransformation(transform);
369
370 qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
371 reference->setTransparency(1.0 - opacity);
372
373 qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1"));
374 reference->setSaturation(saturation);
375
376 return reference;
377}
378
380{
381 if (!d->embed) return true;
382
383 if (!store->open(d->internalFilename)) {
384 return false;
385 }
386
387 bool saved = false;
388
389 KoStoreDevice storeDev(store);
390 if (storeDev.open(QIODevice::WriteOnly)) {
391 saved = d->image.save(&storeDev, "PNG");
392 }
393
394 return store->close() && saved;
395}
396
398{
399 if (!d->embed) {
400 return d->loadFromFile();
401 }
402
403 if (!store->open(d->internalFilename)) {
404 return false;
405 }
406
407 KoStoreDevice storeDev(store);
408 if (!storeDev.open(QIODevice::ReadOnly)) {
409 return false;
410 }
411
412 if (!d->image.load(&storeDev, "PNG")) {
413 return false;
414 }
415
416 return store->close();
417}
418
420{
421 return d->image;
422}
423
425{
426 return new KisReferenceImage(*this);
427}
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)
QRect bounds() const override
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)
QColor 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)
QList< KoShape * > shapes() const
KoShapeContainer * parent
Definition KoShape_p.h:80
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:820
void setKeepAspectRatio(bool keepAspect)
Definition KoShape.cpp:1044
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
void scale(qreal sx, qreal sy)
Scale the shape using the zero-point which is the top-left corner.
Definition KoShape.cpp:237
bool keepAspectRatio() const
Definition KoShape.cpp:1052
QTransform transform() const
return the current matrix that contains the rotation/scale/position of this shape
Definition KoShape.cpp:1145
qreal transparency(bool recursive=false) const
Definition KoShape.cpp:730
virtual void setSize(const QSizeF &size)
Resize the shape.
Definition KoShape.cpp:276
QPointF position() const
Get the position of the shape in pt.
Definition KoShape.cpp:825
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
static KoColorSpaceRegistry * instance()