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//#define DEBUG_PYRAMID
25
26#include <config-ocio.h>
27
28#define ORIGINAL_INDEX 0
29#define FIRST_NOT_ORIGINAL_INDEX 1
30#define SCALE_FROM_INDEX(idx) (1./qreal(1<<(idx)))
31
32
33/************* AUXILIARY FUNCTIONS **********************************/
34
35#include <KoConfig.h>
36#ifdef HAVE_OPENEXR
37#include <half.h>
38#endif
39
40#define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height()))
41#define isOdd(x) ((x) & 0x01)
42
47inline void alignByPow2Hi(qint32 &value, qint32 alignment)
48{
49 qint32 mask = alignment - 1;
50 value |= mask;
51 value++;
52}
53
58inline void alignByPow2ButOneHi(qint32 &value, qint32 alignment)
59{
60 qint32 mask = alignment - 1;
61 value |= mask;
62}
63
68inline void alignByPow2Lo(qint32 &value, qint32 alignment)
69{
70 qint32 mask = alignment - 1;
71 value &= ~mask;
72}
73
74inline void alignRectBy2(qint32 &x, qint32 &y, qint32 &w, qint32 &h)
75{
76 x -= isOdd(x);
77 y -= isOdd(y);
78 w += isOdd(x);
79 w += isOdd(w);
80 h += isOdd(y);
81 h += isOdd(h);
82}
83
84
85/************* class KisImagePyramid ********************************/
86
88 : m_monitorProfile(0)
89 , m_monitorColorSpace(0)
90 , m_pyramidHeight(pyramidHeight)
91{
94}
95
100
103 KoColorConversionTransformation::ConversionFlags conversionFlags)
104{
105 m_monitorProfile = monitorProfile;
111 m_renderingIntent = renderingIntent;
112 m_conversionFlags = conversionFlags;
113
115}
116
117void KisImagePyramid::setChannelFlags(const QBitArray &channelFlags)
118{
119 m_channelFlags = channelFlags;
120 int selectedChannels = 0;
121 const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace();
122 const QList<KoChannelInfo*> channelInfo = projectionCs->channels();
123
124 if (channelInfo.size() != m_channelFlags.size()) {
125 m_channelFlags = QBitArray();
126 }
127
128 for (int i = 0; i < m_channelFlags.size(); ++i) {
129 if (m_channelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) {
130 selectedChannels++;
132 }
133 }
134 m_allChannelsSelected = (selectedChannels == m_channelFlags.size());
135 m_onlyOneChannelSelected = (selectedChannels == 1);
136}
137
142
144{
145 m_pyramid.clear();
146 for (qint32 i = 0; i < m_pyramidHeight; i++) {
148 }
149}
150
152{
153 for (qint32 i = 0; i < m_pyramidHeight; i++) {
154 m_pyramid[i]->clear();
155 }
156}
157
159{
160 if (newImage) {
161 m_originalImage = newImage;
162
163 clearPyramid();
165
166 // Get the full image size
167 QRect rc = m_originalImage->projection()->exactBounds();
168
169 KisImageConfig config(true);
170
171 int patchWidth = config.updatePatchWidth();
172 int patchHeight = config.updatePatchHeight();
173
174 if (rc.width() * rc.height() <= patchWidth * patchHeight) {
176 }
177 else {
178 qint32 firstCol = rc.x() / patchWidth;
179 qint32 firstRow = rc.y() / patchHeight;
180
181 qint32 lastCol = (rc.x() + rc.width()) / patchWidth;
182 qint32 lastRow = (rc.y() + rc.height()) / patchHeight;
183
184 for(qint32 i = firstRow; i <= lastRow; i++) {
185 for(qint32 j = firstCol; j <= lastCol; j++) {
186 QRect maxPatchRect(j * patchWidth,
187 i * patchHeight,
188 patchWidth, patchHeight);
189 QRect patchRect = rc & maxPatchRect;
190 retrieveImageData(patchRect);
191 }
192 }
193
194 }
195 //TODO: check whether there is needed recalculateCache()
196 }
197}
198
199void KisImagePyramid::setImageSize(qint32 w, qint32 h)
200{
201 Q_UNUSED(w);
202 Q_UNUSED(h);
203 /* nothing interesting */
204}
205
206void KisImagePyramid::updateCache(const QRect &dirtyImageRect)
207{
208 retrieveImageData(dirtyImageRect);
209}
210
212{
213 // XXX: use QThreadStorage to cache the two patches (512x512) of pixels. Note
214 // that when we do that, we need to reset that cache when the projection's
215 // colorspace changes.
216 const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace();
217 KisPaintDeviceSP originalProjection = m_originalImage->projection();
218 quint32 numPixels = rect.width() * rect.height();
219
220 QScopedArrayPointer<quint8> originalBytes(
221 new quint8[originalProjection->colorSpace()->pixelSize() * numPixels]);
222
223 originalProjection->readBytes(originalBytes.data(), rect);
224
225 if (m_displayFilter &&
226 m_useOcio &&
227 projectionCs->colorModelId() == RGBAColorModelID) {
228
229#ifdef HAVE_OCIO
230 const KoColorProfile *destinationProfile =
231 m_displayFilter->useInternalColorManagement() ?
232 m_monitorProfile : projectionCs->profile();
233
234 const KoColorSpace *floatCs =
238 destinationProfile);
239
240 const KoColorSpace *modifiedMonitorCs =
244 destinationProfile);
245
246 if (projectionCs->colorDepthId() == Float32BitsColorDepthID) {
247 m_displayFilter->filter(originalBytes.data(), numPixels);
248 } else {
249 QScopedArrayPointer<quint8> dst(new quint8[floatCs->pixelSize() * numPixels]);
251 m_displayFilter->filter(dst.data(), numPixels);
252 originalBytes.swap(dst);
253 }
254
255 {
256 QScopedArrayPointer<quint8> dst(new quint8[modifiedMonitorCs->pixelSize() * numPixels]);
257 floatCs->convertPixelsTo(originalBytes.data(), dst.data(), modifiedMonitorCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
258 originalBytes.swap(dst);
259 }
260#endif
261 }
262 else {
263 if (m_channelFlags.size() != projectionCs->channelCount()) {
264 setChannelFlags(QBitArray());
265 }
266 if (!m_channelFlags.isEmpty() && !m_allChannelsSelected) {
267 QScopedArrayPointer<quint8> dst(new quint8[projectionCs->pixelSize() * numPixels]);
268
269 KisConfig cfg(true);
270
272 projectionCs->convertChannelToVisualRepresentation(originalBytes.data(), dst.data(), numPixels, m_selectedChannelIndex);
273 }
274 else {
275 projectionCs->convertChannelToVisualRepresentation(originalBytes.data(), dst.data(), numPixels, m_channelFlags);
276 }
277 originalBytes.swap(dst);
278 }
279
280 QScopedArrayPointer<quint8> dst(new quint8[m_monitorColorSpace->pixelSize() * numPixels]);
281 projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), m_monitorColorSpace, numPixels, m_renderingIntent, m_conversionFlags);
282 originalBytes.swap(dst);
283 }
284
285 m_pyramid[ORIGINAL_INDEX]->writeBytes(originalBytes.data(), rect);
286}
287
289{
290 KisPaintDevice *src;
291 KisPaintDevice *dst;
292 QRect currentSrcRect = info->dirtyImageRectVar;
293
294 for (int i = FIRST_NOT_ORIGINAL_INDEX; i < m_pyramidHeight; i++) {
295 src = m_pyramid[i-1].data();
296 dst = m_pyramid[i].data();
297 if (!currentSrcRect.isEmpty()) {
298 currentSrcRect = downsampleByFactor2(currentSrcRect, src, dst);
299 }
300 }
301
302#ifdef DEBUG_PYRAMID
304 image.save("./PYRAMID_BASE.png");
305
306 image = m_pyramid[1]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
307 image.save("./LEVEL1.png");
308
309 image = m_pyramid[2]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
310 image.save("./LEVEL2.png");
311 image = m_pyramid[3]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
312 image.save("./LEVEL3.png");
313#endif
314}
315
316QRect KisImagePyramid::downsampleByFactor2(const QRect& srcRect,
317 KisPaintDevice* src,
318 KisPaintDevice* dst)
319{
320 qint32 srcX, srcY, srcWidth, srcHeight;
321 srcRect.getRect(&srcX, &srcY, &srcWidth, &srcHeight);
322 alignRectBy2(srcX, srcY, srcWidth, srcHeight);
323
324 // Nothing to do
325 if (srcWidth < 1) return QRect();
326 if (srcHeight < 1) return QRect();
327
328 qint32 dstX = srcX / 2;
329 qint32 dstY = srcY / 2;
330 qint32 dstWidth = srcWidth / 2;
331 qint32 dstHeight = srcHeight / 2;
332
333 KisHLineConstIteratorSP srcIt0 = src->createHLineConstIteratorNG(srcX, srcY, srcWidth);
334 KisHLineConstIteratorSP srcIt1 = src->createHLineConstIteratorNG(srcX, srcY + 1, srcWidth);
335 KisHLineIteratorSP dstIt = dst->createHLineIteratorNG(dstX, dstY, dstWidth);
336
337 int conseqPixels = 0;
338 for (int row = 0; row < dstHeight; ++row) {
339 do {
340 int srcItConseq = srcIt0->nConseqPixels();
341 int dstItConseq = dstIt->nConseqPixels();
342 conseqPixels = qMin(srcItConseq, dstItConseq * 2);
343
344 Q_ASSERT(!isOdd(conseqPixels));
345
346 downsamplePixels(srcIt0->oldRawData(), srcIt1->oldRawData(),
347 dstIt->rawData(), conseqPixels);
348
349
350 srcIt1->nextPixels(conseqPixels);
351 dstIt->nextPixels(conseqPixels / 2);
352 } while (srcIt0->nextPixels(conseqPixels));
353 srcIt0->nextRow();
354 srcIt0->nextRow();
355 srcIt1->nextRow();
356 srcIt1->nextRow();
357 dstIt->nextRow();
358 }
359 return QRect(dstX, dstY, dstWidth, dstHeight);
360}
361
362void KisImagePyramid::downsamplePixels(const quint8 *srcRow0,
363 const quint8 *srcRow1,
364 quint8 *dstRow,
365 qint32 numSrcPixels)
366{
371 qint16 b = 0;
372 qint16 g = 0;
373 qint16 r = 0;
374 qint16 a = 0;
375
376 static const qint32 pixelSize = 4; // This is preview argb8 mode
377
378 for (qint32 i = 0; i < numSrcPixels / 2; i++) {
379 b = srcRow0[0] + srcRow1[0] + srcRow0[4] + srcRow1[4];
380 g = srcRow0[1] + srcRow1[1] + srcRow0[5] + srcRow1[5];
381 r = srcRow0[2] + srcRow1[2] + srcRow0[6] + srcRow1[6];
382 a = srcRow0[3] + srcRow1[3] + srcRow0[7] + srcRow1[7];
383
384 dstRow[0] = b / 4;
385 dstRow[1] = g / 4;
386 dstRow[2] = r / 4;
387 dstRow[3] = a / 4;
388
389 dstRow += pixelSize;
390 srcRow0 += 2 * pixelSize;
391 srcRow1 += 2 * pixelSize;
392 }
393}
394
396 QSize originalSize)
397{
398 qint32 nearest = 0;
399
400 for (qint32 i = 0; i < m_pyramidHeight; i++) {
401 qreal planeScale = SCALE_FROM_INDEX(i);
402 if (planeScale < scale) {
403 if (originalSize*scale == originalSize*planeScale)
404 nearest = i;
405 break;
406 }
407 nearest = i;
408 }
409
410 // FOR DEBUGGING
411 //nearest = 0;
412 //nearest = qMin(1, nearest);
413
414 dbgRender << "First good plane:" << nearest << "(sc:" << scale << ")";
415 return nearest;
416}
417
418void KisImagePyramid::alignSourceRect(QRect& rect, qreal scale)
419{
420 qint32 index = findFirstGoodPlaneIndex(scale, rect.size());
421 qint32 alignment = 1 << index;
422
423 dbgRender << "Before alignment:\t" << rect;
424
429 Q_ASSERT(rect.left() >= 0 && rect.top() >= 0);
430
431 qint32 x1, y1, x2, y2;
432 rect.getCoords(&x1, &y1, &x2, &y2);
433
434 alignByPow2Lo(x1, alignment);
435 alignByPow2Lo(y1, alignment);
441 alignByPow2ButOneHi(x2, alignment);
442 alignByPow2ButOneHi(y2, alignment);
443
444 rect.setCoords(x1, y1, x2, y2);
445
446 dbgRender << "After alignment:\t" << rect;
447}
448
450{
451 qint32 index = findFirstGoodPlaneIndex(qMax(info->scaleX, info->scaleY),
452 info->imageRect.size());
453 qreal planeScale = SCALE_FROM_INDEX(index);
454 qint32 alignment = 1 << index;
455
456 alignByPow2Hi(info->borderWidth, alignment);
457
458 KisImagePatch patch(info->imageRect, info->borderWidth,
459 planeScale, planeScale);
460
462 patch.patchRect()));
463 return patch;
464}
465
467{
468 KisImagePatch patch = getNearestPatch(info);
469 patch.drawMe(gc, info->viewportRect, info->renderHints);
470}
471
473 const QRect& unscaledRect)
474{
475 qint32 x, y, w, h;
476 unscaledRect.getRect(&x, &y, &w, &h);
477
478 QImage image = QImage(w, h, QImage::Format_ARGB32);
479
480 paintDevice->dataManager()->readBytes(image.bits(), x, y, w, h);
481
482 return image;
483}
484
486{
487 KisConfig cfg(true);
488 m_useOcio = cfg.useOcio();
489}
490
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"))
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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())