Changeset View
Changeset View
Standalone View
Standalone View
plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp
Show All 32 Lines | |||||
33 | #include <kis_painter.h> | 33 | #include <kis_painter.h> | ||
34 | #include <kis_image.h> | 34 | #include <kis_image.h> | ||
35 | #include <kis_selection.h> | 35 | #include <kis_selection.h> | ||
36 | #include <kis_brush_based_paintop_settings.h> | 36 | #include <kis_brush_based_paintop_settings.h> | ||
37 | #include <kis_cross_device_color_picker.h> | 37 | #include <kis_cross_device_color_picker.h> | ||
38 | #include <kis_fixed_paint_device.h> | 38 | #include <kis_fixed_paint_device.h> | ||
39 | #include <kis_lod_transform.h> | 39 | #include <kis_lod_transform.h> | ||
40 | #include <kis_spacing_information.h> | 40 | #include <kis_spacing_information.h> | ||
41 | #include <KoColorModelStandardIds.h> | ||||
41 | 42 | | |||
42 | 43 | | |||
43 | KisColorSmudgeOp::KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) | 44 | KisColorSmudgeOp::KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image) | ||
44 | : KisBrushBasedPaintOp(settings, painter) | 45 | : KisBrushBasedPaintOp(settings, painter) | ||
45 | , m_firstRun(true) | 46 | , m_firstRun(true) | ||
46 | , m_image(image) | 47 | , m_image(image) | ||
47 | , m_tempDev(painter->device()->createCompositionSourceDevice()) | 48 | , m_preciseWrapper(painter->device()) | ||
49 | , m_tempDev(m_preciseWrapper.preciseDevice()->createCompositionSourceDevice()) | ||||
48 | , m_backgroundPainter(new KisPainter(m_tempDev)) | 50 | , m_backgroundPainter(new KisPainter(m_tempDev)) | ||
49 | , m_smudgePainter(new KisPainter(m_tempDev)) | 51 | , m_smudgePainter(new KisPainter(m_tempDev)) | ||
50 | , m_colorRatePainter(new KisPainter(m_tempDev)) | 52 | , m_colorRatePainter(new KisPainter(m_tempDev)) | ||
53 | , m_finalPainter(new KisPainter(m_preciseWrapper.preciseDevice())) | ||||
51 | , m_smudgeRateOption() | 54 | , m_smudgeRateOption() | ||
52 | , m_colorRateOption("ColorRate", KisPaintOpOption::GENERAL, false) | 55 | , m_colorRateOption("ColorRate", KisPaintOpOption::GENERAL, false) | ||
53 | , m_smudgeRadiusOption() | 56 | , m_smudgeRadiusOption() | ||
54 | { | 57 | { | ||
55 | Q_UNUSED(node); | 58 | Q_UNUSED(node); | ||
56 | 59 | | |||
57 | Q_ASSERT(settings); | 60 | Q_ASSERT(settings); | ||
58 | Q_ASSERT(painter); | 61 | Q_ASSERT(painter); | ||
Show All 20 Lines | |||||
79 | m_gradientOption.resetAllSensors(); | 82 | m_gradientOption.resetAllSensors(); | ||
80 | 83 | | |||
81 | m_gradient = painter->gradient(); | 84 | m_gradient = painter->gradient(); | ||
82 | 85 | | |||
83 | m_backgroundPainter->setCompositeOp(COMPOSITE_COPY); | 86 | m_backgroundPainter->setCompositeOp(COMPOSITE_COPY); | ||
84 | // Smudge Painter works in default COMPOSITE_OVER mode | 87 | // Smudge Painter works in default COMPOSITE_OVER mode | ||
85 | m_colorRatePainter->setCompositeOp(painter->compositeOp()->id()); | 88 | m_colorRatePainter->setCompositeOp(painter->compositeOp()->id()); | ||
86 | 89 | | |||
90 | m_finalPainter->setCompositeOp(COMPOSITE_COPY); | ||||
91 | m_finalPainter->setSelection(painter->selection()); | ||||
92 | m_finalPainter->setChannelFlags(painter->channelFlags()); | ||||
93 | m_finalPainter->copyMirrorInformationFrom(painter); | ||||
94 | | ||||
95 | m_paintColor = painter->paintColor().convertedTo(m_preciseWrapper.preciseColorSpace()); | ||||
96 | m_preciseColorRateCompositeOp = m_preciseWrapper.preciseColorSpace()->compositeOp(COMPOSITE_COPY); | ||||
97 | | ||||
87 | m_rotationOption.applyFanCornersInfo(this); | 98 | m_rotationOption.applyFanCornersInfo(this); | ||
88 | } | 99 | } | ||
89 | 100 | | |||
90 | KisColorSmudgeOp::~KisColorSmudgeOp() | 101 | KisColorSmudgeOp::~KisColorSmudgeOp() | ||
91 | { | 102 | { | ||
92 | delete m_backgroundPainter; | | |||
93 | delete m_colorRatePainter; | | |||
94 | delete m_smudgePainter; | | |||
95 | } | 103 | } | ||
96 | 104 | | |||
97 | void KisColorSmudgeOp::updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint) | 105 | void KisColorSmudgeOp::updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint) | ||
98 | { | 106 | { | ||
99 | static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); | 107 | static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); | ||
100 | static KoColor color(Qt::black, cs); | 108 | static KoColor color(Qt::black, cs); | ||
101 | 109 | | |||
102 | m_maskDab = m_dabCache->fetchDab(cs, | 110 | m_maskDab = m_dabCache->fetchDab(cs, | ||
▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Line(s) | 201 | KisSpacingInformation spacingInfo = | |||
194 | effectiveSpacing(scale, rotation, | 202 | effectiveSpacing(scale, rotation, | ||
195 | m_spacingOption, info); | 203 | m_spacingOption, info); | ||
196 | 204 | | |||
197 | if (m_firstRun) { | 205 | if (m_firstRun) { | ||
198 | m_firstRun = false; | 206 | m_firstRun = false; | ||
199 | return spacingInfo; | 207 | return spacingInfo; | ||
200 | } | 208 | } | ||
201 | 209 | | |||
202 | // save the old opacity value and composite mode | 210 | const qreal fpOpacity = (qreal(painter()->opacity()) / 255.0) * m_opacityOption.getOpacityf(info); | ||
203 | quint8 oldOpacity = painter()->opacity(); | | |||
204 | QString oldCompositeOpId = painter()->compositeOp()->id(); | | |||
205 | qreal fpOpacity = (qreal(oldOpacity) / 255.0) * m_opacityOption.getOpacityf(info); | | |||
206 | 211 | | |||
207 | if (m_image && m_overlayModeOption.isChecked()) { | 212 | if (m_image && m_overlayModeOption.isChecked()) { | ||
208 | m_image->blockUpdates(); | 213 | m_image->blockUpdates(); | ||
209 | m_backgroundPainter->bitBlt(QPoint(), m_image->projection(), srcDabRect); | 214 | m_backgroundPainter->bitBlt(QPoint(), m_image->projection(), srcDabRect); | ||
210 | m_image->unblockUpdates(); | 215 | m_image->unblockUpdates(); | ||
211 | } | 216 | } | ||
212 | else { | 217 | else { | ||
213 | // IMPORTANT: clear the temporary painting device to color black with zero opacity: | 218 | // IMPORTANT: clear the temporary painting device to color black with zero opacity: | ||
214 | // it will only clear the extents of the brush. | 219 | // it will only clear the extents of the brush. | ||
215 | m_tempDev->clear(QRect(QPoint(), m_dstDabRect.size())); | 220 | m_tempDev->clear(QRect(QPoint(), m_dstDabRect.size())); | ||
216 | } | 221 | } | ||
217 | 222 | | |||
218 | if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { | 223 | const bool useDullingMode = m_smudgeRateOption.getMode() == KisSmudgeOption::DULLING_MODE; | ||
219 | m_smudgePainter->bitBlt(QPoint(), painter()->device(), srcDabRect); | 224 | | ||
225 | // stored in the color space of the paintColor | ||||
226 | KoColor dullingFillColor = m_paintColor; | ||||
227 | | ||||
228 | if (!useDullingMode) { | ||||
229 | m_preciseWrapper.readRect(srcDabRect); | ||||
230 | m_smudgePainter->bitBlt(QPoint(), m_preciseWrapper.preciseDevice(), srcDabRect); | ||||
220 | } else { | 231 | } else { | ||
221 | QPoint pt = (srcDabRect.topLeft() + hotSpot).toPoint(); | 232 | QPoint pt = (srcDabRect.topLeft() + hotSpot).toPoint(); | ||
222 | 233 | | |||
223 | if (m_smudgeRadiusOption.isChecked()) { | 234 | if (m_smudgeRadiusOption.isChecked()) { | ||
224 | qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height()); | 235 | const qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height()); | ||
225 | m_smudgeRadiusOption.apply(*m_smudgePainter, info, effectiveSize, pt.x(), pt.y(), painter()->device()); | | |||
226 | 236 | | |||
227 | KoColor color2 = m_smudgePainter->paintColor(); | 237 | const QRect sampleRect = m_smudgeRadiusOption.sampleRect(info, effectiveSize, pt); | ||
228 | m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color2); | 238 | m_preciseWrapper.readRect(sampleRect); | ||
229 | 239 | | |||
230 | } else { | 240 | m_smudgeRadiusOption.apply(&dullingFillColor, info, effectiveSize, pt.x(), pt.y(), m_preciseWrapper.preciseDevice()); | ||
231 | KoColor color = painter()->paintColor(); | 241 | KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); | ||
232 | 242 | | |||
243 | } else { | ||||
233 | // get the pixel on the canvas that lies beneath the hot spot | 244 | // get the pixel on the canvas that lies beneath the hot spot | ||
234 | // of the dab and fill the temporary paint device with that color | 245 | // of the dab and fill the temporary paint device with that color | ||
235 | 246 | | |||
236 | KisCrossDeviceColorPickerInt colorPicker(painter()->device(), color); | 247 | m_preciseWrapper.readRect(QRect(pt, QSize(1,1))); | ||
237 | colorPicker.pickColor(pt.x(), pt.y(), color.data()); | 248 | KisCrossDeviceColorPickerInt colorPicker(m_preciseWrapper.preciseDevice(), dullingFillColor); | ||
238 | 249 | colorPicker.pickColor(pt.x(), pt.y(), dullingFillColor.data()); | |||
239 | m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); | 250 | KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); | ||
240 | } | 251 | } | ||
241 | } | 252 | } | ||
242 | 253 | | |||
243 | // if the user selected the color smudge option, | 254 | // if the user selected the color smudge option, | ||
244 | // we will mix some color into the temporary painting device (m_tempDev) | 255 | // we will mix some color into the temporary painting device (m_tempDev) | ||
245 | if (m_colorRateOption.isChecked()) { | 256 | if (m_colorRateOption.isChecked()) { | ||
246 | // this will apply the opacity (selected by the user) to copyPainter | 257 | // this will apply the opacity (selected by the user) to copyPainter | ||
247 | // (but fit the rate inbetween the range 0.0 to (1.0-SmudgeRate)) | 258 | // (but fit the rate inbetween the range 0.0 to (1.0-SmudgeRate)) | ||
248 | qreal maxColorRate = qMax<qreal>(1.0 - m_smudgeRateOption.getRate(), 0.2); | 259 | qreal maxColorRate = qMax<qreal>(1.0 - m_smudgeRateOption.getRate(), 0.2); | ||
249 | m_colorRateOption.apply(*m_colorRatePainter, info, 0.0, maxColorRate, fpOpacity); | 260 | m_colorRateOption.apply(*m_colorRatePainter, info, 0.0, maxColorRate, fpOpacity); | ||
250 | 261 | | |||
251 | // paint a rectangle with the current color (foreground color) | 262 | // paint a rectangle with the current color (foreground color) | ||
252 | // or a gradient color (if enabled) | 263 | // or a gradient color (if enabled) | ||
253 | // into the temporary painting device and use the user selected | 264 | // into the temporary painting device and use the user selected | ||
254 | // composite mode | 265 | // composite mode | ||
255 | KoColor color = painter()->paintColor(); | 266 | KoColor color = m_paintColor; | ||
256 | m_gradientOption.apply(color, m_gradient, info); | 267 | m_gradientOption.apply(color, m_gradient, info); | ||
268 | | ||||
269 | if (!useDullingMode) { | ||||
270 | KIS_SAFE_ASSERT_RECOVER(*m_colorRatePainter->device()->colorSpace() == *color.colorSpace()) { | ||||
271 | color.convertTo(m_colorRatePainter->device()->colorSpace()); | ||||
272 | } | ||||
273 | | ||||
257 | m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); | 274 | m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); | ||
275 | } else { | ||||
276 | KIS_SAFE_ASSERT_RECOVER(*dullingFillColor.colorSpace() == *color.colorSpace()) { | ||||
277 | color.convertTo(dullingFillColor.colorSpace()); | ||||
278 | } | ||||
279 | KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); | ||||
280 | | ||||
281 | m_preciseColorRateCompositeOp->composite(dullingFillColor.data(), 0, | ||||
282 | color.data(), 0, | ||||
283 | 0, 0, | ||||
284 | 1, 1, | ||||
285 | m_colorRatePainter->opacity()); | ||||
286 | } | ||||
258 | } | 287 | } | ||
259 | 288 | | |||
289 | if (useDullingMode) { | ||||
290 | KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); | ||||
291 | m_tempDev->fill(QRect(0, 0, m_dstDabRect.width(), m_dstDabRect.height()), dullingFillColor); | ||||
292 | } | ||||
293 | | ||||
294 | m_preciseWrapper.readRects(m_finalPainter->calculateAllMirroredRects(m_dstDabRect)); | ||||
295 | | ||||
260 | // if color is disabled (only smudge) and "overlay mode" is enabled | 296 | // if color is disabled (only smudge) and "overlay mode" is enabled | ||
261 | // then first blit the region under the brush from the image projection | 297 | // then first blit the region under the brush from the image projection | ||
262 | // to the painting device to prevent a rapid build up of alpha value | 298 | // to the painting device to prevent a rapid build up of alpha value | ||
263 | // if the color to be smudged is semi transparent. | 299 | // if the color to be smudged is semi transparent. | ||
264 | if (m_image && m_overlayModeOption.isChecked() && !m_colorRateOption.isChecked()) { | 300 | if (m_image && m_overlayModeOption.isChecked() && !m_colorRateOption.isChecked()) { | ||
265 | painter()->setCompositeOp(COMPOSITE_COPY); | 301 | m_finalPainter->setOpacity(OPACITY_OPAQUE_U8); | ||
266 | painter()->setOpacity(OPACITY_OPAQUE_U8); | | |||
267 | m_image->blockUpdates(); | 302 | m_image->blockUpdates(); | ||
268 | painter()->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); | 303 | // TODO: check if this code is correct in mirrored mode! Technically, the | ||
304 | // painter renders the mirrored dab only, so we should also prepare | ||||
305 | // the overlay for it in all the places. | ||||
306 | m_finalPainter->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); | ||||
269 | m_image->unblockUpdates(); | 307 | m_image->unblockUpdates(); | ||
270 | } | 308 | } | ||
271 | 309 | | |||
272 | 310 | | |||
273 | // set opacity calculated by the rate option | 311 | // set opacity calculated by the rate option | ||
274 | m_smudgeRateOption.apply(*painter(), info, 0.0, 1.0, fpOpacity); | 312 | m_smudgeRateOption.apply(*m_finalPainter, info, 0.0, 1.0, fpOpacity); | ||
275 | 313 | | |||
276 | // then blit the temporary painting device on the canvas at the current brush position | 314 | // then blit the temporary painting device on the canvas at the current brush position | ||
277 | // the alpha mask (maskDab) will be used here to only blit the pixels that are in the area (shape) of the brush | 315 | // the alpha mask (maskDab) will be used here to only blit the pixels that are in the area (shape) of the brush | ||
278 | 316 | | |||
279 | painter()->setCompositeOp(COMPOSITE_COPY); | 317 | m_finalPainter->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); | ||
280 | painter()->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); | 318 | m_finalPainter->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); | ||
281 | painter()->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); | 319 | | ||
282 | 320 | const QVector<QRect> dirtyRects = m_finalPainter->takeDirtyRegion(); | |||
283 | // restore orginal opacy and composite mode values | 321 | m_preciseWrapper.writeRects(dirtyRects); | ||
woltherav: One teensy problem here: The composite opp doesn't apply. So that means I tried to use "erase"… | |||||
284 | painter()->setOpacity(oldOpacity); | 322 | | ||
285 | painter()->setCompositeOp(oldCompositeOpId); | 323 | painter()->addDirtyRects(dirtyRects); | ||
286 | 324 | | |||
287 | return spacingInfo; | 325 | return spacingInfo; | ||
288 | } | 326 | } | ||
289 | 327 | | |||
290 | KisSpacingInformation KisColorSmudgeOp::updateSpacingImpl(const KisPaintInformation &info) const | 328 | KisSpacingInformation KisColorSmudgeOp::updateSpacingImpl(const KisPaintInformation &info) const | ||
291 | { | 329 | { | ||
292 | const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); | 330 | const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); | ||
293 | const qreal rotation = m_rotationOption.apply(info); | 331 | const qreal rotation = m_rotationOption.apply(info); | ||
294 | return effectiveSpacing(scale, rotation, m_spacingOption, info); | 332 | return effectiveSpacing(scale, rotation, m_spacingOption, info); | ||
295 | } | 333 | } |
One teensy problem here: The composite opp doesn't apply. So that means I tried to use "erase" and suddenly it didn't work.