File indexing completed on 2024-06-09 04:21:57

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  See LayerFX plugin for Gimp as a reference implementation of this style:
0005  *  http://registry.gimp.org/node/186
0006  *
0007  *  SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include "kis_ls_bevel_emboss_filter.h"
0011 
0012 #include <cstdlib>
0013 
0014 #include <QBitArray>
0015 
0016 #include <KoUpdater.h>
0017 #include <resources/KoPattern.h>
0018 
0019 #include <resources/KoAbstractGradient.h>
0020 
0021 #include "psd.h"
0022 
0023 #include "kis_convolution_kernel.h"
0024 #include "kis_convolution_painter.h"
0025 #include "kis_gaussian_kernel.h"
0026 
0027 #include "kis_pixel_selection.h"
0028 #include "kis_fill_painter.h"
0029 #include "kis_gradient_painter.h"
0030 #include "kis_iterator_ng.h"
0031 #include "kis_random_accessor_ng.h"
0032 
0033 #include "kis_psd_layer_style.h"
0034 #include "kis_layer_style_filter_environment.h"
0035 
0036 #include "kis_ls_utils.h"
0037 
0038 #include "gimp_bump_map.h"
0039 #include "kis_transaction.h"
0040 #include "kis_multiple_projection.h"
0041 #include "kis_cached_paint_device.h"
0042 
0043 
0044 KisLsBevelEmbossFilter::KisLsBevelEmbossFilter()
0045     : KisLayerStyleFilter(KoID("lsstroke", i18n("Stroke (style)")))
0046 {
0047 }
0048 
0049 KisLayerStyleFilter *KisLsBevelEmbossFilter::clone() const
0050 {
0051     return new KisLsBevelEmbossFilter(*this);
0052 }
0053 
0054 KisLsBevelEmbossFilter::KisLsBevelEmbossFilter(const KisLsBevelEmbossFilter &rhs)
0055     : KisLayerStyleFilter(rhs)
0056 {
0057 }
0058 
0059 void paintBevelSelection(KisPixelSelectionSP srcSelection,
0060                          KisPixelSelectionSP dstSelection,
0061                          const QRect &applyRect,
0062                          int size,
0063                          int initialSize,
0064                          bool invert,
0065                          KisLayerStyleFilterEnvironment *env)
0066 {
0067     KisCachedSelection::Guard s1(*env->cachedSelection());
0068     KisSelectionSP tmpBaseSelection = s1.selection();
0069     KisPixelSelectionSP tmpSelection = tmpBaseSelection->pixelSelection();
0070 
0071     // NOTE: we are not using createCompositionSourceDevice() intentionally,
0072     //       because the source device doesn't have alpha channel
0073     KisCachedSelection::Guard s2(*env->cachedSelection());
0074     KisPixelSelectionSP fillDevice = s2.selection()->pixelSelection();
0075 
0076     KisPainter gc(dstSelection);
0077     gc.setCompositeOpId(COMPOSITE_COPY);
0078 
0079     for (int i = 0; i < size; i++) {
0080         const int growSize = initialSize - i - 1;
0081 
0082         quint8 selectedness = invert ?
0083             qRound(qreal(size - i - 1) / size * 255.0) :
0084             qRound(qreal(i + 1) / size * 255.0);
0085         fillDevice->setDefaultPixel(KoColor(&selectedness, fillDevice->colorSpace()));
0086 
0087         tmpSelection->makeCloneFromRough(srcSelection, srcSelection->selectedRect());
0088 
0089         QRect changeRect = KisLsUtils::growSelectionUniform(tmpSelection, growSize, applyRect);
0090 
0091         gc.setSelection(tmpBaseSelection);
0092         gc.bitBlt(changeRect.topLeft(), fillDevice, changeRect);
0093     }
0094 }
0095 
0096 struct ContrastOp {
0097     static const bool supportsCaching = false;
0098 
0099     ContrastOp(qreal contrast)
0100         : m_contrast(contrast)
0101     {
0102     }
0103 
0104     int operator() (int iValue) {
0105         qreal value = qreal(iValue - 127) / 127.0;
0106 
0107         qreal slant = std::tan ((m_contrast + 1) * M_PI_4);
0108         value = (value - 0.5) * slant + 0.5;
0109 
0110         return qRound(value * 255.0);
0111     }
0112 
0113 private:
0114     qreal m_contrast;
0115 };
0116 
0117 struct HighlightsFetchOp {
0118     static const bool supportsCaching = true;
0119 
0120     int operator() (int value) {
0121         return qRound(qMax(0, value - 127) * (255.0 / (255 - 127)));
0122     }
0123 };
0124 
0125 struct ShadowsFetchOp {
0126     static const bool supportsCaching = true;
0127 
0128     int operator() (int value) {
0129         return 255 - qRound(qMin(value, 127) * (255.0 / 127.0));
0130     }
0131 };
0132 
0133 template <class MapOp>
0134 void mapPixelValues(KisPixelSelectionSP srcSelection,
0135                     KisPixelSelectionSP dstSelection,
0136                     MapOp mapOp,
0137                     const QRect &applyRect)
0138 {
0139     static quint8 mapTable[256];
0140     static bool mapInitialized = false;
0141 
0142     if (!MapOp::supportsCaching || !mapInitialized) {
0143         mapInitialized = true;
0144 
0145         for (int i = 0; i < 256; i++) {
0146             mapTable[i] = mapOp(i);
0147         }
0148     }
0149 
0150     KisSequentialConstIterator srcIt(srcSelection, applyRect);
0151     KisSequentialIterator dstIt(dstSelection, applyRect);
0152 
0153     while (srcIt.nextPixel() && dstIt.nextPixel()) {
0154         const quint8 *srcPtr = srcIt.rawDataConst();
0155         quint8 *dstPtr = dstIt.rawData();
0156         *dstPtr = mapTable[*srcPtr];
0157     }
0158 }
0159 
0160 template <class MapOp>
0161 void mapPixelValues(KisPixelSelectionSP dstSelection,
0162                     MapOp mapOp,
0163                     const QRect &applyRect)
0164 {
0165     static quint8 mapTable[256];
0166     static bool mapInitialized = false;
0167 
0168     if (!MapOp::supportsCaching || !mapInitialized) {
0169         mapInitialized = true;
0170 
0171         for (int i = 0; i < 256; i++) {
0172             mapTable[i] = mapOp(i);
0173         }
0174     }
0175 
0176     KisSequentialIterator dstIt(dstSelection, applyRect);
0177 
0178     while (dstIt.nextPixel()) {
0179         quint8 *dstPtr = dstIt.rawData();
0180         *dstPtr = mapTable[*dstPtr];
0181     }
0182 }
0183 
0184 struct BevelEmbossRectCalculator
0185 {
0186     BevelEmbossRectCalculator(const QRect &applyRect,
0187                               const psd_layer_effects_bevel_emboss *config) {
0188 
0189         shadowHighlightsFinalRect = applyRect;
0190         applyGaussianRect = shadowHighlightsFinalRect;
0191         applyGlossContourRect = KisLsUtils::growRectFromRadius(applyGaussianRect, config->soften());
0192         applyBumpmapRect = applyGlossContourRect;
0193         applyContourRect = applyBumpmapRect;
0194         applyTextureRect = applyContourRect;
0195         applyBevelRect = calcBevelNeedRect(applyTextureRect, config);
0196         initialFetchRect = kisGrowRect(applyBevelRect, 1);
0197     }
0198 
0199     QRect totalChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) {
0200         QRect changeRect = calcBevelChangeRect(applyRect, config);
0201         changeRect = kisGrowRect(changeRect, 1); // bumpmap method
0202         changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften());
0203         return changeRect;
0204     }
0205 
0206     QRect totalNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) {
0207         QRect changeRect = applyRect;
0208         changeRect = KisLsUtils::growRectFromRadius(changeRect, config->soften());
0209         changeRect = kisGrowRect(changeRect, 1); // bumpmap method
0210         changeRect = calcBevelNeedRect(applyRect, config);
0211         return changeRect;
0212     }
0213 
0214     QRect initialFetchRect;
0215     QRect applyBevelRect;
0216     QRect applyTextureRect;
0217     QRect applyContourRect;
0218     QRect applyBumpmapRect;
0219     QRect applyGlossContourRect;
0220     QRect applyGaussianRect;
0221     QRect shadowHighlightsFinalRect;
0222 
0223 private:
0224     QRect calcBevelChangeRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) {
0225         const int size = config->size();
0226         int limitingGrowSize = 0;
0227 
0228         switch (config->style()) {
0229         case psd_bevel_outer_bevel:
0230             limitingGrowSize = size;
0231             break;
0232         case psd_bevel_inner_bevel:
0233             limitingGrowSize = 0;
0234             break;
0235         case psd_bevel_emboss: {
0236             const int initialSize = std::ceil(qreal(size) / 2.0);
0237             limitingGrowSize = initialSize;
0238             break;
0239         }
0240         case psd_bevel_pillow_emboss: {
0241             const int halfSizeC = std::ceil(qreal(size) / 2.0);
0242             limitingGrowSize = halfSizeC;
0243             break;
0244         }
0245         case psd_bevel_stroke_emboss:
0246             warnKrita << "WARNING: Stroke Emboss style is not implemented yet!";
0247             return applyRect;
0248         }
0249 
0250         return kisGrowRect(applyRect, limitingGrowSize);
0251     }
0252 
0253     QRect calcBevelNeedRect(const QRect &applyRect, const psd_layer_effects_bevel_emboss *config) {
0254         const int size = config->size();
0255         int limitingGrowSize = size;
0256 
0257         return kisGrowRect(applyRect, limitingGrowSize);
0258     }
0259 };
0260 
0261 void KisLsBevelEmbossFilter::applyBevelEmboss(KisPaintDeviceSP srcDevice,
0262                                               KisMultipleProjection *dst,
0263                                               const QRect &applyRect,
0264                                               const psd_layer_effects_bevel_emboss *config,
0265                                               KisResourcesInterfaceSP resourcesInterface,
0266                                               KisLayerStyleFilterEnvironment *env) const
0267 {
0268     if (applyRect.isEmpty()) return;
0269 
0270     BevelEmbossRectCalculator d(applyRect, config);
0271 
0272     KisCachedSelection::Guard s1(*env->cachedSelection());
0273     KisSelectionSP baseSelection = s1.selection();
0274     KisLsUtils::selectionFromAlphaChannel(srcDevice, baseSelection, d.initialFetchRect);
0275 
0276     KisPixelSelectionSP selection = baseSelection->pixelSelection();
0277 
0278     const int size = config->size();
0279 
0280     int limitingGrowSize = 0;
0281     KisCachedSelection::Guard s2(*env->cachedSelection());
0282     KisPixelSelectionSP bumpmapSelection = s2.selection()->pixelSelection();
0283 
0284     switch (config->style()) {
0285     case psd_bevel_outer_bevel:
0286         paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, size, false, env);
0287         limitingGrowSize = size;
0288         break;
0289     case psd_bevel_inner_bevel:
0290         paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, 0, false, env);
0291         limitingGrowSize = 0;
0292         break;
0293     case psd_bevel_emboss: {
0294         const int initialSize = std::ceil(qreal(size) / 2.0);
0295         paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, size, initialSize, false, env);
0296         limitingGrowSize = initialSize;
0297         break;
0298     }
0299     case psd_bevel_pillow_emboss: {
0300         const int halfSizeF = std::floor(qreal(size) / 2.0);
0301         const int halfSizeC = std::ceil(qreal(size) / 2.0);
0302         // TODO: probably not correct!
0303         paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeC, halfSizeC, false, env);
0304         paintBevelSelection(selection, bumpmapSelection, d.applyBevelRect, halfSizeF, 0, true, env);
0305         limitingGrowSize = halfSizeC;
0306         break;
0307     }
0308     case psd_bevel_stroke_emboss:
0309         warnKrita << "WARNING: Stroke Emboss style is not implemented yet!";
0310         return;
0311     }
0312 
0313     KisCachedSelection::Guard s3(*env->cachedSelection());
0314     KisPixelSelectionSP limitingSelection = s3.selection()->pixelSelection();
0315     limitingSelection->makeCloneFromRough(selection, selection->selectedRect());
0316     {
0317         QRect changeRectUnused =
0318             KisLsUtils::growSelectionUniform(limitingSelection,
0319                                              limitingGrowSize,
0320                                              d.applyBevelRect);
0321         Q_UNUSED(changeRectUnused);
0322     }
0323 
0324     //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("1_selection_xconv.png");
0325 
0326     if (config->textureEnabled()) {
0327         KisCachedSelection::Guard s4(*env->cachedSelection());
0328         KisPixelSelectionSP textureSelection = s4.selection()->pixelSelection();
0329 
0330         KisLsUtils::fillPattern(textureSelection, d.applyTextureRect, env,
0331                                 config->textureScale(),
0332                                 config->texturePattern(resourcesInterface),
0333                                 config->textureHorizontalPhase(),
0334                                 config->textureVerticalPhase(),
0335                                 config->textureAlignWithLayer());
0336 
0337         int contrastadj = 0;
0338 
0339         {
0340             using namespace std;
0341 
0342             int tex_depth = config->textureDepth();
0343 
0344             if (tex_depth >= 0.0) {
0345                 if (tex_depth <= 100.0) {
0346                     contrastadj = int(qRound((1-(tex_depth/100.0)) * -127));
0347                 } else {
0348                     contrastadj = int(qRound(((tex_depth-100.0)/900.0) * 127));
0349                 }
0350             } else {
0351                 textureSelection->invert();
0352                 if (tex_depth >= -100.0) {
0353                     contrastadj = int(qRound((1-(abs(tex_depth)/100.0)) * -127));
0354                 } else {
0355                     contrastadj = int(qRound(((abs(tex_depth)-100.0)/900.0) * 127));
0356                 }
0357             }
0358         }
0359 
0360         qreal contrast = qBound(-1.0, qreal(contrastadj) / 127.0, 1.0);
0361         mapPixelValues(textureSelection, ContrastOp(contrast), d.applyTextureRect);
0362 
0363         {
0364             KisPainter gc(bumpmapSelection);
0365             gc.setCompositeOpId(COMPOSITE_MULT);
0366             gc.bitBlt(d.applyTextureRect.topLeft(), textureSelection, d.applyTextureRect);
0367             gc.end();
0368         }
0369     }
0370 
0371     //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("15_selection_texture.png");
0372 
0373     if (config->contourEnabled()) {
0374         if (config->range() != KisLsUtils::FULL_PERCENT_RANGE) {
0375             KisLsUtils::adjustRange(bumpmapSelection, d.applyContourRect, config->range());
0376         }
0377 
0378         KisLsUtils::applyContourCorrection(bumpmapSelection,
0379                                            d.applyContourRect,
0380                                            config->contourLookupTable(),
0381                                            config->antiAliased(),
0382                                            true);
0383     }
0384 
0385     bumpmap_vals_t bmvals;
0386 
0387     bmvals.azimuth = config->angle();
0388     bmvals.elevation = config->altitude();
0389     bmvals.depth = config->depth();
0390     bmvals.ambient = 0;
0391     bmvals.compensate = true;
0392     bmvals.invert = config->direction() == psd_direction_down;
0393     bmvals.type = LINEAR;
0394 
0395     bumpmap(bumpmapSelection, d.applyBumpmapRect, bmvals);
0396 
0397     //bumpmapSelection->convertToQImage(0, QRect(0,0,300,300)).save("3_selection_bumpmap.png");
0398 
0399     { // TODO: optimize!
0400 
0401         KisLsUtils::applyContourCorrection(bumpmapSelection,
0402                                            d.applyGlossContourRect,
0403                                            config->glossContourLookupTable(),
0404                                            config->glossAntiAliased(),
0405                                            true);
0406 
0407     }
0408 
0409     if (config->soften()) {
0410         KisLsUtils::applyGaussianWithTransaction(bumpmapSelection, d.applyGaussianRect, config->soften());
0411     }
0412 
0413 
0414     if (config->textureEnabled() && config->textureInvert()) {
0415         bumpmapSelection->invert();
0416     }
0417 
0418     selection->clear();
0419     mapPixelValues(bumpmapSelection, selection,
0420                    ShadowsFetchOp(), d.shadowHighlightsFinalRect);
0421     selection->applySelection(limitingSelection, SELECTION_INTERSECT);
0422 
0423     //dstDevice->convertToQImage(0, QRect(0,0,300,300)).save("4_dst_before_apply.png");
0424     //selection->convertToQImage(0, QRect(0,0,300,300)).save("4_shadows_sel.png");
0425 
0426     {
0427         KisPaintDeviceSP dstDevice = dst->getProjection("00_bevel_shadow",
0428                                                         config->shadowBlendMode(),
0429                                                         config->shadowOpacity(),
0430                                                         QBitArray(),
0431                                                         srcDevice);
0432 
0433         const KoColor fillColor(config->shadowColor(), dstDevice->colorSpace());
0434         const QRect &fillRect = d.shadowHighlightsFinalRect;
0435 
0436         KisCachedPaintDevice::Guard d1(dstDevice, *env->cachedPaintDevice());
0437         KisPaintDeviceSP fillDevice = d1.device();
0438         fillDevice->setDefaultPixel(fillColor);
0439 
0440         KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection);
0441     }
0442 
0443     selection->clear();
0444     mapPixelValues(bumpmapSelection, selection,
0445                    HighlightsFetchOp(), d.shadowHighlightsFinalRect);
0446     selection->applySelection(limitingSelection, SELECTION_INTERSECT);
0447 
0448     //selection->convertToQImage(0, QRect(0,0,300,300)).save("5_highlights_sel.png");
0449 
0450     {
0451         KisPaintDeviceSP dstDevice = dst->getProjection("01_bevel_highlight",
0452                                                         config->highlightBlendMode(),
0453                                                         config->highlightOpacity(),
0454                                                         QBitArray(),
0455                                                         srcDevice);
0456 
0457         const KoColor fillColor(config->highlightColor(), dstDevice->colorSpace());
0458         const QRect &fillRect = d.shadowHighlightsFinalRect;
0459 
0460         KisCachedPaintDevice::Guard d1(dstDevice, *env->cachedPaintDevice());
0461         KisPaintDeviceSP fillDevice = d1.device();
0462         fillDevice->setDefaultPixel(fillColor);
0463 
0464         KisPainter::copyAreaOptimized(fillRect.topLeft(), fillDevice, dstDevice, fillRect, baseSelection);
0465     }
0466 }
0467 
0468 void KisLsBevelEmbossFilter::processDirectly(KisPaintDeviceSP src,
0469                                              KisMultipleProjection *dst,
0470                                              KisLayerStyleKnockoutBlower *blower,
0471                                              const QRect &applyRect,
0472                                              KisPSDLayerStyleSP style,
0473                                              KisLayerStyleFilterEnvironment *env) const
0474 {
0475     Q_UNUSED(env);
0476     Q_UNUSED(blower);
0477     KIS_ASSERT_RECOVER_RETURN(style);
0478 
0479     const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss();
0480     if (!KisLsUtils::checkEffectEnabled(config, dst)) return;
0481 
0482     KisLsUtils::LodWrapper<psd_layer_effects_bevel_emboss> w(env->currentLevelOfDetail(), config);
0483     applyBevelEmboss(src, dst, applyRect, w.config, style->resourcesInterface(), env);
0484 }
0485 
0486 QRect KisLsBevelEmbossFilter::neededRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const
0487 {
0488     const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss();
0489     if (!config->effectEnabled()) return rect;
0490 
0491     KisLsUtils::LodWrapper<psd_layer_effects_bevel_emboss> w(env->currentLevelOfDetail(), config);
0492 
0493     BevelEmbossRectCalculator d(rect, w.config);
0494     return d.totalNeedRect(rect, w.config);
0495 }
0496 
0497 QRect KisLsBevelEmbossFilter::changedRect(const QRect &rect, KisPSDLayerStyleSP style, KisLayerStyleFilterEnvironment *env) const
0498 {
0499     const psd_layer_effects_bevel_emboss *config = style->bevelAndEmboss();
0500     if (!config->effectEnabled()) return rect;
0501 
0502     KisLsUtils::LodWrapper<psd_layer_effects_bevel_emboss> w(env->currentLevelOfDetail(), config);
0503 
0504     BevelEmbossRectCalculator d(rect, w.config);
0505     return d.totalChangeRect(rect, w.config);
0506 }