File indexing completed on 2024-12-22 04:16:10

0001 /*
0002  *  SPDX-FileCopyrightText: 2008, 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
0003  *  SPDX-FileCopyrightText: 2010 José Luis Vergara <pentalis@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_hatching_paintop.h"
0009 #include "kis_hatching_paintop_settings.h"
0010 
0011 #include <cmath>
0012 #include <QRect>
0013 
0014 #include <KoColor.h>
0015 #include <KoColorSpace.h>
0016 
0017 #include <kis_image.h>
0018 #include <kis_debug.h>
0019 
0020 #include <kis_global.h>
0021 #include <kis_paint_device.h>
0022 #include <kis_painter.h>
0023 #include <kis_types.h>
0024 #include <brushengine/kis_paintop.h>
0025 #include <kis_brush_based_paintop.h>
0026 #include <brushengine/kis_paint_information.h>
0027 #include <kis_fixed_paint_device.h>
0028 #include <kis_lod_transform.h>
0029 #include <kis_spacing_information.h>
0030 
0031 
0032 
0033 #include <KoColorSpaceRegistry.h>
0034 
0035 KisHatchingPaintOp::KisHatchingPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP /*image*/)
0036     : KisBrushBasedPaintOp(settings, painter)
0037     , m_angleOption(settings.data())
0038     , m_crosshatchingOption(settings.data())
0039     , m_separationOption(settings.data())
0040     , m_thicknessOption(settings.data())
0041     , m_opacityOption(settings.data())
0042     , m_sizeOption(settings.data())
0043 {
0044     Q_UNUSED(node);
0045 
0046     m_settings = static_cast<KisHatchingPaintOpSettings*>(settings->clone().data());
0047     static_cast<const KisHatchingPaintOpSettings*>(settings.data())->initializeTwin(m_settings);
0048 
0049     m_hatchingBrush = new HatchingBrush(m_settings);
0050     m_hatchingOptions.read(settings.data());
0051     m_hatchingPreferences.read(settings.data());
0052 
0053 }
0054 
0055 KisHatchingPaintOp::~KisHatchingPaintOp()
0056 {
0057     delete m_hatchingBrush;
0058 }
0059 
0060 KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& info)
0061 {
0062     //------START SIMPLE ERROR CATCHING-------
0063     if (!painter()->device()) return KisSpacingInformation(1.0);
0064 
0065     if (!m_hatchedDab)
0066         m_hatchedDab = source()->createCompositionSourceDevice();
0067     else
0068         m_hatchedDab->clear();
0069 
0070     //Simple convenience renaming, I'm thinking of removing these inherited quirks
0071     KisBrushSP brush = m_brush;
0072     KisPaintDeviceSP device = painter()->device();
0073 
0074     //Macro to catch errors
0075     Q_ASSERT(brush);
0076 
0077     //----------SIMPLE error catching code, maybe it's not even needed------
0078     if (!brush) return KisSpacingInformation(1.0);
0079     if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0);
0080 
0081     //SENSOR-depending settings
0082     m_settings->anglesensorvalue = m_angleOption.apply(info);
0083     m_settings->crosshatchingsensorvalue = m_crosshatchingOption.apply(info);
0084     m_settings->separationsensorvalue = m_separationOption.apply(info);
0085     m_settings->thicknesssensorvalue = m_thicknessOption.apply(info);
0086 
0087     const qreal additionalScale = KisLodTransform::lodToScale(painter()->device());
0088     const double scale = additionalScale * m_sizeOption.apply(info);
0089     if ((scale * brush->width()) <= 0.01 || (scale * brush->height()) <= 0.01) return KisSpacingInformation(1.0);
0090     KisDabShape shape(scale, 1.0, 0.0);
0091 
0092     quint8 origOpacity = m_opacityOption.apply(painter(), info);
0093 
0094     /*----Fetch the Dab----*/
0095     static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
0096     static KoColor color(Qt::black, cs);
0097 
0098     QRect dstRect;
0099     KisFixedPaintDeviceSP maskDab =
0100         m_dabCache->fetchDab(cs, color, info.pos(),
0101                              shape,
0102                              info, 1.0, &dstRect);
0103 
0104     // sanity check
0105     KIS_ASSERT_RECOVER_NOOP(dstRect.size() == maskDab->bounds().size());
0106 
0107     /*-----Convenient renaming for the limits of the maskDab, this will be used
0108     to hatch a dab of just the right size------*/
0109     qint32 x, y, sw, sh;
0110     dstRect.getRect(&x, &y, &sw, &sh);
0111 
0112     //------This If_block pre-fills the future m_hatchedDab with a pretty backgroundColor
0113     if (m_hatchingPreferences.useOpaqueBackground) {
0114         KoColor aersh = painter()->backgroundColor();
0115         m_hatchedDab->fill(0, 0, (sw - 1), (sh - 1), aersh.data()); //this plus yellow background = french fry brush
0116     }
0117 
0118     /* If block describing how to stack hatching passes to generate
0119     crosshatching according to user specifications */
0120     if (m_crosshatchingOption.isChecked()) {
0121         if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::Perpendicular) {
0122             if (m_settings->crosshatchingsensorvalue > 0.5)
0123                 m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale);
0124         }
0125         else if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::MinusThenPlus) {
0126             if (m_settings->crosshatchingsensorvalue > 0.33)
0127                 m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale);
0128             if (m_settings->crosshatchingsensorvalue > 0.67)
0129                 m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale);
0130         }
0131         else if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::PlusThenMinus) {
0132             if (m_settings->crosshatchingsensorvalue > 0.33)
0133                 m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale);
0134             if (m_settings->crosshatchingsensorvalue > 0.67)
0135                 m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale);
0136         }
0137         else if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::MoirePattern) {
0138             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->crosshatchingsensorvalue) * 360), painter()->paintColor(), additionalScale);
0139         }
0140     } else {
0141         if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::Perpendicular) {
0142             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale);
0143         }
0144         else if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::MinusThenPlus) {
0145             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale);
0146             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale);
0147         }
0148         else if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::PlusThenMinus) {
0149             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale);
0150             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale);
0151         }
0152         else if (m_hatchingOptions.crosshatchingStyle == CrosshatchingType::MoirePattern) {
0153             m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-10), painter()->paintColor(), additionalScale);
0154         }
0155     }
0156 
0157     if (m_angleOption.isChecked())
0158       m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->anglesensorvalue)*360+m_hatchingOptions.angle), painter()->paintColor(), additionalScale);
0159 
0160     // The base hatch... unless moiré or angle
0161     if (m_hatchingOptions.crosshatchingStyle != CrosshatchingType::MoirePattern && !m_angleOption.isChecked())
0162         m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, m_hatchingOptions.angle, painter()->paintColor(), additionalScale);
0163 
0164 
0165     // The most important line, the one that paints to the screen.
0166     painter()->bitBltWithFixedSelection(x, y, m_hatchedDab, maskDab, sw, sh);
0167     painter()->renderMirrorMaskSafe(QRect(QPoint(x, y), QSize(sw, sh)), m_hatchedDab, 0, 0, maskDab,
0168                                     !m_dabCache->needSeparateOriginal());
0169     painter()->setOpacity(origOpacity);
0170 
0171     return effectiveSpacing(scale);
0172 }
0173 
0174 KisSpacingInformation KisHatchingPaintOp::updateSpacingImpl(const KisPaintInformation &info) const
0175 {
0176     const qreal scale = KisLodTransform::lodToScale(painter()->device()) * m_sizeOption.apply(info);
0177     return effectiveSpacing(scale);
0178 }
0179 
0180 double KisHatchingPaintOp::spinAngle(double spin)
0181 {
0182     double tempangle = m_hatchingOptions.angle + spin;
0183     qint8 factor = 1;
0184 
0185     if (tempangle < 0)
0186         factor = -1;
0187 
0188     tempangle = fabs(fmod(tempangle, 180));
0189 
0190     if ((tempangle >= 0) && (tempangle <= 90))
0191         return factor * tempangle;
0192     else if ((tempangle > 90) && (tempangle <= 180))
0193         return factor * -(180 - tempangle);
0194 
0195     return 0;   // this should never be executed except if NAN
0196 }