File indexing completed on 2024-06-16 04:17:22

0001 /*
0002  *  SPDX-FileCopyrightText: 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_hairy_paintop.h"
0008 #include "kis_hairy_paintop_settings.h"
0009 
0010 #include <cmath>
0011 #include <QRect>
0012 
0013 #include <kis_image.h>
0014 #include <kis_debug.h>
0015 
0016 #include "kis_paint_device.h"
0017 #include "kis_painter.h"
0018 #include <kis_vec.h>
0019 
0020 #include "KisHairyInkOptionData.h"
0021 #include "KisHairyBristleOptionData.h"
0022 #include <kis_brush_option.h>
0023 #include <kis_brush_based_paintop_settings.h>
0024 #include <kis_fixed_paint_device.h>
0025 #include <kis_lod_transform.h>
0026 #include <kis_spacing_information.h>
0027 #include <KoResourceLoadResult.h>
0028 
0029 
0030 #include "kis_brush.h"
0031 
0032 KisHairyPaintOp::KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image)
0033     : KisPaintOp(painter)
0034     , m_opacityOption(settings.data())
0035     , m_sizeOption(settings.data())
0036     , m_rotationOption(settings.data())
0037 {
0038     Q_UNUSED(image);
0039     Q_ASSERT(settings);
0040 
0041     m_hairyBristleOption.read(settings.data());
0042     m_hairyInkOption.read(settings.data());
0043 
0044     m_dev = node ? node->paintDevice() : 0;
0045 
0046     KisBrushOptionProperties brushOption;
0047     brushOption.readOptionSetting(settings, settings->resourcesInterface(), settings->canvasResourcesInterface());
0048     KisBrushSP brush = brushOption.brush();
0049     KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace());
0050 
0051     // properly initialize fake paint information to avoid warnings
0052     KisPaintInformation fakePaintInformation;
0053     fakePaintInformation.setRandomSource(new KisRandomSource());
0054     fakePaintInformation.setPerStrokeRandomSource(new KisPerStrokeRandomSource());
0055 
0056     if (brush->brushApplication() == IMAGESTAMP) {
0057         dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), fakePaintInformation);
0058     } else {
0059         brush->mask(dab, painter->paintColor(), KisDabShape(), fakePaintInformation);
0060     }
0061 
0062     m_brush.fromDabWithDensity(dab, m_hairyBristleOption.densityFactor * 0.01);
0063     m_brush.setInkColor(painter->paintColor());
0064 
0065     loadSettings();
0066     m_brush.setProperties(&m_properties);
0067 }
0068 
0069 QList<KoResourceLoadResult> KisHairyPaintOp::prepareLinkedResources(const KisPaintOpSettingsSP settings, KisResourcesInterfaceSP resourcesInterface)
0070 {
0071     KisBrushOptionProperties brushOption;
0072     return brushOption.prepareLinkedResources(settings, resourcesInterface);
0073 }
0074 
0075 void KisHairyPaintOp::loadSettings()
0076 {
0077     m_properties.inkAmount = m_hairyInkOption.inkAmount;
0078     //TODO: wait for the transfer function with variable size
0079 
0080     m_properties.inkDepletionCurve = KisCubicCurve(m_hairyInkOption.inkDepletionCurve).floatTransfer(m_hairyInkOption.inkAmount);
0081 
0082     m_properties.inkDepletionEnabled = m_hairyInkOption.inkDepletionEnabled;
0083     m_properties.useSaturation = m_hairyInkOption.useSaturation;
0084     m_properties.useOpacity = m_hairyInkOption.useOpacity;
0085     m_properties.useWeights = m_hairyInkOption.useWeights;
0086 
0087     m_properties.pressureWeight = m_hairyInkOption.pressureWeight / 100.0;
0088     m_properties.bristleLengthWeight = m_hairyInkOption.bristleLengthWeight / 100.0;
0089     m_properties.bristleInkAmountWeight = m_hairyInkOption.bristleInkAmountWeight / 100.0;
0090     m_properties.inkDepletionWeight = m_hairyInkOption.inkDepletionWeight;
0091     m_properties.useSoakInk = m_hairyInkOption.useSoakInk;
0092 
0093     m_properties.useMousePressure = m_hairyBristleOption.useMousePressure;
0094     m_properties.shearFactor = m_hairyBristleOption.shearFactor;
0095     m_properties.randomFactor = m_hairyBristleOption.randomFactor;
0096     m_properties.scaleFactor = m_hairyBristleOption.scaleFactor;
0097     m_properties.threshold = m_hairyBristleOption.threshold;
0098     m_properties.antialias = m_hairyBristleOption.antialias;
0099     m_properties.useCompositing = m_hairyBristleOption.useCompositing;
0100     m_properties.connectedPath = m_hairyBristleOption.connectedPath;
0101 }
0102 
0103 
0104 KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info)
0105 {
0106     return updateSpacingImpl(info);
0107 }
0108 
0109 KisSpacingInformation KisHairyPaintOp::updateSpacingImpl(const KisPaintInformation &info) const
0110 {
0111     Q_UNUSED(info);
0112     return KisSpacingInformation(0.5);
0113 }
0114 
0115 
0116 void KisHairyPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance)
0117 {
0118     Q_UNUSED(currentDistance);
0119     if (!painter()) return;
0120 
0121     if (!m_dab) {
0122         m_dab = source()->createCompositionSourceDevice();
0123     }
0124     else {
0125         m_dab->clear();
0126     }
0127 
0128     /**
0129      * Even though we don't use spacing in hairy brush, we should still
0130      * initialize its distance information to ensure drawing angle and
0131      * other history-based sensors work fine.
0132      */
0133     KisPaintInformation pi(pi2);
0134     KisPaintInformation::DistanceInformationRegistrar r =
0135         pi.registerDistanceInformation(currentDistance);
0136 
0137     // Hairy Brush is capable of working with zero scale,
0138     // so no additional checks for 'zero'ness are needed
0139     qreal scale = m_sizeOption.apply(pi);
0140     scale *= KisLodTransform::lodToScale(painter()->device());
0141     qreal rotation = m_rotationOption.apply(pi);
0142     quint8 origOpacity = m_opacityOption.apply(painter(), pi);
0143 
0144     const bool mirrorFlip = pi1.canvasMirroredH() != pi1.canvasMirroredV();
0145 
0146     // we don't use spacing here (the brush itself is used only once
0147     // during initialization), so we should just skip the distance info
0148     // update
0149 
0150     m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_hairyBristleOption.scaleFactor, mirrorFlip ? -rotation : rotation);
0151 
0152     //QRect rc = m_dab->exactBounds();
0153     QRect rc = m_dab->extent();
0154     painter()->bitBlt(rc.topLeft(), m_dab, rc);
0155     painter()->renderMirrorMask(rc, m_dab);
0156     painter()->setOpacity(origOpacity);
0157 
0158     // we don't use spacing in hairy brush, but history is
0159     // still important for us
0160     currentDistance->registerPaintedDab(pi,
0161                                         KisSpacingInformation(),
0162                                         KisTimingInformation());
0163 }