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 }