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 <krita_utils.h>
27#include <kis_dom_utils.h>
28#include <SvgUtil.h>
31
32#include <KisDocument.h>
33#include <KisPart.h>
34
35#include "kis_clipboard.h"
36
37struct KisReferenceImage::Private : public QSharedData
38{
39 // Filename within .kra (for embedding)
41
42 // File on disk (for linking)
44
45 QImage image;
48
49 qreal saturation{1.0};
50 int id{-1};
51 bool embed{true};
52
53 bool loadFromFile() {
56 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(QFileInfo(externalFilename).isReadable(), false);
57 {
58 QImageReader reader(externalFilename);
59 reader.setDecideFormatFromContent(true);
60 image = reader.read();
61
62 if (image.isNull()) {
63 reader.setAutoDetectImageFormat(true);
64 image = reader.read();
65 }
66
67 }
68
69 if (image.isNull()) {
71 }
72
73 if (image.isNull()) {
76 image = doc->image()->convertToQImage(doc->image()->bounds(), 0);
77 }
79 }
80
81 // See https://bugs.kde.org/show_bug.cgi?id=416515 -- a jpeg image
82 // loaded into a qimage cannot be saved to png unless we explicitly
83 // convert the colorspace of the QImage
84 image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
85
86 return (!image.isNull());
87 }
88
89 bool loadFromQImage(const QImage &img) {
90 image = img;
91 return !image.isNull();
92 }
93
94 void updateCache() {
95 if (saturation < 1.0) {
97
98 if (saturation > 0.0) {
99 QPainter gc2(&cachedImage);
100 gc2.setOpacity(saturation);
101 gc2.drawImage(QPoint(), image);
102 }
103 } else {
105 }
106
108 }
109};
110
111
113 : KUndo2Command(kundo2_i18n("Set saturation"), parent)
114 , newSaturation(newSaturation)
115{
116 images.reserve(shapes.count());
117
118 Q_FOREACH(auto *shape, shapes) {
119 auto *reference = dynamic_cast<KisReferenceImage*>(shape);
121 images.append(reference);
122 }
123
124 Q_FOREACH(auto *image, images) {
125 oldSaturations.append(image->saturation());
126 }
127}
128
130{
131 auto saturationIterator = oldSaturations.begin();
132 Q_FOREACH(auto *image, images) {
133 image->setSaturation(*saturationIterator);
134 image->update();
135 saturationIterator++;
136 }
137}
138
140{
141 Q_FOREACH(auto *image, images) {
142 image->setSaturation(newSaturation);
143 image->update();
144 }
145}
146
152
154 : KoShape(rhs)
155 , d(rhs.d)
156{}
157
160
161KisReferenceImage * KisReferenceImage::fromFile(const QString &filename, const KisCoordinatesConverter &converter, QWidget *parent)
162{
163 KisReferenceImage *reference = new KisReferenceImage();
164 reference->d->externalFilename = filename;
165 bool ok = reference->d->loadFromFile();
166
167 if (ok) {
168 QRect r = QRect(QPoint(), reference->d->image.size());
169 QSizeF shapeSize = converter.imageToDocument(r).size();
170 reference->setSize(shapeSize);
171 } else {
172 delete reference;
173
174 if (parent) {
175 QMessageBox::critical(parent, i18nc("@title:window", "Krita"), i18n("Could not load %1.", filename));
176 }
177
178 return nullptr;
179 }
180
181 return reference;
182}
183
185{
186 const auto sz = KisClipboard::instance()->clipSize();
187 KisPaintDeviceSP clip = KisClipboard::instance()->clip({0, 0, sz.width(), sz.height()}, true);
188 return fromPaintDevice(clip, converter, nullptr);
189}
190
193{
194 if (!src) {
195 return nullptr;
196 }
197
198 auto *reference = new KisReferenceImage();
199 reference->d->image = src->convertToQImage(KoColorSpaceRegistry::instance()->p709SRGBProfile());
200
201 QRect r = QRect(QPoint(), reference->d->image.size());
202 QSizeF size = converter.imageToDocument(r).size();
203 reference->setSize(size);
204
205 return reference;
206}
207
209{
210 KisReferenceImage *reference = new KisReferenceImage();
211 bool ok = reference->d->loadFromQImage(img);
212
213 if (ok) {
214 QRect r = QRect(QPoint(), reference->d->image.size());
215 QSizeF size = converter.imageToDocument(r).size();
216 reference->setSize(size);
217 } else {
218 delete reference;
219 reference = 0;
220 }
221
222 return reference;
223}
224
225void KisReferenceImage::paint(QPainter &gc) const
226{
227 if (!parent()) return;
228
229 gc.save();
230
231 QSizeF shapeSize = size();
232 // scale and rotation done by the user (excluding zoom)
233 QTransform transform = QTransform::fromScale(shapeSize.width() / d->image.width(), shapeSize.height() / d->image.height());
234
235 if (d->cachedImage.isNull()) {
236 // detach the data
237 const_cast<KisReferenceImage*>(this)->d->updateCache();
238 }
239
240 qreal scale;
241 // scale from the highDPI display
242 QTransform devicePixelRatioFTransform = QTransform::fromScale(gc.device()->devicePixelRatioF(), gc.device()->devicePixelRatioF());
243 // all three transformations: scale and rotation done by the user, scale from highDPI display, and zoom + rotation of the view
244 // order: zoom/rotation of the view; scale to high res; scale and rotation done by the user
245 QImage prescaled = d->mipmap.getClosestWithoutWorkaroundBorder(transform * devicePixelRatioFTransform * gc.transform(), &scale);
246 transform.scale(1.0 / scale, 1.0 / scale);
247
248 if (scale > 1.0) {
249 // enlarging should be done without smooth transformation
250 // so the user can see pixels just as they are painted
251 gc.setRenderHints(QPainter::Antialiasing);
252 } else {
253 gc.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
254 }
255 gc.setClipRect(QRectF(QPointF(), shapeSize), Qt::IntersectClip);
256 gc.setTransform(transform, true);
257 gc.drawImage(QPoint(), prescaled);
258
259 gc.restore();
260}
261
263{
264 d->saturation = saturation;
265 d->cachedImage = QImage();
266}
267
269{
270 return d->saturation;
271}
272
274{
275 KIS_SAFE_ASSERT_RECOVER_RETURN(embed || !d->externalFilename.isEmpty());
276 d->embed = embed;
277}
278
280{
281 return d->embed;
282}
283
285{
286 return !d->externalFilename.isEmpty();
287}
288
290{
291 return d->externalFilename;
292}
293
295{
296 return d->internalFilename;
297}
298
299
300void KisReferenceImage::setFilename(const QString &filename)
301{
302 d->externalFilename = filename;
303}
304
305QColor KisReferenceImage::getPixel(QPointF position)
306{
307 if (transparency() == 1.0) return Qt::transparent;
308
309 const QSizeF shapeSize = size();
310 const QTransform scale = QTransform::fromScale(d->image.width() / shapeSize.width(), d->image.height() / shapeSize.height());
311
312 const QTransform transform = absoluteTransformation().inverted() * scale;
313 const QPointF localPosition = position * transform;
314
315 if (d->cachedImage.isNull()) {
316 d->updateCache();
317 }
318
319 return d->cachedImage.pixelColor(localPosition.toPoint());
320}
321
322void KisReferenceImage::saveXml(QDomDocument &document, QDomElement &parentElement, int id)
323{
324 d->id = id;
325
326 QDomElement element = document.createElement("referenceimage");
327
328 if (d->embed) {
329 d->internalFilename = QString("reference_images/%1.png").arg(id);
330 }
331
332 const QString src = d->embed ? d->internalFilename : (QString("file://") + d->externalFilename);
333 element.setAttribute("src", src);
334
335 const QSizeF &shapeSize = size();
336 element.setAttribute("width", KisDomUtils::toString(shapeSize.width()));
337 element.setAttribute("height", KisDomUtils::toString(shapeSize.height()));
338 element.setAttribute("keepAspectRatio", keepAspectRatio() ? "true" : "false");
339 element.setAttribute("transform", SvgUtil::transformToString(transform()));
340
341 element.setAttribute("opacity", KisDomUtils::toString(1.0 - transparency()));
342 element.setAttribute("saturation", KisDomUtils::toString(d->saturation));
343
344 parentElement.appendChild(element);
345}
346
348{
349 auto *reference = new KisReferenceImage();
350
351 const QString &src = elem.attribute("src");
352
353 if (src.startsWith("file://")) {
354 reference->d->externalFilename = src.mid(7);
355 reference->d->embed = false;
356 } else {
357 reference->d->internalFilename = src;
358 reference->d->embed = true;
359 }
360
361 qreal width = KisDomUtils::toDouble(elem.attribute("width", "100"));
362 qreal height = KisDomUtils::toDouble(elem.attribute("height", "100"));
363 reference->setSize(QSizeF(width, height));
364 reference->setKeepAspectRatio(elem.attribute("keepAspectRatio", "true").toLower() == "true");
365
366 auto transform = SvgTransformParser(elem.attribute("transform")).transform();
367 reference->setTransformation(transform);
368
369 qreal opacity = KisDomUtils::toDouble(elem.attribute("opacity", "1"));
370 reference->setTransparency(1.0 - opacity);
371
372 qreal saturation = KisDomUtils::toDouble(elem.attribute("saturation", "1"));
373 reference->setSaturation(saturation);
374
375 return reference;
376}
377
379{
380 if (!d->embed) return true;
381
382 if (!store->open(d->internalFilename)) {
383 return false;
384 }
385
386 bool saved = false;
387
388 KoStoreDevice storeDev(store);
389 if (storeDev.open(QIODevice::WriteOnly)) {
390 saved = d->image.save(&storeDev, "PNG");
391 }
392
393 return store->close() && saved;
394}
395
397{
398 if (!d->embed) {
399 return d->loadFromFile();
400 }
401
402 if (!store->open(d->internalFilename)) {
403 return false;
404 }
405
406 KoStoreDevice storeDev(store);
407 if (!storeDev.open(QIODevice::ReadOnly)) {
408 return false;
409 }
410
411 if (!d->image.load(&storeDev, "PNG")) {
412 return false;
413 }
414
415 return store->close();
416}
417
419{
420 return d->image;
421}
422
424{
425 return new KisReferenceImage(*this);
426}
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)
virtual QSizeF size() const
Get the size of the shape in pt.
Definition KoShape.cpp:740
void setKeepAspectRatio(bool keepAspect)
Definition KoShape.cpp:867
KoShapeContainer * parent() const
Definition KoShape.cpp:862
QTransform absoluteTransformation() const
Definition KoShape.cpp:335
void scale(qreal sx, qreal sy)
Scale the shape using the zero-point which is the top-left corner.
Definition KoShape.cpp:209
bool keepAspectRatio() const
Definition KoShape.cpp:875
QTransform transform() const
return the current matrix that contains the rotation/scale/position of this shape
Definition KoShape.cpp:950
qreal transparency(bool recursive=false) const
Definition KoShape.cpp:650
virtual void setSize(const QSizeF &size)
Resize the shape.
Definition KoShape.cpp:248
QPointF position() const
Get the position of the shape in pt.
Definition KoShape.cpp:745
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()