Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_image_pyramid.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6#include "kis_image_pyramid.h"
7
8#include <QBitArray>
9#include <KoChannelInfo.h>
10#include <KoCompositeOp.h>
13#include <KoColorSpaceMaths.h>
14
15#include "kis_display_filter.h"
16#include "kis_painter.h"
17#include "kis_iterator_ng.h"
18#include "kis_datamanager.h"
19#include "kis_config_notifier.h"
20#include "kis_debug.h"
21#include "kis_config.h"
22#include "kis_image_config.h"
23
24#include <memory>
25
26//#define DEBUG_PYRAMID
27
28#include <config-ocio.h>
29
30#define ORIGINAL_INDEX 0
31#define FIRST_NOT_ORIGINAL_INDEX 1
32#define SCALE_FROM_INDEX(idx) (1./qreal(1<<(idx)))
33
34
35/************* AUXILIARY FUNCTIONS **********************************/
36
37#include <KoConfig.h>
38#ifdef HAVE_OPENEXR
39#include <half.h>
40#endif
41
42#define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height()))
43#define isOdd(x) ((x) & 0x01)
44
49inline void alignByPow2Hi(qint32 &value, qint32 alignment)
50{
51 qint32 mask = alignment - 1;
52 value |= mask;
53 value++;
54}
55
60inline void alignByPow2ButOneHi(qint32 &value, qint32 alignment)
61{
62 qint32 mask = alignment - 1;
63 value |= mask;
64}
65
70inline void alignByPow2Lo(qint32 &value, qint32 alignment)
71{
72 qint32 mask = alignment - 1;
73 value &= ~mask;
74}
75
76inline void alignRectBy2(qint32 &x, qint32 &y, qint32 &w, qint32 &h)
77{
78 x -= isOdd(x);
79 y -= isOdd(y);
80 w += isOdd(x);
81 w += isOdd(w);
82 h += isOdd(y);
83 h += isOdd(h);
84}
85
86
87/************* class KisImagePyramid ********************************/
88
90 : m_monitorProfile(0)
91 , m_monitorColorSpace(0)
92 , m_pyramidHeight(pyramidHeight)
93{
95 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
96}
97
102
105 KoColorConversionTransformation::ConversionFlags conversionFlags)
106{
107 m_monitorProfile = monitorProfile;
113 m_renderingIntent = renderingIntent;
114 m_conversionFlags = conversionFlags;
115
117}
118
119void KisImagePyramid::setChannelFlags(const QBitArray &channelFlags)
120{
121 m_channelFlags = channelFlags;
122 int selectedChannels = 0;
123 const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace();
124 const QList<KoChannelInfo*> channelInfo = projectionCs->channels();
125
126 if (channelInfo.size() != m_channelFlags.size()) {
127 m_channelFlags = QBitArray();
128 }
129
130 for (int i = 0; i < m_channelFlags.size(); ++i) {
131 if (m_channelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) {
132 selectedChannels++;
134 }
135 }
136 m_allChannelsSelected = (selectedChannels == m_channelFlags.size());
137 m_onlyOneChannelSelected = (selectedChannels == 1);
138}
139
144
146{
147 m_pyramid.clear();
148 for (qint32 i = 0; i < m_pyramidHeight; i++) {
150 }
151}
152
154{
155 for (qint32 i = 0; i < m_pyramidHeight; i++) {
156 m_pyramid[i]->clear();
157 }
158}
159
161{
162 if (newImage) {
163 m_originalImage = newImage;
164
165 clearPyramid();
167
168 // Get the full image size
169 QRect rc = m_originalImage->projection()->exactBounds();
170
171 KisImageConfig config(true);
172
173 int patchWidth = config.updatePatchWidth();
174 int patchHeight = config.updatePatchHeight();
175
176 if (rc.width() * rc.height() <= patchWidth * patchHeight) {
178 }
179 else {
180 qint32 firstCol = rc.x() / patchWidth;
181 qint32 firstRow = rc.y() / patchHeight;
182
183 qint32 lastCol = (rc.x() + rc.width()) / patchWidth;
184 qint32 lastRow = (rc.y() + rc.height()) / patchHeight;
185
186 for(qint32 i = firstRow; i <= lastRow; i++) {
187 for(qint32 j = firstCol; j <= lastCol; j++) {
188 QRect maxPatchRect(j * patchWidth,
189 i * patchHeight,
190 patchWidth, patchHeight);
191 QRect patchRect = rc & maxPatchRect;
192 retrieveImageData(patchRect);
193 }
194 }
195
196 }
197 //TODO: check whether there is needed recalculateCache()
198 }
199}
200
201void KisImagePyramid::setImageSize(qint32 w, qint32 h)
202{
203 Q_UNUSED(w);
204 Q_UNUSED(h);
205 /* nothing interesting */
206}
207
208void KisImagePyramid::updateCache(const QRect &dirtyImageRect)
209{
210 retrieveImageData(dirtyImageRect);
211}
212
214{
215 // XXX: use QThreadStorage to cache the two patches (512x512) of pixels. Note
216 // that when we do that, we need to reset that cache when the projection's
217 // colorspace changes.
218 const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace();
219 KisPaintDeviceSP originalProjection = m_originalImage->projection();
220 quint32 numPixels = rect.width() * rect.height();
221
222 std::unique_ptr<quint8[]> originalBytes(
223 new quint8[originalProjection->colorSpace()->pixelSize() * numPixels]);
224
225 originalProjection->readBytes(originalBytes.get(), rect);
226
227 if (m_displayFilter &&
228 m_useOcio &&
229 projectionCs->colorModelId() == RGBAColorModelID) {
230
231#ifdef HAVE_OCIO
232 const KoColorProfile *destinationProfile =
233 m_displayFilter->useInternalColorManagement() ?
234 m_monitorProfile : projectionCs->profile();
235
236 const KoColorSpace *floatCs =
240 destinationProfile);
241
242 const KoColorSpace *modifiedMonitorCs =
246 destinationProfile);
247
248 if (projectionCs->colorDepthId() == Float32BitsColorDepthID) {
249 m_displayFilter->filter(originalBytes.get(), numPixels);
250 } else {
251 std::unique_ptr<quint8[]> dst(new quint8[floatCs->pixelSize() * numPixels]);
253 m_displayFilter->filter(dst.get(), numPixels);
254 originalBytes.swap(dst);
255 }
256
257 {
258 std::unique_ptr<quint8[]> dst(new quint8[modifiedMonitorCs->pixelSize() * numPixels]);
259 floatCs->convertPixelsTo(originalBytes.get(), dst.get(), modifiedMonitorCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
260 originalBytes.swap(dst);
261 }
262#endif
263 }
264 else {
265 if (m_channelFlags.size() != projectionCs->channelCount()) {
266 setChannelFlags(QBitArray());
267 }
268 if (!m_channelFlags.isEmpty() && !m_allChannelsSelected) {
269 std::unique_ptr<quint8[]> dst(new quint8[projectionCs->pixelSize() * numPixels]);
270
271 KisConfig cfg(true);
272
274 projectionCs->convertChannelToVisualRepresentation(originalBytes.get(), dst.get(), numPixels, m_selectedChannelIndex);
275 }
276 else {
277 projectionCs->convertChannelToVisualRepresentation(originalBytes.get(), dst.get(), numPixels, m_channelFlags);
278 }
279 originalBytes.swap(dst);
280 }
281
282 std::unique_ptr<quint8[]> dst(new quint8[m_monitorColorSpace->pixelSize() * numPixels]);
283 projectionCs->convertPixelsTo(originalBytes.get(), dst.get(), m_monitorColorSpace, numPixels, m_renderingIntent, m_conversionFlags);
284 originalBytes.swap(dst);
285 }
286
287 m_pyramid[ORIGINAL_INDEX]->writeBytes(originalBytes.get(), rect);
288}
289
291{
292 KisPaintDevice *src;
293 KisPaintDevice *dst;
294 QRect currentSrcRect = info->dirtyImageRectVar;
295
296 for (int i = FIRST_NOT_ORIGINAL_INDEX; i < m_pyramidHeight; i++) {
297 src = m_pyramid[i-1].data();
298 dst = m_pyramid[i].data();
299 if (!currentSrcRect.isEmpty()) {
300 currentSrcRect = downsampleByFactor2(currentSrcRect, src, dst);
301 }
302 }
303
304#ifdef DEBUG_PYRAMID
306 image.save("./PYRAMID_BASE.png");
307
308 image = m_pyramid[1]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
309 image.save("./LEVEL1.png");
310
311 image = m_pyramid[2]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
312 image.save("./LEVEL2.png");
313 image = m_pyramid[3]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
314 image.save("./LEVEL3.png");
315#endif
316}
317
318QRect KisImagePyramid::downsampleByFactor2(const QRect& srcRect,
319 KisPaintDevice* src,
320 KisPaintDevice* dst)
321{
322 qint32 srcX, srcY, srcWidth, srcHeight;
323 srcRect.getRect(&srcX, &srcY, &srcWidth, &srcHeight);
324 alignRectBy2(srcX, srcY, srcWidth, srcHeight);
325
326 // Nothing to do
327 if (srcWidth < 1) return QRect();
328 if (srcHeight < 1) return QRect();
329
330 qint32 dstX = srcX / 2;
331 qint32 dstY = srcY / 2;
332 qint32 dstWidth = srcWidth / 2;
333 qint32 dstHeight = srcHeight / 2;
334
335 KisHLineConstIteratorSP srcIt0 = src->createHLineConstIteratorNG(srcX, srcY, srcWidth);
336 KisHLineConstIteratorSP srcIt1 = src->createHLineConstIteratorNG(srcX, srcY + 1, srcWidth);
337 KisHLineIteratorSP dstIt = dst->createHLineIteratorNG(dstX, dstY, dstWidth);
338
339 int conseqPixels = 0;
340 for (int row = 0; row < dstHeight; ++row) {
341 do {
342 int srcItConseq = srcIt0->nConseqPixels();
343 int dstItConseq = dstIt->nConseqPixels();
344 conseqPixels = qMin(srcItConseq, dstItConseq * 2);
345
346 Q_ASSERT(!isOdd(conseqPixels));
347
348 downsamplePixels(srcIt0->oldRawData(), srcIt1->oldRawData(),
349 dstIt->rawData(), conseqPixels);
350
351
352 srcIt1->nextPixels(conseqPixels);
353 dstIt->nextPixels(conseqPixels / 2);
354 } while (srcIt0->nextPixels(conseqPixels));
355 srcIt0->nextRow();
356 srcIt0->nextRow();
357 srcIt1->nextRow();
358 srcIt1->nextRow();
359 dstIt->nextRow();
360 }
361 return QRect(dstX, dstY, dstWidth, dstHeight);
362}
363
364void KisImagePyramid::downsamplePixels(const quint8 *srcRow0,
365 const quint8 *srcRow1,
366 quint8 *dstRow,
367 qint32 numSrcPixels)
368{
373 qint16 b = 0;
374 qint16 g = 0;
375 qint16 r = 0;
376 qint16 a = 0;
377
378 static const qint32 pixelSize = 4; // This is preview argb8 mode
379
380 for (qint32 i = 0; i < numSrcPixels / 2; i++) {
381 b = srcRow0[0] + srcRow1[0] + srcRow0[4] + srcRow1[4];
382 g = srcRow0[1] + srcRow1[1] + srcRow0[5] + srcRow1[5];
383 r = srcRow0[2] + srcRow1[2] + srcRow0[6] + srcRow1[6];
384 a = srcRow0[3] + srcRow1[3] + srcRow0[7] + srcRow1[7];
385
386 dstRow[0] = b / 4;
387 dstRow[1] = g / 4;
388 dstRow[2] = r / 4;
389 dstRow[3] = a / 4;
390
391 dstRow += pixelSize;
392 srcRow0 += 2 * pixelSize;
393 srcRow1 += 2 * pixelSize;
394 }
395}
396
398 QSize originalSize)
399{
400 qint32 nearest = 0;
401
402 for (qint32 i = 0; i < m_pyramidHeight; i++) {
403 qreal planeScale = SCALE_FROM_INDEX(i);
404 if (planeScale < scale) {
405 if (originalSize*scale == originalSize*planeScale)
406 nearest = i;
407 break;
408 }
409 nearest = i;
410 }
411
412 // FOR DEBUGGING
413 //nearest = 0;
414 //nearest = qMin(1, nearest);
415
416 dbgRender << "First good plane:" << nearest << "(sc:" << scale << ")";
417 return nearest;
418}
419
420void KisImagePyramid::alignSourceRect(QRect& rect, qreal scale)
421{
422 qint32 index = findFirstGoodPlaneIndex(scale, rect.size());
423 qint32 alignment = 1 << index;
424
425 dbgRender << "Before alignment:\t" << rect;
426
431 Q_ASSERT(rect.left() >= 0 && rect.top() >= 0);
432
433 qint32 x1, y1, x2, y2;
434 rect.getCoords(&x1, &y1, &x2, &y2);
435
436 alignByPow2Lo(x1, alignment);
437 alignByPow2Lo(y1, alignment);
443 alignByPow2ButOneHi(x2, alignment);
444 alignByPow2ButOneHi(y2, alignment);
445
446 rect.setCoords(x1, y1, x2, y2);
447
448 dbgRender << "After alignment:\t" << rect;
449}
450
452{
453 qint32 index = findFirstGoodPlaneIndex(qMax(info->scaleX, info->scaleY),
454 info->imageRect.size());
455 qreal planeScale = SCALE_FROM_INDEX(index);
456 qint32 alignment = 1 << index;
457
458 alignByPow2Hi(info->borderWidth, alignment);
459
460 KisImagePatch patch(info->imageRect, info->borderWidth,
461 planeScale, planeScale);
462
464 patch.patchRect()));
465 return patch;
466}
467
469{
470 KisImagePatch patch = getNearestPatch(info);
471 patch.drawMe(gc, info->viewportRect, info->renderHints);
472}
473
475 const QRect& unscaledRect)
476{
477 qint32 x, y, w, h;
478 unscaledRect.getRect(&x, &y, &w, &h);
479
480 QImage image = QImage(w, h, QImage::Format_ARGB32);
481
482 paintDevice->dataManager()->readBytes(image.bits(), x, y, w, h);
483
484 return image;
485}
486
488{
489 KisConfig cfg(true);
490 m_useOcio = cfg.useOcio();
491}
492
float value(const T *src, size_t ch)
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
virtual const quint8 * oldRawData() const =0
virtual qint32 nConseqPixels() const =0
virtual bool nextPixels(qint32 n)=0
static KisConfigNotifier * instance()
bool useOcio(bool defaultValue=false) const
bool showSingleChannelAsColor(bool defaultValue=false) const
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h, qint32 dataRowStride=-1) const
virtual void nextRow()=0
int updatePatchWidth() const
int updatePatchHeight() const
void drawMe(QPainter &gc, const QRectF &dstRect, QPainter::RenderHints renderHints)
void setImage(QImage image)
QImage convertToQImageFast(KisPaintDeviceSP paintDevice, const QRect &unscaledRect)
KisImagePyramid(qint32 pyramidHeight)
void downsamplePixels(const quint8 *srcRow0, const quint8 *srcRow1, quint8 *dstRow, qint32 numSrcPixels)
QRect downsampleByFactor2(const QRect &srcRect, KisPaintDevice *src, KisPaintDevice *dst)
void updateCache(const QRect &dirtyImageRect) override
int findFirstGoodPlaneIndex(qreal scale, QSize originalSize)
void setDisplayFilter(QSharedPointer< KisDisplayFilter > displayFilter) override
KisImageWSP m_originalImage
QVector< KisPaintDeviceSP > m_pyramid
QSharedPointer< KisDisplayFilter > m_displayFilter
void setImage(KisImageWSP newImage) override
void alignSourceRect(QRect &rect, qreal scale) override
void setImageSize(qint32 w, qint32 h) override
void drawFromOriginalImage(QPainter &gc, KisPPUpdateInfoSP info) override
KoColorConversionTransformation::Intent m_renderingIntent
~KisImagePyramid() override
void retrieveImageData(const QRect &rect)
KoColorConversionTransformation::ConversionFlags m_conversionFlags
void setMonitorProfile(const KoColorProfile *monitorProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) override
const KoColorSpace * m_monitorColorSpace
void setChannelFlags(const QBitArray &channelFlags) override
KisImagePatch getNearestPatch(KisPPUpdateInfoSP info) override
void recalculateCache(KisPPUpdateInfoSP info) override
const KoColorProfile * m_monitorProfile
KisPaintDeviceSP projection() const
qint32 width() const
qint32 height() const
QRect exactBounds() const
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
const KoColorSpace * colorSpace() const
KisDataManagerSP dataManager() const
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
@ COLOR
The channel represents a color.
virtual quint32 pixelSize() const =0
virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const =0
QList< KoChannelInfo * > channels
virtual KoID colorModelId() const =0
virtual quint32 channelCount() const =0
virtual KoID colorDepthId() const =0
virtual bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace *dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
virtual const KoColorProfile * profile() const =0
QString id() const
Definition KoID.cpp:63
#define dbgRender
Definition kis_debug.h:55
void alignRectBy2(qint32 &x, qint32 &y, qint32 &w, qint32 &h)
#define isOdd(x)
#define FIRST_NOT_ORIGINAL_INDEX
void alignByPow2Lo(qint32 &value, qint32 alignment)
void alignByPow2ButOneHi(qint32 &value, qint32 alignment)
#define SCALE_FROM_INDEX(idx)
#define ORIGINAL_INDEX
void alignByPow2Hi(qint32 &value, qint32 alignment)
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())