File indexing completed on 2025-01-26 04:11:17

0001 /*
0002  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0003  *  SPDX-FileCopyrightText: 2004-2008 Boudewijn Rempt <boud@valdyas.org>
0004  *  SPDX-FileCopyrightText: 2004 Clarence Dang <dang@kde.org>
0005  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
0006  *  SPDX-FileCopyrightText: 2004, 2010 Cyrille Berger <cberger@cberger.net>
0007  *
0008  *  SPDX-License-Identifier: GPL-2.0-or-later
0009  */
0010 
0011 #include "kis_duplicateop.h"
0012 #include "kis_duplicateop_p.h"
0013 
0014 #include <string.h>
0015 
0016 #include <QRect>
0017 #include <QWidget>
0018 #include <QLayout>
0019 #include <QLabel>
0020 #include <QCheckBox>
0021 #include <QDomElement>
0022 #include <QHBoxLayout>
0023 #include <QToolButton>
0024 
0025 #include <kis_image.h>
0026 #include <kis_debug.h>
0027 
0028 #include <KoColorTransformation.h>
0029 #include <KoColor.h>
0030 #include <KoColorSpace.h>
0031 #include <KoCompositeOpRegistry.h>
0032 #include <KoColorSpaceRegistry.h>
0033 
0034 #include <kis_brush.h>
0035 #include <kis_datamanager.h>
0036 #include <kis_global.h>
0037 #include <kis_paint_device.h>
0038 #include <kis_painter.h>
0039 #include <brushengine/kis_paintop.h>
0040 #include <kis_properties_configuration.h>
0041 #include <kis_selection.h>
0042 #include <kis_brush_option_widget.h>
0043 #include <kis_paintop_settings_widget.h>
0044 #include <kis_random_sub_accessor.h>
0045 #include <kis_fixed_paint_device.h>
0046 #include <kis_iterator_ng.h>
0047 #include <kis_spacing_information.h>
0048 
0049 #include "kis_duplicateop_settings.h"
0050 #include "kis_duplicateop_settings_widget.h"
0051 #include <KisDuplicateOptionData.h>
0052 
0053 KisDuplicateOp::KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image)
0054     : KisBrushBasedPaintOp(settings, painter)
0055     , m_image(image)
0056     , m_node(node)
0057     , m_settings(static_cast<KisDuplicateOpSettings*>(const_cast<KisPaintOpSettings*>(settings.data())))
0058     , m_sizeOption(settings.data())
0059     , m_opacityOption(settings.data())
0060     , m_rotationOption(settings.data())
0061 {
0062     Q_ASSERT(settings);
0063     Q_ASSERT(painter);
0064 
0065     m_duplicateOptionData.read(settings.data());
0066     m_srcdev = source()->createCompositionSourceDevice();
0067 }
0068 
0069 KisDuplicateOp::~KisDuplicateOp()
0070 {
0071 }
0072 
0073 #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
0074 
0075 KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info)
0076 {
0077     if (!painter()->device()) return KisSpacingInformation(1.0);
0078 
0079     KisBrushSP brush = m_brush;
0080     if (!brush)
0081         return KisSpacingInformation(1.0);
0082 
0083     if (!brush->canPaintFor(info))
0084         return KisSpacingInformation(1.0);
0085 
0086     if (!m_duplicateStartIsSet) {
0087         m_duplicateStartIsSet = true;
0088         m_duplicateStart = info.pos();
0089     }
0090 
0091     KisPaintDeviceSP realSourceDevice;
0092 
0093     if (m_duplicateOptionData.cloneFromProjection && m_image) {
0094         realSourceDevice = m_image->projection();
0095     }
0096     else {
0097         KisNodeSP externalSourceNode = m_settings->sourceNode();
0098 
0099         /**
0100          * The saved layer might have been deleted by then, so check if it
0101          * still belongs to a graph
0102          */
0103         if (!externalSourceNode || !externalSourceNode->graphListener()) {
0104             externalSourceNode = m_node;
0105         }
0106 
0107         realSourceDevice = externalSourceNode->projection();
0108     }
0109 
0110     qreal rotation = m_rotationOption.apply(info);
0111 
0112     qreal opacity = m_opacityOption.apply(painter(),info);
0113 
0114     qreal scale = m_sizeOption.apply(info);
0115 
0116     if (checkSizeTooSmall(scale)) return KisSpacingInformation();
0117     KisDabShape shape(scale, 1.0, rotation);
0118 
0119 
0120     static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8();
0121     static KoColor color(Qt::black, cs);
0122 
0123     QRect dstRect;
0124     KisFixedPaintDeviceSP dab =
0125         m_dabCache->fetchDab(cs, color, info.pos(),
0126                              shape,
0127                              info, 1.0,
0128                              &dstRect);
0129 
0130     if (dstRect.isEmpty()) return KisSpacingInformation(1.0);
0131 
0132 
0133     QPoint srcPoint;
0134 
0135     if (m_duplicateOptionData.moveSourcePoint) {
0136         srcPoint = (dstRect.topLeft() - m_settings->offset()).toPoint();
0137     }
0138     else {
0139         QPointF hotSpot = brush->hotSpot(shape, info);
0140         srcPoint = (m_settings->position() - hotSpot).toPoint();
0141     }
0142 
0143     qint32 sw = dstRect.width();
0144     qint32 sh = dstRect.height();
0145 
0146     // Perspective correction ?
0147 
0148 
0149     // if (m_perspectiveCorrection && m_image && m_image->perspectiveGrid()->countSubGrids() == 1) {
0150     //     Matrix3qreal startM = Matrix3qreal::Identity();
0151     //     Matrix3qreal endM = Matrix3qreal::Identity();
0152 
0153     //     // First look for the grid corresponding to the start point
0154     //     KisSubPerspectiveGrid* subGridStart = *m_image->perspectiveGrid()->begin();
0155     //     QRect r = QRect(0, 0, m_image->width(), m_image->height());
0156 
0157     //     if (subGridStart) {
0158     //         startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight());
0159     //     }
0160 
0161     //     // Second look for the grid corresponding to the end point
0162     //     KisSubPerspectiveGrid* subGridEnd = *m_image->perspectiveGrid()->begin();
0163     //     if (subGridEnd) {
0164     //         endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r);
0165     //     }
0166 
0167     //     // Compute the translation in the perspective transformation space:
0168     //     QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart));
0169     //     QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(m_settings->offset()));
0170     //     QPointF translate = duplicateStartPositionT - positionStartPaintingT;
0171 
0172     //     KisSequentialIterator dstIt(m_srcdev, QRect(0, 0, sw, sh));
0173     //     KisRandomSubAccessorSP srcAcc = realSourceDevice->createRandomSubAccessor();
0174 
0175     //     //Action
0176     //     while (dstIt.nextPixel()) {
0177     //         QPointF p =  KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + dstRect.x(), dstIt.y() + dstRect.y())) + translate);
0178     //         srcAcc->moveTo(p);
0179     //         srcAcc->sampledOldRawData(dstIt.rawData());
0180     //     }
0181 
0182 
0183     // }
0184     // else
0185     {
0186         KisPainter copyPainter(m_srcdev);
0187         copyPainter.setCompositeOpId(COMPOSITE_COPY);
0188         copyPainter.bitBltOldData(0, 0, realSourceDevice, srcPoint.x(), srcPoint.y(), sw, sh);
0189         copyPainter.end();
0190     }
0191 
0192     // heal ?
0193     if (m_duplicateOptionData.healing) {
0194         QRect healRect(dstRect);
0195 
0196         const bool smallWidth = healRect.width() < 3;
0197         const bool smallHeight = healRect.height() < 3;
0198 
0199         if (smallWidth || smallHeight) {
0200             healRect.adjust(-1, -1, 1, 1);
0201         }
0202 
0203         const int healSW = healRect.width();
0204         const int healSH = healRect.height();
0205 
0206 
0207         quint16 srcData[4];
0208         quint16 tmpData[4];
0209         QScopedArrayPointer<qreal> matrix(new qreal[ 3 * healSW * healSH ]);
0210         // First divide
0211         const KoColorSpace* srcCs = realSourceDevice->colorSpace();
0212         const KoColorSpace* tmpCs = m_srcdev->colorSpace();
0213         KisHLineConstIteratorSP srcIt = realSourceDevice->createHLineConstIteratorNG(healRect.x(), healRect.y() , healSW);
0214         KisHLineIteratorSP tmpIt = m_srcdev->createHLineIteratorNG(0, 0, healSW);
0215         qreal* matrixIt = matrix.data();
0216         for (int j = 0; j < healSH; j++) {
0217             for (int i = 0; i < healSW; i++) {
0218                 srcCs->toLabA16(srcIt->oldRawData(), (quint8*)srcData, 1);
0219                 tmpCs->toLabA16(tmpIt->rawData(), (quint8*)tmpData, 1);
0220                 // Division
0221                 for (int k = 0; k < 3; k++) {
0222                     matrixIt[k] = srcData[k] / (qreal)qMax((int)tmpData [k], 1);
0223                 }
0224                 srcIt->nextPixel();
0225                 tmpIt->nextPixel();
0226                 matrixIt += 3;
0227             }
0228             srcIt->nextRow();
0229             tmpIt->nextRow();
0230         }
0231         // Minimize energy
0232         {
0233             int iter = 0;
0234             qreal err;
0235             QScopedArrayPointer<qreal> solution(new qreal[ 3 * healSW * healSH ]);
0236 
0237             do {
0238                 err = DuplicateOpUtils::minimizeEnergy(matrix.data(), solution.data(), healSW, healSH);
0239 
0240                 solution.swap(matrix);
0241 
0242                 iter++;
0243             } while (err > 0.00001 && iter < 100);
0244         }
0245 
0246         // Finally multiply
0247         KisHLineIteratorSP tmpIt2 = m_srcdev->createHLineIteratorNG(0, 0, healSW);
0248         matrixIt = &matrix[0];
0249         for (int j = 0; j < healSH; j++) {
0250             for (int i = 0; i < healSW; i++) {
0251                 tmpCs->toLabA16(tmpIt2->rawData(), (quint8*)tmpData, 1);
0252                 // Multiplication
0253                 for (int k = 0; k < 3; k++) {
0254                     tmpData[k] = (int)CLAMP(matrixIt[k] * qMax((int) tmpData[k], 1), 0, 65535);
0255                 }
0256                 tmpCs->fromLabA16((quint8*)tmpData, tmpIt2->rawData(), 1);
0257                 tmpIt2->nextPixel();
0258                 matrixIt += 3;
0259             }
0260             tmpIt2->nextRow();
0261         }
0262     }
0263 
0264     painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(),
0265                                         m_srcdev, dab,
0266                                         dstRect.width(),
0267                                         dstRect.height());
0268 
0269     painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab,
0270                                     !m_dabCache->needSeparateOriginal());
0271 
0272     painter()->setOpacity(opacity);
0273 
0274     return effectiveSpacing(scale);
0275 }
0276 
0277 KisSpacingInformation KisDuplicateOp::updateSpacingImpl(const KisPaintInformation &info) const
0278 {
0279     return effectiveSpacing(m_sizeOption.apply(info));
0280 }