diff --git a/libs/image/kis_circle_mask_generator.cpp b/libs/image/kis_circle_mask_generator.cpp index 356c5fc9ca..42f9405217 100644 --- a/libs/image/kis_circle_mask_generator.cpp +++ b/libs/image/kis_circle_mask_generator.cpp @@ -1,143 +1,142 @@ /* * Copyright (c) 2004,2007-2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2012 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wundef" #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #endif #include #include "kis_fast_math.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" #include "kis_base_mask_generator.h" #include "kis_brush_mask_applicator_factories.h" #include "kis_brush_mask_applicator_base.h" KisCircleMaskGenerator::KisCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, DefaultId), d(new Private) { setScale(1.0, 1.0); // store the variable locally to allow vector implementation read it easily d->copyOfAntialiasEdges = antialiasEdges; d->applicator.reset(createOptimizedClass >(this)); } KisCircleMaskGenerator::KisCircleMaskGenerator(const KisCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisCircleMaskGenerator::clone() const { return new KisCircleMaskGenerator(*this); } void KisCircleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->xcoef = 2.0 / effectiveSrcWidth(); d->ycoef = 2.0 / effectiveSrcHeight(); d->xfadecoef = qFuzzyCompare(horizontalFade(), 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth())); d->yfadecoef = qFuzzyCompare(verticalFade() , 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight())); - d->transformedFadeX = KisMaskGenerator::softness() * d->xfadecoef; - d->transformedFadeY = KisMaskGenerator::softness() * d->yfadecoef; - + d->transformedFadeX = d->xfadecoef * d->safeSoftnessCoeff; + d->transformedFadeY = d->yfadecoef * d->safeSoftnessCoeff; } KisCircleMaskGenerator::~KisCircleMaskGenerator() { } bool KisCircleMaskGenerator::shouldSupersample() const { return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } bool KisCircleMaskGenerator::shouldVectorize() const { return !shouldSupersample() && spikes() == 2; } KisBrushMaskApplicatorBase* KisCircleMaskGenerator::applicator() { return d->applicator.data(); } quint8 KisCircleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = (x /*- m_xcenter*/); qreal yr = qAbs(y /*- m_ycenter*/); fixRotation(xr, yr); qreal n = norme(xr * d->xcoef, yr * d->ycoef); if (n > 1.0) return 255; // we add +1.0 to ensure correct antialiasing on the border if (antialiasEdges()) { xr = qAbs(xr) + 1.0; yr = qAbs(yr) + 1.0; } qreal nf = norme(xr * d->transformedFadeX, yr * d->transformedFadeY); if (nf < 1.0) return 0; return 255 * n * (nf - 1.0) / (nf - n); } void KisCircleMaskGenerator::setSoftness(qreal softness) { KisMaskGenerator::setSoftness(softness); - qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); + d->safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); - d->transformedFadeX = d->xfadecoef * safeSoftnessCoeff; - d->transformedFadeY = d->yfadecoef * safeSoftnessCoeff; + d->transformedFadeX = d->xfadecoef * d->safeSoftnessCoeff; + d->transformedFadeY = d->yfadecoef * d->safeSoftnessCoeff; } void KisCircleMaskGenerator::resetMaskApplicator(bool forceScalar) { d->applicator.reset(createOptimizedClass >(this,forceScalar)); } diff --git a/libs/image/kis_circle_mask_generator_p.h b/libs/image/kis_circle_mask_generator_p.h index 096cfa3ad4..71445ace67 100644 --- a/libs/image/kis_circle_mask_generator_p.h +++ b/libs/image/kis_circle_mask_generator_p.h @@ -1,53 +1,56 @@ /* * Copyright (c) 2008-2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CIRCLE_MASK_GENERATOR_P_H_ #define _KIS_CIRCLE_MASK_GENERATOR_P_H_ struct Q_DECL_HIDDEN KisCircleMaskGenerator::Private { Private() : xcoef(0), ycoef(0), xfadecoef(0), yfadecoef(0), + safeSoftnessCoeff(1.0), transformedFadeX(0), transformedFadeY(0), copyOfAntialiasEdges(false) { } Private(const Private &rhs) : xcoef(rhs.xcoef), ycoef(rhs.ycoef), xfadecoef(rhs.xfadecoef), yfadecoef(rhs.yfadecoef), + safeSoftnessCoeff(rhs.safeSoftnessCoeff), transformedFadeX(rhs.transformedFadeX), transformedFadeY(rhs.transformedFadeY), copyOfAntialiasEdges(rhs.copyOfAntialiasEdges) { } double xcoef, ycoef; double xfadecoef, yfadecoef; + qreal safeSoftnessCoeff; double transformedFadeX, transformedFadeY; bool copyOfAntialiasEdges; QScopedPointer applicator; }; #endif /* _KIS_CIRCLE_MASK_GENERATOR_P_H_ */ diff --git a/libs/ui/widgets/KisSelectionPropertySlider.cpp b/libs/ui/widgets/KisSelectionPropertySlider.cpp index 59f737470d..b7282c955d 100644 --- a/libs/ui/widgets/KisSelectionPropertySlider.cpp +++ b/libs/ui/widgets/KisSelectionPropertySlider.cpp @@ -1,89 +1,89 @@ /* * Copyright (C) 2018 Jouni Pentikäinen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "KisSelectionPropertySlider.h" struct KisSelectionPropertySliderBase::Private { KisSignalCompressor *signalCompressor; QString normalPrefix; QString mixedPrefix; explicit Private(KisSelectionPropertySliderBase *q) : signalCompressor(new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE, q)) {} }; KisSelectionPropertySliderBase::KisSelectionPropertySliderBase(QWidget *parent) : KisDoubleSliderSpinBox(parent) , m_d(new Private(this)) { connect(m_d->signalCompressor, SIGNAL(timeout()), SLOT(slotCompressedUpdate())); } KisSelectionPropertySliderBase::~KisSelectionPropertySliderBase() {} void KisSelectionPropertySliderBase::setPrefixes(const QString &normalPrefix, const QString &mixedPrefix) { m_d->normalPrefix = normalPrefix; m_d->mixedPrefix = mixedPrefix; setPrefix(normalPrefix); } void KisSelectionPropertySliderBase::setInternalValue(int _value, bool blockUpdateSignal) { static const qreal eps = 1e-3; if (!hasSelection()) return; setPrivateValue(_value); const qreal newValue = value(); const qreal commonValue = getCommonValue(); if (qAbs(commonValue - newValue) < eps) { return; } if(!blockUpdateSignal) { m_d->signalCompressor->start(); } } void KisSelectionPropertySliderBase::slotCompressedUpdate() { emit(valueChanged(value())); } void KisSelectionPropertySliderBase::setSelectionValue(qreal commonValue, bool mixed) { if (mixed) { - setValue(0.0); + setInternalValue(0.0, true); // BUG:409131 setPrefix(m_d->mixedPrefix); } else { setValue(commonValue); setPrefix(m_d->normalPrefix); } } KisShapePropertySlider::KisShapePropertySlider(QWidget *parent) : KisSelectionPropertySlider::KisSelectionPropertySlider(parent) {} diff --git a/plugins/filters/tests/data/guassianhighpass.cfg b/plugins/filters/tests/data/guassianhighpass.cfg new file mode 100644 index 0000000000..9f6f48dde1 --- /dev/null +++ b/plugins/filters/tests/data/guassianhighpass.cfg @@ -0,0 +1,4 @@ + + + 1 + diff --git a/plugins/filters/tests/data/palettize.cfg b/plugins/filters/tests/data/palettize.cfg new file mode 100644 index 0000000000..b804a01583 --- /dev/null +++ b/plugins/filters/tests/data/palettize.cfg @@ -0,0 +1,22 @@ + + + 0.5 + 1189641421 + + 0 + 1 + 0 + true + 0 + 0 + 0 + 0 + 596516649 + 0.125 + + 0 + 1 + 0 + false + + diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp index 4008b502cc..2927af5fae 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -1,167 +1,172 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hairy_paintop.h" #include "kis_hairy_paintop_settings.h" #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include #include #include #include #include #include #include #include #include "kis_brush.h" KisHairyPaintOp::KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image) Q_ASSERT(settings); m_dev = node ? node->paintDevice() : 0; KisBrushOptionProperties brushOption; brushOption.readOptionSetting(settings); KisBrushSP brush = brushOption.brush(); KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace()); // properly initialize fake paint information to avoid warnings KisPaintInformation fakePaintInformation; fakePaintInformation.setRandomSource(new KisRandomSource()); fakePaintInformation.setPerStrokeRandomSource(new KisPerStrokeRandomSource()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), fakePaintInformation); } else { brush->mask(dab, painter->paintColor(), KisDabShape(), fakePaintInformation); } m_brush.fromDabWithDensity(dab, settings->getDouble(HAIRY_BRISTLE_DENSITY) * 0.01); m_brush.setInkColor(painter->paintColor()); loadSettings(static_cast(settings.data())); m_brush.setProperties(&m_properties); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); + + bool mirrorXAxis = settings->getBool("runtimeCanvasMirroredX", false); + bool mirrorYAxis = settings->getBool("runtimeCanvasMirroredY", false); + m_mirrorFlip = mirrorXAxis != mirrorYAxis; } + void KisHairyPaintOp::loadSettings(const KisBrushBasedPaintOpSettings *settings) { m_properties.inkAmount = settings->getInt(HAIRY_INK_AMOUNT); //TODO: wait for the transfer function with variable size m_properties.inkDepletionCurve = settings->getCubicCurve(HAIRY_INK_DEPLETION_CURVE).floatTransfer(m_properties.inkAmount); m_properties.inkDepletionEnabled = settings->getBool(HAIRY_INK_DEPLETION_ENABLED); m_properties.useSaturation = settings->getBool(HAIRY_INK_USE_SATURATION); m_properties.useOpacity = settings->getBool(HAIRY_INK_USE_OPACITY); m_properties.useWeights = settings->getBool(HAIRY_INK_USE_WEIGHTS); m_properties.pressureWeight = settings->getDouble(HAIRY_INK_PRESSURE_WEIGHT) / 100.0; m_properties.bristleLengthWeight = settings->getDouble(HAIRY_INK_BRISTLE_LENGTH_WEIGHT) / 100.0; m_properties.bristleInkAmountWeight = settings->getDouble(HAIRY_INK_BRISTLE_INK_AMOUNT_WEIGHT) / 100.0; m_properties.inkDepletionWeight = settings->getDouble(HAIRY_INK_DEPLETION_WEIGHT); m_properties.useSoakInk = settings->getBool(HAIRY_INK_SOAK); m_properties.useMousePressure = settings->getBool(HAIRY_BRISTLE_USE_MOUSEPRESSURE); m_properties.shearFactor = settings->getDouble(HAIRY_BRISTLE_SHEAR); m_properties.randomFactor = settings->getDouble(HAIRY_BRISTLE_RANDOM); m_properties.scaleFactor = settings->getDouble(HAIRY_BRISTLE_SCALE); m_properties.threshold = settings->getBool(HAIRY_BRISTLE_THRESHOLD); m_properties.antialias = settings->getBool(HAIRY_BRISTLE_ANTI_ALIASING); m_properties.useCompositing = settings->getBool(HAIRY_BRISTLE_USE_COMPOSITING); m_properties.connectedPath = settings->getBool(HAIRY_BRISTLE_CONNECTED); } KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisHairyPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(0.5); } void KisHairyPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } /** * Even though we don't use spacing in hairy brush, we should still * initialize its distance information to ensure drawing angle and * other history-based sensors work fine. */ KisPaintInformation pi(pi2); KisPaintInformation::DistanceInformationRegistrar r = pi.registerDistanceInformation(currentDistance); // Hairy Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(pi); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(pi); quint8 origOpacity = m_opacityOption.apply(painter(), pi); // we don't use spacing here (the brush itself is used only once // during initialization), so we should just skip the distance info // update - m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_properties.scaleFactor, rotation); + m_brush.paintLine(m_dab, m_dev, pi1, pi, scale * m_properties.scaleFactor, m_mirrorFlip? -rotation : rotation); //QRect rc = m_dab->exactBounds(); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); // we don't use spacing in hairy brush, but history is // still important for us currentDistance->registerPaintedDab(pi, KisSpacingInformation(), KisTimingInformation()); } diff --git a/plugins/paintops/hairy/kis_hairy_paintop.h b/plugins/paintops/hairy/kis_hairy_paintop.h index e9de44d51d..9bda32cbe2 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.h +++ b/plugins/paintops/hairy/kis_hairy_paintop.h @@ -1,63 +1,65 @@ /* * Copyright (c) 2008 Boudewijn Rempt * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_HAIRYPAINTOP_H_ #define KIS_HAIRYPAINTOP_H_ #include #include #include #include #include "hairy_brush.h" #include #include #include class KisPainter; class KisBrushBasedPaintOpSettings; class KisHairyPaintOp : public KisPaintOp { public: KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: KisHairyProperties m_properties; KisPaintDeviceSP m_dab; KisPaintDeviceSP m_dev; HairyBrush m_brush; KisPressureRotationOption m_rotationOption; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; + bool m_mirrorFlip; + void loadSettings(const KisBrushBasedPaintOpSettings* settings); }; #endif // KIS_HAIRYPAINTOP_H_ diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc index 0b8c97c8d9..ad6a3173a8 100644 --- a/plugins/tools/basictools/kis_tool_line.cc +++ b/plugins/tools/basictools/kis_tool_line.cc @@ -1,368 +1,369 @@ /* * kis_tool_line.cc - part of Krayon * * Copyright (c) 2000 John Califf * Copyright (c) 2002 Patrick Julien * Copyright (c) 2003 Boudewijn Rempt * Copyright (c) 2009 Lukáš Tvrdý * Copyright (c) 2007,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_line.h" #include #include #include #include #include #include #include #include #include #include #include "kis_figure_painting_tool_helper.h" #include "kis_canvas2.h" #include "kis_painting_information_builder.h" #include "kis_tool_line_helper.h" const KisCoordinatesConverter* getCoordinatesConverter(KoCanvasBase * canvas) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas); return kritaCanvas->coordinatesConverter(); } KisToolLine::KisToolLine(KoCanvasBase * canvas) : KisToolShape(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)), m_showGuideline(true), m_strokeIsRunning(false), m_infoBuilder(new KisConverterPaintingInformationBuilder(getCoordinatesConverter(canvas))), m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))), m_strokeUpdateCompressor(500, KisSignalCompressor::POSTPONE), m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE) { setObjectName("tool_line"); setSupportOutline(true); connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke())); } KisToolLine::~KisToolLine() { } void KisToolLine::resetCursorStyle() { KisToolPaint::resetCursorStyle(); overrideCursorIfNotEditable(); } void KisToolLine::activate(ToolActivation activation, const QSet &shapes) { KisToolPaint::activate(activation, shapes); configGroup = KSharedConfig::openConfig()->group(toolId()); } void KisToolLine::deactivate() { KisToolPaint::deactivate(); cancelStroke(); } QWidget* KisToolLine::createOptionWidget() { QWidget* widget = KisToolPaint::createOptionWidget(); m_chkUseSensors = new QCheckBox(i18n("Use sensors")); addOptionWidgetOption(m_chkUseSensors); m_chkShowPreview = new QCheckBox(i18n("Show Preview")); addOptionWidgetOption(m_chkShowPreview); m_chkShowGuideline = new QCheckBox(i18n("Show Guideline")); addOptionWidgetOption(m_chkShowGuideline); // hook up connections for value changing connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) ); connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) ); connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) ); // read values in from configuration m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true)); m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true)); m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true)); return widget; } void KisToolLine::setUseSensors(bool value) { configGroup.writeEntry("useSensors", value); } void KisToolLine::setShowGuideline(bool value) { m_showGuideline = value; configGroup.writeEntry("showGuideline", value); } void KisToolLine::setShowPreview(bool value) { configGroup.writeEntry("showPreview", value); } void KisToolLine::requestStrokeCancellation() { cancelStroke(); } void KisToolLine::requestStrokeEnd() { // Terminate any in-progress strokes if (nodePaintAbility() == PAINT && m_helper->isRunning()) { endStroke(); } } void KisToolLine::updatePreviewTimer(bool showGuideline) { // If the user disables the guideline, we will want to try to draw some // preview lines even if they're slow, so set the timer to FIRST_ACTIVE. if (showGuideline) { m_strokeUpdateCompressor.setMode(KisSignalCompressor::POSTPONE); } else { m_strokeUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE); } } void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if(mode() == KisTool::PAINT_MODE) { paintLine(gc,QRect()); } KisToolPaint::paint(gc,converter); } void KisToolLine::beginPrimaryAction(KoPointerEvent *event) { NodePaintAbility nodeAbility = nodePaintAbility(); if (nodeAbility == UNPAINTABLE || !nodeEditable()) { event->ignore(); return; } setMode(KisTool::PAINT_MODE); const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); // Always show guideline on vector layers m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT; updatePreviewTimer(m_showGuideline); m_helper->setEnabled((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape); m_helper->setUseSensors(m_chkUseSensors->isChecked()); m_helper->start(event, canvas()->resourceManager()); m_startPoint = convertToPixelCoordAndSnap(event); m_endPoint = m_startPoint; m_lastUpdatedPoint = m_startPoint; m_strokeIsRunning = true; } void KisToolLine::updateStroke() { if (!m_strokeIsRunning) return; m_helper->repaintLine(canvas()->resourceManager(), image(), currentNode(), image().data()); } void KisToolLine::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeIsRunning) return; // First ensure the old guideline is deleted updateGuideline(); QPointF pos = convertToPixelCoordAndSnap(event); if (event->modifiers() == Qt::AltModifier) { QPointF trans = pos - m_endPoint; m_helper->translatePoints(trans); m_startPoint += trans; m_endPoint += trans; } else if (event->modifiers() == Qt::ShiftModifier) { pos = straightLine(pos); m_helper->addPoint(event, pos); } else { m_helper->addPoint(event, pos); } m_endPoint = pos; // Draw preview if requested if (m_chkShowPreview->isChecked()) { // If the cursor has moved a significant amount, immediately clear the // current preview and redraw. Otherwise, do slow redraws periodically. auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength(); if (updateDistance > 10) { m_helper->clearPaint(); m_longStrokeUpdateCompressor.stop(); m_strokeUpdateCompressor.start(); m_lastUpdatedPoint = pos; } else if (updateDistance > 1) { m_longStrokeUpdateCompressor.start(); } } updateGuideline(); KisToolPaint::requestUpdateOutline(event->point, event); } void KisToolLine::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); updateGuideline(); endStroke(); } void KisToolLine::endStroke() { NodePaintAbility nodeAbility = nodePaintAbility(); if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == UNPAINTABLE) { + m_helper->clearPoints(); return; } const KisToolShape::ShapeAddInfo info = shouldAddShape(currentNode()); if ((nodeAbility == PAINT && !info.shouldAddShape) || info.shouldAddSelectionShape) { updateStroke(); m_helper->end(); } else { KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); QTransform resolutionMatrix; resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes()); path->moveTo(resolutionMatrix.map(m_startPoint)); path->lineTo(resolutionMatrix.map(m_endPoint)); path->normalize(); KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor())); path->setStroke(border); KUndo2Command * cmd = canvas()->shapeController()->addShape(path, 0); canvas()->addCommand(cmd); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } void KisToolLine::cancelStroke() { if (!m_strokeIsRunning) return; if (m_startPoint == m_endPoint) return; /** * The actual stroke is run by the timer so it is a legal * situation when m_strokeIsRunning is true, but the actual redraw * stroke is not running. */ if (m_helper->isRunning()) { m_helper->cancel(); } m_strokeIsRunning = false; m_endPoint = m_startPoint; } QPointF KisToolLine::straightLine(QPointF point) { const QPointF lineVector = point - m_startPoint; qreal lineAngle = std::atan2(lineVector.y(), lineVector.x()); if (lineAngle < 0) { lineAngle += 2 * M_PI; } const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24; const quint32 constrainedLineIndex = static_cast((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5); const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES; const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y())); const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle)); const QPointF result = m_startPoint + constrainedLineVector; return result; } void KisToolLine::updateGuideline() { if (canvas()) { QRectF bound(m_startPoint, m_endPoint); canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3))); } } void KisToolLine::paintLine(QPainter& gc, const QRect&) { QPointF viewStartPos = pixelToView(m_startPoint); QPointF viewStartEnd = pixelToView(m_endPoint); if (m_showGuideline && canvas()) { QPainterPath path; path.moveTo(viewStartPos); path.lineTo(viewStartEnd); paintToolOutline(&gc, path); } } QString KisToolLine::quickHelp() const { return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines"); } diff --git a/plugins/tools/basictools/kis_tool_line_helper.cpp b/plugins/tools/basictools/kis_tool_line_helper.cpp index 4e1726a3da..28a268a608 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.cpp +++ b/plugins/tools/basictools/kis_tool_line_helper.cpp @@ -1,184 +1,190 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_line_helper.h" #include "kis_algebra_2d.h" #include "kis_painting_information_builder.h" #include "kis_image.h" struct KisToolLineHelper::Private { Private(KisPaintingInformationBuilder *_infoBuilder) : infoBuilder(_infoBuilder), useSensors(true), enabled(true) { } QVector linePoints; KisPaintingInformationBuilder *infoBuilder; bool useSensors; bool enabled; }; KisToolLineHelper::KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText) : KisToolFreehandHelper(infoBuilder, transactionText, new KisSmoothingOptions(false)), m_d(new Private(infoBuilder)) { } KisToolLineHelper::~KisToolLineHelper() { delete m_d; } void KisToolLineHelper::setEnabled(bool value) { m_d->enabled = value; } void KisToolLineHelper::setUseSensors(bool value) { m_d->useSensors = value; } void KisToolLineHelper::repaintLine(KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade) { if (!m_d->enabled) return; cancelPaint(); if (m_d->linePoints.isEmpty()) return; qreal startAngle = 0.0; if (m_d->linePoints.length() > 1) { startAngle = KisAlgebra2D::directionBetweenPoints(m_d->linePoints[0].pos(), m_d->linePoints[1].pos(), 0.0); } QVector::const_iterator it = m_d->linePoints.constBegin(); QVector::const_iterator end = m_d->linePoints.constEnd(); initPaintImpl(startAngle, *it, resourceManager, image, node, strokesFacade); ++it; while (it != end) { paintLine(*(it - 1), *it); ++it; } } void KisToolLineHelper::start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->startStroke(event, 0, resourceManager); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } m_d->linePoints.append(pi); } void KisToolLineHelper::addPoint(KoPointerEvent *event, const QPointF &overridePos) { if (!m_d->enabled) return; // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->continueStroke(event, 0); if (!m_d->useSensors) { pi = KisPaintInformation(pi.pos()); } if (!overridePos.isNull()) { pi.setPos(overridePos); } if (m_d->linePoints.size() > 1) { const QPointF startPos = m_d->linePoints.first().pos(); const QPointF endPos = pi.pos(); const qreal maxDistance = kisDistance(startPos, endPos); const QPointF unit = (endPos - startPos) / maxDistance; QVector::iterator it = m_d->linePoints.begin(); ++it; while (it != m_d->linePoints.end()) { qreal dist = kisDistance(startPos, it->pos()); if (dist < maxDistance) { QPointF pos = startPos + unit * dist; it->setPos(pos); ++it; } else { it = m_d->linePoints.erase(it); } } } m_d->linePoints.append(pi); } void KisToolLineHelper::translatePoints(const QPointF &offset) { if (!m_d->enabled) return; QVector::iterator it = m_d->linePoints.begin(); while (it != m_d->linePoints.end()) { it->setPos(it->pos() + offset); ++it; } } void KisToolLineHelper::end() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); endPaint(); - m_d->linePoints.clear(); + clearPoints(); } void KisToolLineHelper::cancel() { if (!m_d->enabled) return; KIS_ASSERT_RECOVER_RETURN(isRunning()); cancelPaint(); + clearPoints(); +} + + +void KisToolLineHelper::clearPoints() +{ m_d->linePoints.clear(); } void KisToolLineHelper::clearPaint() { if (!m_d->enabled) return; cancelPaint(); } diff --git a/plugins/tools/basictools/kis_tool_line_helper.h b/plugins/tools/basictools/kis_tool_line_helper.h index 8835c6a02a..13e3863e2c 100644 --- a/plugins/tools/basictools/kis_tool_line_helper.h +++ b/plugins/tools/basictools/kis_tool_line_helper.h @@ -1,55 +1,56 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KIS_TOOL_LINE_HELPER_H #define __KIS_TOOL_LINE_HELPER_H #include "kis_tool_freehand_helper.h" class KisToolLineHelper : private KisToolFreehandHelper { public: KisToolLineHelper(KisPaintingInformationBuilder *infoBuilder, const KUndo2MagicString &transactionText); ~KisToolLineHelper() override; void setEnabled(bool value); void setUseSensors(bool value); void repaintLine(KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade); void start(KoPointerEvent *event, KoCanvasResourceProvider *resourceManager); void addPoint(KoPointerEvent *event, const QPointF &overridePos = QPointF()); void translatePoints(const QPointF &offset); void end(); void cancel(); + void clearPoints(); void clearPaint(); using KisToolFreehandHelper::isRunning; private: struct Private; Private * const m_d; }; #endif /* __KIS_TOOL_LINE_HELPER_H */