Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_prescaled_projection.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
4 * SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
9
10#include <math.h>
11
12#include <QImage>
13#include <QColor>
14#include <QRect>
15#include <QPoint>
16#include <QSize>
17#include <QPainter>
18
19#include <KoColorProfile.h>
20#include <KoViewConverter.h>
21
22#include "kis_config.h"
23#include "kis_image_config.h"
24#include "kis_config_notifier.h"
25#include "kis_image.h"
26#include "krita_utils.h"
27
30#include "kis_image_pyramid.h"
31#include "kis_display_filter.h"
32#include <KisDisplayConfig.h>
33
34#include <KisCanvasState.h>
35
36namespace {
43struct RelevantCanvasState
44{
45 qreal zoom = 1.0;
46 qreal rotation = 0.0;
47 QPointF viewportOffsetF;
48
49 std::tuple<qreal,qreal> transformations() const {
50 return {zoom, rotation};
51 }
52
53 bool operator==(const RelevantCanvasState& other) const {
54 return qFuzzyCompare(zoom, other.zoom) &&
55 qFuzzyCompare(rotation, other.rotation) &&
56 viewportOffsetF == other.viewportOffsetF;
57 }
58
59 bool operator!=(const RelevantCanvasState& other) const {
60 return !(*this == other);
61 }
62
63 static RelevantCanvasState fromCanvasState(const KisCanvasState &state) {
64 return {state.effectiveZoom, state.rotation, state.viewportOffsetF};
65 }
66};
67}
68
69#define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height()))
70
71inline void copyQImageBuffer(uchar* dst, const uchar* src , qint32 deltaX, qint32 width)
72{
73 if (deltaX >= 0) {
74 memcpy(dst + 4 * deltaX, src, 4 *(width - deltaX) * sizeof(uchar));
75 } else {
76 memcpy(dst, src - 4 * deltaX, 4 *(width + deltaX) * sizeof(uchar));
77 }
78}
79
80void copyQImage(qint32 deltaX, qint32 deltaY, QImage* dstImage, const QImage& srcImage)
81{
82 qint32 height = dstImage->height();
83 qint32 width = dstImage->width();
84 Q_ASSERT(dstImage->width() == srcImage.width() && dstImage->height() == srcImage.height());
85 if (deltaY >= 0) {
86 for (int y = 0; y < height - deltaY; y ++) {
87 const uchar* src = srcImage.scanLine(y);
88 uchar* dst = dstImage->scanLine(y + deltaY);
89 copyQImageBuffer(dst, src, deltaX, width);
90 }
91 } else {
92 for (int y = 0; y < height + deltaY; y ++) {
93 const uchar* src = srcImage.scanLine(y - deltaY);
94 uchar* dst = dstImage->scanLine(y);
95 copyQImageBuffer(dst, src, deltaX, width);
96 }
97 }
98}
99
116
118 : QObject(0)
119 , m_d(new Private())
120{
122
123 // we disable building the pyramid with setting its height to 1
124 // XXX: setting it higher than 1 is broken because it's not updated until you show/hide the layer
126
127 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings()));
128}
129
135
136
138{
139 Q_ASSERT(image);
140 m_d->image = image;
142}
143
145{
146 return m_d->prescaledQImage;
147}
148
150{
151 m_d->coordinatesConverter = coordinatesConverter;
152 m_d->currentRelevantCanvasState = RelevantCanvasState::fromCanvasState(KisCanvasState::fromConverter(*coordinatesConverter));
153}
154
156{
157 KisImageConfig imageConfig(false);
158 m_d->updatePatchSize.setWidth(imageConfig.updatePatchWidth());
159 m_d->updatePatchSize.setHeight(imageConfig.updatePatchHeight());
160}
161
163{
164 auto relevantState = RelevantCanvasState::fromCanvasState(state);
165
166 if (m_d->currentRelevantCanvasState == relevantState) return;
167
169 m_d->currentRelevantCanvasState->transformations() != relevantState.transformations()) {
170
172 preScale();
173 } else {
174 const QPointF moveOffset = m_d->currentRelevantCanvasState->viewportOffsetF - relevantState.viewportOffsetF;
175 viewportMoved(-moveOffset);
176 }
177
178 m_d->currentRelevantCanvasState = relevantState;
179}
180
181void KisPrescaledProjection::viewportMoved(const QPointF &offset)
182{
183 // FIXME: \|/
184 if (m_d->prescaledQImage.isNull()) return;
185 if (offset.isNull()) return;
186
187 QPoint alignedOffset = offset.toPoint();
188
189 if(offset != alignedOffset) {
194 dbgRender << "prescaling the entire image because the offset is float";
195 preScale();
196 return;
197 }
198
199 QImage newImage = QImage(m_d->viewportSize, QImage::Format_ARGB32);
200 newImage.fill(0);
201
207 QRect newViewportRect = QRect(QPoint(0,0), m_d->viewportSize);
208 QRect oldViewportRect = newViewportRect.translated(alignedOffset);
209
210 QRegion updateRegion = newViewportRect;
211 QRect savedArea = newViewportRect & oldViewportRect;
212 if(!savedArea.isEmpty()) {
213 copyQImage(alignedOffset.x(), alignedOffset.y(), &newImage, m_d->prescaledQImage);
214 updateRegion -= savedArea;
215 }
216
217 QPainter gc(&newImage);
218 auto rc = updateRegion.begin();
219 while (rc != updateRegion.end()) {
220 QRect rect = *rc;
221 QRect imageRect =
222 m_d->coordinatesConverter->viewportToImage(rect).toAlignedRect();
223 QVector<QRect> patches =
225
226 Q_FOREACH (const QRect& rc, patches) {
227 QRect viewportPatch =
228 m_d->coordinatesConverter->imageToViewport(rc).toAlignedRect();
229
231 fillInUpdateInformation(viewportPatch, info);
232 drawUsingBackend(gc, info);
233 }
234 rc++;
235 }
236
237 m_d->prescaledQImage = newImage;
238}
239
241{
243 // viewport size is cropped by the size of the image
244 // so we need to update it as well
246}
247
249{
250 if (!m_d->image) {
251 dbgRender.noquote() << "Calling updateCache without an image:" << kisBacktrace() << Qt::endl;
252 // return invalid info
253 return new KisPPUpdateInfo();
254 }
255
260 QRect croppedImageRect = dirtyImageRect & m_d->image->bounds();
261 if (croppedImageRect.isEmpty()) return new KisPPUpdateInfo();
262
263 KisPPUpdateInfoSP info = getInitialUpdateInformation(croppedImageRect);
264 m_d->projectionBackend->updateCache(croppedImageRect);
265
266 return info;
267}
268
270{
271 KisPPUpdateInfoSP ppInfo = dynamic_cast<KisPPUpdateInfo*>(info.data());
272 if(!ppInfo) return;
273
274 QRect rawViewRect =
276 imageToViewport(ppInfo->dirtyImageRectVar).toAlignedRect();
277
278 fillInUpdateInformation(rawViewRect, ppInfo);
279
281
282 if(!info->dirtyViewportRect().isEmpty())
283 updateScaledImage(ppInfo);
284}
285
287{
288 if (!m_d->image) return;
289
290 m_d->prescaledQImage.fill(0);
291
292 QRect viewportRect(QPoint(0, 0), m_d->viewportSize);
293 QRect imageRect =
294 m_d->coordinatesConverter->viewportToImage(viewportRect).toAlignedRect();
295
296 QVector<QRect> patches =
298
299 Q_FOREACH (const QRect& rc, patches) {
300 QRect viewportPatch = m_d->coordinatesConverter->imageToViewport(rc).toAlignedRect();
302 fillInUpdateInformation(viewportPatch, info);
303 QPainter gc(&m_d->prescaledQImage);
304 gc.setCompositionMode(QPainter::CompositionMode_Source);
305 drawUsingBackend(gc, info);
306 }
307
308}
309
314
315void KisPrescaledProjection::setChannelFlags(const QBitArray &channelFlags)
316{
317 m_d->projectionBackend->setChannelFlags(channelFlags);
318}
319
324
325
327{
328 QRect imageRect = m_d->coordinatesConverter->imageRectInWidgetPixels().toAlignedRect();
329 QSizeF minimalSize(qMin(imageRect.width(), m_d->canvasSize.width()),
330 qMin(imageRect.height(), m_d->canvasSize.height()));
331 QRectF minimalRect(QPointF(0,0), minimalSize);
332
333 m_d->viewportSize = m_d->coordinatesConverter->widgetToViewport(minimalRect).toAlignedRect().size();
334
335 if (m_d->prescaledQImage.isNull() ||
336 m_d->prescaledQImage.size() != m_d->viewportSize) {
337
338 m_d->prescaledQImage = QImage(m_d->viewportSize, QImage::Format_ARGB32);
339 m_d->prescaledQImage.fill(0);
340 }
341}
342
344{
345 m_d->canvasSize = widgetSize;
347 preScale();
348}
349
351{
360 info->dirtyImageRectVar = dirtyImageRect;
361
362 return info;
363}
364
367{
368 m_d->coordinatesConverter->imageScale(&info->scaleX, &info->scaleY);
369
370 // first, crop the part of the view rect that is outside of the canvas
371 QRect croppedViewRect = viewportRect.intersected(QRect(QPoint(0, 0), m_d->viewportSize));
372
373 // second, align this rect to the KisImage's pixels and pixels
374 // of projection backend.
375 info->imageRect = m_d->coordinatesConverter->viewportToImage(QRectF(croppedViewRect)).toAlignedRect();
376
384 const int borderSize = BORDER_SIZE(qMax(info->scaleX, info->scaleY));
385 info->imageRect.adjust(-borderSize, -borderSize, borderSize, borderSize);
386
387 info->imageRect = info->imageRect & m_d->image->bounds();
388
389 m_d->projectionBackend->alignSourceRect(info->imageRect, info->scaleX);
390
391 // finally, compute the dirty rect of the canvas
392 info->viewportRect = m_d->coordinatesConverter->imageToViewport(info->imageRect);
393
394 info->borderWidth = 0;
395 if (SCALE_MORE_OR_EQUAL_TO(info->scaleX, info->scaleY, 1.0)) {
396 if (SCALE_LESS_THAN(info->scaleX, info->scaleY, 2.0)) {
397 dbgRender << "smoothBetween100And200Percent" << Qt::endl;
398 info->renderHints = QPainter::SmoothPixmapTransform;
399 info->borderWidth = borderSize;
400 }
401 info->transfer = KisPPUpdateInfo::DIRECT;
402 } else { // <100%
403 info->renderHints = QPainter::SmoothPixmapTransform;
404 info->borderWidth = borderSize;
405 info->transfer = KisPPUpdateInfo::PATCH;
406 }
407
408 dbgRender << "#####################################";
409 dbgRender << ppVar(info->scaleX) << ppVar(info->scaleY);
410 dbgRender << ppVar(info->borderWidth) << ppVar(info->renderHints);
411 dbgRender << ppVar(info->transfer);
412 dbgRender << ppVar(info->dirtyImageRectVar);
413 dbgRender << "Not aligned rect of the canvas (raw):\t" << croppedViewRect;
414 dbgRender << "Update rect in KisImage's pixels:\t" << info->imageRect;
415 dbgRender << "Update rect in canvas' pixels:\t" << info->viewportRect;
416 dbgRender << "#####################################";
417}
418
420{
421 QPainter gc(&m_d->prescaledQImage);
422 gc.setCompositionMode(QPainter::CompositionMode_Source);
423 drawUsingBackend(gc, info);
424}
425
427{
428 if (info->imageRect.isEmpty()) return;
429
430 if (info->transfer == KisPPUpdateInfo::DIRECT) {
432 } else /* if info->transfer == KisPPUpdateInformation::PATCH */ {
434 // prescale the patch because otherwise we'd scale using QPainter, which gives
435 // a crap result compared to QImage's smoothscale
436 patch.preScale(info->viewportRect);
437 patch.drawMe(gc, info->viewportRect, info->renderHints);
438 }
439}
440
bool operator==(const KisRegion &lhs, const KisRegion &rhs)
bool operator!=(const KoID &v1, const KoID &v2)
Definition KoID.h:103
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static KisCanvasState fromConverter(const KisCoordinatesConverter &converter)
QPointF viewportOffsetF
static KisConfigNotifier * instance()
void imageScale(qreal *scaleX, qreal *scaleY) const
_Private::Traits< T >::Result viewportToImage(const T &obj) const
_Private::Traits< T >::Result widgetToViewport(const T &obj) const
_Private::Traits< T >::Result imageToViewport(const T &obj) const
KisDisplayConfig This class keeps track of the color management configuration for image to display....
KoColorConversionTransformation::ConversionFlags conversionFlags
const KoColorProfile * profile
KoColorConversionTransformation::Intent intent
int updatePatchWidth() const
int updatePatchHeight() const
void preScale(const QRectF &dstRect)
void drawMe(QPainter &gc, const QRectF &dstRect, QPainter::RenderHints renderHints)
QRect bounds() const override
void updateScaledImage(KisPPUpdateInfoSP info)
void viewportMoved(const QPointF &offset)
void fillInUpdateInformation(const QRect &viewportRect, KisPPUpdateInfoSP info)
void notifyCanvasStateChanged(const KisCanvasState &state)
void notifyCanvasSizeChanged(const QSize &widgetSize)
void setChannelFlags(const QBitArray &channelFlags)
void slotImageSizeChanged(qint32 w, qint32 h)
KisPPUpdateInfoSP getInitialUpdateInformation(const QRect &dirtyImageRect)
void recalculateCache(KisUpdateInfoSP info)
void drawUsingBackend(QPainter &gc, KisPPUpdateInfoSP info)
void setDisplayConfig(const KisDisplayConfig &config)
void setImage(KisImageWSP image)
KisUpdateInfoSP updateCache(const QRect &dirtyImageRect)
void setCoordinatesConverter(KisCoordinatesConverter *coordinatesConverter)
void setDisplayFilter(QSharedPointer< KisDisplayFilter > displayFilter)
virtual void setDisplayFilter(QSharedPointer< KisDisplayFilter > displayFilter)=0
virtual KisImagePatch getNearestPatch(KisPPUpdateInfoSP info)=0
virtual void alignSourceRect(QRect &rect, qreal scale)
virtual void recalculateCache(KisPPUpdateInfoSP info)=0
virtual void setChannelFlags(const QBitArray &channelFlags)=0
virtual void updateCache(const QRect &dirtyImageRect)=0
virtual void setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)=0
virtual void setImageSize(qint32 w, qint32 h)=0
virtual void setImage(KisImageWSP image)=0
virtual void drawFromOriginalImage(QPainter &gc, KisPPUpdateInfoSP info)=0
static bool qFuzzyCompare(half p1, half p2)
#define SCALE_LESS_THAN(scX, scY, value)
#define SCALE_MORE_OR_EQUAL_TO(scX, scY, value)
QString kisBacktrace()
Definition kis_debug.cpp:51
#define ppVar(var)
Definition kis_debug.h:155
#define dbgRender
Definition kis_debug.h:55
#define BORDER_SIZE(scale)
void copyQImageBuffer(uchar *dst, const uchar *src, qint32 deltaX, qint32 width)
void copyQImage(qint32 deltaX, qint32 deltaY, QImage *dstImage, const QImage &srcImage)
QAction * zoom(const QObject *recvr, const char *slot, QObject *parent)
QVector< QRect > splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
std::optional< RelevantCanvasState > currentRelevantCanvasState
KisCoordinatesConverter * coordinatesConverter