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

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
0003  *  SPDX-FileCopyrightText: 2005 C. Boemann <cbo@boemann.dk>
0004  *  SPDX-FileCopyrightText: 2013 Juan Palacios <jpalaciosdev@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_filter_strategy.h"
0010 
0011 #include <math.h>
0012 
0013 #include <klocalizedstring.h>
0014 #include <QGlobalStatic>
0015 
0016 #include "kis_debug.h"
0017 #include <QtMath>
0018 #include <QSize>
0019 
0020 Q_GLOBAL_STATIC(KisFilterStrategyRegistry, s_instance)
0021 
0022 qreal KisHermiteFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0023 {
0024     Q_UNUSED(weightsPositionScale);
0025     /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
0026     if (t < 0.0) t = -t;
0027     if (t < 1.0) return((2.0 * t - 3.0) * t * t + 1.0);
0028     return(0.0);
0029 }
0030 
0031 qint32 KisHermiteFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
0032 {
0033     Q_UNUSED(weightsPositionScale);
0034     /* f(t) = 2|t|^3 - 3|t|^2 + 1, -1 <= t <= 1 */
0035     if (t < 0) t = -t;
0036     if (t < 256) {
0037         t = (2 * t - 3 * 256) * t * t + (256 << 16);
0038 
0039         //go from .24 fixed point to .8 fixedpoint (hack only works with positive numbers, which it is)
0040         t = (t + 0x8000) >> 16;
0041 
0042         // go from .8 fixed point to 8bitscale. ie t = (t*255)/256;
0043         if (t >= 128)
0044             return t - 1;
0045         return t;
0046     }
0047     return(0);
0048 }
0049 
0050 qint32 KisBicubicFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
0051 {
0052     Q_UNUSED(weightsPositionScale);
0053     /* f(t) = 1.5|t|^3 - 2.5|t|^2 + 1, -1 <= t <= 1 */
0054     if (t < 0) t = -t;
0055     if (t < 256) {
0056         t = (3 * t - 5 * 256) * t * t / 2 + (256 << 16);
0057 
0058         //go from .24 fixed point to .8 fixedpoint (hack only works with positive numbers, which it is)
0059         t = (t + 0x8000) >> 16;
0060 
0061         // go from .8 fixed point to 8bitscale. ie t = (t*255)/256;
0062         if (t >= 128)
0063             return t - 1;
0064         return t;
0065     }
0066     if (t < 512) {
0067         /* f(t) = -0.5|t|^3 + 2.5|t|^2 + 4|t| - 2, -2 <= t <= 2 */
0068         t = ((-t + 5 * 256) * t / 2 - 4 * 256 * 256) * t + (2 * 256 << 16);
0069 
0070         //go from .24 fixed point to .8 fixedpoint (hack only works with positive numbers, which it is)
0071         t = (t + 0x8000) >> 16;
0072 
0073         // go from .8 fixed point to 8bitscale. ie t = (t*255)/256;
0074         if (t >= 128)
0075             return t - 1;
0076         return t;
0077     }
0078     return(0);
0079 }
0080 
0081 qreal KisBoxFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0082 {
0083     if ((t >= -0.5  * weightsPositionScale) && (t < 0.5 * weightsPositionScale)) return(1.0);
0084     return(0.0);
0085 }
0086 
0087 qint32 KisBoxFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
0088 {
0089     /* f(t) = 1, -0.5 < t <= 0.5 */
0090     if ((t >= -128  * weightsPositionScale) && (t < 128 * weightsPositionScale))
0091         return 255;
0092     return 0;
0093 }
0094 
0095 
0096 qreal KisBoxFilterStrategy::support(qreal weightsPositionScale)
0097 {
0098     return supportVal*weightsPositionScale;
0099 }
0100 
0101 qint32 KisBoxFilterStrategy::intSupport(qreal weightsPositionScale)
0102 {
0103     return qCeil(intSupportVal*weightsPositionScale);
0104 }
0105 
0106 qreal KisBilinearFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0107 {
0108     Q_UNUSED(weightsPositionScale);
0109     if (t < 0.0) t = -t;
0110     if (t < 1.0) return(1.0 - t);
0111     return(0.0);
0112 }
0113 
0114 qint32 KisBilinearFilterStrategy::intValueAt(qint32 t, qreal weightsPositionScale) const
0115 {
0116     Q_UNUSED(weightsPositionScale);
0117     /* f(t) = |t|, -1 <= t <= 1 */
0118     if (t < 0) t = -t;
0119     if (t < 256) {
0120         // calc 256-1 but also go from .8 fixed point to 8bitscale. ie t = (t*255)/256; ie: if(t>=128) return t-1;
0121         if (t >= 128) return 256 - t;
0122         return 255 - t;
0123     }
0124     return(0);
0125 }
0126 
0127 
0128 qreal KisBellFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0129 {
0130     Q_UNUSED(weightsPositionScale);
0131     if (t < 0) t = -t;
0132     if (t < .5) return(.75 - (t * t));
0133     if (t < 1.5) {
0134         t = (t - 1.5);
0135         return(.5 *(t * t));
0136     }
0137     return(0.0);
0138 }
0139 
0140 qreal KisBSplineFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0141 {
0142     Q_UNUSED(weightsPositionScale);
0143     qreal tt;
0144 
0145     if (t < 0) t = -t;
0146     if (t < 1) {
0147         tt = t * t;
0148         return((.5 * tt * t) - tt + (2.0 / 3.0));
0149     } else if (t < 2) {
0150         t = 2 - t;
0151         return((1.0 / 6.0) *(t * t * t));
0152     }
0153     return(0.0);
0154 }
0155 
0156 qreal KisLanczos3FilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0157 {
0158     Q_UNUSED(weightsPositionScale);
0159     if (t < 0) t = -t;
0160     if (t < 3.0) return(sinc(t) * sinc(t / 3.0));
0161     return(0.0);
0162 }
0163 
0164 qreal KisLanczos3FilterStrategy::sinc(qreal x) const
0165 {
0166     const qreal pi = 3.1415926535897932385;
0167     x *= pi;
0168     if (x != 0) return(sin(x) / x);
0169     return(1.0);
0170 }
0171 
0172 qreal KisMitchellFilterStrategy::valueAt(qreal t, qreal weightsPositionScale) const
0173 {
0174     Q_UNUSED(weightsPositionScale);
0175     const qreal B = 1.0 / 3.0;
0176     const qreal C = 1.0 / 3.0;
0177     qreal tt;
0178 
0179     tt = t * t;
0180     if (t < 0) t = -t;
0181     if (t < 1.0) {
0182         t = (((12.0 - 9.0 * B - 6.0 * C) * (t * tt)) + ((-18.0 + 12.0 * B + 6.0 * C) * tt) + (6.0 - 2 * B));
0183         return(t / 6.0);
0184     } else if (t < 2.0) {
0185         t = (((-1.0 * B - 6.0 * C) * (t * tt)) + ((6.0 * B + 30.0 * C) * tt) + ((-12.0 * B - 48.0 * C) * t) + (8.0 * B + 24 * C));
0186         return(t / 6.0);
0187     }
0188     return(0.0);
0189 }
0190 
0191 KisFilterStrategyRegistry::KisFilterStrategyRegistry()
0192 {
0193 }
0194 
0195 KisFilterStrategyRegistry::~KisFilterStrategyRegistry()
0196 {
0197     Q_FOREACH (const QString &id, keys()) {
0198         delete get(id);
0199     }
0200     dbgRegistry << "deleting KisFilterStrategyRegistry";
0201 }
0202 
0203 KisFilterStrategyRegistry* KisFilterStrategyRegistry::instance()
0204 {
0205     if (!s_instance.exists()) {
0206         s_instance->add(new KisBoxFilterStrategy);
0207         s_instance->addAlias("Box", "NearestNeighbor");
0208 
0209         s_instance->add(new KisHermiteFilterStrategy);
0210         s_instance->add(new KisBicubicFilterStrategy);
0211         s_instance->add(new KisBilinearFilterStrategy);
0212         s_instance->add(new KisBellFilterStrategy);
0213         s_instance->add(new KisBSplineFilterStrategy);
0214         s_instance->add(new KisLanczos3FilterStrategy);
0215         s_instance->add(new KisMitchellFilterStrategy);
0216     }
0217     return s_instance;
0218 }
0219 
0220 QList<KoID> KisFilterStrategyRegistry::listKeys() const
0221 {
0222     QList<KoID> answer;
0223     Q_FOREACH (const QString key, keys()) {
0224         answer.append(KoID(key, get(key)->name()));
0225     }
0226 
0227     return answer;
0228 }
0229 
0230 QString KisFilterStrategyRegistry::formattedDescriptions() const
0231 {
0232     QString formatedDescription("<html><head/><body>");
0233 
0234     Q_FOREACH (const QString key, keys()) {
0235         KisFilterStrategy *strategy = get(key);
0236         QString description = strategy->description();
0237 
0238         if (!description.isEmpty()) {
0239             formatedDescription.append("<p><span style=\"font-weight:600;\">");
0240             formatedDescription.append(strategy->name());
0241             formatedDescription.append("</span>: ");
0242             formatedDescription.append(description);
0243             formatedDescription.append("</p>");
0244         }
0245     }
0246     formatedDescription.append("</body></html>");
0247 
0248     return formatedDescription;
0249 }
0250 
0251 KisFilterStrategy *KisFilterStrategyRegistry::autoFilterStrategy(QSize originalSize, QSize desiredSize) const
0252 {
0253     // Default to nearest neighbor scaling for tiny source images. (i.e: icons or small sprite sheets.)
0254     const int pixelArtThreshold = 256;
0255     if (originalSize.width() <= pixelArtThreshold ||
0256         originalSize.height() <= pixelArtThreshold) {
0257         return KisFilterStrategyRegistry::instance()->value("NearestNeighbor");
0258     }
0259 
0260     const float xScaleFactor = (float)desiredSize.width() / originalSize.width();
0261     const float yScaleFactor = (float)desiredSize.height() / originalSize.height();
0262 
0263     if (xScaleFactor > 1.f || yScaleFactor > 1.f) { // Enlargement.
0264         return KisFilterStrategyRegistry::instance()->value("Bicubic");
0265     } else if (xScaleFactor < 1.f || yScaleFactor < 1.f) { // Reduction.
0266         return KisFilterStrategyRegistry::instance()->value("Bicubic");
0267     }
0268 
0269     return KisFilterStrategyRegistry::instance()->value("NearestNeighbor");
0270 }