File indexing completed on 2025-02-23 04:09:00

0001 /*
0002  *  SPDX-FileCopyrightText: 2009 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "kis_image_pyramid.h"
0007 
0008 #include <QBitArray>
0009 #include <KoChannelInfo.h>
0010 #include <KoCompositeOp.h>
0011 #include <KoColorSpaceRegistry.h>
0012 #include <KoColorModelStandardIds.h>
0013 #include <KoColorSpaceMaths.h>
0014 
0015 #include "kis_display_filter.h"
0016 #include "kis_painter.h"
0017 #include "kis_iterator_ng.h"
0018 #include "kis_datamanager.h"
0019 #include "kis_config_notifier.h"
0020 #include "kis_debug.h"
0021 #include "kis_config.h"
0022 #include "kis_image_config.h"
0023 
0024 //#define DEBUG_PYRAMID
0025 
0026 #include <config-ocio.h>
0027 
0028 #define ORIGINAL_INDEX           0
0029 #define FIRST_NOT_ORIGINAL_INDEX 1
0030 #define SCALE_FROM_INDEX(idx) (1./qreal(1<<(idx)))
0031 
0032 
0033 /************* AUXILIARY FUNCTIONS **********************************/
0034 
0035 #include <KoConfig.h>
0036 #ifdef HAVE_OPENEXR
0037 #include <half.h>
0038 #endif
0039 
0040 #define ceiledSize(sz) QSize(ceil((sz).width()), ceil((sz).height()))
0041 #define isOdd(x) ((x) & 0x01)
0042 
0043 /**
0044  * Aligns @p value to the lowest integer not smaller than @p value and
0045  * that is a divident of alignment
0046  */
0047 inline void alignByPow2Hi(qint32 &value, qint32 alignment)
0048 {
0049     qint32 mask = alignment - 1;
0050     value |= mask;
0051     value++;
0052 }
0053 
0054 /**
0055  * Aligns @p value to the lowest integer not smaller than @p value and
0056  * that is, increased by one, a divident of alignment
0057  */
0058 inline void alignByPow2ButOneHi(qint32 &value, qint32 alignment)
0059 {
0060     qint32 mask = alignment - 1;
0061     value |= mask;
0062 }
0063 
0064 /**
0065  * Aligns @p value to the highest integer not exceeding @p value and
0066  * that is a divident of @p alignment
0067  */
0068 inline void alignByPow2Lo(qint32 &value, qint32 alignment)
0069 {
0070     qint32 mask = alignment - 1;
0071     value &= ~mask;
0072 }
0073 
0074 inline void alignRectBy2(qint32 &x, qint32 &y, qint32 &w, qint32 &h)
0075 {
0076     x -= isOdd(x);
0077     y -= isOdd(y);
0078     w += isOdd(x);
0079     w += isOdd(w);
0080     h += isOdd(y);
0081     h += isOdd(h);
0082 }
0083 
0084 
0085 /************* class KisImagePyramid ********************************/
0086 
0087 KisImagePyramid::KisImagePyramid(qint32 pyramidHeight)
0088         : m_monitorProfile(0)
0089         , m_monitorColorSpace(0)
0090         , m_pyramidHeight(pyramidHeight)
0091 {
0092     configChanged();
0093     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
0094 }
0095 
0096 KisImagePyramid::~KisImagePyramid()
0097 {
0098     setImage(0);
0099 }
0100 
0101 void KisImagePyramid::setMonitorProfile(const KoColorProfile* monitorProfile,
0102                                         KoColorConversionTransformation::Intent renderingIntent,
0103                                         KoColorConversionTransformation::ConversionFlags conversionFlags)
0104 {
0105     m_monitorProfile = monitorProfile;
0106     /**
0107      * If you change pixel size here, don't forget to change it
0108      * in optimized function downsamplePixels()
0109      */
0110     m_monitorColorSpace = KoColorSpaceRegistry::instance()->rgb8(monitorProfile);
0111     m_renderingIntent = renderingIntent;
0112     m_conversionFlags = conversionFlags;
0113 
0114     rebuildPyramid();
0115 }
0116 
0117 void KisImagePyramid::setChannelFlags(const QBitArray &channelFlags)
0118 {
0119     m_channelFlags = channelFlags;
0120     int selectedChannels = 0;
0121     const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace();
0122     const QList<KoChannelInfo*> channelInfo = projectionCs->channels();
0123 
0124     if (channelInfo.size() != m_channelFlags.size()) {
0125         m_channelFlags = QBitArray();
0126     }
0127 
0128     for (int i = 0; i < m_channelFlags.size(); ++i) {
0129         if (m_channelFlags.testBit(i) && channelInfo[i]->channelType() == KoChannelInfo::COLOR) {
0130             selectedChannels++;
0131             m_selectedChannelIndex = i;
0132         }
0133     }
0134     m_allChannelsSelected = (selectedChannels == m_channelFlags.size());
0135     m_onlyOneChannelSelected = (selectedChannels == 1);
0136 }
0137 
0138 void KisImagePyramid::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
0139 {
0140     m_displayFilter = displayFilter;
0141 }
0142 
0143 void KisImagePyramid::rebuildPyramid()
0144 {
0145     m_pyramid.clear();
0146     for (qint32 i = 0; i < m_pyramidHeight; i++) {
0147         m_pyramid.append(new KisPaintDevice(m_monitorColorSpace));
0148     }
0149 }
0150 
0151 void KisImagePyramid::clearPyramid()
0152 {
0153     for (qint32 i = 0; i < m_pyramidHeight; i++) {
0154         m_pyramid[i]->clear();
0155     }
0156 }
0157 
0158 void KisImagePyramid::setImage(KisImageWSP newImage)
0159 {
0160     if (newImage) {
0161         m_originalImage = newImage;
0162 
0163         clearPyramid();
0164         setImageSize(m_originalImage->width(), m_originalImage->height());
0165 
0166         // Get the full image size
0167         QRect rc = m_originalImage->projection()->exactBounds();
0168 
0169         KisImageConfig config(true);
0170 
0171         int patchWidth = config.updatePatchWidth();
0172         int patchHeight = config.updatePatchHeight();
0173 
0174         if (rc.width() * rc.height() <= patchWidth * patchHeight) {
0175             retrieveImageData(rc);
0176         }
0177         else {
0178             qint32 firstCol = rc.x() / patchWidth;
0179             qint32 firstRow = rc.y() / patchHeight;
0180 
0181             qint32 lastCol = (rc.x() + rc.width()) / patchWidth;
0182             qint32 lastRow = (rc.y() + rc.height()) / patchHeight;
0183 
0184             for(qint32 i = firstRow; i <= lastRow; i++) {
0185                 for(qint32 j = firstCol; j <= lastCol; j++) {
0186                     QRect maxPatchRect(j * patchWidth,
0187                                        i * patchHeight,
0188                                        patchWidth, patchHeight);
0189                     QRect patchRect = rc & maxPatchRect;
0190                     retrieveImageData(patchRect);
0191                 }
0192             }
0193 
0194         }
0195         //TODO: check whether there is needed recalculateCache()
0196     }
0197 }
0198 
0199 void KisImagePyramid::setImageSize(qint32 w, qint32 h)
0200 {
0201     Q_UNUSED(w);
0202     Q_UNUSED(h);
0203     /* nothing interesting */
0204 }
0205 
0206 void KisImagePyramid::updateCache(const QRect &dirtyImageRect)
0207 {
0208     retrieveImageData(dirtyImageRect);
0209 }
0210 
0211 void KisImagePyramid::retrieveImageData(const QRect &rect)
0212 {
0213     // XXX: use QThreadStorage to cache the two patches (512x512) of pixels. Note
0214     // that when we do that, we need to reset that cache when the projection's
0215     // colorspace changes.
0216     const KoColorSpace *projectionCs = m_originalImage->projection()->colorSpace();
0217     KisPaintDeviceSP originalProjection = m_originalImage->projection();
0218     quint32 numPixels = rect.width() * rect.height();
0219 
0220     QScopedArrayPointer<quint8> originalBytes(
0221         new quint8[originalProjection->colorSpace()->pixelSize() * numPixels]);
0222 
0223     originalProjection->readBytes(originalBytes.data(), rect);
0224 
0225     if (m_displayFilter &&
0226         m_useOcio &&
0227         projectionCs->colorModelId() == RGBAColorModelID) {
0228 
0229 #ifdef HAVE_OCIO
0230         const KoColorProfile *destinationProfile =
0231             m_displayFilter->useInternalColorManagement() ?
0232             m_monitorProfile : projectionCs->profile();
0233 
0234         const KoColorSpace *floatCs =
0235             KoColorSpaceRegistry::instance()->colorSpace(
0236                 RGBAColorModelID.id(),
0237                 Float32BitsColorDepthID.id(),
0238                 destinationProfile);
0239 
0240         const KoColorSpace *modifiedMonitorCs =
0241             KoColorSpaceRegistry::instance()->colorSpace(
0242                 RGBAColorModelID.id(),
0243                 Integer8BitsColorDepthID.id(),
0244                 destinationProfile);
0245 
0246         if (projectionCs->colorDepthId() == Float32BitsColorDepthID) {
0247             m_displayFilter->filter(originalBytes.data(), numPixels);
0248         } else {
0249             QScopedArrayPointer<quint8> dst(new quint8[floatCs->pixelSize() * numPixels]);
0250             projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), floatCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
0251             m_displayFilter->filter(dst.data(), numPixels);
0252             originalBytes.swap(dst);
0253         }
0254 
0255         {
0256             QScopedArrayPointer<quint8> dst(new quint8[modifiedMonitorCs->pixelSize() * numPixels]);
0257             floatCs->convertPixelsTo(originalBytes.data(), dst.data(), modifiedMonitorCs, numPixels, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
0258             originalBytes.swap(dst);
0259         }
0260 #endif
0261     }
0262     else {
0263         if (m_channelFlags.size() != projectionCs->channelCount()) {
0264             setChannelFlags(QBitArray());
0265         }
0266         if (!m_channelFlags.isEmpty() && !m_allChannelsSelected) {
0267             QScopedArrayPointer<quint8> dst(new quint8[projectionCs->pixelSize() * numPixels]);
0268 
0269             KisConfig cfg(true);
0270 
0271             if (m_onlyOneChannelSelected && !cfg.showSingleChannelAsColor()) {
0272                 projectionCs->convertChannelToVisualRepresentation(originalBytes.data(), dst.data(), numPixels, m_selectedChannelIndex);
0273             }
0274             else {
0275                 projectionCs->convertChannelToVisualRepresentation(originalBytes.data(), dst.data(), numPixels, m_channelFlags);
0276             }
0277             originalBytes.swap(dst);
0278         }
0279 
0280         QScopedArrayPointer<quint8> dst(new quint8[m_monitorColorSpace->pixelSize() * numPixels]);
0281         projectionCs->convertPixelsTo(originalBytes.data(), dst.data(), m_monitorColorSpace, numPixels, m_renderingIntent, m_conversionFlags);
0282         originalBytes.swap(dst);
0283     }
0284 
0285     m_pyramid[ORIGINAL_INDEX]->writeBytes(originalBytes.data(), rect);
0286 }
0287 
0288 void KisImagePyramid::recalculateCache(KisPPUpdateInfoSP info)
0289 {
0290     KisPaintDevice *src;
0291     KisPaintDevice *dst;
0292     QRect currentSrcRect = info->dirtyImageRectVar;
0293 
0294     for (int i = FIRST_NOT_ORIGINAL_INDEX; i < m_pyramidHeight; i++) {
0295         src = m_pyramid[i-1].data();
0296         dst = m_pyramid[i].data();
0297         if (!currentSrcRect.isEmpty()) {
0298             currentSrcRect = downsampleByFactor2(currentSrcRect, src, dst);
0299         }
0300     }
0301 
0302 #ifdef DEBUG_PYRAMID
0303     QImage image = m_pyramid[ORIGINAL_INDEX]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
0304     image.save("./PYRAMID_BASE.png");
0305 
0306     image = m_pyramid[1]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
0307     image.save("./LEVEL1.png");
0308 
0309     image = m_pyramid[2]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
0310     image.save("./LEVEL2.png");
0311     image = m_pyramid[3]->convertToQImage(m_monitorProfile, m_renderingIntent, m_conversionFlags);
0312     image.save("./LEVEL3.png");
0313 #endif
0314 }
0315 
0316 QRect KisImagePyramid::downsampleByFactor2(const QRect& srcRect,
0317         KisPaintDevice* src,
0318         KisPaintDevice* dst)
0319 {
0320     qint32 srcX, srcY, srcWidth, srcHeight;
0321     srcRect.getRect(&srcX, &srcY, &srcWidth, &srcHeight);
0322     alignRectBy2(srcX, srcY, srcWidth, srcHeight);
0323 
0324     // Nothing to do
0325     if (srcWidth < 1) return QRect();
0326     if (srcHeight < 1) return QRect();
0327 
0328     qint32 dstX = srcX / 2;
0329     qint32 dstY = srcY / 2;
0330     qint32 dstWidth = srcWidth / 2;
0331     qint32 dstHeight = srcHeight / 2;
0332 
0333     KisHLineConstIteratorSP srcIt0 = src->createHLineConstIteratorNG(srcX, srcY, srcWidth);
0334     KisHLineConstIteratorSP srcIt1 = src->createHLineConstIteratorNG(srcX, srcY + 1, srcWidth);
0335     KisHLineIteratorSP dstIt = dst->createHLineIteratorNG(dstX, dstY, dstWidth);
0336 
0337     int conseqPixels = 0;
0338     for (int row = 0; row < dstHeight; ++row) {
0339         do {
0340             int srcItConseq = srcIt0->nConseqPixels();
0341             int dstItConseq = dstIt->nConseqPixels();
0342             conseqPixels = qMin(srcItConseq, dstItConseq * 2);
0343 
0344             Q_ASSERT(!isOdd(conseqPixels));
0345 
0346             downsamplePixels(srcIt0->oldRawData(), srcIt1->oldRawData(),
0347                              dstIt->rawData(), conseqPixels);
0348 
0349 
0350             srcIt1->nextPixels(conseqPixels);
0351             dstIt->nextPixels(conseqPixels / 2);
0352         } while (srcIt0->nextPixels(conseqPixels));
0353         srcIt0->nextRow();
0354         srcIt0->nextRow();
0355         srcIt1->nextRow();
0356         srcIt1->nextRow();
0357         dstIt->nextRow();
0358     }
0359     return QRect(dstX, dstY, dstWidth, dstHeight);
0360 }
0361 
0362 void  KisImagePyramid::downsamplePixels(const quint8 *srcRow0,
0363                                         const quint8 *srcRow1,
0364                                         quint8 *dstRow,
0365                                         qint32 numSrcPixels)
0366 {
0367     /**
0368      * FIXME (mandatory): Use SSE and friends here.
0369      */
0370 
0371     qint16 b = 0;
0372     qint16 g = 0;
0373     qint16 r = 0;
0374     qint16 a = 0;
0375 
0376     static const qint32 pixelSize = 4; // This is preview argb8 mode
0377 
0378     for (qint32 i = 0; i < numSrcPixels / 2; i++) {
0379         b = srcRow0[0] + srcRow1[0] + srcRow0[4] + srcRow1[4];
0380         g = srcRow0[1] + srcRow1[1] + srcRow0[5] + srcRow1[5];
0381         r = srcRow0[2] + srcRow1[2] + srcRow0[6] + srcRow1[6];
0382         a = srcRow0[3] + srcRow1[3] + srcRow0[7] + srcRow1[7];
0383 
0384         dstRow[0] = b / 4;
0385         dstRow[1] = g / 4;
0386         dstRow[2] = r / 4;
0387         dstRow[3] = a / 4;
0388 
0389         dstRow += pixelSize;
0390         srcRow0 += 2 * pixelSize;
0391         srcRow1 += 2 * pixelSize;
0392     }
0393 }
0394 
0395 int KisImagePyramid::findFirstGoodPlaneIndex(qreal scale,
0396         QSize originalSize)
0397 {
0398     qint32 nearest = 0;
0399 
0400     for (qint32 i = 0; i < m_pyramidHeight; i++) {
0401         qreal planeScale = SCALE_FROM_INDEX(i);
0402         if (planeScale < scale) {
0403             if (originalSize*scale == originalSize*planeScale)
0404                 nearest = i;
0405             break;
0406         }
0407         nearest = i;
0408     }
0409 
0410     // FOR DEBUGGING
0411     //nearest = 0;
0412     //nearest = qMin(1, nearest);
0413 
0414     dbgRender << "First good plane:" << nearest << "(sc:" << scale << ")";
0415     return nearest;
0416 }
0417 
0418 void KisImagePyramid::alignSourceRect(QRect& rect, qreal scale)
0419 {
0420     qint32 index = findFirstGoodPlaneIndex(scale, rect.size());
0421     qint32 alignment = 1 << index;
0422 
0423     dbgRender << "Before alignment:\t" << rect;
0424 
0425     /**
0426      * Assume that KisImage pixels are always positive
0427      * It allows us to use binary op-s for aligning
0428      */
0429     Q_ASSERT(rect.left() >= 0 && rect.top() >= 0);
0430 
0431     qint32 x1, y1, x2, y2;
0432     rect.getCoords(&x1, &y1, &x2, &y2);
0433 
0434     alignByPow2Lo(x1, alignment);
0435     alignByPow2Lo(y1, alignment);
0436     /**
0437      * Here is a workaround of Qt's QRect::right()/bottom()
0438      * "historical reasons". It should be one pixel smaller
0439      * than actual right/bottom position
0440      */
0441     alignByPow2ButOneHi(x2, alignment);
0442     alignByPow2ButOneHi(y2, alignment);
0443 
0444     rect.setCoords(x1, y1, x2, y2);
0445 
0446     dbgRender << "After alignment:\t" << rect;
0447 }
0448 
0449 KisImagePatch KisImagePyramid::getNearestPatch(KisPPUpdateInfoSP info)
0450 {
0451     qint32 index = findFirstGoodPlaneIndex(qMax(info->scaleX, info->scaleY),
0452                                            info->imageRect.size());
0453     qreal planeScale = SCALE_FROM_INDEX(index);
0454     qint32 alignment = 1 << index;
0455 
0456     alignByPow2Hi(info->borderWidth, alignment);
0457 
0458     KisImagePatch patch(info->imageRect, info->borderWidth,
0459                         planeScale, planeScale);
0460 
0461     patch.setImage(convertToQImageFast(m_pyramid[index],
0462                                        patch.patchRect()));
0463     return patch;
0464 }
0465 
0466 void KisImagePyramid::drawFromOriginalImage(QPainter& gc, KisPPUpdateInfoSP info)
0467 {
0468     KisImagePatch patch = getNearestPatch(info);
0469     patch.drawMe(gc, info->viewportRect, info->renderHints);
0470 }
0471 
0472 QImage KisImagePyramid::convertToQImageFast(KisPaintDeviceSP paintDevice,
0473         const QRect& unscaledRect)
0474 {
0475     qint32 x, y, w, h;
0476     unscaledRect.getRect(&x, &y, &w, &h);
0477 
0478     QImage image = QImage(w, h, QImage::Format_ARGB32);
0479 
0480     paintDevice->dataManager()->readBytes(image.bits(), x, y, w, h);
0481 
0482     return image;
0483 }
0484 
0485 void KisImagePyramid::configChanged()
0486 {
0487     KisConfig cfg(true);
0488     m_useOcio = cfg.useOcio();
0489 }
0490