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_APPLICATOR_H
0008 #define __KIS_FILTER_WEIGHTS_APPLICATOR_H
0009 
0010 #include "kis_fixed_point_maths.h"
0011 #include "kis_filter_weights_buffer.h"
0012 #include "kis_iterator_ng.h"
0013 
0014 
0015 #include <KoColorSpace.h>
0016 #include <KoMixColorsOp.h>
0017 
0018 
0019 namespace tmp {
0020     template <class iter> iter createIterator(KisPaintDeviceSP dev, qint32 start, qint32 lineNum, qint32 len);
0021 
0022     template <> KisHLineIteratorSP createIterator <KisHLineIteratorSP>
0023     (KisPaintDeviceSP dev, qint32 start, qint32 lineNum, qint32 len)
0024     {
0025         return dev->createHLineIteratorNG(start, lineNum, len);
0026     }
0027 
0028     template <> KisVLineIteratorSP createIterator <KisVLineIteratorSP>
0029     (KisPaintDeviceSP dev, qint32 start, qint32 lineNum, qint32 len)
0030     {
0031         return dev->createVLineIteratorNG(lineNum, start, len);
0032     }
0033 }
0034 
0035 /**
0036  * \class KisFilterWeightsApplicator
0037  *
0038  * This is a main class for transforming a line of pixel data.  It
0039  * transforms lines from \p src into \p dst using \p scale, \p shear
0040  * and offset (\p dx) parameters.
0041  *
0042  * Notation:
0043  * \<pixel_name\>_l -- leftmost border of the pixel
0044  * \<pixel_name\>_c -- center of the pixel
0045  *
0046  *
0047  * Example calculation of an offset (see calculateBlendSpan()):
0048  * scale = 0.5;
0049  * offset = \<very small value\>
0050  *
0051  *                    +------ dst_l
0052  *                    |
0053  *                    |   +-- dst_c
0054  *                    |   |
0055  *
0056  * dst:       |   *   |   *   |   *   |
0057  *
0058  * src:      | * | * | * | * | * | * | * | * |
0059  *
0060  *                       |||
0061  *                       ||+--- next_c_in_src
0062  *                       |||
0063  *                       |+---- dst_c_in_src
0064  *                       |||
0065  *                       |++--- offset (near zero, measured in dst coordinates)
0066  *                       |
0067  *                       +-- _l position of the pixel, which is considered
0068  *                           central in the weights buffer
0069  *
0070  * Another example calculation of an offset (see calculateBlendSpan()):
0071  * scale = 0.5;
0072  * offset = \<high value near 0.5\>
0073  *
0074  *                      +------ dst_l
0075  *                      |
0076  *                      |   +-- dst_c
0077  *                      |   |
0078  *
0079  * dst:         |   *   |   *   |   *   |
0080  *
0081  * src:      | * | * | * | * | * | * | * | * |
0082  *
0083  *                          || |
0084  *                          || +--- next_c_in_src
0085  *                          || |
0086  *                          +------ dst_c_in_src
0087  *                          || |
0088  *                          +|-+--- offset (near 0.5, measured in dst coordinates)
0089  *                           |
0090  *                           +-- _l position of the pixel, which is considered
0091  *                               central in the weights buffer
0092  */
0093 
0094 class KisFilterWeightsApplicator
0095 {
0096 public:
0097     KisFilterWeightsApplicator(KisPaintDeviceSP src,
0098                                KisPaintDeviceSP dst,
0099                                qreal realScale, qreal shear,
0100                                qreal dx,
0101                                bool clampToEdge)
0102         : m_src(src),
0103           m_dst(dst),
0104           m_realScale(realScale),
0105           m_shear(shear),
0106           m_dx(dx),
0107           m_clampToEdge(clampToEdge)
0108     {
0109     }
0110 
0111     struct BlendSpan {
0112         KisFilterWeightsBuffer::FilterWeights *weights;
0113         int firstBlendPixel; // in src coords
0114         KisFixedPoint offset;
0115         KisFixedPoint offsetInc;
0116     };
0117 
0118     inline BlendSpan calculateBlendSpan(int dst_l, int line, KisFilterWeightsBuffer *buffer) const {
0119         KisFixedPoint dst_c = l_to_c(dst_l);
0120         KisFixedPoint dst_c_in_src = dstToSrc(dst_c.toFloat(), line);
0121 
0122         // gives the nearest center of the pixel in src ( x e (0, 1> => f(x) = 0.5, x e (1, 2> => f(x) = 1.5 etc. )
0123         KisFixedPoint next_c_in_src = (dst_c_in_src - qreal(0.5)).toIntCeil() + qreal(0.5);
0124 
0125         BlendSpan span;
0126         span.offset = (next_c_in_src - dst_c_in_src) * buffer->weightsPositionScale();
0127         span.offsetInc = buffer->weightsPositionScale();
0128 
0129         Q_ASSERT(span.offset <= span.offsetInc);
0130 
0131         span.weights = buffer->weights(span.offset);
0132         span.firstBlendPixel = next_c_in_src.toIntFloor() - span.weights->centerIndex;
0133 
0134         return span;
0135     }
0136 
0137     class LinePos {
0138     public:
0139         LinePos()
0140             : m_start(0), m_size(0)
0141         {
0142         }
0143 
0144         LinePos(int start, int size)
0145             : m_start(start), m_size(size)
0146         {
0147         }
0148 
0149         inline int start() const {
0150             return m_start;
0151         }
0152 
0153         /**
0154          * WARNING: be careful! This is not the same as
0155          * QRect::right()!  This is an equivalent of (QRect::right() +
0156          * QRect::width()) or QRectF::right(), that is it points to
0157          * the pixel after(!) the actual last pixel.  See Qt docs for
0158          * more info about this historical difference.
0159          */
0160         inline int end() const {
0161             return m_start + m_size;
0162         }
0163 
0164         inline int size() const {
0165             return m_size;
0166         }
0167 
0168         inline void unite(const LinePos &rhs) {
0169             if (m_size > 0) {
0170                 int newStart = qMin(start(), rhs.start());
0171                 int newEnd = qMax(end(), rhs.end());
0172 
0173                 m_start = newStart;
0174                 m_size = newEnd - newStart;
0175             } else {
0176                 m_start = rhs.start();
0177                 m_size = rhs.size();
0178             }
0179         }
0180 
0181     private:
0182         int m_start;
0183         int m_size;
0184     };
0185 
0186     template <class T>
0187     LinePos processLine(LinePos srcLine, int line, KisFilterWeightsBuffer *buffer, qreal filterSupport) {
0188         int dstStart;
0189         int dstEnd;
0190 
0191         int leftSrcBorder;
0192         int rightSrcBorder;
0193 
0194         if (m_realScale >= 0) {
0195             dstStart = findAntialiasedDstStart(srcLine.start(), filterSupport, line);
0196             dstEnd = findAntialiasedDstEnd(srcLine.end(), filterSupport, line);
0197 
0198             /// Since we are rounding the borders of the line we might
0199             /// end up to squashing our line into a single pixel. In such
0200             /// a case we should correct our line to be exactly one pixel
0201             if (dstStart == dstEnd) {
0202                 dstEnd = dstStart + 1;
0203             }
0204 
0205             leftSrcBorder = getLeftSrcNeedBorder(dstStart, line, buffer);
0206             rightSrcBorder = getRightSrcNeedBorder(dstEnd - 1, line, buffer);
0207         }
0208         else {
0209             dstStart = findAntialiasedDstStart(srcLine.end(), filterSupport, line);
0210             dstEnd = findAntialiasedDstEnd(srcLine.start(), filterSupport, line);
0211 
0212             /// Since we are rounding the borders of the line we might
0213             /// end up to squashing our line into a single pixel. In such
0214             /// a case we should correct our line to be exactly one pixel
0215             if (dstStart == dstEnd) {
0216                 dstEnd = dstStart + 1;
0217             }
0218 
0219             leftSrcBorder = getLeftSrcNeedBorder(dstEnd - 1, line, buffer);
0220             rightSrcBorder = getRightSrcNeedBorder(dstStart, line, buffer);
0221         }
0222 
0223         if (dstStart >= dstEnd)  return LinePos(dstStart, 0);
0224         if (leftSrcBorder >= rightSrcBorder) return LinePos(dstStart, 0);
0225         if (leftSrcBorder > srcLine.start()) {
0226             leftSrcBorder = srcLine.start();
0227         }
0228         if (srcLine.end() > rightSrcBorder) {
0229             rightSrcBorder = srcLine.end();
0230         }
0231 
0232         int pixelSize = m_src->pixelSize();
0233         KoMixColorsOp *mixOp = m_src->colorSpace()->mixColorsOp();
0234         const KoColor defaultPixelObject = m_src->defaultPixel();
0235         const quint8 *defaultPixel = defaultPixelObject.data();
0236         const quint8 *borderPixel = defaultPixel;
0237         quint8 *srcLineBuf = new quint8[pixelSize * (rightSrcBorder - leftSrcBorder)];
0238 
0239         int i = leftSrcBorder;
0240         quint8 *bufPtr = srcLineBuf;
0241 
0242         T srcIt = tmp::createIterator<T>(m_src, srcLine.start(), line, srcLine.size());
0243 
0244         if (m_clampToEdge) {
0245             borderPixel = srcIt->rawData();
0246         }
0247 
0248         for (; i < srcLine.start(); i++, bufPtr+=pixelSize) {
0249             memcpy(bufPtr, borderPixel, pixelSize);
0250         }
0251 
0252         for (; i < srcLine.end(); i++, bufPtr+=pixelSize) {
0253             quint8 *data = srcIt->rawData();
0254             memcpy(bufPtr, data, pixelSize);
0255             memcpy(data, defaultPixel, pixelSize);
0256             srcIt->nextPixel();
0257         }
0258 
0259         if (m_clampToEdge) {
0260             borderPixel = bufPtr - pixelSize;
0261         }
0262 
0263         for (; i < rightSrcBorder; i++, bufPtr+=pixelSize) {
0264             memcpy(bufPtr, borderPixel, pixelSize);
0265         }
0266 
0267         const quint8 **colors = new const quint8* [buffer->maxSpan()];
0268 
0269         T dstIt = tmp::createIterator<T>(m_dst, dstStart, line, dstEnd - dstStart);
0270         for (int i = dstStart; i < dstEnd; i++) {
0271             BlendSpan span = calculateBlendSpan(i, line, buffer);
0272 
0273             int bufIndexStart = span.firstBlendPixel - leftSrcBorder;
0274             int bufIndexEnd = bufIndexStart + span.weights->span;
0275 
0276             const quint8 **colorsPtr = colors;
0277             for (int j = bufIndexStart; j < bufIndexEnd; j++) {
0278                 *(colorsPtr++) = srcLineBuf + j * pixelSize;
0279             }
0280 
0281             mixOp->mixColors(colors, span.weights->weight, span.weights->span, dstIt->rawData());
0282             dstIt->nextPixel();
0283         }
0284 
0285         delete[] colors;
0286         delete[] srcLineBuf;
0287 
0288         return LinePos(dstStart, qMax(0, dstEnd - dstStart));
0289     }
0290 
0291 private:
0292 
0293     int findAntialiasedDstStart(int src_l, qreal support, int line) {
0294         qreal dst = srcToDst(src_l, line);
0295         return !m_clampToEdge ? qRound(dst - support) : qRound(dst);
0296     }
0297 
0298     int findAntialiasedDstEnd(int src_l, qreal support, int line) {
0299         qreal dst = srcToDst(src_l, line);
0300         return !m_clampToEdge ? qRound(dst + support) : qRound(dst);
0301     }
0302 
0303     int getLeftSrcNeedBorder(int dst_l, int line, KisFilterWeightsBuffer *buffer) {
0304         BlendSpan span = calculateBlendSpan(dst_l, line, buffer);
0305         return span.firstBlendPixel;
0306     }
0307 
0308     int getRightSrcNeedBorder(int dst_l, int line, KisFilterWeightsBuffer *buffer) {
0309         BlendSpan span = calculateBlendSpan(dst_l, line, buffer);
0310         return span.firstBlendPixel + span.weights->span;
0311     }
0312 
0313     inline KisFixedPoint l_to_c(KisFixedPoint pixel_l) const {
0314         return pixel_l + KisFixedPoint(qreal(0.5));
0315     }
0316 
0317     inline KisFixedPoint c_to_l(KisFixedPoint pixel_c) const {
0318         return pixel_c - KisFixedPoint(qreal(0.5));
0319     }
0320 
0321     inline qreal srcToDst(qreal src, int line) const {
0322         return src * m_realScale + m_dx + line * m_shear;
0323     }
0324 
0325     inline qreal dstToSrc(qreal dst, int line) const {
0326         return (dst - m_dx - line * m_shear) / m_realScale;
0327     }
0328 
0329 private:
0330     KisPaintDeviceSP m_src;
0331     KisPaintDeviceSP m_dst;
0332 
0333     qreal m_realScale;
0334     qreal m_shear;
0335     qreal m_dx;
0336     bool m_clampToEdge;
0337 };
0338 
0339 #endif /* __KIS_FILTER_WEIGHTS_APPLICATOR_H */