Changeset View
Changeset View
Standalone View
Standalone View
plugins/tools/tool_smart_patch/kis_tool_smart_patch.cpp
Show All 13 Lines | |||||
14 | * You should have received a copy of the GNU General Public License | 14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the Free Software | 15 | * along with this program; if not, write to the Free Software | ||
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
17 | */ | 17 | */ | ||
18 | 18 | | |||
19 | #include "kis_tool_smart_patch.h" | 19 | #include "kis_tool_smart_patch.h" | ||
20 | 20 | | |||
21 | #include "QApplication" | 21 | #include "QApplication" | ||
22 | #include "QPainterPath" | ||||
22 | 23 | | |||
23 | #include <klocalizedstring.h> | 24 | #include <klocalizedstring.h> | ||
24 | #include <KoCanvasBase.h> | 25 | #include <KoColor.h> | ||
25 | | ||||
26 | #include <KisViewManager.h> | 26 | #include <KisViewManager.h> | ||
27 | #include "kis_canvas2.h" | 27 | #include "kis_canvas2.h" | ||
28 | #include "kis_cursor.h" | 28 | #include "kis_cursor.h" | ||
29 | #include "kis_config.h" | 29 | #include "kis_painter.h" | ||
30 | #include "kis_paintop_preset.h" | ||||
31 | | ||||
30 | #include "kundo2magicstring.h" | 32 | #include "kundo2magicstring.h" | ||
33 | #include "kundo2stack.h" | ||||
34 | #include "kis_transaction_based_command.h" | ||||
35 | #include "kis_transaction.h" | ||||
36 | | ||||
37 | #include "kis_processing_applicator.h" | ||||
38 | #include "kis_datamanager.h" | ||||
31 | 39 | | |||
32 | #include "KoProperties.h" | | |||
33 | #include "KoColorSpaceRegistry.h" | 40 | #include "KoColorSpaceRegistry.h" | ||
34 | #include "KoShapeController.h" | | |||
35 | #include "KoDocumentResourceManager.h" | | |||
36 | #include "kis_node_manager.h" | | |||
37 | #include "kis_cursor.h" | | |||
38 | 41 | | |||
39 | #include "kis_tool_smart_patch_options_widget.h" | 42 | #include "kis_tool_smart_patch_options_widget.h" | ||
40 | #include "libs/image/kis_paint_device_debug_utils.h" | 43 | #include "libs/image/kis_paint_device_debug_utils.h" | ||
41 | 44 | | |||
42 | #include "kis_resources_snapshot.h" | | |||
43 | #include "kis_layer.h" | | |||
44 | #include "kis_transaction.h" | | |||
45 | #include "kis_paint_layer.h" | 45 | #include "kis_paint_layer.h" | ||
46 | 46 | | |||
47 | #include "kis_inpaint_mask.h" | | |||
48 | | ||||
49 | QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int radius, int accuracy); | 47 | QRect patchImage(KisPaintDeviceSP imageDev, KisPaintDeviceSP maskDev, int radius, int accuracy); | ||
50 | 48 | | |||
49 | class KisToolSmartPatch::InpaintCommand : public KisTransactionBasedCommand { | ||||
50 | public: | ||||
51 | InpaintCommand( KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev, int accuracy, int patchRadius ) : | ||||
52 | m_maskDev(maskDev), m_imageDev(imageDev), m_accuracy(accuracy), m_patchRadius(patchRadius) {} | ||||
53 | | ||||
54 | KUndo2Command* paint() override { | ||||
55 | KisTransaction transaction(m_imageDev); | ||||
56 | patchImage(m_imageDev, m_maskDev, m_patchRadius, m_accuracy); | ||||
57 | return transaction.endAndTake(); | ||||
58 | } | ||||
dkazakov: Outch, we already have an undo system for pixel access operations:
You should use… | |||||
59 | | ||||
60 | private: | ||||
61 | KisPaintDeviceSP m_maskDev, m_imageDev; | ||||
62 | int m_accuracy, m_patchRadius; | ||||
63 | }; | ||||
64 | | ||||
51 | struct KisToolSmartPatch::Private { | 65 | struct KisToolSmartPatch::Private { | ||
52 | KisMaskSP mask = nullptr; | | |||
53 | KisNodeSP maskNode = nullptr; | | |||
54 | KisNodeSP paintNode = nullptr; | | |||
55 | KisPaintDeviceSP imageDev = nullptr; | | |||
56 | KisPaintDeviceSP maskDev = nullptr; | 66 | KisPaintDeviceSP maskDev = nullptr; | ||
57 | KisResourcesSnapshotSP resources = nullptr; | 67 | KisPainter maskDevPainter; | ||
58 | KoColor currentFgColor; | 68 | float brushRadius = 50.; //initial default. actually read from ui. | ||
59 | KisToolSmartPatchOptionsWidget *optionsWidget = nullptr; | 69 | KisToolSmartPatchOptionsWidget *optionsWidget = nullptr; | ||
70 | QRectF oldOutlineRect; | ||||
71 | QPainterPath brushOutline; | ||||
60 | }; | 72 | }; | ||
61 | 73 | | |||
62 | 74 | | |||
63 | KisToolSmartPatch::KisToolSmartPatch(KoCanvasBase * canvas) | 75 | KisToolSmartPatch::KisToolSmartPatch(KoCanvasBase * canvas) | ||
64 | : KisToolFreehand(canvas, | 76 | : KisToolPaint(canvas, KisCursor::blankCursor()), | ||
65 | KisCursor::load("tool_freehand_cursor.png", 5, 5), | | |||
66 | kundo2_i18n("Smart Patch Stroke")), | | |||
67 | m_d(new Private) | 77 | m_d(new Private) | ||
68 | { | 78 | { | ||
79 | setSupportOutline(true); | ||||
69 | setObjectName("tool_SmartPatch"); | 80 | setObjectName("tool_SmartPatch"); | ||
81 | m_d->maskDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); | ||||
82 | m_d->maskDevPainter.begin( m_d->maskDev ); | ||||
83 | | ||||
84 | m_d->maskDevPainter.setPaintColor(KoColor(Qt::magenta, m_d->maskDev->colorSpace())); | ||||
85 | m_d->maskDevPainter.setBackgroundColor(KoColor(Qt::white, m_d->maskDev->colorSpace())); | ||||
86 | m_d->maskDevPainter.setFillStyle( KisPainter::FillStyleForegroundColor ); | ||||
70 | } | 87 | } | ||
71 | 88 | | |||
72 | KisToolSmartPatch::~KisToolSmartPatch() | 89 | KisToolSmartPatch::~KisToolSmartPatch() | ||
73 | { | 90 | { | ||
74 | m_d->optionsWidget = nullptr; | 91 | m_d->optionsWidget = nullptr; | ||
92 | m_d->maskDevPainter.end(); | ||||
75 | } | 93 | } | ||
76 | 94 | | |||
77 | void KisToolSmartPatch::activate(ToolActivation activation, const QSet<KoShape*> &shapes) | 95 | void KisToolSmartPatch::activate(ToolActivation activation, const QSet<KoShape*> &shapes) | ||
78 | { | 96 | { | ||
79 | KisToolFreehand::activate(activation, shapes); | 97 | KisToolPaint::activate(activation, shapes); | ||
80 | } | 98 | } | ||
81 | 99 | | |||
82 | void KisToolSmartPatch::deactivate() | 100 | void KisToolSmartPatch::deactivate() | ||
83 | { | 101 | { | ||
84 | KisToolFreehand::deactivate(); | 102 | KisToolPaint::deactivate(); | ||
85 | } | 103 | } | ||
86 | 104 | | |||
87 | void KisToolSmartPatch::resetCursorStyle() | 105 | void KisToolSmartPatch::resetCursorStyle() | ||
88 | { | 106 | { | ||
89 | KisToolFreehand::resetCursorStyle(); | 107 | KisToolPaint::resetCursorStyle(); | ||
90 | } | | |||
91 | | ||||
92 | | ||||
93 | bool KisToolSmartPatch::canCreateInpaintMask() const | | |||
94 | { | | |||
95 | KisNodeSP node = currentNode(); | | |||
96 | return node && node->inherits("KisPaintLayer"); | | |||
97 | } | | |||
98 | | ||||
99 | QRect KisToolSmartPatch::inpaintImage(KisPaintDeviceSP maskDev, KisPaintDeviceSP imageDev) | | |||
100 | { | | |||
101 | int accuracy = 50; //default accuracy - middle value | | |||
102 | int patchRadius = 4; //default radius, which works well for most cases tested | | |||
103 | | ||||
104 | if (!m_d.isNull() && m_d->optionsWidget) { | | |||
105 | accuracy = m_d->optionsWidget->getAccuracy(); | | |||
106 | patchRadius = m_d->optionsWidget->getPatchRadius(); | | |||
107 | } | | |||
108 | return patchImage(imageDev, maskDev, patchRadius, accuracy); | | |||
109 | } | 108 | } | ||
110 | 109 | | |||
111 | void KisToolSmartPatch::activatePrimaryAction() | 110 | void KisToolSmartPatch::activatePrimaryAction() | ||
112 | { | 111 | { | ||
113 | KisToolFreehand::activatePrimaryAction(); | 112 | setOutlineEnabled(true); | ||
113 | KisToolPaint::activatePrimaryAction(); | ||||
114 | } | 114 | } | ||
115 | 115 | | |||
116 | void KisToolSmartPatch::deactivatePrimaryAction() | 116 | void KisToolSmartPatch::deactivatePrimaryAction() | ||
117 | { | 117 | { | ||
118 | KisToolFreehand::deactivatePrimaryAction(); | 118 | setOutlineEnabled(false); | ||
119 | KisToolPaint::deactivatePrimaryAction(); | ||||
119 | } | 120 | } | ||
120 | 121 | | |||
121 | void KisToolSmartPatch::createInpaintMask(void) | 122 | void KisToolSmartPatch::addMaskPath( KoPointerEvent *event ) | ||
122 | { | 123 | { | ||
123 | m_d->mask = new KisInpaintMask(); | 124 | QPointF imagePos = currentImage()->documentToPixel(event->point); | ||
125 | QPainterPath currentBrushOutline = brushOutline().translated(imagePos); | ||||
126 | m_d->maskDevPainter.fillPainterPath(currentBrushOutline); | ||||
124 | 127 | | |||
125 | KisLayerSP parentLayer = qobject_cast<KisLayer*>(m_d->paintNode.data()); | 128 | canvas()->updateCanvas(currentImage()->pixelToDocument(m_d->maskDev->exactBounds())); | ||
126 | m_d->mask->initSelection(parentLayer); | | |||
127 | image()->addNode(m_d->mask, m_d->paintNode); | | |||
128 | } | 129 | } | ||
129 | 130 | | |||
130 | void KisToolSmartPatch::deleteInpaintMask(void) | 131 | void KisToolSmartPatch::beginPrimaryAction(KoPointerEvent *event) | ||
131 | { | 132 | { | ||
133 | //we can only apply inpaint operation to paint layer | ||||
134 | if ( currentNode().isNull() || !currentNode()->inherits("KisPaintLayer") || nodePaintAbility()!=NodePaintAbility::PAINT ) { | ||||
132 | KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); | 135 | KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); | ||
133 | KisViewManager* viewManager = kiscanvas->viewManager(); | 136 | kiscanvas->viewManager()-> | ||
134 | if (! m_d->paintNode.isNull()) | 137 | showFloatingMessage( | ||
135 | viewManager->nodeManager()->slotNonUiActivatedNode(m_d->paintNode); | 138 | i18n("Select a paint layer to use this tool"), | ||
139 | QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); | ||||
140 | event->ignore(); | ||||
141 | return; | ||||
142 | } | ||||
136 | 143 | | |||
137 | image()->removeNode(m_d->mask); | 144 | addMaskPath(event); | ||
138 | m_d->mask = nullptr; | 145 | setMode(KisTool::PAINT_MODE); | ||
146 | KisToolPaint::beginPrimaryAction(event); | ||||
139 | } | 147 | } | ||
140 | 148 | | |||
141 | void KisToolSmartPatch::beginPrimaryAction(KoPointerEvent *event) | 149 | void KisToolSmartPatch::continuePrimaryAction(KoPointerEvent *event) | ||
142 | { | 150 | { | ||
143 | m_d->paintNode = currentNode(); | 151 | CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); | ||
152 | addMaskPath(event); | ||||
153 | KisToolPaint::continuePrimaryAction(event); | ||||
154 | } | ||||
144 | 155 | | |||
145 | KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); | | |||
146 | KisViewManager* viewManager = kiscanvas->viewManager(); | | |||
147 | 156 | | |||
148 | //we can only apply inpaint operation to paint layer | 157 | void KisToolSmartPatch::endPrimaryAction(KoPointerEvent *event) | ||
149 | if (!m_d->paintNode.isNull() && m_d->paintNode->inherits("KisPaintLayer")) { | 158 | { | ||
159 | CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); | ||||
160 | addMaskPath(event); | ||||
161 | KisToolPaint::endPrimaryAction(event); | ||||
162 | setMode(KisTool::HOVER_MODE); | ||||
150 | 163 | | |||
164 | QApplication::setOverrideCursor(KisCursor::waitCursor()); | ||||
151 | 165 | | |||
152 | if (!m_d->mask.isNull()) { | 166 | int accuracy = 50; //default accuracy - middle value | ||
153 | viewManager->nodeManager()->slotNonUiActivatedNode(m_d->mask); | 167 | int patchRadius = 4; //default radius, which works well for most cases tested | ||
154 | } else { | 168 | | ||
169 | if (m_d->optionsWidget) { | ||||
170 | accuracy = m_d->optionsWidget->getAccuracy(); | ||||
171 | patchRadius = m_d->optionsWidget->getPatchRadius(); | ||||
172 | } | ||||
155 | 173 | | |||
156 | createInpaintMask(); | 174 | KisProcessingApplicator applicator( image(), currentNode(), KisProcessingApplicator::NONE, KisImageSignalVector() << ModifiedSignal, | ||
157 | viewManager->nodeManager()->slotNonUiActivatedNode(m_d->mask); | 175 | kundo2_i18n("Smart Patch")); | ||
158 | 176 | | |||
159 | //Collapse freehand drawing of the mask followed by inpaint operation into a single undo node | 177 | //actual inpaint operation. filling in areas masked by user | ||
160 | canvas()->shapeController()->resourceManager()->undoStack()->beginMacro(kundo2_i18n("Smart Patch")); | 178 | applicator.applyCommand( new InpaintCommand( KisPainter::convertToAlphaAsAlpha(m_d->maskDev), currentNode()->paintDevice(), accuracy, patchRadius ), | ||
179 | KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE ); | ||||
161 | 180 | | |||
181 | applicator.end(); | ||||
m_d.isNull() sounds weird. I don't think it is needed, especially, when you already accessed it in line 176 dkazakov: `m_d.isNull()` sounds weird. I don't think it is needed, especially, when you already accessed… | |||||
182 | image()->waitForDone(); | ||||
162 | 183 | | |||
163 | //User will be drawing on an alpha mask. Show color matching inpaint mask color. | 184 | QApplication::restoreOverrideCursor(); | ||
164 | m_d->currentFgColor = canvas()->resourceManager()->foregroundColor(); | 185 | m_d->maskDev->clear(); | ||
165 | canvas()->resourceManager()->setForegroundColor(KoColor(Qt::magenta, image()->colorSpace())); | | |||
166 | } | | |||
167 | KisToolFreehand::beginPrimaryAction(event); | | |||
168 | } else { | | |||
169 | viewManager-> | | |||
170 | showFloatingMessage( | | |||
171 | i18n("Select a paint layer to use this tool"), | | |||
172 | QIcon(), 2000, KisFloatingMessage::Medium, Qt::AlignCenter); | | |||
173 | } | | |||
174 | } | 186 | } | ||
175 | 187 | | |||
176 | void KisToolSmartPatch::continuePrimaryAction(KoPointerEvent *event) | 188 | QPainterPath KisToolSmartPatch::brushOutline( void ) | ||
177 | { | 189 | { | ||
178 | if (!m_d->mask.isNull()) | 190 | const qreal diameter = m_d->brushRadius; | ||
179 | KisToolFreehand::continuePrimaryAction(event); | 191 | QPainterPath outline; | ||
192 | outline.addEllipse(QPointF(0,0), -0.5 * diameter, -0.5 * diameter ); | ||||
193 | return outline; | ||||
180 | } | 194 | } | ||
As far as I can see waitForDone() is not needed here. Does InpaintCommand access any members of the tool from the inside? The only trouble I can see is m_d->maskDev and m_d->imageDev. But the former is already passed as a copy, so it won't be any problem. And m_d->imageDev is not used anywhere outside this function, so I guess it can be easily removed. Just pass currentNode()->paintDevice() directly. dkazakov: As far as I can see waitForDone() is not needed here. Does `InpaintCommand` access any members… | |||||
181 | 195 | | |||
182 | 196 | QPainterPath KisToolSmartPatch::getBrushOutlinePath(const QPointF &documentPos, | |||
183 | void KisToolSmartPatch::endPrimaryAction(KoPointerEvent *event) | 197 | const KoPointerEvent *event) | ||
184 | { | 198 | { | ||
185 | if (mode() != KisTool::PAINT_MODE) | 199 | Q_UNUSED(event); | ||
186 | return; | | |||
187 | 200 | | |||
188 | if (m_d->mask.isNull()) | 201 | QPointF imagePos = currentImage()->documentToPixel(documentPos); | ||
189 | return; | 202 | QPainterPath path = brushOutline(); | ||
190 | 203 | | |||
191 | KisToolFreehand::endPrimaryAction(event); | 204 | return path.translated( imagePos.rx(), imagePos.ry() ); | ||
205 | } | ||||
192 | 206 | | |||
193 | //Next line is important. We need to wait for the paint operation to finish otherwise | 207 | void KisToolSmartPatch::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event) | ||
194 | //mask will be incomplete. | 208 | { | ||
195 | image()->waitForDone(); | 209 | static QPointF lastDocPoint = QPointF(0,0); | ||
210 | if( event ) | ||||
211 | lastDocPoint=outlineDocPoint; | ||||
196 | 212 | | |||
197 | //User drew a mask on the temporary inpaint mask layer. Get this mask to pass to the inpaint algorithm | 213 | m_d->brushRadius = currentPaintOpPreset()->settings()->paintOpSize(); | ||
198 | m_d->maskDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); | 214 | m_d->brushOutline = getBrushOutlinePath(lastDocPoint, event); | ||
199 | 215 | | |||
200 | if (!m_d->mask.isNull()) { | 216 | QRectF outlinePixelRect = m_d->brushOutline.boundingRect(); | ||
201 | m_d->maskDev->makeCloneFrom(m_d->mask->paintDevice(), m_d->mask->paintDevice()->extent()); | 217 | QRectF outlineDocRect = currentImage()->pixelToDocument(outlinePixelRect); | ||
202 | 218 | | |||
203 | //Once we get the mask we delete the temporary layer on which user painted it | 219 | // This adjusted call is needed as we paint with a 3 pixel wide brush and the pen is outside the bounds of the path | ||
204 | deleteInpaintMask(); | 220 | // Pen uses view coordinates so we have to zoom the document value to match 2 pixel in view coordiates | ||
221 | // See BUG 275829 | ||||
222 | qreal zoomX; | ||||
223 | qreal zoomY; | ||||
224 | canvas()->viewConverter()->zoom(&zoomX, &zoomY); | ||||
225 | qreal xoffset = 2.0/zoomX; | ||||
226 | qreal yoffset = 2.0/zoomY; | ||||
205 | 227 | | |||
206 | image()->waitForDone(); | 228 | if (!outlineDocRect.isEmpty()) { | ||
207 | m_d->imageDev = currentNode()->paintDevice(); | 229 | outlineDocRect.adjust(-xoffset,-yoffset,xoffset,yoffset); | ||
230 | } | ||||
208 | 231 | | |||
209 | KisTransaction inpaintTransaction(kundo2_i18n("Inpaint Operation"), m_d->imageDev); | 232 | if (!m_d->oldOutlineRect.isEmpty()) { | ||
233 | canvas()->updateCanvas(m_d->oldOutlineRect); | ||||
234 | } | ||||
210 | 235 | | |||
211 | QApplication::setOverrideCursor(KisCursor::waitCursor()); | 236 | if (!outlineDocRect.isEmpty()) { | ||
237 | canvas()->updateCanvas(outlineDocRect); | ||||
238 | } | ||||
212 | 239 | | |||
213 | //actual inpaint operation. filling in areas masked by user | 240 | m_d->oldOutlineRect = outlineDocRect; | ||
214 | QRect changedRect = inpaintImage(m_d->maskDev, m_d->imageDev); | 241 | } | ||
215 | currentNode()->setDirty(changedRect); | | |||
216 | inpaintTransaction.commit(image()->undoAdapter()); | | |||
217 | 242 | | |||
218 | //Matching endmacro for inpaint operation | 243 | void KisToolSmartPatch::paint(QPainter &painter, const KoViewConverter &converter) | ||
219 | canvas()->shapeController()->resourceManager()->undoStack()->endMacro(); | 244 | { | ||
245 | Q_UNUSED(converter); | ||||
220 | 246 | | |||
221 | QApplication::restoreOverrideCursor(); | 247 | painter.save(); | ||
248 | painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); | ||||
249 | painter.setPen(QColor(128, 255, 128)); | ||||
250 | painter.drawPath(pixelToView(m_d->brushOutline)); | ||||
251 | painter.restore(); | ||||
222 | 252 | | |||
223 | canvas()->resourceManager()->setForegroundColor(m_d->currentFgColor); | 253 | painter.save(); | ||
254 | painter.setBrush(Qt::magenta); | ||||
255 | QImage img = m_d->maskDev->convertToQImage(0); | ||||
256 | if( !img.size().isEmpty() ){ | ||||
257 | painter.drawImage(pixelToView(m_d->maskDev->exactBounds()), img); | ||||
224 | } | 258 | } | ||
259 | painter.restore(); | ||||
225 | } | 260 | } | ||
226 | 261 | | |||
227 | QWidget * KisToolSmartPatch::createOptionWidget() | 262 | QWidget * KisToolSmartPatch::createOptionWidget() | ||
228 | { | 263 | { | ||
229 | KisCanvas2 * kiscanvas = dynamic_cast<KisCanvas2*>(canvas()); | 264 | KisCanvas2 * kiscanvas = dynamic_cast<KisCanvas2*>(canvas()); | ||
230 | 265 | | |||
231 | m_d->optionsWidget = new KisToolSmartPatchOptionsWidget(kiscanvas->viewManager()->resourceProvider(), 0); | 266 | m_d->optionsWidget = new KisToolSmartPatchOptionsWidget(kiscanvas->viewManager()->resourceProvider(), 0); | ||
232 | m_d->optionsWidget->setObjectName(toolId() + "option widget"); | 267 | m_d->optionsWidget->setObjectName(toolId() + "option widget"); | ||
233 | 268 | | |||
234 | return m_d->optionsWidget; | 269 | return m_d->optionsWidget; | ||
235 | } | 270 | } | ||
236 | 271 | |
Outch, we already have an undo system for pixel access operations:
You should use KisTransactionBasedCommand. You can check en example in e.g. ApplyToPixelSelection command. Basically, you should override paint() method of that command and return the transaction command from it. In pseudocode it looks like this: