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 */