File indexing completed on 2024-05-12 15:58:15

0001 /*
0002  *  SPDX-FileCopyrightText: 2012 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #ifndef __KIS_FILTER_WEIGHTS_BUFFER_H
0008 #define __KIS_FILTER_WEIGHTS_BUFFER_H
0009 
0010 #include "kis_fixed_point_maths.h"
0011 #include "kis_filter_strategy.h"
0012 #include "kis_debug.h"
0013 
0014 #ifdef SANITY_CHECKS_ENABLED
0015 static bool checkForAsymmetricZeros = false;
0016 
0017 #define SANITY_CENTER_POSITION()                        \
0018     do {                                                \
0019         Q_ASSERT(scaledIter >= beginDst);               \
0020         Q_ASSERT(scaledIter <= endDst);                 \
0021                                                         \
0022         if (j == centerIndex) {                         \
0023             Q_ASSERT(scaledIter == centerSrc);          \
0024         }                                               \
0025     } while(0)
0026 
0027 #define SANITY_ZEROS()                                                  \
0028     do {                                                                \
0029         if (checkForAsymmetricZeros) {                                  \
0030             for (int j = 0; j < span; j++) {                            \
0031                 int idx2 = span - j - 1;                                \
0032                                                                         \
0033                 if ((m_filterWeights[i].weight[j] && !m_filterWeights[i].weight[idx2]) || \
0034                     (!m_filterWeights[i].weight[j] && m_filterWeights[i].weight[idx2])) { \
0035                                                                         \
0036                     dbgKrita << "*******";                              \
0037                     dbgKrita << "Non-symmetric zero found:" << centerSrc; \
0038                     dbgKrita << "Weight" << j << ":" << m_filterWeights[i].weight[j]; \
0039                     dbgKrita << "Weight" << idx2 << ":" << m_filterWeights[i].weight[idx2]; \
0040                     qFatal("Non-symmetric zero -> fail");               \
0041                 }                                                       \
0042             }                                                           \
0043         }                                                               \
0044     } while (0)
0045 
0046 #define SANITY_CHECKSUM()                       \
0047     do {                                        \
0048         Q_ASSERT(sum == 255);                   \
0049     } while (0)
0050 
0051 #else
0052 #define SANITY_CENTER_POSITION()
0053 #define SANITY_ZEROS()
0054 #define SANITY_CHECKSUM()
0055 #endif
0056 
0057 #ifdef DEBUG_ENABLED
0058 #define DEBUG_ALL()                                                     \
0059     do {                                                                \
0060         dbgKrita << "************** i =" << i;                          \
0061         dbgKrita << ppVar(centerSrc);                                   \
0062         dbgKrita << ppVar(centerIndex);                                 \
0063         dbgKrita << ppVar(beginSrc) << ppVar(endSrc);                   \
0064         dbgKrita << ppVar(beginDst) << ppVar(endDst);                   \
0065         dbgKrita << ppVar(scaledIter) << ppVar(scaledInc);              \
0066         dbgKrita << ppVar(span);                                        \
0067         dbgKrita << "===";                                              \
0068     } while (0)
0069 
0070 #define DEBUG_SAMPLE()                                                  \
0071     do {                                                                \
0072         dbgKrita << ppVar(scaledIter) << ppVar(t);                      \
0073     } while (0)
0074 #else
0075 #define DEBUG_ALL() Q_UNUSED(beginDst); Q_UNUSED(endDst);
0076 #define DEBUG_SAMPLE()
0077 #endif
0078 
0079 
0080 
0081 /**
0082  * \class KisFilterWeightsBuffer
0083  *
0084  * Stores the cached values for the weights of neighbouring pixels
0085  * that would form the pixel in a result of resampling. The object of
0086  * this class is created before a pass of the transformation basing on
0087  * the desired scale factor and the filter strategy used for resampling.
0088  *
0089  * Here is an example of a calculation of the span for a pixel with
0090  * scale equal to 1.0. The result of the blending will be written into
0091  * the dst(0) pixel, which is marked with '*' sign. Note that all the
0092  * coordinates here are related to the center of the pixel, not to its
0093  * leftmost border as it is common in other systems. The centerSrc
0094  * coordinate represents the offset between the source and the
0095  * destination buffers.
0096  *
0097  * dst-coordinates: the coordinates in the resulting image. The values
0098  *                  of the filter strategy are calculated in these
0099  *                  coordinates.
0100  *
0101  * src-coordinates: the coordinates in the source image/buffer. We
0102  *                  pick integer values from there and calculate their
0103  *                  dst-position to know their weights.
0104  *
0105  *
0106  *                       +----+----+----+-- scaledIter (samples, measured in dst pixels,
0107  *                       |    |    |    |               correspond to integers in src)
0108  *
0109  *                              +---------+-- supportDst == filterStrategy->intSupport()
0110  *                              |         |
0111  *                    +-- beginDst        +-- endDst
0112  *                    |         |         |
0113  *                    |         +-- centerDst (always zero)
0114  *                    |         |         |
0115  *
0116  * dst: ----|----|----|----|----*----|----|----|----|----|-->
0117  *         -4   -3   -2   -1    0    1    2    3    4    5
0118  *
0119  * src: --|----|----|----|----|----|----|----|----|----|---->
0120  *       -4   -3   -2   -1    0    1    2    3    4    5
0121  *
0122  *                    ^         ^         ^
0123  *                    |         |         |
0124  *                    |         +-- centerSrc
0125  *                    |         |         |
0126  *                    +-- beginSrc        +endSrc
0127  *                    |         |         |
0128  *                    |         +---------+-- supportSrc ~= supportDst / realScale
0129  *                    |                   |
0130  *                    +-------------------+-- span (number of integers in the region)
0131  */
0132 
0133 class KisFilterWeightsBuffer
0134 {
0135 public:
0136     struct FilterWeights {
0137         ~FilterWeights() {
0138             delete[] weight;
0139         }
0140 
0141         qint16 *weight;
0142         int span;
0143         int centerIndex;
0144     };
0145 
0146 public:
0147     KisFilterWeightsBuffer(KisFilterStrategy *filterStrategy, qreal realScale) {
0148         Q_ASSERT(realScale > 0);
0149 
0150         m_filterWeights = new FilterWeights[256];
0151         m_maxSpan = 0;
0152         m_weightsPositionScale = 1;
0153 
0154         KisFixedPoint supportSrc;
0155         KisFixedPoint supportDst;
0156 
0157         if (realScale < 1.0 && realScale > (1.0 / (1 << 8))) {
0158             m_weightsPositionScale = KisFixedPoint(realScale);
0159             supportSrc.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()) / realScale);
0160             supportDst.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()));
0161 
0162         } else {
0163             supportSrc.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()));
0164             supportDst.from256Frac(filterStrategy->intSupport(m_weightsPositionScale.toFloat()));
0165         }
0166 
0167         for (int i = 0; i < 256; i++) {
0168             KisFixedPoint centerSrc;
0169             centerSrc.from256Frac(i);
0170 
0171             KisFixedPoint beginDst = -supportDst;
0172             KisFixedPoint endDst = supportDst;
0173 
0174             KisFixedPoint beginSrc = -supportSrc - centerSrc / m_weightsPositionScale;
0175             KisFixedPoint endSrc = supportSrc - centerSrc / m_weightsPositionScale;
0176 
0177             int span = (2 * supportSrc).toInt() +
0178                 (beginSrc.isInteger() && endSrc.isInteger());
0179 
0180             int centerIndex = -beginSrc.toInt();
0181 
0182             m_filterWeights[i].centerIndex = centerIndex;
0183             m_filterWeights[i].span = span;
0184             m_filterWeights[i].weight = new qint16[span];
0185             m_maxSpan = qMax(m_maxSpan, span);
0186 
0187             // in dst coordinate system:
0188             KisFixedPoint scaledIter = centerSrc + beginSrc.toInt() * m_weightsPositionScale;
0189             KisFixedPoint scaledInc = m_weightsPositionScale;
0190 
0191             DEBUG_ALL();
0192 
0193             int sum = 0;
0194             for (int j = 0; j < span; j++) {
0195                 int t = filterStrategy->intValueAt(scaledIter.to256Frac(), m_weightsPositionScale.toFloat());
0196                 m_filterWeights[i].weight[j] = t;
0197                 sum += t;
0198 
0199                 DEBUG_SAMPLE();
0200                 SANITY_CENTER_POSITION();
0201 
0202                 scaledIter += scaledInc;
0203             }
0204 
0205             SANITY_ZEROS();
0206 
0207             if (sum != 255 && sum > 0) {
0208                 qreal fixFactor = 255.0 / sum;
0209                 sum = 0;
0210 
0211                 for (int j = 0; j < span; j++) {
0212                     int t = qRound(m_filterWeights[i].weight[j] * fixFactor);
0213 
0214                     m_filterWeights[i].weight[j] = t;
0215                     sum += t;
0216                 }
0217             }
0218 
0219             while (sum != 255) {
0220                 int diff = sum < 255 ? 1 : -1;
0221                 int index = findMaxIndex(m_filterWeights[i].weight, span);
0222                 m_filterWeights[i].weight[index] += diff;
0223                 sum += diff;
0224             }
0225 
0226             SANITY_CHECKSUM();
0227         }
0228     }
0229 
0230     ~KisFilterWeightsBuffer() {
0231         delete[] m_filterWeights;
0232     }
0233 
0234     /**
0235      * Return a weights buffer for a particular value of offset
0236      */
0237     FilterWeights* weights(KisFixedPoint pos) const {
0238         return m_filterWeights + pos.to256Frac();
0239     }
0240 
0241     /**
0242      * The maximum width of the buffer that would be needed for
0243      * calculation of a pixel value. In other words, the maximum
0244      * number of support pixels that are needed for calculation of a
0245      * single result pixel
0246      */
0247     int maxSpan() const {
0248         return m_maxSpan;
0249     }
0250 
0251     /**
0252      * The scale of the support buffer. Note that it is not always
0253      * equal to the real scale of the transformation due to
0254      * interpolation/blending difference.
0255      */
0256     KisFixedPoint weightsPositionScale() const {
0257         return m_weightsPositionScale;
0258     }
0259 
0260 private:
0261     int findMaxIndex(qint16 *buf, int size) {
0262         int maxValue = buf[0];
0263         int maxIndex = 0;
0264 
0265         for (int i = 1; i < size; i++) {
0266             if (buf[i] > maxValue) {
0267                 maxValue = buf[i];
0268                 maxIndex = i;
0269             }
0270         }
0271 
0272         return maxIndex;
0273     }
0274 
0275 private:
0276     FilterWeights *m_filterWeights;
0277     int m_maxSpan;
0278     KisFixedPoint m_weightsPositionScale;
0279 };
0280 
0281 #endif /* __KIS_FILTER_WEIGHTS_BUFFER_H */