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 }