diff --git a/plugins/paintops/hatching/hatching_brush.cpp b/plugins/paintops/hatching/hatching_brush.cpp index 547f262396..be18590707 100644 --- a/plugins/paintops/hatching/hatching_brush.cpp +++ b/plugins/paintops/hatching/hatching_brush.cpp @@ -1,299 +1,290 @@ /* * Copyright (c) 2008,2009,2010 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * 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 "hatching_brush.h" #include #include #include #include #include "kis_random_accessor_ng.h" #include #include void inline myround(double *x) { *x = ((*x - floor(*x)) >= 0.5) ? ceil(*x) : floor(*x); } HatchingBrush::HatchingBrush(KisHatchingPaintOpSettingsSP settings) + : m_settings(settings) + , separation(m_settings->separation) + , origin_x(m_settings->origin_x) + , origin_y(m_settings->origin_y) { - m_settings = settings; - - // Initializing - separation = m_settings->separation; - origin_x = m_settings->origin_x; - origin_y = m_settings->origin_y; - cursorLineIntercept = 0; - baseLineIntercept = 0; - scanIntercept = 0; - hotIntercept = 0; - slope = 0; - dx = 0; - dy = 0; } HatchingBrush::~HatchingBrush() { } void HatchingBrush::init() { } void HatchingBrush::hatch(KisPaintDeviceSP dev, qreal x, qreal y, double width, double height, double givenangle, const KoColor &color, qreal additionalScale) { m_painter.begin(dev); m_painter.setFillStyle(KisPainter::FillStyleForegroundColor); m_painter.setPaintColor(color); m_painter.setBackgroundColor(color); angle = givenangle; double tempthickness = m_settings->thickness * m_settings->thicknesssensorvalue; thickness = qMax(1, qRound(additionalScale * tempthickness)); separation = additionalScale * (m_settings->enabledcurveseparation ? separationAsFunctionOfParameter(m_settings->separationsensorvalue, m_settings->separation, m_settings->separationintervals) : m_settings->separation); height_ = height; width_ = width; m_painter.setMaskImageSize(width_, height_); /* dx and dy are the separation between lines in the x and y axis dx = separation / sin(angle*M_PI/180); csc = 1/sin(angle) */ dy = fabs(separation / cos(angle * M_PI / 180)); // sec = 1/cos(angle) // I took the absolute value to avoid confusions with negative numbers if (!m_settings->subpixelprecision) modf(dy, &dy); // Exception for vertical lines, for which a tangent does not exist if ((angle == 90) || (angle == -90)) { verticalHotX = fmod((origin_x - x), separation); iterateVerticalLines(true, 1, false); // Forward iterateVerticalLines(true, 0, true); // In Between both iterateVerticalLines(false, 1, false); // Backward } else { // Turn Angle + Point into Slope + Intercept slope = tan(angle * M_PI / 180); // Angle into slope baseLineIntercept = origin_y - slope * origin_x; // Slope and Point of the Base Line into Intercept cursorLineIntercept = y - slope * x; hotIntercept = fmod((baseLineIntercept - cursorLineIntercept), dy); // This hotIntercept belongs to a line that intersects with the hatching area iterateLines(true, 1, false); // Forward iterateLines(true, 0, true); // In Between both iterateLines(false, 1, false); // Backward // I tried to make this cleaner but there's too many possibilities to be // worth the micromanagement to optimize } } void HatchingBrush::iterateLines(bool forward, int lineindex, bool oneline) { //---Preparations before the loop--- double xdraw[2] = {0, 0}; double ydraw[2] = {0, 0}; //points A and B of the segments to trace QPointF A, B; int append_index = 0; bool remaininginnerlines = true; while (remaininginnerlines) { //---------START INTERSECTION POINT VERIFICATION-------- append_index = 0; remaininginnerlines = false; // We assume there's no more lines unless proven contrary if (forward) scanIntercept = hotIntercept + dy * lineindex; // scanIntercept will represent the Intercept of the current line else scanIntercept = hotIntercept - dy * lineindex; // scanIntercept will represent the Intercept of the current line lineindex++; // We are descending vertically out of convenience, see blog entry at pentalis.org/kritablog /* Explanation: only 2 out of the 4 segments can include limit values to verify intersections, otherwise we could encounter a situation where our new lines intersect with all 4 segments and is still considered an inner line (for example, a line that goes from corner to corner), thus triggering an error. The idea is of the algorithm is that only 2 intersections at most are considered at a time. Graphically this is indistinguishable, it's just there to avoid making unnecessary control structures (like additional "ifs"). */ if ((scanIntercept >= 0) && (scanIntercept <= height_)) { xdraw[append_index] = 0; ydraw[append_index] = scanIntercept; //interseccion at left remaininginnerlines = true; append_index++; } if ((slope * width_ + scanIntercept <= height_) && (slope * width_ + scanIntercept >= 0)) { xdraw[append_index] = width_; ydraw[append_index] = scanIntercept + slope * width_; //interseccion at right remaininginnerlines = true; append_index++; } if ((-scanIntercept / slope > 0) && (-scanIntercept / slope < width_)) { xdraw[append_index] = -scanIntercept / slope; ydraw[append_index] = 0; //interseccion at top remaininginnerlines = true; append_index++; } if (((height_ - scanIntercept) / slope > 0) && ((height_ - scanIntercept) / slope < width_)) { xdraw[append_index] = (height_ - scanIntercept) / slope; ydraw[append_index] = height_; //interseccion at bottom remaininginnerlines = true; append_index++; } //--------END INTERSECTION POINT VERIFICATION--------- if (!remaininginnerlines) break; if (!m_settings->subpixelprecision) { myround(&xdraw[0]); myround(&xdraw[1]); myround(&ydraw[0]); myround(&ydraw[1]); } A.setX(xdraw[0]); A.setY(ydraw[0]); // If 2 lines intersect with the dab square if (append_index == 2) { B.setX(xdraw[1]); B.setY(ydraw[1]); if (m_settings->antialias) m_painter.drawThickLine(A, B, thickness, thickness); else m_painter.drawLine(A, B, thickness, false); //testing no subpixel; if (oneline) break; } else { continue; /*Drawing points at the vertices causes inconsistent results due to floating point calculations not being quite in sync with algebra, therefore if I have only 1 intersection (= corner = this case), don't draw*/ } } } void HatchingBrush::iterateVerticalLines(bool forward, int lineindex, bool oneline) { //---Preparations before the loop--- double xdraw = 0; double ydraw[2] = {0, height_}; //points A and B of the segments to trace QPointF A, B; bool remaininginnerlines = true; while (remaininginnerlines) { //---------START INTERSECTION POINT VERIFICATION-------- remaininginnerlines = false; // We assume there's no more lines unless proven contrary if (forward) verticalScanX = verticalHotX + separation * lineindex; else verticalScanX = verticalHotX - separation * lineindex; lineindex++; /*Read the explanation in HatchingBrush::iterateLines for more information*/ if ((verticalScanX >= 0) && (verticalScanX <= width_)) { xdraw = verticalScanX; remaininginnerlines = true; } //--------END INTERSECTION POINT VERIFICATION--------- if (!remaininginnerlines) break; if (!m_settings->subpixelprecision) { myround(&xdraw); myround(&ydraw[1]); } A.setX(xdraw); A.setY(ydraw[0]); B.setX(xdraw); B.setY(ydraw[1]); if (m_settings->antialias) m_painter.drawThickLine(A, B, thickness, thickness); else m_painter.drawLine(A, B, thickness, false); //testing no subpixel; if (oneline) break; else continue; } } double HatchingBrush::separationAsFunctionOfParameter(double parameter, double separation, int numintervals) { if ((numintervals < 2) || (numintervals > 7)) { dbgKrita << "Fix your function" << numintervals << "<> 2-7" ; return separation; } double sizeinterval = 1 / double(numintervals); double lowerlimit = 0; double upperlimit = 0; double factor = 0; int basefactor = numintervals / 2; // Make the base separation factor tend to greater instead of lesser numbers when numintervals is even if ((numintervals % 2) == 0) basefactor--; for (quint8 currentinterval = 0; currentinterval < numintervals; currentinterval++) { lowerlimit = upperlimit; upperlimit += sizeinterval; if (currentinterval == (numintervals - 1)) upperlimit = 1; if ((parameter >= lowerlimit) && (parameter <= upperlimit)) { factor = pow(2.0, (basefactor - currentinterval)); //dbgKrita << factor; return (separation * factor); } } dbgKrita << "Fix your function" << parameter << ">" << upperlimit ; return separation; } diff --git a/plugins/paintops/hatching/hatching_brush.h b/plugins/paintops/hatching/hatching_brush.h index 5b1a69e3a0..3a98e36ddd 100644 --- a/plugins/paintops/hatching/hatching_brush.h +++ b/plugins/paintops/hatching/hatching_brush.h @@ -1,134 +1,134 @@ /* * Copyright (c) 2008,2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * 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 HATCHING_BRUSH_H_ #define HATCHING_BRUSH_H_ #include #include "kis_hatching_paintop_settings.h" #include #include #include #include "kis_hatching_options.h" class HatchingBrush { public: HatchingBrush(KisHatchingPaintOpSettingsSP settings); ~HatchingBrush(); HatchingBrush(KoColor inkColor); /** * Performs a single hatching pass according to specifications */ void hatch(KisPaintDeviceSP dev, qreal x, qreal y, double width, double height, double givenangle, const KoColor &color, qreal additionalScale); private: void init(); KoColor m_inkColor; int m_counter; int m_radius; KisHatchingPaintOpSettingsSP m_settings; KisPainter m_painter; /** Thickness in pixels of each hatch line */ int thickness; /** Angle in degrees of all the lines in a single hatching pass*/ double angle; /** Distance separating one line from the other, in pixels */ double separation; /** Height of the imaginary square to be hatched, the "hatching area" */ double height_; /** Width of the imaginary square to be hatched, the "hatching area" */ double width_; /** X coordinate of the point that determines the base line */ double origin_x; /** Y coordinate of the point that determines the base line */ double origin_y; /** Intercept of the base line */ - double baseLineIntercept; + double baseLineIntercept {0}; /** Intercept of the first line _found to_ pass or be neighbour of a line * that passes through the hatching area, this line is used as a base * to start iterating with HatchingBrush::iterateLines() */ - double hotIntercept; + double hotIntercept {0}; /** Intercept of each line as it is scanned, this value changes constantly */ - double scanIntercept; + double scanIntercept {0}; /** X position of the first vertical line _found to_ pass or be neighbour * of a line that passes through the hatching area, this line is used as * a base to start iterating with HatchingBrush::iterateVerticalLines() */ double verticalHotX; /** X position of the vertical lines as they are scanned, this value changes constantly */ double verticalScanX; /** Angle of the lines expressed algebraically, as in slope*x + intercept = y */ - double slope; + double slope {0}; /** Unused variable, distance separating non-vertical lines in the X axis*/ - double dx; + double dx {0}; /** Distance separating non-vertical lines in the Y axis*/ - double dy; + double dy {0}; /** Intercept of the line that extends from the mouse cursor position, calculated from * the point (x, y) of the cursor and 'slope' */ - double cursorLineIntercept; + double cursorLineIntercept {0}; /** Function that begins exploring the field from hotIntercept and * moves in the direction of dy (forward==true) or -dy (forward==false) * to draw all the lines it finds to KisPaintDeviceSP 'dev' */ void iterateLines(bool forward, int lineindex, bool oneline); /** Function that begins exploring the field from verticalHotX and * moves in the direction of separation (forward==true) or * -separation (forward==false) to draw all the lines it finds * to KisPaintDeviceSP 'dev'. This function should only be called * when (angle == 90) or (angle == -90) */ void iterateVerticalLines(bool forward, int lineindex, bool oneline); /** Simple function that returns a new distance equal to a multiple or * divisor of separation depending on the magnitude of 'parameter' and * the number of intervals of its magnitude. * The multiples and divisors used are all powers of 2 to prevent * desynchronization of the lines during drawing. */ double separationAsFunctionOfParameter(double parameter, double separation, int numintervals); }; #endif diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp index 45093c54aa..8889e11a40 100644 --- a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.cpp @@ -1,110 +1,109 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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 "kis_pressure_sharpness_option.h" #include #include #include #include #include #include KisPressureSharpnessOption::KisPressureSharpnessOption() : KisCurveOption("Sharpness", KisPaintOpOption::GENERAL, false) { - m_threshold = 40; } void KisPressureSharpnessOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { KisCurveOption::writeOptionSetting(setting); setting->setProperty(SHARPNESS_THRESHOLD, m_threshold); } void KisPressureSharpnessOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { KisCurveOption::readOptionSetting(setting); m_threshold = setting->getInt(SHARPNESS_THRESHOLD, 4); // backward compatibility: test for a "sharpness factor" property // and use this value if it does exist if (setting->hasProperty(SHARPNESS_FACTOR) && !setting->hasProperty("SharpnessValue")) KisCurveOption::setValue(setting->getDouble(SHARPNESS_FACTOR)); } void KisPressureSharpnessOption::apply(const KisPaintInformation &info, const QPointF &pt, qint32 &x, qint32 &y, qreal &xFraction, qreal &yFraction) const { if (!isChecked() || KisCurveOption::value() == 0.0) { // brush KisPaintOp::splitCoordinate(pt.x(), &x, &xFraction); KisPaintOp::splitCoordinate(pt.y(), &y, &yFraction); } else { qreal processedSharpness = computeSizeLikeValue(info); if (processedSharpness == 1.0) { // pen xFraction = 0.0; yFraction = 0.0; x = qRound(pt.x()); y = qRound(pt.y()); } else { // something in between qint32 xi = qRound(pt.x()); qint32 yi = qRound(pt.y()); qreal xf = processedSharpness * xi + (1.0 - processedSharpness) * pt.x(); qreal yf = processedSharpness * yi + (1.0 - processedSharpness) * pt.y(); KisPaintOp::splitCoordinate(xf, &x, &xFraction); KisPaintOp::splitCoordinate(yf, &y, &yFraction); } } } void KisPressureSharpnessOption::applyThreshold(KisFixedPaintDeviceSP dab) { if (!isChecked()) return; const KoColorSpace * cs = dab->colorSpace(); // Set all alpha > opaque/2 to opaque, the rest to transparent. // XXX: Using 4/10 as the 1x1 circle brush paints nothing with 0.5. quint8* dabPointer = dab->data(); QRect rc = dab->bounds(); int pixelSize = dab->pixelSize(); int pixelCount = rc.width() * rc.height(); for (int i = 0; i < pixelCount; i++) { quint8 alpha = cs->opacityU8(dabPointer); if (alpha < (m_threshold * OPACITY_OPAQUE_U8) / 100) { cs->setOpacity(dabPointer, OPACITY_TRANSPARENT_U8, 1); } else { cs->setOpacity(dabPointer, OPACITY_OPAQUE_U8, 1); } dabPointer += pixelSize; } } diff --git a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h index 005f7254b4..7111f8d14c 100644 --- a/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h +++ b/plugins/paintops/libpaintop/kis_pressure_sharpness_option.h @@ -1,72 +1,72 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * 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. */ #ifndef KIS_PRESSURE_SHARPNESS_OPTION_H #define KIS_PRESSURE_SHARPNESS_OPTION_H #include "kis_curve_option.h" #include #include #include const QString SHARPNESS_FACTOR = "Sharpness/factor"; const QString SHARPNESS_THRESHOLD = "Sharpness/threshold"; /** * This option is responsible to mimic pencil effect from former Pixel Pencil brush engine.auto */ class PAINTOP_EXPORT KisPressureSharpnessOption : public KisCurveOption { public: KisPressureSharpnessOption(); /** * First part of the sharpness is the coordinates: in pen mode they are integers without fractions */ void apply(const KisPaintInformation &info, const QPointF &pt, qint32 &x, qint32 &y, qreal &xFraction, qreal &yFraction) const; /** * Apply threshold specified by user */ void applyThreshold(KisFixedPaintDeviceSP dab); void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; /// threshold has 100 levels (like opacity) void setThreshold(qint32 threshold) { m_threshold = qBound(0, threshold, 100); } qint32 threshold() { return m_threshold; } void setSharpnessFactor(qreal factor) { KisCurveOption::setValue(factor); } qreal sharpnessFactor() { return KisCurveOption::value(); } private: - qint32 m_threshold; + qint32 m_threshold {40}; }; #endif diff --git a/plugins/tools/basictools/kis_tool_measure.cc b/plugins/tools/basictools/kis_tool_measure.cc index 76fa05e05d..f5c0e3edca 100644 --- a/plugins/tools/basictools/kis_tool_measure.cc +++ b/plugins/tools/basictools/kis_tool_measure.cc @@ -1,221 +1,219 @@ /* * * Copyright (c) 2007 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 "kis_tool_measure.h" #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_cursor.h" #include "KoPointerEvent.h" #include "KoCanvasBase.h" #include #include "krita_utils.h" #define INNER_RADIUS 50 KisToolMeasureOptionsWidget::KisToolMeasureOptionsWidget(QWidget* parent, double resolution) : QWidget(parent), m_resolution(resolution), m_unit(KoUnit::Pixel) { m_distance = 0.0; QGridLayout* optionLayout = new QGridLayout(this); Q_CHECK_PTR(optionLayout); optionLayout->setMargin(0); optionLayout->addWidget(new QLabel(i18n("Distance:"), this), 0, 0); optionLayout->addWidget(new QLabel(i18n("Angle:"), this), 1, 0); m_distanceLabel = new QLabel(this); m_distanceLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); optionLayout->addWidget(m_distanceLabel, 0, 1); m_angleLabel = new QLabel(this); m_angleLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); optionLayout->addWidget(m_angleLabel, 1, 1); KComboBox* unitBox = new KComboBox(this); unitBox->addItems(KoUnit::listOfUnitNameForUi(KoUnit::ListAll)); connect(unitBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUnitChanged(int))); unitBox->setCurrentIndex(m_unit.indexInListForUi(KoUnit::ListAll)); optionLayout->addWidget(unitBox, 0, 2); optionLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 0, 1, 2); } void KisToolMeasureOptionsWidget::slotSetDistance(double distance) { m_distance = distance / m_resolution; updateDistance(); } void KisToolMeasureOptionsWidget::slotSetAngle(double angle) { m_angleLabel->setText(i18nc("angle value in degrees", "%1°", KritaUtils::prettyFormatReal(angle))); } void KisToolMeasureOptionsWidget::slotUnitChanged(int index) { m_unit = KoUnit::fromListForUi(index, KoUnit::ListAll, m_resolution); updateDistance(); } void KisToolMeasureOptionsWidget::updateDistance() { m_distanceLabel->setText(QString("%1").arg(m_unit.toUserValue(m_distance), 5, 'f', 1)); } KisToolMeasure::KisToolMeasure(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::crossCursor()) { - m_startPos = QPointF(0, 0); - m_endPos = QPointF(0, 0); } KisToolMeasure::~KisToolMeasure() { } void KisToolMeasure::paint(QPainter& gc, const KoViewConverter &converter) { qreal sx, sy; converter.zoom(&sx, &sy); gc.scale(sx / currentImage()->xRes(), sy / currentImage()->yRes()); QPen old = gc.pen(); QPen pen(Qt::SolidLine); gc.setPen(pen); gc.drawLine(m_startPos, m_endPos); if (deltaX() >= 0) gc.drawLine(QPointF(m_startPos.x(), m_startPos.y()), QPointF(m_startPos.x() + INNER_RADIUS, m_startPos.y())); else gc.drawLine(QPointF(m_startPos.x(), m_startPos.y()), QPointF(m_startPos.x() - INNER_RADIUS, m_startPos.y())); if (distance() >= INNER_RADIUS) { QRectF rectangle(m_startPos.x() - INNER_RADIUS, m_startPos.y() - INNER_RADIUS, 2*INNER_RADIUS, 2*INNER_RADIUS); int startAngle = (deltaX() >= 0) ? 0 : 180 * 16; int spanAngle; if ((deltaY() >= 0 && deltaX() >= 0) || (deltaY() < 0 && deltaX() < 0)) spanAngle = static_cast(angle() * 16); else spanAngle = static_cast(-angle() * 16); gc.drawArc(rectangle, startAngle, spanAngle); } gc.setPen(old); } void KisToolMeasure::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); // Erase old temporary lines canvas()->updateCanvas(convertToPt(boundingRect())); m_startPos = convertToPixelCoord(event); m_endPos = m_startPos; emit sigDistanceChanged(0.0); emit sigAngleChanged(0.0); } void KisToolMeasure::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); // Erase old temporary lines canvas()->updateCanvas(convertToPt(boundingRect())); QPointF pos = convertToPixelCoord(event); if (event->modifiers() == Qt::AltModifier) { QPointF trans = pos - m_endPos; m_startPos += trans; m_endPos += trans; } else { m_endPos = pos; } canvas()->updateCanvas(convertToPt(boundingRect())); emit sigDistanceChanged(distance()); emit sigAngleChanged(angle()); } void KisToolMeasure::endPrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); Q_UNUSED(event); setMode(KisTool::HOVER_MODE); } QWidget* KisToolMeasure::createOptionWidget() { if (!currentImage()) return 0; m_optionsWidget = new KisToolMeasureOptionsWidget(0, currentImage()->xRes()); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_optionsWidget->setObjectName(toolId() + " option widget"); connect(this, SIGNAL(sigDistanceChanged(double)), m_optionsWidget, SLOT(slotSetDistance(double))); connect(this, SIGNAL(sigAngleChanged(double)), m_optionsWidget, SLOT(slotSetAngle(double))); m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); return m_optionsWidget; } double KisToolMeasure::angle() { return atan(qAbs(deltaY()) / qAbs(deltaX())) / (2*M_PI)*360; } double KisToolMeasure::distance() { return sqrt(deltaX()*deltaX() + deltaY()*deltaY()); } QRectF KisToolMeasure::boundingRect() { QRectF bound; bound.setTopLeft(m_startPos); bound.setBottomRight(m_endPos); bound = bound.united(QRectF(m_startPos.x() - INNER_RADIUS, m_startPos.y() - INNER_RADIUS, 2 * INNER_RADIUS, 2 * INNER_RADIUS)); return bound.normalized(); } diff --git a/plugins/tools/basictools/kis_tool_measure.h b/plugins/tools/basictools/kis_tool_measure.h index 0f3b695460..e32cdfbabb 100644 --- a/plugins/tools/basictools/kis_tool_measure.h +++ b/plugins/tools/basictools/kis_tool_measure.h @@ -1,129 +1,129 @@ /* * * Copyright (c) 2007 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. */ #ifndef KIS_TOOL_MEASURE_H_ #define KIS_TOOL_MEASURE_H_ #include #include #include "kis_tool.h" #include "kis_global.h" #include "kis_types.h" #include "KoToolFactoryBase.h" #include "flake/kis_node_shape.h" #include class QPointF; class QWidget; class KoCanvasBase; class KisToolMeasureOptionsWidget : public QWidget { Q_OBJECT public: KisToolMeasureOptionsWidget(QWidget* parent, double resolution); public Q_SLOTS: void slotSetDistance(double distance); void slotSetAngle(double angle); void slotUnitChanged(int index); private: void updateDistance(); double m_resolution; QLabel* m_distanceLabel; QLabel* m_angleLabel; double m_distance; KoUnit m_unit; }; class KisToolMeasure : public KisTool { Q_OBJECT public: KisToolMeasure(KoCanvasBase * canvas); ~KisToolMeasure() override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void paint(QPainter& gc, const KoViewConverter &converter) override; QWidget * createOptionWidget() override; Q_SIGNALS: void sigDistanceChanged(double distance); void sigAngleChanged(double angle); private: QRectF boundingRect(); double angle(); double distance(); double deltaX() { return m_endPos.x() - m_startPos.x(); } double deltaY() { return m_startPos.y() - m_endPos.y(); } private: KisToolMeasureOptionsWidget *m_optionsWidget; - QPointF m_startPos; - QPointF m_endPos; + QPointF m_startPos {QPointF(0, 0)}; + QPointF m_endPos {QPointF(0, 0)}; }; class KisToolMeasureFactory : public KoToolFactoryBase { public: KisToolMeasureFactory() : KoToolFactoryBase("KritaShape/KisToolMeasure") { setSection(TOOL_TYPE_VIEW); setToolTip(i18n("Measure Tool")); setIconName(koIconNameCStr("krita_tool_measure")); setPriority(1); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolMeasureFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolMeasure(canvas); } }; #endif //KIS_TOOL_MEASURE_H_ diff --git a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterInputChangeCommand.cpp b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterInputChangeCommand.cpp index 00b37741e6..45230ee4e2 100644 --- a/plugins/tools/karbonplugins/tools/filterEffectTool/FilterInputChangeCommand.cpp +++ b/plugins/tools/karbonplugins/tools/filterEffectTool/FilterInputChangeCommand.cpp @@ -1,70 +1,70 @@ /* This file is part of the KDE project * Copyright (c) 2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 Lesser 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 "FilterInputChangeCommand.h" #include "KoFilterEffect.h" #include "KoShape.h" FilterInputChangeCommand::FilterInputChangeCommand(const InputChangeData &data, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent) , m_shape(shape) { m_data.append(data); } FilterInputChangeCommand::FilterInputChangeCommand(const QList &data, KoShape *shape, KUndo2Command *parent) : KUndo2Command(parent) + , m_data(data) , m_shape(shape) { - m_data = data; } void FilterInputChangeCommand::redo() { if (m_shape) { m_shape->update(); } Q_FOREACH (const InputChangeData &data, m_data) { data.filterEffect->setInput(data.inputIndex, data.newInput); } if (m_shape) { m_shape->update(); } KUndo2Command::redo(); } void FilterInputChangeCommand::undo() { if (m_shape) { m_shape->update(); } Q_FOREACH (const InputChangeData &data, m_data) { data.filterEffect->setInput(data.inputIndex, data.oldInput); } if (m_shape) { m_shape->update(); } KUndo2Command::undo(); } diff --git a/plugins/tools/tool_dyna/kis_tool_dyna.h b/plugins/tools/tool_dyna/kis_tool_dyna.h index dbdea5870c..8c3c7345a1 100644 --- a/plugins/tools/tool_dyna/kis_tool_dyna.h +++ b/plugins/tools/tool_dyna/kis_tool_dyna.h @@ -1,180 +1,171 @@ /* * Copyright (c) 2009-2011 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_TOOL_DYNA_H_ #define KIS_TOOL_DYNA_H_ #include "kis_tool_freehand.h" #include "KoToolFactoryBase.h" #include "KoPointerEvent.h" #include #include #include #include class KisDoubleSliderSpinBox; class QCheckBox; class QGridLayout; class KoCanvasBase; class DynaFilter { public: - DynaFilter() { - curx = 0; - cury = 0; - lastx = 0; - lasty = 0; - velx = 0.0; - vely = 0.0; - accx = 0.0; - accy = 0.0; - } + DynaFilter() {} void init(qreal x, qreal y) { curx = x; cury = y; lastx = x; lasty = y; velx = 0.0; vely = 0.0; accx = 0.0; accy = 0.0; } ~DynaFilter() {} public: - qreal curx, cury; - qreal velx, vely, vel; - qreal accx, accy, acc; - qreal angx, angy; - qreal mass, drag; - qreal lastx, lasty; - bool fixedangle; + qreal curx {0.0}, cury {0.0}; + qreal velx {0.0}, vely {0.0}, vel {0.0}; + qreal accx {0.0}, accy {0.0}, acc {0.0}; + qreal angx {0.0}, angy {0.0}; + qreal mass {0.0}, drag {0.0}; + qreal lastx {0.0}, lasty {0.0}; + bool fixedangle {false}; }; class KisToolDyna : public KisToolFreehand { Q_OBJECT public: KisToolDyna(KoCanvasBase * canvas); ~KisToolDyna() override; QWidget * createOptionWidget() override; void activate(ToolActivation toolActivation, const QSet &shapes) override; void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; protected: void initStroke(KoPointerEvent *event) override; protected Q_SLOTS: void resetCursorStyle() override; private Q_SLOTS: void slotSetDynaWidth(double width); void slotSetMass(qreal mass); void slotSetDrag(qreal drag); void slotSetAngle(qreal angle); void slotSetWidthRange(double widthRange); void slotSetFixedAngle(bool fixedAngle); private: QGridLayout* m_optionLayout; // dyna gui QCheckBox * m_chkFixedAngle; KisDoubleSliderSpinBox * m_massSPBox; KisDoubleSliderSpinBox * m_dragSPBox; KisDoubleSliderSpinBox * m_angleDSSBox; // dyna algorithm QVector m_prevPosition; qreal m_odelx, m_odely; // mouse info QPointF m_mousePos; qreal m_surfaceWidth; qreal m_surfaceHeight; // settings variables KConfigGroup m_configGroup; qreal m_width; qreal m_curmass; qreal m_curdrag; DynaFilter m_mouse; qreal m_xangle; qreal m_yangle; qreal m_widthRange; // methods qreal flerp(qreal f0, qreal f1, qreal p) { return ((f0 *(1.0 - p)) + (f1 * p)); } void setMousePosition(const QPointF &point) { m_mousePos.setX(point.x() / m_surfaceWidth ); m_mousePos.setY(point.y() / m_surfaceHeight); } void initDyna(); int applyFilter(qreal mx, qreal my); KoPointerEvent filterEvent(KoPointerEvent * event); }; class KisToolDynaFactory : public KoToolFactoryBase { public: KisToolDynaFactory() : KoToolFactoryBase("KritaShape/KisToolDyna") { setToolTip(i18n("Dynamic Brush Tool")); // Temporarily setSection(TOOL_TYPE_SHAPE); setIconName(koIconNameCStr("krita_tool_dyna")); // TODO //setShortcut(QKeySequence(Qt::Key_F)); setPriority(10); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); } ~KisToolDynaFactory() override {} KoToolBase * createTool(KoCanvasBase *canvas) override { return new KisToolDyna(canvas); } }; #endif // KIS_TOOL_DYNA_H_ diff --git a/plugins/tools/tool_smart_patch/kis_inpaint.cpp b/plugins/tools/tool_smart_patch/kis_inpaint.cpp index 4f941364b3..08b225f192 100644 --- a/plugins/tools/tool_smart_patch/kis_inpaint.cpp +++ b/plugins/tools/tool_smart_patch/kis_inpaint.cpp @@ -1,994 +1,994 @@ /* * Copyright (c) 2017 Eugene Ingerman * * 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. */ /** * Inpaint using the PatchMatch Algorithm * * | PatchMatch : A Randomized Correspondence Algorithm for Structural Image Editing * | by Connelly Barnes and Eli Shechtman and Adam Finkelstein and Dan B Goldman * | ACM Transactions on Graphics (Proc. SIGGRAPH), vol.28, aug-2009 * * Original author Xavier Philippeau * Code adopted from: David Chatting https://github.com/davidchatting/PatchMatch */ #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_debug.h" #include "kis_paint_device_debug_utils.h" //#include "kis_random_accessor_ng.h" #include #include #include #include "KoColor.h" #include "KoColorSpace.h" #include "KoChannelInfo.h" #include "KoMixColorsOp.h" #include "KoColorModelStandardIds.h" #include "KoColorSpaceRegistry.h" #include "KoColorSpaceTraits.h" const int MAX_DIST = 65535; const quint8 MASK_SET = 255; const quint8 MASK_CLEAR = 0; class MaskedImage; //forward decl for the forward decl below template float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo); class ImageView { protected: quint8* m_data; int m_imageWidth; int m_imageHeight; int m_pixelSize; public: void Init(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize) { m_data = _data; m_imageWidth = _imageWidth; m_imageHeight = _imageHeight; m_pixelSize = _pixelSize; } ImageView() : m_data(nullptr) { m_imageHeight = m_imageWidth = m_pixelSize = 0; } ImageView(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize) { Init(_data, _imageWidth, _imageHeight, _pixelSize); } quint8* operator()(int x, int y) const { Q_ASSERT(m_data); Q_ASSERT((x >= 0) && (x < m_imageWidth) && (y >= 0) && (y < m_imageHeight)); return (m_data + x * m_pixelSize + y * m_imageWidth * m_pixelSize); } ImageView& operator=(const ImageView& other) { if (this != &other) { if (other.num_bytes() != num_bytes()) { delete[] m_data; m_data = nullptr; //to preserve invariance if next line throws exception m_data = new quint8[other.num_bytes()]; } std::copy(other.data(), other.data() + other.num_bytes(), m_data); m_imageHeight = other.m_imageHeight; m_imageWidth = other.m_imageWidth; m_pixelSize = other.m_pixelSize; } return *this; } //move assignment operator ImageView& operator=(ImageView&& other) noexcept { if (this != &other) { delete[] m_data; m_data = nullptr; Init(other.data(), other.m_imageWidth, other.m_imageHeight, other.m_pixelSize); other.m_data = nullptr; } return *this; } virtual ~ImageView() {} //this class doesn't own m_data, so it ain't going to delete it either. quint8* data(void) const { return m_data; } inline int num_elements(void) const { return m_imageHeight * m_imageWidth; } inline int num_bytes(void) const { return m_imageHeight * m_imageWidth * m_pixelSize; } inline int pixel_size(void) const { return m_pixelSize; } void saveToDevice(KisPaintDeviceSP outDev, QRect rect) { Q_ASSERT(outDev->colorSpace()->pixelSize() == (quint32) m_pixelSize); outDev->writeBytes(m_data, rect); } void DebugDump(const QString& fnamePrefix) { QRect imSize(QPoint(0, 0), QSize(m_imageWidth, m_imageHeight)); const KoColorSpace* cs = (m_pixelSize == 1) ? KoColorSpaceRegistry::instance()->alpha8() : (m_pixelSize == 3) ? KoColorSpaceRegistry::instance()->colorSpace("RGB", "U8", "") : KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", ""); KisPaintDeviceSP dbout = new KisPaintDevice(cs); saveToDevice(dbout, imSize); KIS_DUMP_DEVICE_2(dbout, imSize, fnamePrefix, "./"); } }; class ImageData : public ImageView { public: ImageData() : ImageView() {} void Init(int _imageWidth, int _imageHeight, int _pixelSize) { m_data = new quint8[ _imageWidth * _imageHeight * _pixelSize ]; ImageView::Init(m_data, _imageWidth, _imageHeight, _pixelSize); } ImageData(int _imageWidth, int _imageHeight, int _pixelSize) : ImageView() { Init(_imageWidth, _imageHeight, _pixelSize); } void Init(KisPaintDeviceSP imageDev, const QRect& imageSize) { const KoColorSpace* cs = imageDev->colorSpace(); m_pixelSize = cs->pixelSize(); m_data = new quint8[ imageSize.width()*imageSize.height()*cs->pixelSize() ]; imageDev->readBytes(m_data, imageSize.x(), imageSize.y(), imageSize.width(), imageSize.height()); ImageView::Init(m_data, imageSize.width(), imageSize.height(), m_pixelSize); } ImageData(KisPaintDeviceSP imageDev, const QRect& imageSize) : ImageView() { Init(imageDev, imageSize); } ~ImageData() override { delete[] m_data; //ImageData owns m_data, so it has to delete it } }; class MaskedImage : public KisShared { private: template friend float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo); QRect imageSize; int nChannels; const KoColorSpace* cs; const KoColorSpace* csMask; ImageData maskData; ImageData imageData; void cacheImage(KisPaintDeviceSP imageDev, QRect rect) { cs = imageDev->colorSpace(); nChannels = cs->channelCount(); imageData.Init(imageDev, rect); imageSize = rect; } void cacheMask(KisPaintDeviceSP maskDev, QRect rect) { Q_ASSERT(maskDev->colorSpace()->pixelSize() == 1); csMask = maskDev->colorSpace(); maskData.Init(maskDev, rect); //hard threshold for the initial mask //may be optional. needs testing std::for_each(maskData.data(), maskData.data() + maskData.num_bytes(), [](quint8 & v) { v = (v > MASK_CLEAR) ? MASK_SET : MASK_CLEAR; }); } MaskedImage() {} public: std::function< float(const MaskedImage&, int, int, const MaskedImage& , int , int ) > distance; void toPaintDevice(KisPaintDeviceSP imageDev, QRect rect) { imageData.saveToDevice(imageDev, rect); } void DebugDump(const QString& name) { imageData.DebugDump(name + "_img"); maskData.DebugDump(name + "_mask"); } void clearMask(void) { std::fill(maskData.data(), maskData.data() + maskData.num_bytes(), MASK_CLEAR); } void initialize(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect) { cacheImage(_imageDev, _maskRect); cacheMask(_maskDev, _maskRect); //distance function is the only that needs to know the type //For performance reasons we can't use functions provided by color space KoID colorDepthId = _imageDev->colorSpace()->colorDepthId(); //Use RGB traits to assign actual pixel data types. distance = &distance_impl; if( colorDepthId == Integer16BitsColorDepthID ) distance = &distance_impl; #ifdef HAVE_OPENEXR if( colorDepthId == Float16BitsColorDepthID ) distance = &distance_impl; #endif if( colorDepthId == Float32BitsColorDepthID ) distance = &distance_impl; if( colorDepthId == Float64BitsColorDepthID ) distance = &distance_impl; } MaskedImage(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect) { initialize(_imageDev, _maskDev, _maskRect); } void downsample2x(void) { int H = imageSize.height(); int W = imageSize.width(); int newW = W / 2, newH = H / 2; KisPaintDeviceSP imageDev = new KisPaintDevice(cs); KisPaintDeviceSP maskDev = new KisPaintDevice(csMask); imageDev->writeBytes(imageData.data(), 0, 0, W, H); maskDev->writeBytes(maskData.data(), 0, 0, W, H); ImageData newImage(newW, newH, cs->pixelSize()); ImageData newMask(newW, newH, 1); KoDummyUpdater updater; KisTransformWorker worker(imageDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); KisTransformWorker workerMask(maskDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); workerMask.run(); imageDev->readBytes(newImage.data(), 0, 0, newW, newH); maskDev->readBytes(newMask.data(), 0, 0, newW, newH); imageData = std::move(newImage); maskData = std::move(newMask); for (int i = 0; i < imageData.num_elements(); ++i) { quint8* maskPix = maskData.data() + i * maskData.pixel_size(); if (*maskPix == MASK_SET) { for (int k = 0; k < imageData.pixel_size(); k++) *(imageData.data() + i * imageData.pixel_size() + k) = 0; } else { *maskPix = MASK_CLEAR; } } imageSize = QRect(0, 0, newW, newH); } void upscale(int newW, int newH) { int H = imageSize.height(); int W = imageSize.width(); ImageData newImage(newW, newH, cs->pixelSize()); ImageData newMask(newW, newH, 1); QVector colors(nChannels, 0.f); QVector v(nChannels, 0.f); for (int y = 0; y < newH; ++y) { for (int x = 0; x < newW; ++x) { // original pixel int xs = (x * W) / newW; int ys = (y * H) / newH; // copy to new image if (!isMasked(xs, ys)) { std::copy(imageData(xs, ys), imageData(xs, ys) + imageData.pixel_size(), newImage(x, y)); *newMask(x, y) = MASK_CLEAR; } else { std::fill(newImage(x, y), newImage(x, y) + newImage.pixel_size(), 0); *newMask(x, y) = MASK_SET; } } } imageData = std::move(newImage); maskData = std::move(newMask); imageSize = QRect(0, 0, newW, newH); } QRect size() { return imageSize; } KisSharedPtr copy(void) { KisSharedPtr clone = new MaskedImage(); clone->imageSize = this->imageSize; clone->nChannels = this->nChannels; clone->maskData = this->maskData; clone->imageData = this->imageData; clone->cs = this->cs; clone->csMask = this->csMask; clone->distance = this->distance; return clone; } int countMasked(void) { int count = std::count_if(maskData.data(), maskData.data() + maskData.num_elements(), [](quint8 v) { return v > MASK_CLEAR; }); return count; } inline bool isMasked(int x, int y) { return (*maskData(x, y) > MASK_CLEAR); } //returns true if the patch contains a masked pixel bool containsMasked(int x, int y, int S) { for (int dy = -S; dy <= S; ++dy) { int ys = y + dy; if (ys < 0 || ys >= imageSize.height()) continue; for (int dx = -S; dx <= S; ++dx) { int xs = x + dx; if (xs < 0 || xs >= imageSize.width()) continue; if (isMasked(xs, ys)) return true; } } return false; } inline quint8 getImagePixelU8(int x, int y, int chan) const { return cs->scaleToU8(imageData(x, y), chan); } inline QVector getImagePixels(int x, int y) const { QVector v(cs->channelCount()); cs->normalisedChannelsValue(imageData(x, y), v); return v; } inline quint8* getImagePixel(int x, int y) { return imageData(x, y); } inline void setImagePixels(int x, int y, QVector& value) { cs->fromNormalisedChannelsValue(imageData(x, y), value); } inline void mixColors(std::vector< quint8* > pixels, std::vector< float > w, float wsum, quint8* dst) { const KoMixColorsOp* mixOp = cs->mixColorsOp(); size_t n = w.size(); assert(pixels.size() == n); std::vector< qint16 > weights; weights.clear(); float dif = 0; float scale = 255 / (wsum + 0.001); for (auto& v : w) { //compensated summation to increase accuracy float v1 = v * scale + dif; float v2 = std::round(v1); dif = v1 - v2; weights.push_back(v2); } mixOp->mixColors(pixels.data(), weights.data(), n, dst); } inline void setMask(int x, int y, quint8 v) { *(maskData(x, y)) = v; } inline int channelCount(void) const { return cs->channelCount(); } }; //Generic version of the distance function. produces distance between colors in the range [0, MAX_DIST]. This //is a fast distance computation. More accurate, but very slow implementation is to use color space operations. template float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo) { float dsq = 0; quint32 nchannels = my.channelCount(); quint8* v1 = my.imageData(x, y); quint8* v2 = other.imageData(xo, yo); for (quint32 chan = 0; chan < nchannels; chan++) { //It's very important not to lose precision in the next line float v = ((float)(*((T*)v1 + chan)) - (float)(*((T*)v2 + chan))); dsq += v * v; } return dsq / ( (float)KoColorSpaceMathsTraits::unitValue * (float)KoColorSpaceMathsTraits::unitValue / MAX_DIST ); } typedef KisSharedPtr MaskedImageSP; struct NNPixel { int x; int y; int distance; }; typedef boost::multi_array NNArray_type; struct Vote_elem { QVector channel_values; float w; }; typedef boost::multi_array Vote_type; class NearestNeighborField : public KisShared { private: template< typename T> T randomInt(T range) { return rand() % range; } //compute initial value of the distance term void initialize(void) { for (int y = 0; y < imSize.height(); y++) { for (int x = 0; x < imSize.width(); x++) { field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y); //if the distance is "infinity", try to find a better link int iter = 0; const int maxretry = 20; while (field[x][y].distance == MAX_DIST && iter < maxretry) { field[x][y].x = randomInt(imSize.width() + 1); field[x][y].y = randomInt(imSize.height() + 1); field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y); iter++; } } } } void init_similarity_curve(void) { float s_zero = 0.999; float t_halfmax = 0.10; float x = (s_zero - 0.5) * 2; float invtanh = 0.5 * std::log((1. + x) / (1. - x)); float coef = invtanh / t_halfmax; similarity.resize(MAX_DIST + 1); for (int i = 0; i < (int)similarity.size(); i++) { float t = (float)i / similarity.size(); similarity[i] = 0.5 - 0.5 * std::tanh(coef * (t - t_halfmax)); } } private: int patchSize; //patch size public: MaskedImageSP input; MaskedImageSP output; QRect imSize; NNArray_type field; std::vector similarity; quint32 nColors; QList channels; public: NearestNeighborField(const MaskedImageSP _input, MaskedImageSP _output, int _patchsize) : patchSize(_patchsize), input(_input), output(_output) { imSize = input->size(); field.resize(boost::extents[imSize.width()][imSize.height()]); init_similarity_curve(); nColors = input->channelCount(); //only color count, doesn't include alpha channels } void randomize(void) { for (int y = 0; y < imSize.height(); y++) { for (int x = 0; x < imSize.width(); x++) { field[x][y].x = randomInt(imSize.width() + 1); field[x][y].y = randomInt(imSize.height() + 1); field[x][y].distance = MAX_DIST; } } initialize(); } //initialize field from an existing (possibly smaller) nearest neighbor field void initialize(const NearestNeighborField& nnf) { float xscale = qreal(imSize.width()) / nnf.imSize.width(); float yscale = qreal(imSize.height()) / nnf.imSize.height(); for (int y = 0; y < imSize.height(); y++) { for (int x = 0; x < imSize.width(); x++) { int xlow = std::min((int)(x / xscale), nnf.imSize.width() - 1); int ylow = std::min((int)(y / yscale), nnf.imSize.height() - 1); field[x][y].x = nnf.field[xlow][ylow].x * xscale; field[x][y].y = nnf.field[xlow][ylow].y * yscale; field[x][y].distance = MAX_DIST; } } initialize(); } //multi-pass NN-field minimization (see "PatchMatch" paper referenced above - page 4) void minimize(int pass) { int min_x = 0; int min_y = 0; int max_x = imSize.width() - 1; int max_y = imSize.height() - 1; for (int i = 0; i < pass; i++) { //scanline order for (int y = min_y; y < max_y; y++) for (int x = min_x; x <= max_x; x++) if (field[x][y].distance > 0) minimizeLink(x, y, 1); //reverse scanline order for (int y = max_y; y >= min_y; y--) for (int x = max_x; x >= min_x; x--) if (field[x][y].distance > 0) minimizeLink(x, y, -1); } } void minimizeLink(int x, int y, int dir) { int xp, yp, dp; //Propagation Left/Right if (x - dir > 0 && x - dir < imSize.width()) { xp = field[x - dir][y].x + dir; yp = field[x - dir][y].y; dp = distance(x, y, xp, yp); if (dp < field[x][y].distance) { field[x][y].x = xp; field[x][y].y = yp; field[x][y].distance = dp; } } //Propagation Up/Down if (y - dir > 0 && y - dir < imSize.height()) { xp = field[x][y - dir].x; yp = field[x][y - dir].y + dir; dp = distance(x, y, xp, yp); if (dp < field[x][y].distance) { field[x][y].x = xp; field[x][y].y = yp; field[x][y].distance = dp; } } //Random search int wi = std::max(output->size().width(), output->size().height()); int xpi = field[x][y].x; int ypi = field[x][y].y; while (wi > 0) { xp = xpi + randomInt(2 * wi) - wi; yp = ypi + randomInt(2 * wi) - wi; xp = std::max(0, std::min(output->size().width() - 1, xp)); yp = std::max(0, std::min(output->size().height() - 1, yp)); dp = distance(x, y, xp, yp); if (dp < field[x][y].distance) { field[x][y].x = xp; field[x][y].y = yp; field[x][y].distance = dp; } wi /= 2; } } //compute distance between two patches int distance(int x, int y, int xp, int yp) { float distance = 0; float wsum = 0; float ssdmax = nColors * 255 * 255; //for each pixel in the source patch for (int dy = -patchSize; dy <= patchSize; dy++) { for (int dx = -patchSize; dx <= patchSize; dx++) { wsum += ssdmax; int xks = x + dx; int yks = y + dy; if (xks < 0 || xks >= input->size().width()) { distance += ssdmax; continue; } if (yks < 0 || yks >= input->size().height()) { distance += ssdmax; continue; } //cannot use masked pixels as a valid source of information if (input->isMasked(xks, yks)) { distance += ssdmax; continue; } //corresponding pixel in target patch int xkt = xp + dx; int ykt = yp + dy; if (xkt < 0 || xkt >= output->size().width()) { distance += ssdmax; continue; } if (ykt < 0 || ykt >= output->size().height()) { distance += ssdmax; continue; } //cannot use masked pixels as a valid source of information if (output->isMasked(xkt, ykt)) { distance += ssdmax; continue; } //SSD distance between pixels float ssd = input->distance(*input, xks, yks, *output, xkt, ykt); distance += ssd; } } return (int)(MAX_DIST * (distance / wsum)); } static MaskedImageSP ExpectationMaximization(KisSharedPtr TargetToSource, int level, int radius, QList& pyramid); static void ExpectationStep(KisSharedPtr nnf, MaskedImageSP source, MaskedImageSP target, bool upscale); void EM_Step(MaskedImageSP source, MaskedImageSP target, int R, bool upscaled); }; typedef KisSharedPtr NearestNeighborFieldSP; class Inpaint { private: KisPaintDeviceSP devCache; MaskedImageSP initial; NearestNeighborFieldSP nnf_TargetToSource; NearestNeighborFieldSP nnf_SourceToTarget; int radius; QList pyramid; public: Inpaint(KisPaintDeviceSP dev, KisPaintDeviceSP devMask, int _radius, QRect maskRect) + : devCache(dev) + , initial(new MaskedImage(dev, devMask, maskRect)) + , radius(_radius) { - initial = new MaskedImage(dev, devMask, maskRect); - radius = _radius; - devCache = dev; } MaskedImageSP patch(void); MaskedImageSP patch_simple(void); }; MaskedImageSP Inpaint::patch() { MaskedImageSP source = initial->copy(); pyramid.append(initial); QRect size = source->size(); //qDebug() << "countMasked: " << source->countMasked() << "\n"; while ((size.width() > radius) && (size.height() > radius) && source->countMasked() > 0) { source->downsample2x(); //source->DebugDump("Pyramid"); //qDebug() << "countMasked1: " << source->countMasked() << "\n"; pyramid.append(source->copy()); size = source->size(); } int maxlevel = pyramid.size(); //qDebug() << "MaxLevel: " << maxlevel << "\n"; // The initial target is the same as the smallest source. // We consider that this target contains no masked pixels MaskedImageSP target = source->copy(); target->clearMask(); //recursively building nearest neighbor field for (int level = maxlevel - 1; level > 0; level--) { source = pyramid.at(level); if (level == maxlevel - 1) { //random initial guess nnf_TargetToSource = new NearestNeighborField(target, source, radius); nnf_TargetToSource->randomize(); } else { // then, we use the rebuilt (upscaled) target // and reuse the previous NNF as initial guess NearestNeighborFieldSP new_nnf_rev = new NearestNeighborField(target, source, radius); new_nnf_rev->initialize(*nnf_TargetToSource); nnf_TargetToSource = new_nnf_rev; } //Build an upscaled target by EM-like algorithm (see "PatchMatch" paper referenced above - page 6) target = NearestNeighborField::ExpectationMaximization(nnf_TargetToSource, level, radius, pyramid); //target->DebugDump( "target" ); } return target; } //EM-Like algorithm (see "PatchMatch" - page 6) //Returns a float sized target image MaskedImageSP NearestNeighborField::ExpectationMaximization(NearestNeighborFieldSP nnf_TargetToSource, int level, int radius, QList& pyramid) { int iterEM = std::min(2 * level, 4); int iterNNF = std::min(5, 1 + level); MaskedImageSP source = nnf_TargetToSource->output; MaskedImageSP target = nnf_TargetToSource->input; MaskedImageSP newtarget = nullptr; //EM loop for (int emloop = 1; emloop <= iterEM; emloop++) { //set the new target as current target if (!newtarget.isNull()) { nnf_TargetToSource->input = newtarget; target = newtarget; newtarget = nullptr; } for (int x = 0; x < target->size().width(); ++x) { for (int y = 0; y < target->size().height(); ++y) { if (!source->containsMasked(x, y, radius)) { nnf_TargetToSource->field[x][y].x = x; nnf_TargetToSource->field[x][y].y = y; nnf_TargetToSource->field[x][y].distance = 0; } } } //minimize the NNF nnf_TargetToSource->minimize(iterNNF); //Now we rebuild the target using best patches from source MaskedImageSP newsource = nullptr; bool upscaled = false; // Instead of upsizing the final target, we build the last target from the next level source image // So the final target is less blurry (see "Space-Time Video Completion" - page 5) if (level >= 1 && (emloop == iterEM)) { newsource = pyramid.at(level - 1); QRect sz = newsource->size(); newtarget = target->copy(); newtarget->upscale(sz.width(), sz.height()); upscaled = true; } else { newsource = pyramid.at(level); newtarget = target->copy(); upscaled = false; } //EM Step //EM_Step(newsource, newtarget, radius, upscaled); ExpectationStep(nnf_TargetToSource, newsource, newtarget, upscaled); } return newtarget; } void NearestNeighborField::ExpectationStep(NearestNeighborFieldSP nnf, MaskedImageSP source, MaskedImageSP target, bool upscale) { //int*** field = nnf->field; int R = nnf->patchSize; if (upscale) R *= 2; int H_nnf = nnf->input->size().height(); int W_nnf = nnf->input->size().width(); int H_target = target->size().height(); int W_target = target->size().width(); int H_source = source->size().height(); int W_source = source->size().width(); std::vector< quint8* > pixels; std::vector< float > weights; pixels.reserve(R * R); weights.reserve(R * R); for (int x = 0 ; x < W_target ; ++x) { for (int y = 0 ; y < H_target; ++y) { float wsum = 0; pixels.clear(); weights.clear(); if (!source->containsMasked(x, y, R + 4) /*&& upscale*/) { //speedup computation by copying parts that are not masked. pixels.push_back(source->getImagePixel(x, y)); weights.push_back(1.f); target->mixColors(pixels, weights, 1.f, target->getImagePixel(x, y)); } else { for (int dx = -R ; dx <= R; ++dx) { for (int dy = -R ; dy <= R ; ++dy) { // xpt,ypt = center pixel of the target patch int xpt = x + dx; int ypt = y + dy; int xst, yst; float w; if (!upscale) { if (xpt < 0 || xpt >= W_nnf || ypt < 0 || ypt >= H_nnf) continue; xst = nnf->field[xpt][ypt].x; yst = nnf->field[xpt][ypt].y; float dp = nnf->field[xpt][ypt].distance; // similarity measure between the two patches w = nnf->similarity[dp]; } else { if (xpt < 0 || (xpt / 2) >= W_nnf || ypt < 0 || (ypt / 2) >= H_nnf) continue; xst = 2 * nnf->field[xpt / 2][ypt / 2].x + (xpt % 2); yst = 2 * nnf->field[xpt / 2][ypt / 2].y + (ypt % 2); float dp = nnf->field[xpt / 2][ypt / 2].distance; // similarity measure between the two patches w = nnf->similarity[dp]; } int xs = xst - dx; int ys = yst - dy; if (xs < 0 || xs >= W_source || ys < 0 || ys >= H_source) continue; if (source->isMasked(xs, ys)) continue; pixels.push_back(source->getImagePixel(xs, ys)); weights.push_back(w); wsum += w; } } if (wsum < 1) continue; target->mixColors(pixels, weights, wsum, target->getImagePixel(x, y)); } } } } QRect getMaskBoundingBox(KisPaintDeviceSP maskDev) { QRect maskRect = maskDev->nonDefaultPixelArea(); return maskRect; } QRect patchImage(const KisPaintDeviceSP imageDev, const KisPaintDeviceSP maskDev, int patchRadius, int accuracy) { QRect maskRect = getMaskBoundingBox(maskDev); QRect imageRect = imageDev->exactBounds(); float scale = 1.0 + (accuracy / 25.0); //higher accuracy means we include more surrounding area around the patch. Minimum 2x padding. int dx = maskRect.width() * scale; int dy = maskRect.height() * scale; maskRect.adjust(-dx, -dy, dx, dy); maskRect = maskRect.intersected(imageRect); if (!maskRect.isEmpty()) { Inpaint inpaint(imageDev, maskDev, patchRadius, maskRect); MaskedImageSP output = inpaint.patch(); output->toPaintDevice(imageDev, maskRect); } return maskRect; } diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc index 7453961e8e..74272ce705 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.cc +++ b/plugins/tools/tool_transform2/tool_transform_args.cc @@ -1,491 +1,483 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * 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 "tool_transform_args.h" #include #include #include #include #include "kis_liquify_transform_worker.h" #include "kis_dom_utils.h" ToolTransformArgs::ToolTransformArgs() - : m_liquifyProperties(new KisLiquifyProperties()) + : m_mode(FREE_TRANSFORM) + , m_defaultPoints(true) + , m_origPoints {QVector()} + , m_transfPoints {QVector()} + , m_warpType(KisWarpTransformWorker::RIGID_TRANSFORM) + , m_alpha(1.0) + , m_transformedCenter(QPointF(0, 0)) + , m_originalCenter(QPointF(0, 0)) + , m_rotationCenterOffset(QPointF(0, 0)) + , m_aX(0) + , m_aY(0) + , m_aZ(0) + , m_scaleX(1.0) + , m_scaleY(1.0) + , m_shearX(0.0) + , m_shearY(0.0) + , m_liquifyProperties(new KisLiquifyProperties()) { - m_mode = FREE_TRANSFORM; - m_transformedCenter = QPointF(0, 0); - m_originalCenter = QPointF(0, 0); - m_rotationCenterOffset = QPointF(0, 0); - m_cameraPos = QVector3D(0,0,1024); - m_aX = 0; - m_aY = 0; - m_aZ = 0; - m_scaleX = 1.0; - m_scaleY = 1.0; - m_shearX = 0.0; - m_shearY = 0.0; - m_origPoints = QVector(); - m_transfPoints = QVector(); - m_warpType = KisWarpTransformWorker::RIGID_TRANSFORM; - m_alpha = 1.0; - m_keepAspectRatio = false; - m_defaultPoints = true; - KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); QString savedFilterId = configGroup.readEntry("filterId", "Bicubic"); setFilterId(savedFilterId); m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt(); - - m_editTransformPoints = false; } void ToolTransformArgs::setFilterId(const QString &id) { m_filter = KisFilterStrategyRegistry::instance()->value(id); if (m_filter) { KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("filterId", id); } } void ToolTransformArgs::setTransformAroundRotationCenter(bool value) { m_transformAroundRotationCenter = value; KConfigGroup configGroup = KSharedConfig::openConfig()->group("KisToolTransform"); configGroup.writeEntry("transformAroundRotationCenter", int(value)); } void ToolTransformArgs::init(const ToolTransformArgs& args) { m_mode = args.mode(); m_transformedCenter = args.transformedCenter(); m_originalCenter = args.originalCenter(); m_rotationCenterOffset = args.rotationCenterOffset(); m_transformAroundRotationCenter = args.transformAroundRotationCenter(); m_cameraPos = args.m_cameraPos; m_aX = args.aX(); m_aY = args.aY(); m_aZ = args.aZ(); m_scaleX = args.scaleX(); m_scaleY = args.scaleY(); m_shearX = args.shearX(); m_shearY = args.shearY(); m_origPoints = args.origPoints(); //it's a copy m_transfPoints = args.transfPoints(); m_warpType = args.warpType(); m_alpha = args.alpha(); m_defaultPoints = args.defaultPoints(); m_keepAspectRatio = args.keepAspectRatio(); m_filter = args.m_filter; m_flattenedPerspectiveTransform = args.m_flattenedPerspectiveTransform; m_editTransformPoints = args.m_editTransformPoints; if (args.m_liquifyWorker) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(*args.m_liquifyWorker.data())); } m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0); } void ToolTransformArgs::clear() { m_origPoints.clear(); m_transfPoints.clear(); } ToolTransformArgs::ToolTransformArgs(const ToolTransformArgs& args) : m_liquifyProperties(args.m_liquifyProperties) { init(args); } KisToolChangesTrackerData *ToolTransformArgs::clone() const { return new ToolTransformArgs(*this); } ToolTransformArgs& ToolTransformArgs::operator=(const ToolTransformArgs& args) { clear(); m_liquifyProperties = args.m_liquifyProperties; init(args); return *this; } bool ToolTransformArgs::operator==(const ToolTransformArgs& other) const { return m_mode == other.m_mode && m_defaultPoints == other.m_defaultPoints && m_origPoints == other.m_origPoints && m_transfPoints == other.m_transfPoints && m_warpType == other.m_warpType && m_alpha == other.m_alpha && m_transformedCenter == other.m_transformedCenter && m_originalCenter == other.m_originalCenter && m_rotationCenterOffset == other.m_rotationCenterOffset && m_transformAroundRotationCenter == other.m_transformAroundRotationCenter && m_aX == other.m_aX && m_aY == other.m_aY && m_aZ == other.m_aZ && m_cameraPos == other.m_cameraPos && m_scaleX == other.m_scaleX && m_scaleY == other.m_scaleY && m_shearX == other.m_shearX && m_shearY == other.m_shearY && m_keepAspectRatio == other.m_keepAspectRatio && m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform && m_editTransformPoints == other.m_editTransformPoints && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties) && // pointer types ((m_filter && other.m_filter && m_filter->id() == other.m_filter->id()) || m_filter == other.m_filter) && ((m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker); } bool ToolTransformArgs::isSameMode(const ToolTransformArgs& other) const { if (m_mode != other.m_mode) return false; bool result = true; if (m_mode == FREE_TRANSFORM) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_aX == other.m_aX; result &= m_aY == other.m_aY; result &= m_aZ == other.m_aZ; } else if (m_mode == PERSPECTIVE_4POINT) { result &= m_transformedCenter == other.m_transformedCenter; result &= m_originalCenter == other.m_originalCenter; result &= m_scaleX == other.m_scaleX; result &= m_scaleY == other.m_scaleY; result &= m_shearX == other.m_shearX; result &= m_shearY == other.m_shearY; result &= m_flattenedPerspectiveTransform == other.m_flattenedPerspectiveTransform; } else if(m_mode == WARP || m_mode == CAGE) { result &= m_origPoints == other.m_origPoints; result &= m_transfPoints == other.m_transfPoints; } else if (m_mode == LIQUIFY) { result &= m_liquifyProperties && (m_liquifyProperties == other.m_liquifyProperties || *m_liquifyProperties == *other.m_liquifyProperties); result &= (m_liquifyWorker && other.m_liquifyWorker && *m_liquifyWorker == *other.m_liquifyWorker) || m_liquifyWorker == other.m_liquifyWorker; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } return result; } ToolTransformArgs::ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId) - : m_liquifyProperties(new KisLiquifyProperties()) + : m_mode(mode) + , m_defaultPoints(defaultPoints) + , m_origPoints {QVector()} + , m_transfPoints {QVector()} + , m_warpType(warpType) + , m_alpha(alpha) + , m_transformedCenter(transformedCenter) + , m_originalCenter(originalCenter) + , m_rotationCenterOffset(rotationCenterOffset) + , m_transformAroundRotationCenter(transformAroundRotationCenter) + , m_aX(aX) + , m_aY(aY) + , m_aZ(aZ) + , m_scaleX(scaleX) + , m_scaleY(scaleY) + , m_shearX(shearX) + , m_shearY(shearY) + , m_liquifyProperties(new KisLiquifyProperties()) { - m_mode = mode; - m_transformedCenter = transformedCenter; - m_originalCenter = originalCenter; - m_rotationCenterOffset = rotationCenterOffset; - m_transformAroundRotationCenter = transformAroundRotationCenter; - m_cameraPos = QVector3D(0,0,1024); - m_aX = aX; - m_aY = aY; - m_aZ = aZ; - m_scaleX = scaleX; - m_scaleY = scaleY; - m_shearX = shearX; - m_shearY = shearY; - m_origPoints = QVector(); - m_transfPoints = QVector(); - m_warpType = warpType; - m_alpha = alpha; - m_defaultPoints = defaultPoints; - m_keepAspectRatio = false; setFilterId(filterId); - m_editTransformPoints = false; } ToolTransformArgs::~ToolTransformArgs() { clear(); } void ToolTransformArgs::translate(const QPointF &offset) { if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { m_originalCenter += offset; m_rotationCenterOffset += offset; m_transformedCenter += offset; } else if(m_mode == WARP || m_mode == CAGE) { for (auto &pt : m_origPoints) { pt += offset; } for (auto &pt : m_transfPoints) { pt += offset; } } else if (m_mode == LIQUIFY) { KIS_ASSERT_RECOVER_RETURN(m_liquifyWorker); m_liquifyWorker->translate(offset); } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); } } bool ToolTransformArgs::isIdentity() const { if (m_mode == FREE_TRANSFORM) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_aX == 0 && m_aY == 0 && m_aZ == 0); } else if (m_mode == PERSPECTIVE_4POINT) { return (m_transformedCenter == m_originalCenter && m_scaleX == 1 && m_scaleY == 1 && m_shearX == 0 && m_shearY == 0 && m_flattenedPerspectiveTransform.isIdentity()); } else if(m_mode == WARP || m_mode == CAGE) { for (int i = 0; i < m_origPoints.size(); ++i) if (m_origPoints[i] != m_transfPoints[i]) return false; return true; } else if (m_mode == LIQUIFY) { // Not implemented! return false; } else { KIS_ASSERT_RECOVER_NOOP(0 && "unknown transform mode"); return true; } } void ToolTransformArgs::initLiquifyTransformMode(const QRect &srcRect) { m_liquifyWorker.reset(new KisLiquifyTransformWorker(srcRect, 0, 8)); m_liquifyProperties->loadAndResetMode(); } void ToolTransformArgs::saveLiquifyTransformMode() const { m_liquifyProperties->saveMode(); } void ToolTransformArgs::toXML(QDomElement *e) const { e->setAttribute("mode", (int) m_mode); QDomDocument doc = e->ownerDocument(); if (m_mode == FREE_TRANSFORM || m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl = doc.createElement("free_transform"); e->appendChild(freeEl); KisDomUtils::saveValue(&freeEl, "transformedCenter", m_transformedCenter); KisDomUtils::saveValue(&freeEl, "originalCenter", m_originalCenter); KisDomUtils::saveValue(&freeEl, "rotationCenterOffset", m_rotationCenterOffset); KisDomUtils::saveValue(&freeEl, "transformAroundRotationCenter", m_transformAroundRotationCenter); KisDomUtils::saveValue(&freeEl, "aX", m_aX); KisDomUtils::saveValue(&freeEl, "aY", m_aY); KisDomUtils::saveValue(&freeEl, "aZ", m_aZ); KisDomUtils::saveValue(&freeEl, "cameraPos", m_cameraPos); KisDomUtils::saveValue(&freeEl, "scaleX", m_scaleX); KisDomUtils::saveValue(&freeEl, "scaleY", m_scaleY); KisDomUtils::saveValue(&freeEl, "shearX", m_shearX); KisDomUtils::saveValue(&freeEl, "shearY", m_shearY); KisDomUtils::saveValue(&freeEl, "keepAspectRatio", m_keepAspectRatio); KisDomUtils::saveValue(&freeEl, "flattenedPerspectiveTransform", m_flattenedPerspectiveTransform); KisDomUtils::saveValue(&freeEl, "filterId", m_filter->id()); } else if (m_mode == WARP || m_mode == CAGE) { QDomElement warpEl = doc.createElement("warp_transform"); e->appendChild(warpEl); KisDomUtils::saveValue(&warpEl, "defaultPoints", m_defaultPoints); KisDomUtils::saveValue(&warpEl, "originalPoints", m_origPoints); KisDomUtils::saveValue(&warpEl, "transformedPoints", m_transfPoints); KisDomUtils::saveValue(&warpEl, "warpType", (int)m_warpType); // limited! KisDomUtils::saveValue(&warpEl, "alpha", m_alpha); } else if (m_mode == LIQUIFY) { QDomElement liqEl = doc.createElement("liquify_transform"); e->appendChild(liqEl); m_liquifyProperties->toXML(&liqEl); m_liquifyWorker->toXML(&liqEl); } else { KIS_ASSERT_RECOVER_RETURN(0 && "Unknown transform mode"); } // m_editTransformPoints should not be saved since it is reset explicitly } ToolTransformArgs ToolTransformArgs::fromXML(const QDomElement &e) { ToolTransformArgs args; int newMode = e.attribute("mode", "0").toInt(); if (newMode < 0 || newMode >= N_MODES) return ToolTransformArgs(); args.m_mode = (TransformMode) newMode; // reset explicitly args.m_editTransformPoints = false; bool result = false; if (args.m_mode == FREE_TRANSFORM || args.m_mode == PERSPECTIVE_4POINT) { QDomElement freeEl; QString filterId; result = KisDomUtils::findOnlyElement(e, "free_transform", &freeEl) && KisDomUtils::loadValue(freeEl, "transformedCenter", &args.m_transformedCenter) && KisDomUtils::loadValue(freeEl, "originalCenter", &args.m_originalCenter) && KisDomUtils::loadValue(freeEl, "rotationCenterOffset", &args.m_rotationCenterOffset) && KisDomUtils::loadValue(freeEl, "transformAroundRotationCenter", &args.m_transformAroundRotationCenter) && KisDomUtils::loadValue(freeEl, "aX", &args.m_aX) && KisDomUtils::loadValue(freeEl, "aY", &args.m_aY) && KisDomUtils::loadValue(freeEl, "aZ", &args.m_aZ) && KisDomUtils::loadValue(freeEl, "cameraPos", &args.m_cameraPos) && KisDomUtils::loadValue(freeEl, "scaleX", &args.m_scaleX) && KisDomUtils::loadValue(freeEl, "scaleY", &args.m_scaleY) && KisDomUtils::loadValue(freeEl, "shearX", &args.m_shearX) && KisDomUtils::loadValue(freeEl, "shearY", &args.m_shearY) && KisDomUtils::loadValue(freeEl, "keepAspectRatio", &args.m_keepAspectRatio) && KisDomUtils::loadValue(freeEl, "flattenedPerspectiveTransform", &args.m_flattenedPerspectiveTransform) && KisDomUtils::loadValue(freeEl, "filterId", &filterId); if (result) { args.m_filter = KisFilterStrategyRegistry::instance()->value(filterId); result = (bool) args.m_filter; } } else if (args.m_mode == WARP || args.m_mode == CAGE) { QDomElement warpEl; int warpType = 0; result = KisDomUtils::findOnlyElement(e, "warp_transform", &warpEl) && KisDomUtils::loadValue(warpEl, "defaultPoints", &args.m_defaultPoints) && KisDomUtils::loadValue(warpEl, "originalPoints", &args.m_origPoints) && KisDomUtils::loadValue(warpEl, "transformedPoints", &args.m_transfPoints) && KisDomUtils::loadValue(warpEl, "warpType", &warpType) && KisDomUtils::loadValue(warpEl, "alpha", &args.m_alpha); if (result && warpType >= 0 && warpType < KisWarpTransformWorker::N_MODES) { args.m_warpType = (KisWarpTransformWorker::WarpType_) warpType; } else { result = false; } } else if (args.m_mode == LIQUIFY) { QDomElement liquifyEl; result = KisDomUtils::findOnlyElement(e, "liquify_transform", &liquifyEl); *args.m_liquifyProperties = KisLiquifyProperties::fromXML(e); args.m_liquifyWorker.reset(KisLiquifyTransformWorker::fromXML(e)); } else { KIS_ASSERT_RECOVER_NOOP(0 && "Unknown transform mode"); } if (!result) { args = ToolTransformArgs(); } return args; } void ToolTransformArgs::saveContinuedState() { m_continuedTransformation.reset(); m_continuedTransformation.reset(new ToolTransformArgs(*this)); } void ToolTransformArgs::restoreContinuedState() { QScopedPointer tempTransformation( new ToolTransformArgs(*m_continuedTransformation)); *this = *tempTransformation; m_continuedTransformation.swap(tempTransformation); } const ToolTransformArgs* ToolTransformArgs::continuedTransform() const { return m_continuedTransformation.data(); } diff --git a/plugins/tools/tool_transform2/tool_transform_args.h b/plugins/tools/tool_transform2/tool_transform_args.h index 073e15f8eb..40fb4aed30 100644 --- a/plugins/tools/tool_transform2/tool_transform_args.h +++ b/plugins/tools/tool_transform2/tool_transform_args.h @@ -1,335 +1,335 @@ /* * tool_transform_args.h - part of Krita * * Copyright (c) 2010 Marc Pegon * * 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 TOOL_TRANSFORM_ARGS_H_ #define TOOL_TRANSFORM_ARGS_H_ #include #include #include #include #include "kis_liquify_properties.h" #include "kritatooltransform_export.h" #include "kis_global.h" #include "KisToolChangesTrackerData.h" #include class KisLiquifyTransformWorker; class QDomElement; /** * Class used to store the parameters of a transformation. * Some parameters are specific to free transform mode, and * others to warp mode : maybe add a union to save a little more * memory. */ class KRITATOOLTRANSFORM_EXPORT ToolTransformArgs : public KisToolChangesTrackerData { public: enum TransformMode {FREE_TRANSFORM = 0, WARP, CAGE, LIQUIFY, PERSPECTIVE_4POINT, N_MODES}; /** * Initializes the parameters for an identity transformation, * with mode set to free transform. */ ToolTransformArgs(); /** * The object return will be a copy of args. */ ToolTransformArgs(const ToolTransformArgs& args); KisToolChangesTrackerData *clone() const; /** * If mode is warp, original and transformed vector points will be of size 0. * Use setPoints method to set those vectors. */ ToolTransformArgs(TransformMode mode, QPointF transformedCenter, QPointF originalCenter, QPointF rotationCenterOffset, bool transformAroundRotationCenter, double aX, double aY, double aZ, double scaleX, double scaleY, double shearX, double shearY, KisWarpTransformWorker::WarpType warpType, double alpha, bool defaultPoints, const QString &filterId); ~ToolTransformArgs(); ToolTransformArgs& operator=(const ToolTransformArgs& args); bool operator==(const ToolTransformArgs& other) const; bool isSameMode(const ToolTransformArgs& other) const; inline TransformMode mode() const { return m_mode; } inline void setMode(TransformMode mode) { m_mode = mode; } //warp-related inline int numPoints() const { KIS_ASSERT_RECOVER_NOOP(m_origPoints.size() == m_transfPoints.size()); return m_origPoints.size(); } inline QPointF &origPoint(int i) { return m_origPoints[i]; } inline QPointF &transfPoint(int i) { return m_transfPoints[i]; } inline const QVector &origPoints() const { return m_origPoints; } inline const QVector &transfPoints() const { return m_transfPoints; } inline QVector &refOriginalPoints() { return m_origPoints; } inline QVector &refTransformedPoints() { return m_transfPoints; } inline KisWarpTransformWorker::WarpType warpType() const { return m_warpType; } inline double alpha() const { return m_alpha; } inline bool defaultPoints() const { return m_defaultPoints; } inline void setPoints(QVector origPoints, QVector transfPoints) { m_origPoints = QVector(origPoints); m_transfPoints = QVector(transfPoints); } inline void setWarpType(KisWarpTransformWorker::WarpType warpType) { m_warpType = warpType; } inline void setWarpCalculation(KisWarpTransformWorker::WarpCalculation warpCalc) { m_warpCalculation = warpCalc; } inline KisWarpTransformWorker::WarpCalculation warpCalculation() { return m_warpCalculation; } inline void setAlpha(double alpha) { m_alpha = alpha; } inline void setDefaultPoints(bool defaultPoints) { m_defaultPoints = defaultPoints; } //"free transform"-related inline QPointF transformedCenter() const { return m_transformedCenter; } inline QPointF originalCenter() const { return m_originalCenter; } inline QPointF rotationCenterOffset() const { return m_rotationCenterOffset; } inline bool transformAroundRotationCenter() const { return m_transformAroundRotationCenter; } inline double aX() const { return m_aX; } inline double aY() const { return m_aY; } inline double aZ() const { return m_aZ; } inline QVector3D cameraPos() const { return m_cameraPos; } inline double scaleX() const { return m_scaleX; } inline double scaleY() const { return m_scaleY; } inline bool keepAspectRatio() const { return m_keepAspectRatio; } inline double shearX() const { return m_shearX; } inline double shearY() const { return m_shearY; } inline void setTransformedCenter(QPointF transformedCenter) { m_transformedCenter = transformedCenter; } inline void setOriginalCenter(QPointF originalCenter) { m_originalCenter = originalCenter; } inline void setRotationCenterOffset(QPointF rotationCenterOffset) { m_rotationCenterOffset = rotationCenterOffset; } void setTransformAroundRotationCenter(bool value); inline void setAX(double aX) { KIS_ASSERT_RECOVER_NOOP(aX == normalizeAngle(aX)); m_aX = aX; } inline void setAY(double aY) { KIS_ASSERT_RECOVER_NOOP(aY == normalizeAngle(aY)); m_aY = aY; } inline void setAZ(double aZ) { KIS_ASSERT_RECOVER_NOOP(aZ == normalizeAngle(aZ)); m_aZ = aZ; } inline void setCameraPos(const QVector3D &pos) { m_cameraPos = pos; } inline void setScaleX(double scaleX) { m_scaleX = scaleX; } inline void setScaleY(double scaleY) { m_scaleY = scaleY; } inline void setKeepAspectRatio(bool value) { m_keepAspectRatio = value; } inline void setShearX(double shearX) { m_shearX = shearX; } inline void setShearY(double shearY) { m_shearY = shearY; } inline QString filterId() const { return m_filter->id(); } void setFilterId(const QString &id); inline KisFilterStrategy* filter() const { return m_filter; } bool isIdentity() const; inline QTransform flattenedPerspectiveTransform() const { return m_flattenedPerspectiveTransform; } inline void setFlattenedPerspectiveTransform(const QTransform &value) { m_flattenedPerspectiveTransform = value; } bool isEditingTransformPoints() const { return m_editTransformPoints; } void setEditingTransformPoints(bool value) { m_editTransformPoints = value; } const KisLiquifyProperties* liquifyProperties() const { return m_liquifyProperties.data(); } KisLiquifyProperties* liquifyProperties() { return m_liquifyProperties.data(); } void initLiquifyTransformMode(const QRect &srcRect); void saveLiquifyTransformMode() const; KisLiquifyTransformWorker* liquifyWorker() const { return m_liquifyWorker.data(); } void toXML(QDomElement *e) const; static ToolTransformArgs fromXML(const QDomElement &e); void translate(const QPointF &offset); void saveContinuedState(); void restoreContinuedState(); const ToolTransformArgs* continuedTransform() const; private: void clear(); void init(const ToolTransformArgs& args); TransformMode m_mode; // warp-related arguments // these are basically the arguments taken by the warp transform worker bool m_defaultPoints; // true : the original points are set to make a grid // which density is given by numPoints() QVector m_origPoints; QVector m_transfPoints; KisWarpTransformWorker::WarpType m_warpType; KisWarpTransformWorker::WarpCalculation m_warpCalculation; // DRAW or GRID double m_alpha; //'free transform'-related // basically the arguments taken by the transform worker QPointF m_transformedCenter; QPointF m_originalCenter; QPointF m_rotationCenterOffset; // the position of the rotation center relative to // the original top left corner of the selection // before any transformation bool m_transformAroundRotationCenter; // In freehand mode makes the scaling and other transformations // be anchored to the rotation center point. double m_aX; double m_aY; double m_aZ; - QVector3D m_cameraPos; + QVector3D m_cameraPos {QVector3D(0,0,1024)}; double m_scaleX; double m_scaleY; double m_shearX; double m_shearY; - bool m_keepAspectRatio; + bool m_keepAspectRatio {false}; // perspective trasform related QTransform m_flattenedPerspectiveTransform; KisFilterStrategy *m_filter; - bool m_editTransformPoints; + bool m_editTransformPoints {false}; QSharedPointer m_liquifyProperties; QScopedPointer m_liquifyWorker; /** * When we continue a transformation, m_continuedTransformation * stores the initial step of our transform. All cancel and revert * operations should revert to it. */ QScopedPointer m_continuedTransformation; }; #endif // TOOL_TRANSFORM_ARGS_H_