diff --git a/libs/brush/kis_auto_brush.cpp b/libs/brush/kis_auto_brush.cpp index eb490f1982..ec7ded3474 100644 --- a/libs/brush/kis_auto_brush.cpp +++ b/libs/brush/kis_auto_brush.cpp @@ -1,392 +1,393 @@ /* * 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 //MSVC requires that Vc come first #include "kis_auto_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) || defined(_WIN64) #include #define srand48 srand inline double drand48() { return double(rand()) / RAND_MAX; } #endif struct KisAutoBrush::Private { Private() : randomness(0), density(1.0), idealThreadCountCached(1) {} Private(const Private &rhs) : shape(rhs.shape->clone()), randomness(rhs.randomness), density(rhs.density), idealThreadCountCached(rhs.idealThreadCountCached) { } QScopedPointer shape; qreal randomness; qreal density; int idealThreadCountCached; }; KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density) : KisBrush(), d(new Private) { d->shape.reset(as); d->randomness = randomness; d->density = density; d->idealThreadCountCached = QThread::idealThreadCount(); setBrushType(MASK); setWidth(qMax(qreal(1.0), d->shape->width())); setHeight(qMax(qreal(1.0), d->shape->height())); QImage image = createBrushPreview(); setBrushTipImage(image); // Set angle here so brush tip image is generated unrotated setAngle(angle); image = createBrushPreview(); setImage(image); } KisAutoBrush::~KisAutoBrush() { } qreal KisAutoBrush::userEffectiveSize() const { return d->shape->diameter(); } void KisAutoBrush::setUserEffectiveSize(qreal value) { d->shape->setDiameter(value); } KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs) : KisBrush(rhs), d(new Private(*rhs.d)) { } KisBrush* KisAutoBrush::clone() const { return new KisAutoBrush(*this); } /* It's difficult to predict the mask height exactly when there are * more than 2 spikes, so we return an upperbound instead. */ static KisDabShape lieAboutDabShape(KisDabShape const& shape, int spikes) { return spikes > 2 ? KisDabShape(shape.scale(), 1.0, shape.rotation()) : shape; } qint32 KisAutoBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { return KisBrush::maskHeight( lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info); } qint32 KisAutoBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { return KisBrush::maskWidth( lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info); } QSizeF KisAutoBrush::characteristicSize(KisDabShape const& shape) const { return KisBrush::characteristicSize(lieAboutDabShape(shape, maskGenerator()->spikes())); } inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size) { /** * This version of filling uses low granularity of data transfers * (32-bit chunks) and internal processor's parallelism. It reaches * 25% better performance in KisStrokeBenchmark in comparison to * per-pixel memcpy version (tested on Sandy Bridge). */ int block1 = size / 8; int block2 = size % 8; quint32 *src = reinterpret_cast(color); quint32 *dst = reinterpret_cast(buf); // check whether all buffers are 4 bytes aligned // (uncomment if experience some problems) // Q_ASSERT(((qint64)src & 3) == 0); // Q_ASSERT(((qint64)dst & 3) == 0); for (int i = 0; i < block1; i++) { *dst = *src; *(dst + 1) = *src; *(dst + 2) = *src; *(dst + 3) = *src; *(dst + 4) = *src; *(dst + 5) = *src; *(dst + 6) = *src; *(dst + 7) = *src; dst += 8; } for (int i = 0; i < block2; i++) { *dst = *src; dst++; } } inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize) { /** * This version uses internal processor's parallelism and gives * 20% better performance in KisStrokeBenchmark in comparison to * per-pixel memcpy version (tested on Sandy Bridge (+20%) and * on Merom (+10%)). */ int block1 = size / 8; int block2 = size % 8; for (int i = 0; i < block1; i++) { quint8 *d1 = buf; quint8 *d2 = buf + pixelSize; quint8 *d3 = buf + 2 * pixelSize; quint8 *d4 = buf + 3 * pixelSize; quint8 *d5 = buf + 4 * pixelSize; quint8 *d6 = buf + 5 * pixelSize; quint8 *d7 = buf + 6 * pixelSize; quint8 *d8 = buf + 7 * pixelSize; for (int j = 0; j < pixelSize; j++) { *(d1 + j) = color[j]; *(d2 + j) = color[j]; *(d3 + j) = color[j]; *(d4 + j) = color[j]; *(d5 + j) = color[j]; *(d6 + j) = color[j]; *(d7 + j) = color[j]; *(d8 + j) = color[j]; } buf += 8 * pixelSize; } for (int i = 0; i < block2; i++) { memcpy(buf, color, pixelSize); buf += pixelSize; } } void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, - double subPixelX , double subPixelY, qreal softnessFactor) const + double subPixelX , double subPixelY, qreal softnessFactor, qreal lightnessStrength) const { Q_UNUSED(info); + Q_UNUSED(lightnessStrength); // Generate the paint device from the mask const KoColorSpace* cs = dst->colorSpace(); quint32 pixelSize = cs->pixelSize(); // mask dimension methods already includes KisBrush::angle() int dstWidth = maskWidth(shape, subPixelX, subPixelY, info); int dstHeight = maskHeight(shape, subPixelX, subPixelY, info); QPointF hotSpot = this->hotSpot(shape, info); // mask size and hotSpot function take the KisBrush rotation into account qreal angle = shape.rotation() + KisBrush::angle(); // if there's coloring information, we merely change the alpha: in that case, // the dab should be big enough! if (coloringInformation) { // new bounds. we don't care if there is some extra memory occcupied. dst->setRect(QRect(0, 0, dstWidth, dstHeight)); dst->lazyGrowBufferWithoutInitialization(); } else { KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth && dst->bounds().height() >= dstHeight); } KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation); quint8* dabPointer = dst->data(); quint8* color = 0; if (dynamic_cast(coloringInformation)) { color = const_cast(coloringInformation->color()); } double centerX = hotSpot.x() - 0.5 + subPixelX; double centerY = hotSpot.y() - 0.5 + subPixelY; d->shape->setSoftness(softnessFactor); // softness must be set first d->shape->setScale(shape.scaleX(), shape.scaleY()); if (!color) { for (int y = 0; y < dstHeight; y++) { for (int x = 0; x < dstWidth; x++) { memcpy(dabPointer, coloringInformation->color(), pixelSize); coloringInformation->nextColumn(); dabPointer += pixelSize; } coloringInformation->nextRow(); } } MaskProcessingData data(dst, cs, color, d->randomness, d->density, centerX, centerY, angle); KisBrushMaskApplicatorBase *applicator = d->shape->applicator(); applicator->initializeData(&data); int jobs = d->idealThreadCountCached; if (threadingAllowed() && dstHeight > 100 && jobs >= 4) { int splitter = dstHeight / jobs; QVector rects; for (int i = 0; i < jobs - 1; i++) { rects << QRect(0, i * splitter, dstWidth, splitter); } rects << QRect(0, (jobs - 1)*splitter, dstWidth, dstHeight - (jobs - 1)*splitter); OperatorWrapper wrapper(applicator); QtConcurrent::blockingMap(rects, wrapper); } else { QRect rect(0, 0, dstWidth, dstHeight); applicator->process(rect); } } void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const { QDomElement shapeElt = doc.createElement("MaskGenerator"); d->shape->toXML(doc, shapeElt); e.appendChild(shapeElt); e.setAttribute("type", "auto_brush"); e.setAttribute("spacing", QString::number(spacing())); e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); e.setAttribute("angle", QString::number(KisBrush::angle())); e.setAttribute("randomness", QString::number(d->randomness)); e.setAttribute("density", QString::number(d->density)); KisBrush::toXML(doc, e); } QImage KisAutoBrush::createBrushPreview() { int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation()); int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation()); KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0); KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); fdev->setRect(QRect(0, 0, width, height)); fdev->initialize(); mask(fdev, KoColor(Qt::black, fdev->colorSpace()), KisDabShape(), info); return fdev->convertToQImage(0); } const KisMaskGenerator* KisAutoBrush::maskGenerator() const { return d->shape.data(); } qreal KisAutoBrush::density() const { return d->density; } qreal KisAutoBrush::randomness() const { return d->randomness; } QPainterPath KisAutoBrush::outline() const { bool simpleOutline = (d->density < 1.0); if (simpleOutline) { QPainterPath path; QRectF brushBoundingbox(0, 0, width(), height()); if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) { path.addEllipse(brushBoundingbox); } else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE) path.addRect(brushBoundingbox); } return path; } return KisBrush::boundary()->path(); } void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const { KisBrush::lodLimitations(l); if (!qFuzzyCompare(density(), 1.0)) { l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0")); } if (!qFuzzyCompare(randomness(), 0.0)) { l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0")); } } diff --git a/libs/brush/kis_auto_brush.h b/libs/brush/kis_auto_brush.h index 0966f6eaa8..4ea7395bc2 100644 --- a/libs/brush/kis_auto_brush.h +++ b/libs/brush/kis_auto_brush.h @@ -1,103 +1,104 @@ /* * Copyright (c) 2004 Cyrille Berger * Copyright (c) 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_AUTOBRUSH_RESOURCE_H_ #define _KIS_AUTOBRUSH_RESOURCE_H_ #include "kritabrush_export.h" #include "kis_brush.h" #include class KisMaskGenerator; /** * XXX: docs! */ class BRUSH_EXPORT KisAutoBrush : public KisBrush { public: KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density = 1.0); KisAutoBrush(const KisAutoBrush& rhs); KisBrush* clone() const override; ~KisAutoBrush() override; public: qreal userEffectiveSize() const override; void setUserEffectiveSize(qreal value) override; qint32 maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const override; qint32 maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const override; QSizeF characteristicSize(KisDabShape const&) const override; KisFixedPaintDeviceSP paintDevice(const KoColorSpace*, KisDabShape const&, const KisPaintInformation&, double = 0, double = 0) const override { return 0; // The autobrush does NOT support images! } void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, - KisBrush::ColoringInformation* src, - KisDabShape const&, - const KisPaintInformation& info, - double subPixelX = 0, double subPixelY = 0, - qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const override; + KisBrush::ColoringInformation* src, + KisDabShape const&, + const KisPaintInformation& info, + double subPixelX = 0, double subPixelY = 0, + qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR, + qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) const override; QPainterPath outline() const override; public: bool load() override { return false; } bool loadFromDevice(QIODevice *) override { return false; } bool save() override { return false; } bool saveToDevice(QIODevice*) const override { return false; } void toXML(QDomDocument& , QDomElement&) const override; const KisMaskGenerator* maskGenerator() const; qreal randomness() const; qreal density() const; void lodLimitations(KisPaintopLodLimitations *l) const override; private: QImage createBrushPreview(); private: struct Private; const QScopedPointer d; }; #endif // _KIS_AUTOBRUSH_RESOURCE_H_ diff --git a/libs/brush/kis_brush.cpp b/libs/brush/kis_brush.cpp index 451b5ecb2c..33e72773e4 100644 --- a/libs/brush/kis_brush.cpp +++ b/libs/brush/kis_brush.cpp @@ -1,655 +1,664 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Adrian Page * Copyright (c) 2005 Bart Coppens * Copyright (c) 2007 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_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_boundary.h" #include "kis_image.h" #include "kis_iterator_ng.h" #include "kis_brush_registry.h" #include #include #include #include #include KisBrush::ColoringInformation::~ColoringInformation() { } KisBrush::PlainColoringInformation::PlainColoringInformation(const quint8* color) : m_color(color) { } KisBrush::PlainColoringInformation::~PlainColoringInformation() { } const quint8* KisBrush::PlainColoringInformation::color() const { return m_color; } void KisBrush::PlainColoringInformation::nextColumn() { } void KisBrush::PlainColoringInformation::nextRow() { } KisBrush::PaintDeviceColoringInformation::PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width) : m_source(source) , m_iterator(m_source->createHLineConstIteratorNG(0, 0, width)) { } KisBrush::PaintDeviceColoringInformation::~PaintDeviceColoringInformation() { } const quint8* KisBrush::PaintDeviceColoringInformation::color() const { return m_iterator->oldRawData(); } void KisBrush::PaintDeviceColoringInformation::nextColumn() { m_iterator->nextPixel(); } void KisBrush::PaintDeviceColoringInformation::nextRow() { m_iterator->nextRow(); } struct KisBrush::Private { Private() : boundary(0) , angle(0) , scale(1.0) , hasColor(false) , preserveLightness(false) , brushType(INVALID) , autoSpacingActive(false) , autoSpacingCoeff(1.0) , threadingAllowed(true) {} ~Private() { delete boundary; } mutable KisBoundary* boundary; qreal angle; qreal scale; bool hasColor; bool preserveLightness; enumBrushType brushType; qint32 width; qint32 height; double spacing; QPointF hotSpot; mutable QSharedPointer brushPyramid; QImage brushTipImage; bool autoSpacingActive; qreal autoSpacingCoeff; bool threadingAllowed; }; KisBrush::KisBrush() : KoResource(QString()) , d(new Private) { } KisBrush::KisBrush(const QString& filename) : KoResource(filename) , d(new Private) { } KisBrush::KisBrush(const KisBrush& rhs) : KoResource(QString()) , KisShared() , d(new Private) { setBrushTipImage(rhs.brushTipImage()); d->brushType = rhs.d->brushType; d->width = rhs.d->width; d->height = rhs.d->height; d->spacing = rhs.d->spacing; d->hotSpot = rhs.d->hotSpot; d->hasColor = rhs.d->hasColor; d->preserveLightness = rhs.d->preserveLightness; d->angle = rhs.d->angle; d->scale = rhs.d->scale; d->autoSpacingActive = rhs.d->autoSpacingActive; d->autoSpacingCoeff = rhs.d->autoSpacingCoeff; d->threadingAllowed = rhs.d->threadingAllowed; setFilename(rhs.filename()); /** * Be careful! The pyramid is shared between two brush objects, * therefore you cannot change it, only recreate! That is the * reason why it is defined as const! */ d->brushPyramid = rhs.d->brushPyramid; // don't copy the boundary, it will be regenerated -- see bug 291910 } KisBrush::~KisBrush() { delete d; } QImage KisBrush::brushTipImage() const { if (d->brushTipImage.isNull()) { const_cast(this)->load(); } return d->brushTipImage; } qint32 KisBrush::width() const { return d->width; } void KisBrush::setWidth(qint32 width) { d->width = width; } qint32 KisBrush::height() const { return d->height; } void KisBrush::setHeight(qint32 height) { d->height = height; } void KisBrush::setHotSpot(QPointF pt) { double x = pt.x(); double y = pt.y(); if (x < 0) x = 0; else if (x >= width()) x = width() - 1; if (y < 0) y = 0; else if (y >= height()) y = height() - 1; d->hotSpot = QPointF(x, y); } QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const { Q_UNUSED(info); QSizeF metric = characteristicSize(shape); qreal w = metric.width(); qreal h = metric.height(); // The smallest brush we can produce is a single pixel. if (w < 1) { w = 1; } if (h < 1) { h = 1; } // XXX: This should take d->hotSpot into account, though it // isn't specified by gimp brushes so it would default to the center // anyway. QPointF p(w / 2, h / 2); return p; } bool KisBrush::hasColor() const { return d->hasColor; } void KisBrush::setHasColor(bool hasColor) { d->hasColor = hasColor; } bool KisBrush::preserveLightness() const { return d->preserveLightness; } void KisBrush::setPreserveLightness(bool preserveLightness) { if (d->preserveLightness != preserveLightness) { d->preserveLightness = preserveLightness; clearBrushPyramid(); } } bool KisBrush::isPiercedApprox() const { QImage image = brushTipImage(); qreal w = image.width(); qreal h = image.height(); qreal xPortion = qMin(0.1, 5.0 / w); qreal yPortion = qMin(0.1, 5.0 / h); int x0 = std::floor((0.5 - xPortion) * w); int x1 = std::ceil((0.5 + xPortion) * w); int y0 = std::floor((0.5 - yPortion) * h); int y1 = std::ceil((0.5 + yPortion) * h); const int maxNumSamples = (x1 - x0 + 1) * (y1 - y0 + 1); const int failedPixelsThreshold = 0.1 * maxNumSamples; const int thresholdValue = 0.95 * 255; int failedPixels = 0; for (int y = y0; y <= y1; y++) { for (int x = x0; x <= x1; x++) { QRgb pixel = image.pixel(x,y); if (qRed(pixel) > thresholdValue) { failedPixels++; } } } return failedPixels > failedPixelsThreshold; } bool KisBrush::canPaintFor(const KisPaintInformation& /*info*/) { return true; } void KisBrush::setBrushTipImage(const QImage& image) { d->brushTipImage = image; if (!image.isNull()) { if (image.width() > 128 || image.height() > 128) { KoResource::setImage(image.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { KoResource::setImage(image); } setWidth(image.width()); setHeight(image.height()); } clearBrushPyramid(); } void KisBrush::setBrushType(enumBrushType type) { d->brushType = type; } enumBrushType KisBrush::brushType() const { return d->brushType; } void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const { e.setAttribute("type", type); e.setAttribute("filename", shortFilename()); e.setAttribute("spacing", QString::number(spacing())); e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); e.setAttribute("angle", QString::number(angle())); e.setAttribute("scale", QString::number(scale())); e.setAttribute("preserveLightness", QString::number((int)preserveLightness())); } void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const { element.setAttribute("BrushVersion", "2"); } KisBrushSP KisBrush::fromXML(const QDomElement& element) { KisBrushSP brush = KisBrushRegistry::instance()->createBrush(element); if (brush && element.attribute("BrushVersion", "1") == "1") { brush->setScale(brush->scale() * 2.0); } return brush; } QSizeF KisBrush::characteristicSize(KisDabShape const& shape) const { KisDabShape normalizedShape( shape.scale() * d->scale, shape.ratio(), normalizeAngle(shape.rotation() + d->angle)); return KisQImagePyramid::characteristicSize( QSize(width(), height()), normalizedShape); } qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); qreal angle = normalizeAngle(shape.rotation() + d->angle); qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).width(); } qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); qreal angle = normalizeAngle(shape.rotation() + d->angle); qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).height(); } double KisBrush::maskAngle(double angle) const { return normalizeAngle(angle + d->angle); } quint32 KisBrush::brushIndex(const KisPaintInformation& info) const { Q_UNUSED(info); return 0; } void KisBrush::setSpacing(double s) { if (s < 0.02) s = 0.02; d->spacing = s; } double KisBrush::spacing() const { return d->spacing; } void KisBrush::setAutoSpacing(bool active, qreal coeff) { d->autoSpacingCoeff = coeff; d->autoSpacingActive = active; } bool KisBrush::autoSpacingActive() const { return d->autoSpacingActive; } qreal KisBrush::autoSpacingCoeff() const { return d->autoSpacingCoeff; } void KisBrush::notifyStrokeStarted() { } void KisBrush::notifyCachedDabPainted(const KisPaintInformation& info) { Q_UNUSED(info); } void KisBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo) { Q_UNUSED(info); Q_UNUSED(seqNo); } void KisBrush::setThreadingAllowed(bool value) { d->threadingAllowed = value; } bool KisBrush::threadingAllowed() const { return d->threadingAllowed; } void KisBrush::clearBrushPyramid() { d->brushPyramid.reset(new KisSharedQImagePyramid()); } -void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const +void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const { PlainColoringInformation pci(color.data()); - generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor); + generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength); } -void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const +void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const { PaintDeviceColoringInformation pdci(src, maskWidth(shape, subPixelX, subPixelY, info)); - generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor); + generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength); } namespace { void fetchPremultipliedRed(const QRgb* src, quint8 *dst, int maskWidth) { for (int x = 0; x < maskWidth; x++) { *dst = KoColorSpaceMaths::multiply(255 - *src, qAlpha(*src)); src++; dst++; } } } +void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, + ColoringInformation* coloringInformation, + KisDabShape const& shape, + const KisPaintInformation& info_, + double subPixelX, double subPixelY, qreal softnessFactor) const +{ + generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info_, subPixelX, subPixelY, softnessFactor, DEFAULT_LIGHTNESS_STRENGTH); +} + void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info_, - double subPixelX, double subPixelY, qreal softnessFactor) const + double subPixelX, double subPixelY, qreal softnessFactor, qreal lightnessStrength) const { KIS_SAFE_ASSERT_RECOVER_RETURN(valid()); Q_UNUSED(info_); Q_UNUSED(softnessFactor); QImage outputImage = d->brushPyramid->pyramid(this)->createImage(KisDabShape( shape.scale() * d->scale, shape.ratio(), -normalizeAngle(shape.rotation() + d->angle)), subPixelX, subPixelY); qint32 maskWidth = outputImage.width(); qint32 maskHeight = outputImage.height(); dst->setRect(QRect(0, 0, maskWidth, maskHeight)); dst->lazyGrowBufferWithoutInitialization(); KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation); quint8* color = 0; if (dynamic_cast(coloringInformation)) { color = const_cast(coloringInformation->color()); } const KoColorSpace *cs = dst->colorSpace(); const quint32 pixelSize = cs->pixelSize(); quint8 *rowPointer = dst->data(); const bool preserveLightness = this->preserveLightness(); for (int y = 0; y < maskHeight; y++) { const quint8* maskPointer = outputImage.constScanLine(y); if (color) { if (preserveLightness) { - cs->fillGrayBrushWithColorAndLightnessOverlay(rowPointer, reinterpret_cast(maskPointer), color, maskWidth); + cs->fillGrayBrushWithColorAndLightnessWithStrength(rowPointer, reinterpret_cast(maskPointer), color, lightnessStrength, maskWidth); } else { cs->fillGrayBrushWithColor(rowPointer, reinterpret_cast(maskPointer), color, maskWidth); } } else { { quint8 *dst = rowPointer; for (int x = 0; x < maskWidth; x++) { memcpy(dst, coloringInformation->color(), pixelSize); coloringInformation->nextColumn(); dst += pixelSize; } } QScopedArrayPointer alphaArray(new quint8[maskWidth]); fetchPremultipliedRed(reinterpret_cast(maskPointer), alphaArray.data(), maskWidth); cs->applyAlphaU8Mask(rowPointer, alphaArray.data(), maskWidth); } rowPointer += maskWidth * pixelSize; if (!color) { coloringInformation->nextRow(); } } } KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { Q_ASSERT(valid()); Q_UNUSED(info); double angle = normalizeAngle(shape.rotation() + d->angle); double scale = shape.scale() * d->scale; QImage outputImage = d->brushPyramid->pyramid(this)->createImage( KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY); KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace); Q_CHECK_PTR(dab); dab->convertFromQImage(outputImage, ""); return dab; } void KisBrush::resetBoundary() { delete d->boundary; d->boundary = 0; } void KisBrush::generateBoundary() const { KisFixedPaintDeviceSP dev; KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle()); if (brushType() == IMAGE || brushType() == PIPE_IMAGE) { dev = paintDevice(KoColorSpaceRegistry::instance()->rgb8(), inverseTransform, KisPaintInformation()); } else { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); dev = new KisFixedPaintDevice(cs); mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation()); } d->boundary = new KisBoundary(dev); d->boundary->generateBoundary(); } const KisBoundary* KisBrush::boundary() const { if (!d->boundary) generateBoundary(); return d->boundary; } void KisBrush::setScale(qreal _scale) { d->scale = _scale; } qreal KisBrush::scale() const { return d->scale; } void KisBrush::setAngle(qreal _rotation) { d->angle = _rotation; } qreal KisBrush::angle() const { return d->angle; } QPainterPath KisBrush::outline() const { return boundary()->path(); } void KisBrush::lodLimitations(KisPaintopLodLimitations *l) const { if (spacing() > 0.5) { l->limitations << KoID("huge-spacing", i18nc("PaintOp instant preview limitation", "Spacing > 0.5, consider disabling Instant Preview")); } } diff --git a/libs/brush/kis_brush.h b/libs/brush/kis_brush.h index 95551a2a47..58d944b574 100644 --- a/libs/brush/kis_brush.h +++ b/libs/brush/kis_brush.h @@ -1,406 +1,417 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * 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_BRUSH_ #define KIS_BRUSH_ #include #include #include #include #include #include class KisQImagemask; typedef KisSharedPtr KisQImagemaskSP; class QString; class KoColor; class KoColorSpace; class KisPaintInformation; class KisBoundary; class KisPaintopLodLimitations; enum enumBrushType { INVALID, MASK, IMAGE, PIPE_MASK, PIPE_IMAGE }; static const qreal DEFAULT_SOFTNESS_FACTOR = 1.0; +static const qreal DEFAULT_LIGHTNESS_STRENGTH = 1.0; class KisBrush; typedef KisSharedPtr KisBrushSP; /** * KisBrush is the base class for brush resources. A brush resource * defines one or more images that are used to potato-stamp along * the drawn path. The brush type defines how this brush is used -- * the important difference is between masks (which take the current * painting color) and images (which do not). It is up to the paintop * to make use of this feature. * * Brushes must be serializable to an xml representation and provide * a factory class that can recreate or retrieve the brush based on * this representation. * * XXX: This api is still a big mess -- it needs a good refactoring. * And the whole KoResource architecture is way over-designed. */ class BRUSH_EXPORT KisBrush : public KoResource, public KisShared { public: class ColoringInformation { public: virtual ~ColoringInformation(); virtual const quint8* color() const = 0; virtual void nextColumn() = 0; virtual void nextRow() = 0; }; protected: class PlainColoringInformation : public ColoringInformation { public: PlainColoringInformation(const quint8* color); ~PlainColoringInformation() override; const quint8* color() const override ; void nextColumn() override; void nextRow() override; private: const quint8* m_color; }; class PaintDeviceColoringInformation : public ColoringInformation { public: PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width); ~PaintDeviceColoringInformation() override; const quint8* color() const override ; void nextColumn() override; void nextRow() override; private: const KisPaintDeviceSP m_source; KisHLineConstIteratorSP m_iterator; }; public: KisBrush(); KisBrush(const QString& filename); ~KisBrush() override; virtual qreal userEffectiveSize() const = 0; virtual void setUserEffectiveSize(qreal value) = 0; bool load() override { return false; } bool loadFromDevice(QIODevice *) override { return false; } bool save() override { return false; } bool saveToDevice(QIODevice* ) const override { return false; } /** * @brief brushImage the image the brush tip can paint with. Not all brush types have a single * image. * @return a valid QImage. */ virtual QImage brushTipImage() const; /** * Change the spacing of the brush. * @param spacing a spacing of 1.0 means that strokes will be separated from one time the size * of the brush. */ virtual void setSpacing(double spacing); /** * @return the spacing between two strokes for this brush */ double spacing() const; void setAutoSpacing(bool active, qreal coeff); bool autoSpacingActive() const; qreal autoSpacingCoeff() const; /** * @return the width (for scale == 1.0) */ qint32 width() const; /** * @return the height (for scale == 1.0) */ qint32 height() const; /** * @return the width of the mask for the given scale and angle */ virtual qint32 maskWidth(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const; /** * @return the height of the mask for the given scale and angle */ virtual qint32 maskHeight(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const; /** * @return the logical size of the brush, that is the size measured * in floating point value. * * This value should not be used for calculating future dab sizes * because it doesn't take any rounding into account. The only use * of this metric is calculation of brush-size derivatives like * hotspots and spacing. */ virtual QSizeF characteristicSize(KisDabShape const&) const; /** * @return the angle of the mask adding the given angle */ double maskAngle(double angle = 0) const; /** * @return the index of the brush * if the brush consists of multiple images */ virtual quint32 brushIndex(const KisPaintInformation& info) const; /** * The brush type defines how the brush is used. */ virtual enumBrushType brushType() const; QPointF hotSpot(KisDabShape const&, const KisPaintInformation& info) const; /** * Returns true if this brush can return something useful for the info. This is used * by Pipe Brushes that can't paint sometimes **/ virtual bool canPaintFor(const KisPaintInformation& /*info*/); /** * Is called by the paint op when a paintop starts a stroke. The * point is that we store brushes a server while the paint ops are * are recreated all the time. Is means that upon a stroke start * the brushes may need to clear its state. */ virtual void notifyStrokeStarted(); /** * Is called by the cache, when cache hit has happened. * Having got this notification the brush can update the counters * of dabs, generate some new random values if needed. * * * NOTE: one should use **either** notifyCachedDabPainted() or prepareForSeqNo() * * Currently, this is used by pipe'd brushes to implement * incremental and random parasites */ virtual void notifyCachedDabPainted(const KisPaintInformation& info); /** * Is called by the multithreaded queue to prepare a specific brush * tip for the particular seqNo. * * NOTE: one should use **either** notifyCachedDabPainted() or prepareForSeqNo() * * Currently, this is used by pipe'd brushes to implement * incremental and random parasites */ virtual void prepareForSeqNo(const KisPaintInformation& info, int seqNo); /** * Notify the brush if it can use QtConcurrent's threading capabilities in its * internal routines. By default it is allowed, but some paintops (who do their * own multithreading) may ask the brush to avoid internal threading. */ void setThreadingAllowed(bool value); /** * \see setThreadingAllowed() for details */ bool threadingAllowed() const; /** * Return a fixed paint device that contains a correctly scaled image dab. */ virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0) const; /** * clear dst fill it with a mask colored with KoColor */ void mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, - double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; + double subPixelX = 0, double subPixelY = 0, + qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) const; /** * clear dst and fill it with a mask colored with the corresponding colors of src */ void mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, - double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; + double subPixelX = 0, double subPixelY = 0, + qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) const; virtual bool hasColor() const; virtual bool preserveLightness() const; /** * If the brush image data are colorful (e.g. you created the brush from the canvas with custom brush) * and you want to paint with it as with masks, but preserve Lightness (Value), set to true. */ virtual void setPreserveLightness(bool preserveLightness); /** * Create a mask and either mask dst (that is, change all alpha values of the * existing pixels to those of the mask) or, if coloringInfo is present, clear * dst and fill dst with pixels according to coloringInfo, masked according to the * generated mask. * * @param dst the destination that will be draw on the image, and this function * will edit its alpha channel * @param coloringInfo coloring information that will be copied on the dab, it can be null * @param shape a shape applied on the alpha mask * @param info the painting information (this is only and should only be used by * KisImagePipeBrush and only to be backward compatible with the Gimp, * KisImagePipeBrush is ignoring scale and angle information) * @param subPixelX sub position of the brush (contained between 0.0 and 1.0) * @param subPixelY sub position of the brush (contained between 0.0 and 1.0) * @param softnessFactor softness factor of the brush * * @return a mask computed from the grey-level values of the * pixels in the brush. */ virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInfo, KisDabShape const&, const KisPaintInformation& info, - double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; + double subPixelX, double subPixelY, + qreal softnessFactor, qreal lightnessStrength) const; + + virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, + ColoringInformation* coloringInfo, + KisDabShape const&, + const KisPaintInformation& info, + double subPixelX = 0, double subPixelY = 0, + qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; /** * Serialize this brush to XML. */ virtual void toXML(QDomDocument& , QDomElement&) const; static KisBrushSP fromXML(const QDomElement& element); virtual const KisBoundary* boundary() const; virtual QPainterPath outline() const; virtual void setScale(qreal _scale); qreal scale() const; virtual void setAngle(qreal _angle); qreal angle() const; void clearBrushPyramid(); virtual void lodLimitations(KisPaintopLodLimitations *l) const; virtual KisBrush* clone() const = 0; protected: KisBrush(const KisBrush& rhs); void setWidth(qint32 width); void setHeight(qint32 height); void setHotSpot(QPointF); /** * XXX */ virtual void setBrushType(enumBrushType type); virtual void setHasColor(bool hasColor); public: /** * The image is used to represent the brush in the gui, and may also, depending on the brush type * be used to define the actual brush instance. */ virtual void setBrushTipImage(const QImage& image); /** * Returns true if the brush has a bunch of pixels almost * fully transparent in the very center. If the brush is pierced, * then dulling mode may not work correctly due to empty samples. * * WARNING: this method is relatively expensive since it iterates * up to 100 pixels of the brush. */ bool isPiercedApprox() const; protected: void resetBoundary(); void predefinedBrushToXML(const QString &type, QDomElement& e) const; private: // Initialize our boundary void generateBoundary() const; struct Private; Private* const d; }; #endif // KIS_BRUSH_ diff --git a/libs/brush/kis_brushes_pipe.h b/libs/brush/kis_brushes_pipe.h index 0ff6a2c42d..c7b64f3d68 100644 --- a/libs/brush/kis_brushes_pipe.h +++ b/libs/brush/kis_brushes_pipe.h @@ -1,201 +1,201 @@ /* * Copyright (c) 2012 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_BRUSHES_PIPE_H #define __KIS_BRUSHES_PIPE_H #include template class KisBrushesPipe { public: KisBrushesPipe() { } KisBrushesPipe(const KisBrushesPipe &rhs) { qDeleteAll(m_brushes); m_brushes.clear(); Q_FOREACH (BrushType * brush, rhs.m_brushes) { BrushType *clonedBrush = dynamic_cast(brush->clone()); KIS_ASSERT_RECOVER(clonedBrush) {continue;} m_brushes.append(clonedBrush); } } virtual ~KisBrushesPipe() { qDeleteAll(m_brushes); } virtual void clear() { qDeleteAll(m_brushes); m_brushes.clear(); } BrushType* firstBrush() const { return m_brushes.first(); } BrushType* lastBrush() const { return m_brushes.last(); } BrushType* currentBrush(const KisPaintInformation& info) { Q_UNUSED(info); return !m_brushes.isEmpty() ? m_brushes.at(currentBrushIndex()) : 0; } int brushIndex(const KisPaintInformation& info) { return chooseNextBrush(info); } qint32 maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) { BrushType *brush = currentBrush(info); return brush ? brush->maskWidth(shape, subPixelX, subPixelY, info) : 0; } qint32 maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) { BrushType *brush = currentBrush(info); return brush ? brush->maskHeight(shape, subPixelX, subPixelY, info) : 0; } void setAngle(qreal angle) { Q_FOREACH (BrushType * brush, m_brushes) { brush->setAngle(angle); } } void setScale(qreal scale) { Q_FOREACH (BrushType * brush, m_brushes) { brush->setScale(scale); } } void setSpacing(double spacing) { Q_FOREACH (BrushType * brush, m_brushes) { brush->setSpacing(spacing); } } bool hasColor() const { Q_FOREACH (BrushType * brush, m_brushes) { if (brush->hasColor()) return true; } return false; } void setPreserveLightness(bool preserveLightness) const { Q_FOREACH(BrushType * brush, m_brushes) { brush->setPreserveLightness(preserveLightness); } } void notifyCachedDabPainted(const KisPaintInformation& info) { updateBrushIndexes(info, -1); } void prepareForSeqNo(const KisPaintInformation& info, int seqNo) { updateBrushIndexes(info, seqNo); } void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, - qreal softnessFactor) { + qreal softnessFactor, qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) { BrushType *brush = currentBrush(info); if (!brush) return; - brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); + brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength); notifyCachedDabPainted(info); } KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) { BrushType *brush = currentBrush(info); if (!brush) return 0; KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, shape, info, subPixelX, subPixelY); notifyCachedDabPainted(info); return device; } QVector brushes() { return m_brushes; } void testingSelectNextBrush(const KisPaintInformation& info) { (void) chooseNextBrush(info); notifyCachedDabPainted(info); } /** * Is called by the paint op when a paintop starts a stroke. The * brushes are shared among different strokes, so sometimes the * brush should be reset. */ virtual void notifyStrokeStarted() = 0; protected: void addBrush(BrushType *brush) { m_brushes.append(brush); } int sizeBrush() { return m_brushes.size(); } /** * Returns the index of the next brush that corresponds to the current * values of \p info. This method is called *before* the dab is * actually painted. * */ virtual int chooseNextBrush(const KisPaintInformation& info) = 0; /** * Returns the current index of the brush * This method is called *before* the dab is * actually painted. * * The method is const, so no internal counters of the brush should * change during its execution */ virtual int currentBrushIndex() = 0; /** * Updates internal counters of the brush *after* a dab has been * painted on the canvas. Some incremental switching of the brushes * may me implemented in this method. * * If \p seqNo is equal or greater than zero, then incremental iteration is * overridden by this seqNo value */ virtual void updateBrushIndexes(const KisPaintInformation& info, int seqNo) = 0; protected: QVector m_brushes; }; #endif /* __KIS_BRUSHES_PIPE_H */ diff --git a/libs/brush/kis_imagepipe_brush.cpp b/libs/brush/kis_imagepipe_brush.cpp index 1e6560d40d..3d51bc9018 100644 --- a/libs/brush/kis_imagepipe_brush.cpp +++ b/libs/brush/kis_imagepipe_brush.cpp @@ -1,582 +1,582 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Bart Coppens * * 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_imagepipe_brush.h" #include "kis_pipebrush_parasite.h" #include "kis_brushes_pipe.h" class KisImageBrushesPipe : public KisBrushesPipe { public: KisImageBrushesPipe() : m_currentBrushIndex(0) , m_isInitialized(false) { } /* pre and post are split because: 21:12:20 < dmitryK> boud: i guess it was somehow related to the fact that the maskWidth/maskHeight should correspond to the size of the mask returned by paintDevice() 21:13:33 < dmitryK> boud: the random stuff is called once per brush->paintDevice() call, after the device is returned to the paint op, that is "preparing the randomness for the next call" 21:14:16 < dmitryK> boud: and brushesPipe->currentBrush() always returning the same brush for any particular paintInfo. */ protected: static int selectPre(KisParasite::SelectionMode mode, int index, int rank, const KisPaintInformation& info) { qreal angle; qreal velocity; qreal capSpeed = 3; switch (mode) { case KisParasite::Constant: case KisParasite::Incremental: case KisParasite::Random: break; case KisParasite::Pressure: index = static_cast(info.pressure() * (rank - 1) + 0.5); break; case KisParasite::Angular: // + M_PI_2 + M_PI_4 to be compatible with the gimp angle = info.drawingAngle() + M_PI_2 + M_PI_4; angle = normalizeAngle(angle); index = static_cast(angle / (2.0 * M_PI) * rank); break; case KisParasite::TiltX: index = qRound(info.xTilt() / 2.0 * rank) + rank / 2; break; case KisParasite::TiltY: index = qRound(info.yTilt() / 2.0 * rank) + rank / 2; break; case KisParasite::Velocity: // log is slow, but allows for nicer dab transition velocity = log(info.drawingSpeed() + 1); if (velocity > capSpeed) { velocity = capSpeed; } velocity /= capSpeed; velocity *= (rank - 1) + 0.5; index = qRound(velocity); break; default: warnImage << "Parasite" << mode << "is not implemented"; index = 0; } return index; } static int selectPost(KisParasite::SelectionMode mode, int index, int rank, const KisPaintInformation& info, int seqNo) { switch (mode) { case KisParasite::Constant: break; case KisParasite::Incremental: index = (seqNo >= 0 ? seqNo : (index + 1)) % rank; break; case KisParasite::Random: index = info.randomSource()->generate(0, rank-1); break; case KisParasite::Pressure: case KisParasite::Angular: break; case KisParasite::TiltX: case KisParasite::TiltY: case KisParasite::Velocity: break; default: warnImage << "Parasite" << mode << "is not implemented"; index = 0; } return index; } int chooseNextBrush(const KisPaintInformation& info) override { quint32 brushIndex = 0; if (!m_isInitialized) { /** * Reset all the indexes to the initial values and do the * generation based on parameters. */ for (int i = 0; i < m_parasite.dim; i++) { m_parasite.index[i] = 0; } updateBrushIndexes(info, 0); m_isInitialized = true; } for (int i = 0; i < m_parasite.dim; i++) { int index = selectPre(m_parasite.selection[i], m_parasite.index[i], m_parasite.rank[i], info); brushIndex += m_parasite.brushesCount[i] * index; } brushIndex %= (quint32)m_brushes.size(); m_currentBrushIndex = brushIndex; return brushIndex; } int currentBrushIndex() override { return m_currentBrushIndex; } void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override { for (int i = 0; i < m_parasite.dim; i++) { m_parasite.index[i] = selectPost(m_parasite.selection[i], m_parasite.index[i], m_parasite.rank[i], info, seqNo); } } public: using KisBrushesPipe::addBrush; using KisBrushesPipe::sizeBrush; void setParasite(const KisPipeBrushParasite& parasite) { m_parasite = parasite; } const KisPipeBrushParasite& parasite() const { return m_parasite; } void setUseColorAsMask(bool useColorAsMask) { Q_FOREACH (KisGbrBrush * brush, m_brushes) { brush->setUseColorAsMask(useColorAsMask); } } void setAdjustmentMidPoint(quint8 value) { Q_FOREACH (KisGbrBrush * brush, m_brushes) { brush->setAdjustmentMidPoint(value); } } void setBrightnessAdjustment(qreal value) { Q_FOREACH (KisGbrBrush * brush, m_brushes) { brush->setBrightnessAdjustment(value); } } void setContrastAdjustment(qreal value) { Q_FOREACH (KisGbrBrush * brush, m_brushes) { brush->setContrastAdjustment(value); } } void makeMaskImage(bool preserveAlpha) { Q_FOREACH (KisGbrBrush * brush, m_brushes) { brush->makeMaskImage(preserveAlpha); } } bool saveToDevice(QIODevice* dev) const { Q_FOREACH (KisGbrBrush * brush, m_brushes) { if (!brush->saveToDevice(dev)) { return false; } } return true; } void notifyStrokeStarted() override { m_isInitialized = false; } private: KisPipeBrushParasite m_parasite; int m_currentBrushIndex; bool m_isInitialized; }; struct KisImagePipeBrush::Private { public: KisImageBrushesPipe brushesPipe; }; KisImagePipeBrush::KisImagePipeBrush(const QString& filename) : KisGbrBrush(filename) , m_d(new Private()) { } KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h, QVector< QVector > devices, QVector modes) : KisGbrBrush(QString()) , m_d(new Private()) { Q_ASSERT(devices.count() == modes.count()); Q_ASSERT(devices.count() > 0); Q_ASSERT(devices.count() < 2); // XXX Multidimensionals not supported yet, change to MaxDim! setName(name); KisPipeBrushParasite parasite; parasite.dim = devices.count(); // XXX Change for multidim! : parasite.ncells = devices.at(0).count(); parasite.rank[0] = parasite.ncells; // ### This can masquerade some bugs, be careful here in the future parasite.selection[0] = modes.at(0); // XXX needsmovement! parasite.setBrushesCount(); setParasite(parasite); setDevices(devices, w, h); setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage()); } KisImagePipeBrush::KisImagePipeBrush(const KisImagePipeBrush& rhs) : KisGbrBrush(rhs), m_d(new Private(*rhs.m_d)) { } KisImagePipeBrush::~KisImagePipeBrush() { delete m_d; } bool KisImagePipeBrush::load() { QFile file(filename()); file.open(QIODevice::ReadOnly); bool res = loadFromDevice(&file); file.close(); return res; } bool KisImagePipeBrush::loadFromDevice(QIODevice *dev) { QByteArray data = dev->readAll(); return initFromData(data); } bool KisImagePipeBrush::initFromData(const QByteArray &data) { if (data.size() == 0) return false; // XXX: this doesn't correctly load the image pipe brushes yet. // XXX: This stuff is in utf-8, too. // The first line contains the name -- this means we look until we arrive at the first newline QByteArray line1; qint32 i = 0; while (i < data.size() && data[i] != '\n') { line1.append(data[i]); i++; } setName(QString::fromUtf8(line1, line1.size())); i++; // Skip past the first newline // The second line contains the number of brushes, separated by a space from the parasite // XXX: This stuff is in utf-8, too. QByteArray line2; while (i < data.size() && data[i] != '\n') { line2.append(data[i]); i++; } QString paramline = QString::fromUtf8(line2, line2.size()); qint32 numOfBrushes = paramline.left(paramline.indexOf(' ')).toUInt(); QString parasiteString = paramline.mid(paramline.indexOf(' ') + 1); KisPipeBrushParasite parasite = KisPipeBrushParasite(parasiteString); parasite.sanitize(); parasiteSelectionString = parasite.selectionMode; // selection mode to return to UI m_d->brushesPipe.setParasite(parasite); i++; // Skip past the second newline for (int brushIndex = m_d->brushesPipe.sizeBrush(); brushIndex < numOfBrushes && i < data.size(); brushIndex++) { KisGbrBrush* brush = new KisGbrBrush(name() + '_' + QString().setNum(brushIndex), data, i); m_d->brushesPipe.addBrush(brush); } if (numOfBrushes > 0) { setValid(true); setSpacing(m_d->brushesPipe.lastBrush()->spacing()); setWidth(m_d->brushesPipe.firstBrush()->width()); setHeight(m_d->brushesPipe.firstBrush()->height()); setBrushTipImage(m_d->brushesPipe.firstBrush()->brushTipImage()); } return true; } bool KisImagePipeBrush::save() { QFile file(filename()); file.open(QIODevice::WriteOnly | QIODevice::Truncate); bool ok = saveToDevice(&file); file.close(); return ok; } bool KisImagePipeBrush::saveToDevice(QIODevice* dev) const { QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8 char const* name = utf8Name.data(); int len = qstrlen(name); if (m_d->brushesPipe.parasite().dim >= KisPipeBrushParasite::MaxDim) { warnImage << "Save to file for pipe brushes with dim != not yet supported!"; return false; } // Save this pipe brush: first the header, and then all individual brushes consecutively // XXX: this needs some care for when we have > 1 dimension) // Gimp Pipe Brush header format: Name\n \n // The name\n if (dev->write(name, len) == -1) return false; if (!dev->putChar('\n')) return false; // Write the parasite (also writes number of brushes) if (!m_d->brushesPipe.parasite().saveToDevice(dev)) return false; if (!dev->putChar('\n')) return false; KoResource::saveToDevice(dev); // return m_d->brushesPipe.saveToDevice(dev); } void KisImagePipeBrush::notifyStrokeStarted() { m_d->brushesPipe.notifyStrokeStarted(); } void KisImagePipeBrush::notifyCachedDabPainted(const KisPaintInformation& info) { m_d->brushesPipe.notifyCachedDabPainted(info); } void KisImagePipeBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo) { m_d->brushesPipe.prepareForSeqNo(info, seqNo); } void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, - qreal softnessFactor) const + qreal softnessFactor, qreal lightnessStrength) const { - m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); + m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor, lightnessStrength); } QVector KisImagePipeBrush::brushes() const { return m_d->brushesPipe.brushes(); } KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice( const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { return m_d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY); } enumBrushType KisImagePipeBrush::brushType() const { return !hasColor() || useColorAsMask() ? PIPE_MASK : PIPE_IMAGE; } QString KisImagePipeBrush::parasiteSelection() { return parasiteSelectionString; } bool KisImagePipeBrush::hasColor() const { return m_d->brushesPipe.hasColor(); } void KisImagePipeBrush::makeMaskImage(bool preserveAlpha) { m_d->brushesPipe.makeMaskImage(preserveAlpha); setUseColorAsMask(true); } void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask) { KisGbrBrush::setUseColorAsMask(useColorAsMask); m_d->brushesPipe.setUseColorAsMask(useColorAsMask); } void KisImagePipeBrush::setAdjustmentMidPoint(quint8 value) { KisGbrBrush::setAdjustmentMidPoint(value); m_d->brushesPipe.setAdjustmentMidPoint(value); } void KisImagePipeBrush::setBrightnessAdjustment(qreal value) { KisGbrBrush::setBrightnessAdjustment(value); m_d->brushesPipe.setBrightnessAdjustment(value); } void KisImagePipeBrush::setContrastAdjustment(qreal value) { KisGbrBrush::setContrastAdjustment(value); m_d->brushesPipe.setContrastAdjustment(value); } const KisBoundary* KisImagePipeBrush::boundary() const { KisGbrBrush *brush = m_d->brushesPipe.firstBrush(); Q_ASSERT(brush); return brush->boundary(); } bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info) { return (!m_d->brushesPipe.parasite().needsMovement || info.drawingDistance() >= 0.5); } KisBrush* KisImagePipeBrush::clone() const { return new KisImagePipeBrush(*this); } QString KisImagePipeBrush::defaultFileExtension() const { return QString(".gih"); } quint32 KisImagePipeBrush::brushIndex(const KisPaintInformation& info) const { return m_d->brushesPipe.brushIndex(info); } qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return m_d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info); } qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return m_d->brushesPipe.maskHeight(shape, subPixelX, subPixelY, info); } void KisImagePipeBrush::setAngle(qreal _angle) { KisGbrBrush::setAngle(_angle); m_d->brushesPipe.setAngle(_angle); } void KisImagePipeBrush::setScale(qreal _scale) { KisGbrBrush::setScale(_scale); m_d->brushesPipe.setScale(_scale); } void KisImagePipeBrush::setSpacing(double _spacing) { KisGbrBrush::setSpacing(_spacing); m_d->brushesPipe.setSpacing(_spacing); } void KisImagePipeBrush::setBrushType(enumBrushType type) { Q_UNUSED(type); qFatal("FATAL: protected member setBrushType has no meaning for KisImagePipeBrush"); // brushType() is a function of hasColor() and useColorAsMask() } void KisImagePipeBrush::setHasColor(bool hasColor) { Q_UNUSED(hasColor); qFatal("FATAL: protected member setHasColor has no meaning for KisImagePipeBrush"); // hasColor() is a function of the underlying brushes } void KisImagePipeBrush::setPreserveLightness(bool preserveLightness) { //Set all underlying brushes to preserve lightness KisGbrBrush::setPreserveLightness(preserveLightness); m_d->brushesPipe.setPreserveLightness(preserveLightness); } KisGbrBrush* KisImagePipeBrush::testingGetCurrentBrush(const KisPaintInformation& info) const { return m_d->brushesPipe.currentBrush(info); } void KisImagePipeBrush::testingSelectNextBrush(const KisPaintInformation& info) const { return m_d->brushesPipe.testingSelectNextBrush(info); } const KisPipeBrushParasite& KisImagePipeBrush::parasite() const { return m_d->brushesPipe.parasite(); } void KisImagePipeBrush::setParasite(const KisPipeBrushParasite ¶site) { m_d->brushesPipe.setParasite(parasite); } void KisImagePipeBrush::setDevices(QVector > devices, int w, int h) { for (int i = 0; i < devices.at(0).count(); i++) { m_d->brushesPipe.addBrush(new KisGbrBrush(devices.at(0).at(i), 0, 0, w, h)); } } diff --git a/libs/brush/kis_imagepipe_brush.h b/libs/brush/kis_imagepipe_brush.h index 9e57e75d91..47bbe3ca40 100644 --- a/libs/brush/kis_imagepipe_brush.h +++ b/libs/brush/kis_imagepipe_brush.h @@ -1,149 +1,150 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Bart Coppens * * 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_IMAGEPIPE_BRUSH_ #define KIS_IMAGEPIPE_BRUSH_ #include #include #include #include #include "kis_gbr_brush.h" #include "kis_global.h" class KisPipeBrushParasite; /** * Velocity won't be supported, atm Tilt isn't either, * but have chances of implementation */ namespace KisParasite { enum SelectionMode { Constant, Incremental, Angular, Velocity, Random, Pressure, TiltX, TiltY }; } class BRUSH_EXPORT KisImagePipeBrush : public KisGbrBrush { public: KisImagePipeBrush(const QString& filename); /** * Specialized constructor that makes a new pipe brush from a sequence of samesize * devices. The fact that it's a vector of a vector, is to support multidimensional * brushes (not yet supported!) */ KisImagePipeBrush(const QString& name, int w, int h, QVector< QVector > devices, QVector modes); ~KisImagePipeBrush() override; bool load() override; bool loadFromDevice(QIODevice *dev) override; bool save() override; bool saveToDevice(QIODevice* dev) const override; /** * @return the next image in the pipe. */ KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0) const override; void setUseColorAsMask(bool useColorAsMask) override; bool hasColor() const override; void setAdjustmentMidPoint(quint8 value) override; void setBrightnessAdjustment(qreal value) override; void setContrastAdjustment(qreal value) override; enumBrushType brushType() const override; QString parasiteSelection(); // returns random, constant, etc const KisBoundary* boundary() const override; bool canPaintFor(const KisPaintInformation& info) override; void makeMaskImage(bool preserveAlpha) override; KisBrush* clone() const override; QString defaultFileExtension() const override; void setAngle(qreal _angle) override; void setScale(qreal _scale) override; void setSpacing(double _spacing) override; quint32 brushIndex(const KisPaintInformation& info) const override; qint32 maskWidth(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override; qint32 maskHeight(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override; void notifyStrokeStarted() override; void notifyCachedDabPainted(const KisPaintInformation& info) override; void prepareForSeqNo(const KisPaintInformation& info, int seqNo) override; void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const&, const KisPaintInformation& info, - double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const override; + double subPixelX = 0, double subPixelY = 0, + qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength = DEFAULT_LIGHTNESS_STRENGTH) const override; QVector brushes() const; const KisPipeBrushParasite ¶site() const; void setParasite(const KisPipeBrushParasite& parasite); void setDevices(QVector< QVector > devices, int w, int h); protected: void setBrushType(enumBrushType type) override; void setHasColor(bool hasColor) override; void setPreserveLightness(bool preserveLightness) override; /// Will call KisBrush's saveToDevice as well KisImagePipeBrush(const KisImagePipeBrush& rhs); private: friend class KisImagePipeBrushTest; KisGbrBrush* testingGetCurrentBrush(const KisPaintInformation& info) const; void testingSelectNextBrush(const KisPaintInformation& info) const; bool initFromData(const QByteArray &data); QString parasiteSelectionString; // incremental, random, etc. private: struct Private; Private * const m_d; }; #endif // KIS_IMAGEPIPE_BRUSH_ diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp index 93aedd4913..4466867a17 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -1,444 +1,447 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * Copyright (c) 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_brushop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "krita_utils.h" #include #include "kis_algebra_2d.h" #include #include #include #include "KisBrushOpResources.h" #include #include #include #include #include "kis_image_config.h" #include "kis_wrapped_rect.h" KisBrushOp::KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_opacityOption(node) , m_avgSpacing(50) , m_avgNumDabs(50) , m_avgUpdateTimePerDab(50) , m_idealNumRects(KisImageConfig(true).maxNumberOfThreads()) , m_minUpdatePeriod(10) , m_maxUpdatePeriod(100) { Q_UNUSED(image); Q_ASSERT(settings); /** * We do our own threading here, so we need to forbid the brushes * to do threading internally */ m_brush->setThreadingAllowed(false); m_airbrushOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_ratioOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_rateOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); + m_lightnessStrengthOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_ratioOption.resetAllSensors(); m_rateOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); + m_lightnessStrengthOption.resetAllSensors(); m_rotationOption.applyFanCornersInfo(this); m_precisionOption.setHasImprecisePositionOptions( m_precisionOption.hasImprecisePositionOptions() | m_scatterOption.isChecked() | m_rotationOption.isChecked() | m_airbrushOption.enabled); KisBrushSP baseBrush = m_brush; auto resourcesFactory = [baseBrush, settings, painter] () { KisDabCacheUtils::DabRenderingResources *resources = new KisBrushOpResources(settings, painter); resources->brush = baseBrush->clone(); return resources; }; m_dabExecutor.reset( new KisDabRenderingExecutor( painter->device()->compositionSourceColorSpace(), resourcesFactory, painter->runnableStrokeJobsInterface(), &m_mirrorOption, &m_precisionOption)); } KisBrushOp::~KisBrushOp() { } KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); qreal rotation = m_rotationOption.apply(info); qreal ratio = m_ratioOption.apply(info); KisDabShape shape(scale, ratio, rotation); QPointF cursorPos = m_scatterOption.apply(info, brush->maskWidth(shape, 0, 0, info), brush->maskHeight(shape, 0, 0, info)); m_opacityOption.setFlow(m_flowOption.apply(info)); quint8 dabOpacity = OPACITY_OPAQUE_U8; quint8 dabFlow = OPACITY_OPAQUE_U8; m_opacityOption.apply(info, &dabOpacity, &dabFlow); KisDabCacheUtils::DabRequestInfo request(painter()->paintColor(), cursorPos, shape, info, - m_softnessOption.apply(info)); + m_softnessOption.apply(info), + m_lightnessStrengthOption.apply(info)); m_dabExecutor->addDab(request, qreal(dabOpacity) / 255.0, qreal(dabFlow) / 255.0); KisSpacingInformation spacingInfo = effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); // gather statistics about dabs m_avgSpacing(spacingInfo.scalarApprox()); return spacingInfo; } struct KisBrushOp::UpdateSharedState { // rendering data KisPainter *painter = 0; QList dabsQueue; // speed metrics QVector dabPoints; QElapsedTimer dabRenderingTimer; // final report QVector allDirtyRects; }; void KisBrushOp::addMirroringJobs(Qt::Orientation direction, QVector &rects, UpdateSharedStateSP state, QVector &jobs) { jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL)); /** * Some KisRenderedDab may share their devices, so we should mirror them * carefully, avoiding doing that twice. KisDabRenderingQueue is implemented in * a way that duplicated dabs can go only sequentially, one after another, so * we don't have to use complex deduplication algorithms here. */ KisFixedPaintDeviceSP prevDabDevice = 0; for (KisRenderedDab &dab : state->dabsQueue) { const bool skipMirrorPixels = prevDabDevice && prevDabDevice == dab.device; jobs.append( new KisRunnableStrokeJobData( [state, &dab, direction, skipMirrorPixels] () { state->painter->mirrorDab(direction, &dab, skipMirrorPixels); }, KisStrokeJobData::CONCURRENT)); prevDabDevice = dab.device; } jobs.append(new KisRunnableStrokeJobData(0, KisStrokeJobData::SEQUENTIAL)); for (QRect &rc : rects) { state->painter->mirrorRect(direction, &rc); jobs.append( new KisRunnableStrokeJobData( [rc, state] () { state->painter->bltFixed(rc, state->dabsQueue); }, KisStrokeJobData::CONCURRENT)); } state->allDirtyRects.append(rects); } std::pair KisBrushOp::doAsyncronousUpdate(QVector &jobs) { bool someDabsAreStillInQueue = false; const bool hasPreparedDabsAtStart = m_dabExecutor->hasPreparedDabs(); if (!m_updateSharedState && hasPreparedDabsAtStart) { m_updateSharedState = toQShared(new UpdateSharedState()); UpdateSharedStateSP state = m_updateSharedState; state->painter = painter(); { const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); const qreal totalRenderingTimePerDab = dabRenderingTime + m_avgUpdateTimePerDab.rollingMeanSafe(); // we limit the number of fetched dabs to fit the maximum update period and not // make visual hiccups const int dabsLimit = totalRenderingTimePerDab > 0 ? qMax(10, int(m_maxUpdatePeriod / totalRenderingTimePerDab * m_idealNumRects)) : -1; state->dabsQueue = m_dabExecutor->takeReadyDabs(painter()->hasMirroring(), dabsLimit, &someDabsAreStillInQueue); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!state->dabsQueue.isEmpty(), std::make_pair(m_currentUpdatePeriod, false)); const int diameter = m_dabExecutor->averageDabSize(); const qreal spacing = m_avgSpacing.rollingMean(); const int idealNumRects = m_idealNumRects; QVector rects; // wrap the dabs if needed if (painter()->device()->defaultBounds()->wrapAroundMode()) { /** * In WA mode we do two things: * * 1) We ensure that the parallel threads do not access the same are on * the image. For normal updates that is ensured by the code in KisImage * and the scheduler. Here we should do that manually by adjusting 'rects' * so that they would not intersect in the wrapped space. * * 2) We duplicate dabs, to ensure that all the pieces of dabs are painted * inside the wrapped rect. No pieces are dabs are painted twice, because * we paint only the parts intersecting the wrap rect. */ const QRect wrapRect = painter()->device()->defaultBounds()->imageBorderRect(); QList wrappedDabs; Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { const QVector normalizationOrigins = KisWrappedRect::normalizationOriginsForRect(dab.realBounds(), wrapRect); Q_FOREACH(const QPoint &pt, normalizationOrigins) { KisRenderedDab newDab = dab; newDab.offset = pt; rects.append(newDab.realBounds() & wrapRect); wrappedDabs.append(newDab); } } state->dabsQueue = wrappedDabs; } else { // just get all rects Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { rects.append(dab.realBounds()); } } // split/merge rects into non-overlapping areas rects = KisPaintOpUtils::splitDabsIntoRects(rects, idealNumRects, diameter, spacing); state->allDirtyRects = rects; Q_FOREACH (const KisRenderedDab &dab, state->dabsQueue) { state->dabPoints.append(dab.realBounds().center()); } state->dabRenderingTimer.start(); Q_FOREACH (const QRect &rc, rects) { jobs.append( new KisRunnableStrokeJobData( [rc, state] () { state->painter->bltFixed(rc, state->dabsQueue); }, KisStrokeJobData::CONCURRENT)); } /** * After the dab has been rendered once, we should mirror it either one * (h __or__ v) or three (h __and__ v) times. This sequence of 'if's achieves * the goal without any extra copying. Please note that it has __no__ 'else' * branches, which is done intentionally! */ if (state->painter->hasHorizontalMirroring()) { addMirroringJobs(Qt::Horizontal, rects, state, jobs); } if (state->painter->hasVerticalMirroring()) { addMirroringJobs(Qt::Vertical, rects, state, jobs); } if (state->painter->hasHorizontalMirroring() && state->painter->hasVerticalMirroring()) { addMirroringJobs(Qt::Horizontal, rects, state, jobs); } jobs.append( new KisRunnableStrokeJobData( [state, this, someDabsAreStillInQueue] () { Q_FOREACH(const QRect &rc, state->allDirtyRects) { state->painter->addDirtyRect(rc); } state->painter->setAverageOpacity(state->dabsQueue.last().averageOpacity); const int updateRenderingTime = state->dabRenderingTimer.elapsed(); const qreal dabRenderingTime = m_dabExecutor->averageDabRenderingTime(); m_avgNumDabs(state->dabsQueue.size()); const qreal currentUpdateTimePerDab = qreal(updateRenderingTime) / state->dabsQueue.size(); m_avgUpdateTimePerDab(currentUpdateTimePerDab); /** * NOTE: using currentUpdateTimePerDab in the calculation for the next update time instead * of the average one makes rendering speed about 40% faster. It happens because the * adaptation period is shorter than if it used */ const qreal totalRenderingTimePerDab = dabRenderingTime + currentUpdateTimePerDab; const int approxDabRenderingTime = qreal(totalRenderingTimePerDab) * m_avgNumDabs.rollingMean() / m_idealNumRects; m_currentUpdatePeriod = someDabsAreStillInQueue ? m_minUpdatePeriod : qBound(m_minUpdatePeriod, int(1.5 * approxDabRenderingTime), m_maxUpdatePeriod); { // debug chunk // ENTER_FUNCTION() << ppVar(state->allDirtyRects.size()) << ppVar(state->dabsQueue.size()) << ppVar(dabRenderingTime) << ppVar(updateRenderingTime); // ENTER_FUNCTION() << ppVar(m_currentUpdatePeriod) << ppVar(someDabsAreStillInQueue); } // release all the dab devices state->dabsQueue.clear(); m_updateSharedState.clear(); }, KisStrokeJobData::SEQUENTIAL)); } else if (m_updateSharedState && hasPreparedDabsAtStart) { someDabsAreStillInQueue = true; } return std::make_pair(m_currentUpdatePeriod, someDabsAreStillInQueue); } KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const { const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, info); } KisTimingInformation KisBrushOp::updateTimingImpl(const KisPaintInformation &info) const { return KisPaintOpPluginUtils::effectiveTiming(&m_airbrushOption, &m_rateOption, info); } void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { if (!m_lineCacheDevice) { m_lineCacheDevice = source()->createCompositionSourceDevice(); } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); p.setPaintColor(painter()->paintColor()); p.drawDDALine(pi1.pos(), pi2.pos()); QRect rc = m_lineCacheDevice->extent(); painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height()); //fixes Bug 338011 painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } } diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h index 1b7cfafccb..a2fab1c2d4 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h @@ -1,107 +1,109 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 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_BRUSHOP_H_ #define KIS_BRUSHOP_H_ #include "kis_brush_based_paintop.h" #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include class KisPainter; class KisColorSource; class KisDabRenderingExecutor; struct KisRenderedDab; class KisRunnableStrokeJobData; class KisBrushOp : public KisBrushBasedPaintOp { public: KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisBrushOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; std::pair doAsyncronousUpdate(QVector &jobs) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override; struct UpdateSharedState; typedef QSharedPointer UpdateSharedStateSP; void addMirroringJobs(Qt::Orientation direction, QVector &rects, UpdateSharedStateSP state, QVector &jobs); UpdateSharedStateSP m_updateSharedState; private: KisAirbrushOptionProperties m_airbrushOption; KisPressureSizeOption m_sizeOption; KisPressureRatioOption m_ratioOption; KisPressureSpacingOption m_spacingOption; KisPressureRateOption m_rateOption; KisPressureFlowOption m_flowOption; KisFlowOpacityOption m_opacityOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureRotationOption m_rotationOption; KisPressureScatterOption m_scatterOption; + KisPressureLightnessStrengthOption m_lightnessStrengthOption; KisPaintDeviceSP m_lineCacheDevice; QScopedPointer m_dabExecutor; qreal m_currentUpdatePeriod = 20.0; KisRollingMeanAccumulatorWrapper m_avgSpacing; KisRollingMeanAccumulatorWrapper m_avgNumDabs; KisRollingMeanAccumulatorWrapper m_avgUpdateTimePerDab; const int m_idealNumRects; const int m_minUpdatePeriod; const int m_maxUpdatePeriod; }; #endif // KIS_BRUSHOP_H_ diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp index 856877f0ae..56f871ed72 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp @@ -1,165 +1,173 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 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_brushop_settings_widget.h" #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include "kis_texture_option.h" #include #include "kis_pressure_texture_strength_option.h" #include #include #include KisBrushOpSettingsWidget::KisBrushOpSettingsWidget(QWidget* parent) : KisBrushBasedPaintopOptionWidget(parent) { setObjectName("brush option widget"); setPrecisionEnabled(true); setHSLBrushTipEnabled(true); // Brush tip options addPaintOpOption(new KisCompositeOpOption(true), i18n("Blending Mode")); addPaintOpOption(new KisFlowOpacityOptionWidget(), i18n("Opacity")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureFlowOption(), i18n("0%"), i18n("100%")), i18n("Flow")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureSizeOption(), i18n("0%"), i18n("100%")), i18n("Size")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureRatioOption(), i18n("0%"), i18n("100%")), i18n("Ratio")); addPaintOpOption(new KisPressureSpacingOptionWidget(), i18n("Spacing")); addPaintOpOption(new KisPressureMirrorOptionWidget(), i18n("Mirror")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureSoftnessOption(), i18n("Soft"), i18n("Hard")), i18n("Softness")); addPaintOpOption(new KisPressureSharpnessOptionWidget(), i18n("Sharpness")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureRotationOption(), i18n("-180°"), i18n("180°")), i18n("Rotation")); + m_lightnessStrengthOptionWidget = new KisCurveOptionWidget(new KisPressureLightnessStrengthOption(), i18n("0%"), i18n("100%")); + addPaintOpOption(m_lightnessStrengthOptionWidget, i18n("Lightness Strength (RGBA brushes only)")); addPaintOpOption(new KisPressureScatterOptionWidget(), i18n("Scatter")); // Colors options addPaintOpOption(new KisColorSourceOptionWidget(), i18n("Source")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureDarkenOption(), i18n("0.0"), i18n("1.0")), i18n("Darken")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureMixOption(), i18n("Foreground"), i18n("Background")), i18n("Mix")); addPaintOpOption(new KisCurveOptionWidget(KisPressureHSVOption::createHueOption(), KisPressureHSVOption::hueMinLabel(), KisPressureHSVOption::huemaxLabel()), i18n("Hue")); addPaintOpOption(new KisCurveOptionWidget(KisPressureHSVOption::createSaturationOption(), KisPressureHSVOption::saturationMinLabel(), KisPressureHSVOption::saturationmaxLabel()), i18n("Saturation")); addPaintOpOption(new KisCurveOptionWidget(KisPressureHSVOption::createValueOption(), KisPressureHSVOption::valueMinLabel(), KisPressureHSVOption::valuemaxLabel()), i18nc("HSV Value", "Value")); addPaintOpOption(new KisAirbrushOptionWidget(false), i18n("Airbrush")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureRateOption(), i18n("0%"), i18n("100%")), i18n("Rate")); KisPaintActionTypeOption *actionTypeOption = new KisPaintActionTypeOption(); addPaintOpOption(actionTypeOption, i18n("Painting Mode")); addPaintOpOption(new KisTextureOption(), i18n("Pattern")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength")); KisMaskingBrushOption::MasterBrushSizeAdapter sizeAdapter = [this] () { return this->brush()->userEffectiveSize(); }; KisMaskingBrushOption *maskingOption = new KisMaskingBrushOption(sizeAdapter); addPaintOpOption(maskingOption, i18n("Brush Tip")); connect(maskingOption, SIGNAL(sigCheckedChanged(bool)), actionTypeOption, SLOT(slotForceWashMode(bool))); { KisCurveOption *maskingSizeOption = new KisPressureSizeOption(); maskingSizeOption->setChecked(false); addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSizeOption, i18n("0%"), i18n("100%")), i18n("Size"), KisPaintOpOption::MASKING_BRUSH); } addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix), i18n("Opacity"), KisPaintOpOption::MASKING_BRUSH); KisCurveOption *maskingFlowOption = new KisPressureFlowOption(); maskingFlowOption->setChecked(false); KisCurveOption *maskingRatioOption = new KisPressureRatioOption(); maskingRatioOption->setChecked(false); addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix, maskingFlowOption, i18n("0%"), i18n("100%")), i18n("Flow"), KisPaintOpOption::MASKING_BRUSH); addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix, maskingRatioOption, i18n("0%"), i18n("100%")), i18n("Ratio"), KisPaintOpOption::MASKING_BRUSH); addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix), i18n("Mirror"), KisPaintOpOption::MASKING_BRUSH); addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix, new KisPressureRotationOption(), i18n("-180°"), i18n("180°")), i18n("Rotation"), KisPaintOpOption::MASKING_BRUSH); addPaintOpOption( new KisPrefixedPaintOpOptionWrapper( KisPaintOpUtils::MaskingBrushPresetPrefix), i18n("Scatter"), KisPaintOpOption::MASKING_BRUSH); } KisBrushOpSettingsWidget::~KisBrushOpSettingsWidget() { } KisPropertiesConfigurationSP KisBrushOpSettingsWidget::configuration() const { KisBrushBasedPaintOpSettingsSP config = new KisBrushOpSettings(); config->setOptionsWidget(const_cast(this)); config->setProperty("paintop", "paintbrush"); // XXX: make this a const id string writeConfiguration(config); return config; } +void KisBrushOpSettingsWidget::notifyPageChanged() +{ + KisBrushSP brush = this->brush(); + m_lightnessStrengthOptionWidget->setEnabled(brush->preserveLightness()); +} diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.h b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.h index 0bff308245..7466afd0ce 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.h @@ -1,45 +1,52 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 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_BRUSHOP_SETTINGS_WIDGET_H_ #define KIS_BRUSHOP_SETTINGS_WIDGET_H_ #include +#include class KisBrushOpSettingsWidget : public KisBrushBasedPaintopOptionWidget { Q_OBJECT public: KisBrushOpSettingsWidget(QWidget* parent = 0); ~KisBrushOpSettingsWidget() override; KisPropertiesConfigurationSP configuration() const override; + +protected: + void notifyPageChanged() override; + +private: + KisCurveOptionWidget* m_lightnessStrengthOptionWidget; }; #endif // KIS_BRUSHOP_SETTINGS_WIDGET_H_ diff --git a/plugins/paintops/libpaintop/CMakeLists.txt b/plugins/paintops/libpaintop/CMakeLists.txt index 4d6d831760..5596b39c08 100644 --- a/plugins/paintops/libpaintop/CMakeLists.txt +++ b/plugins/paintops/libpaintop/CMakeLists.txt @@ -1,107 +1,108 @@ set(kritalibpaintop_LIB_SRCS kis_airbrush_option_widget.cpp kis_auto_brush_widget.cpp kis_spacing_selection_widget.cpp kis_bidirectional_mixing_option.cpp kis_bidirectional_mixing_option_widget.cpp kis_brush_based_paintop.cpp kis_brush_chooser.cpp kis_brush_option_widget.cpp kis_brush_option.cpp kis_brush_selection_widget.cpp kis_color_option.cpp kis_color_source.cpp kis_color_source_option.cpp kis_color_source_option_widget.cpp kis_curve_option.cpp kis_curve_option_widget.cpp kis_curve_option_uniform_property.cpp kis_custom_brush_widget.cpp kis_clipboard_brush_widget.cpp kis_dynamic_sensor.cc KisDabCacheUtils.cpp kis_dab_cache_base.cpp kis_dab_cache.cpp kis_filter_option.cpp kis_multi_sensors_model_p.cpp kis_multi_sensors_selector.cpp kis_paint_action_type_option.cpp kis_precision_option.cpp kis_pressure_darken_option.cpp kis_pressure_hsv_option.cpp + kis_pressure_lightness_strength_option.cpp kis_pressure_opacity_option.cpp kis_pressure_flow_option.cpp kis_pressure_mirror_option.cpp kis_pressure_scatter_option.cpp kis_pressure_scatter_option_widget.cpp kis_pressure_sharpness_option.cpp kis_pressure_sharpness_option_widget.cpp kis_pressure_mirror_option_widget.cpp kis_pressure_rotation_option.cpp kis_pressure_size_option.cpp kis_pressure_spacing_option.cpp kis_pressure_rate_option.cpp kis_pressure_softness_option.cpp kis_pressure_mix_option.cpp kis_pressure_gradient_option.cpp kis_pressure_flow_opacity_option.cpp kis_pressure_flow_opacity_option_widget.cpp kis_pressure_spacing_option_widget.cpp kis_pressure_ratio_option.cpp kis_current_outline_fetcher.cpp kis_text_brush_chooser.cpp kis_brush_based_paintop_options_widget.cpp kis_brush_based_paintop_settings.cpp kis_compositeop_option.cpp kis_texture_option.cpp kis_texture_chooser.cpp KisTextureMaskInfo.cpp kis_pressure_texture_strength_option.cpp kis_embedded_pattern_manager.cpp KisMaskingBrushOption.cpp KisMaskingBrushOptionProperties.cpp sensors/kis_dynamic_sensors.cc sensors/kis_dynamic_sensor_drawing_angle.cpp sensors/kis_dynamic_sensor_distance.cc sensors/kis_dynamic_sensor_time.cc sensors/kis_dynamic_sensor_fade.cpp sensors/kis_dynamic_sensor_fuzzy.cpp ) ki18n_wrap_ui(kritalibpaintop_LIB_SRCS forms/wdgautobrush.ui forms/wdgBrushSizeOptions.ui forms/wdgcurveoption.ui forms/wdgcustombrush.ui forms/wdgclipboardbrush.ui forms/wdgtextbrush.ui forms/wdgincremental.ui forms/wdgmultisensorsselector.ui forms/wdgairbrush.ui forms/wdgfilteroption.ui forms/wdgcoloroptions.ui forms/wdgbrushchooser.ui forms/wdgpredefinedbrushchooser.ui forms/wdgtexturechooser.ui forms/wdgCompositeOpOption.ui forms/wdgflowopacityoption.ui sensors/SensorDistanceConfiguration.ui sensors/SensorTimeConfiguration.ui sensors/SensorFadeConfiguration.ui ) add_library(kritalibpaintop SHARED ${kritalibpaintop_LIB_SRCS} ) generate_export_header(kritalibpaintop BASE_NAME kritapaintop EXPORT_MACRO_NAME PAINTOP_EXPORT) target_link_libraries(kritalibpaintop kritaui kritalibbrush kritawidgetutils) target_link_libraries(kritalibpaintop LINK_INTERFACE_LIBRARIES kritaui kritalibbrush) set_target_properties(kritalibpaintop PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritalibpaintop ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory(tests) diff --git a/plugins/paintops/libpaintop/KisDabCacheUtils.cpp b/plugins/paintops/libpaintop/KisDabCacheUtils.cpp index 3479056e5a..4118fb6c02 100644 --- a/plugins/paintops/libpaintop/KisDabCacheUtils.cpp +++ b/plugins/paintops/libpaintop/KisDabCacheUtils.cpp @@ -1,118 +1,120 @@ /* * Copyright (c) 2017 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 "KisDabCacheUtils.h" #include "kis_brush.h" #include "kis_paint_device.h" #include "kis_fixed_paint_device.h" #include "kis_color_source.h" #include #include #include namespace KisDabCacheUtils { DabRenderingResources::DabRenderingResources() { } DabRenderingResources::~DabRenderingResources() { } void DabRenderingResources::syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info) { brush->prepareForSeqNo(info, seqNo); } QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect, const QSize &realDabSize) { int diffX = (realDabSize.width() - dabRect.width()) / 2; int diffY = (realDabSize.height() - dabRect.height()) / 2; return QRect(dabRect.x() - diffX, dabRect.y() - diffY, realDabSize.width() , realDabSize.height()); } void generateDab(const DabGenerationInfo &di, DabRenderingResources *resources, KisFixedPaintDeviceSP *dab) { KIS_SAFE_ASSERT_RECOVER_RETURN(*dab); const KoColorSpace *cs = (*dab)->colorSpace(); if (resources->brush->brushType() == IMAGE || resources->brush->brushType() == PIPE_IMAGE) { *dab = resources->brush->paintDevice(cs, di.shape, di.info, di.subPixel.x(), di.subPixel.y()); } else if (di.solidColorFill) { resources->brush->mask(*dab, di.paintColor, di.shape, di.info, di.subPixel.x(), di.subPixel.y(), - di.softnessFactor); + di.softnessFactor, + di.lightnessStrength); } else { if (!resources->colorSourceDevice || *cs != *resources->colorSourceDevice->colorSpace()) { resources->colorSourceDevice = new KisPaintDevice(cs); } else { resources->colorSourceDevice->clear(); } QRect maskRect(QPoint(), di.dstDabRect.size()); resources->colorSource->colorize(resources->colorSourceDevice, maskRect, di.info.pos().toPoint()); resources->colorSourceDevice->convertTo(cs); resources->brush->mask(*dab, resources->colorSourceDevice, di.shape, di.info, di.subPixel.x(), di.subPixel.y(), - di.softnessFactor); + di.softnessFactor, + di.lightnessStrength); } if (!di.mirrorProperties.isEmpty()) { (*dab)->mirror(di.mirrorProperties.horizontalMirror, di.mirrorProperties.verticalMirror); } } void postProcessDab(KisFixedPaintDeviceSP dab, const QPoint &dabTopLeft, const KisPaintInformation& info, DabRenderingResources *resources) { if (resources->sharpnessOption) { resources->sharpnessOption->applyThreshold(dab, info); } if (resources->textureOption) { resources->textureOption->apply(dab, dabTopLeft, info); } } } diff --git a/plugins/paintops/libpaintop/KisDabCacheUtils.h b/plugins/paintops/libpaintop/KisDabCacheUtils.h index 6e694a08d3..9edc51992a 100644 --- a/plugins/paintops/libpaintop/KisDabCacheUtils.h +++ b/plugins/paintops/libpaintop/KisDabCacheUtils.h @@ -1,122 +1,126 @@ /* * Copyright (c) 2017 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 KISDABCACHEUTILS_H #define KISDABCACHEUTILS_H #include #include #include "kis_types.h" #include #include "kis_dab_shape.h" #include "kritapaintop_export.h" #include class KisBrush; typedef KisSharedPtr KisBrushSP; class KisColorSource; class KisPressureSharpnessOption; class KisTextureProperties; namespace KisDabCacheUtils { struct PAINTOP_EXPORT DabRenderingResources { DabRenderingResources(); virtual ~DabRenderingResources(); virtual void syncResourcesToSeqNo(int seqNo, const KisPaintInformation &info); KisBrushSP brush; QScopedPointer colorSource; QScopedPointer sharpnessOption; QScopedPointer textureOption; KisPaintDeviceSP colorSourceDevice; private: DabRenderingResources(const DabRenderingResources &rhs) = delete; }; typedef std::function ResourcesFactory; struct PAINTOP_EXPORT DabRequestInfo { DabRequestInfo(const KoColor &_color, const QPointF &_cursorPoint, const KisDabShape &_shape, const KisPaintInformation &_info, - qreal _softnessFactor) + qreal _softnessFactor, + qreal _lightnessStrength = 1.0) : color(_color), cursorPoint(_cursorPoint), shape(_shape), info(_info), - softnessFactor(_softnessFactor) + softnessFactor(_softnessFactor), + lightnessStrength(_lightnessStrength) { } const KoColor &color; const QPointF &cursorPoint; const KisDabShape &shape; const KisPaintInformation &info; const qreal softnessFactor; + const qreal lightnessStrength; private: DabRequestInfo(const DabRequestInfo &rhs); }; struct PAINTOP_EXPORT DabGenerationInfo { MirrorProperties mirrorProperties; KisDabShape shape; QRect dstDabRect; QPointF subPixel; bool solidColorFill = true; KoColor paintColor; KisPaintInformation info; qreal softnessFactor = 1.0; + qreal lightnessStrength = 1.0; bool needsPostprocessing = false; }; PAINTOP_EXPORT QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect, const QSize &realDabSize); PAINTOP_EXPORT void generateDab(const DabGenerationInfo &di, DabRenderingResources *resources, KisFixedPaintDeviceSP *dab); PAINTOP_EXPORT void postProcessDab(KisFixedPaintDeviceSP dab, const QPoint &dabTopLeft, const KisPaintInformation& info, DabRenderingResources *resources); } template class QSharedPointer; class KisDabRenderingJob; typedef QSharedPointer KisDabRenderingJobSP; #endif // KISDABCACHEUTILS_H diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp index 52be81b200..9b82cc9b3e 100644 --- a/plugins/paintops/libpaintop/kis_curve_option_widget.cpp +++ b/plugins/paintops/libpaintop/kis_curve_option_widget.cpp @@ -1,360 +1,365 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * * 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_curve_option_widget.h" #include "ui_wdgcurveoption.h" #include "widgets/kis_curve_widget.h" #include "kis_dynamic_sensor.h" #include "kis_global.h" #include "kis_curve_option.h" #include "kis_signals_blocker.h" #include "kis_icon_utils.h" inline void setLabel(QLabel* label, const KisCurveLabel& curve_label) { if (curve_label.icon().isNull()) { label->setText(curve_label.name()); } else { label->setPixmap(QPixmap::fromImage(curve_label.icon())); } } KisCurveOptionWidget::KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider) : KisPaintOpOption(curveOption->category(), curveOption->isChecked()) , m_widget(new QWidget) , m_curveOptionWidget(new Ui_WdgCurveOption()) , m_curveOption(curveOption) { setObjectName("KisCurveOptionWidget"); m_curveOptionWidget->setupUi(m_widget); setConfigurationPage(m_widget); m_curveOptionWidget->sensorSelector->setCurveOption(curveOption); updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); connect(m_curveOptionWidget->curveWidget, SIGNAL(modified()), this, SLOT(slotModified())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(emitSettingChanged())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(parametersChanged()), SLOT(updateLabelsOfCurrentSensor())); connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateSensorCurveLabels(KisDynamicSensorSP))); connect(m_curveOptionWidget->sensorSelector, SIGNAL(highlightedSensorChanged(KisDynamicSensorSP)), SLOT(updateCurve(KisDynamicSensorSP))); connect(m_curveOptionWidget->checkBoxUseSameCurve, SIGNAL(stateChanged(int)), SLOT(slotUseSameCurveChanged())); // set all the icons for the curve preset shapes updateThemedIcons(); // various curve preset buttons with predefined curves connect(m_curveOptionWidget->linearCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLinear())); connect(m_curveOptionWidget->revLinearButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseLinear())); connect(m_curveOptionWidget->jCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveJShape())); connect(m_curveOptionWidget->lCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveLShape())); connect(m_curveOptionWidget->sCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveSShape())); connect(m_curveOptionWidget->reverseSCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveReverseSShape())); connect(m_curveOptionWidget->uCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveUShape())); connect(m_curveOptionWidget->revUCurveButton, SIGNAL(clicked(bool)), this, SLOT(changeCurveArchShape())); m_curveOptionWidget->label_ymin->setText(minLabel); m_curveOptionWidget->label_ymax->setText(maxLabel); // strength settings is shown as 0-100% m_curveOptionWidget->strengthSlider->setRange(curveOption->minValue()*100, curveOption->maxValue()*100, 0); m_curveOptionWidget->strengthSlider->setValue(curveOption->value()*100); m_curveOptionWidget->strengthSlider->setPrefix(i18n("Strength: ")); m_curveOptionWidget->strengthSlider->setSuffix(i18n("%")); if (hideSlider) { m_curveOptionWidget->strengthSlider->hide(); } connect(m_curveOptionWidget->checkBoxUseCurve, SIGNAL(stateChanged(int)) , SLOT(updateValues())); connect(m_curveOptionWidget->curveMode, SIGNAL(currentIndexChanged(int)), SLOT(updateMode())); connect(m_curveOptionWidget->strengthSlider, SIGNAL(valueChanged(qreal)), SLOT(updateValues())); } KisCurveOptionWidget::~KisCurveOptionWidget() { delete m_curveOption; delete m_curveOptionWidget; } void KisCurveOptionWidget::writeOptionSetting(KisPropertiesConfigurationSP setting) const { m_curveOption->writeOptionSetting(setting); } void KisCurveOptionWidget::readOptionSetting(const KisPropertiesConfigurationSP setting) { //setting->dump(); m_curveOption->readOptionSetting(setting); // Signals needs to be blocked, otherwise checking the checkbox will trigger // setting the common curve to the widget curve, which is incorrect in this case. bool blockedBefore = m_curveOptionWidget->checkBoxUseSameCurve->blockSignals(true); m_curveOptionWidget->checkBoxUseSameCurve->setChecked(m_curveOption->isSameCurveUsed()); m_curveOptionWidget->checkBoxUseSameCurve->blockSignals(blockedBefore); m_curveOptionWidget->checkBoxUseCurve->setChecked(m_curveOption->isCurveUsed()); m_curveOptionWidget->strengthSlider->setValue(m_curveOption->value()*100); m_curveOptionWidget->curveMode->setCurrentIndex(m_curveOption->getCurveMode()); disableWidgets(!m_curveOption->isCurveUsed()); m_curveOptionWidget->sensorSelector->reload(); m_curveOptionWidget->sensorSelector->setCurrent(m_curveOption->activeSensors().first()); updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); } void KisCurveOptionWidget::lodLimitations(KisPaintopLodLimitations *l) const { m_curveOption->lodLimitations(l); } bool KisCurveOptionWidget::isCheckable() const { return m_curveOption->isCheckable(); } bool KisCurveOptionWidget::isChecked() const { return m_curveOption->isChecked(); } void KisCurveOptionWidget::setChecked(bool checked) { m_curveOption->setChecked(checked); } +void KisCurveOptionWidget::setEnabled(bool enabled) +{ + m_widget->setEnabled(enabled); +} + KisCurveOption* KisCurveOptionWidget::curveOption() { return m_curveOption; } QWidget* KisCurveOptionWidget::curveWidget() { return m_widget; } void KisCurveOptionWidget::slotModified() { if (!m_curveOption->isSameCurveUsed()) { m_curveOptionWidget->sensorSelector->currentHighlighted()->setCurve(getWidgetCurve()); } else { m_curveOption->setCommonCurve(getWidgetCurve()); } emitSettingChanged(); } void KisCurveOptionWidget::slotUseSameCurveChanged() { // this is a slot that answers on "Share Curve across all settings" checkbox m_curveOption->setUseSameCurve(m_curveOptionWidget->checkBoxUseSameCurve->isChecked()); if (m_curveOption->isSameCurveUsed()) { // !(UseSameCurve) => UseSameCurve // set the current curve to the common curve m_curveOption->setCommonCurve(getWidgetCurve()); } else { updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); } emitSettingChanged(); } void KisCurveOptionWidget::updateSensorCurveLabels(KisDynamicSensorSP sensor) { if (sensor) { m_curveOptionWidget->label_xmin->setText(KisDynamicSensor::minimumLabel(sensor->sensorType())); m_curveOptionWidget->label_xmax->setText(KisDynamicSensor::maximumLabel(sensor->sensorType(), sensor->length())); int inMinValue = KisDynamicSensor::minimumValue(sensor->sensorType()); int inMaxValue = KisDynamicSensor::maximumValue(sensor->sensorType(), sensor->length()); QString inSuffix = KisDynamicSensor::valueSuffix(sensor->sensorType()); int outMinValue = m_curveOption->intMinValue(); int outMaxValue = m_curveOption->intMaxValue(); QString outSuffix = m_curveOption->valueSuffix(); m_curveOptionWidget->intIn->setSuffix(inSuffix); m_curveOptionWidget->intOut->setSuffix(outSuffix); m_curveOptionWidget->curveWidget->setupInOutControls(m_curveOptionWidget->intIn,m_curveOptionWidget->intOut, inMinValue,inMaxValue,outMinValue,outMaxValue); } } void KisCurveOptionWidget::updateCurve(KisDynamicSensorSP sensor) { if (sensor) { bool blockSignal = m_curveOptionWidget->curveWidget->blockSignals(true); KisCubicCurve curve = m_curveOption->isSameCurveUsed() ? m_curveOption->getCommonCurve() : sensor->curve(); m_curveOptionWidget->curveWidget->setCurve(curve); m_curveOptionWidget->curveWidget->blockSignals(blockSignal); } } void KisCurveOptionWidget::updateLabelsOfCurrentSensor() { updateSensorCurveLabels(m_curveOptionWidget->sensorSelector->currentHighlighted()); updateCurve(m_curveOptionWidget->sensorSelector->currentHighlighted()); } void KisCurveOptionWidget::updateValues() { m_curveOption->setValue(m_curveOptionWidget->strengthSlider->value()/100.0); // convert back to 0-1 for data m_curveOption->setCurveUsed(m_curveOptionWidget->checkBoxUseCurve->isChecked()); disableWidgets(!m_curveOptionWidget->checkBoxUseCurve->isChecked()); emitSettingChanged(); } void KisCurveOptionWidget::updateMode() { m_curveOption->setCurveMode(m_curveOptionWidget->curveMode->currentIndex()); emitSettingChanged(); } void KisCurveOptionWidget::changeCurveLinear() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveReverseLinear() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveSShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.25,0.1)); points.push_back(QPointF(0.75,0.9)); points.push_back(QPointF(1, 1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveReverseSShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.25,0.9)); points.push_back(QPointF(0.75,0.1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveJShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.35,0.1)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveLShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.25,0.48)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveUShape() { QList points; points.push_back(QPointF(0,1)); points.push_back(QPointF(0.5,0)); points.push_back(QPointF(1,1)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::changeCurveArchShape() { QList points; points.push_back(QPointF(0,0)); points.push_back(QPointF(0.5,1)); points.push_back(QPointF(1,0)); m_curveOptionWidget->curveWidget->setCurve(KisCubicCurve(points)); } void KisCurveOptionWidget::disableWidgets(bool disable) { m_curveOptionWidget->checkBoxUseSameCurve->setDisabled(disable); m_curveOptionWidget->curveWidget->setDisabled(disable); m_curveOptionWidget->sensorSelector->setDisabled(disable); m_curveOptionWidget->label_xmax->setDisabled(disable); m_curveOptionWidget->label_xmin->setDisabled(disable); m_curveOptionWidget->label_ymax->setDisabled(disable); m_curveOptionWidget->label_ymin->setDisabled(disable); } void KisCurveOptionWidget::updateThemedIcons() { // set all the icons for the curve preset shapes m_curveOptionWidget->linearCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear")); m_curveOptionWidget->revLinearButton->setIcon(KisIconUtils::loadIcon("curve-preset-linear-reverse")); m_curveOptionWidget->jCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-j")); m_curveOptionWidget->lCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-l")); m_curveOptionWidget->sCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s")); m_curveOptionWidget->reverseSCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-s-reverse")); m_curveOptionWidget->uCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-u")); m_curveOptionWidget->revUCurveButton->setIcon(KisIconUtils::loadIcon("curve-preset-arch")); // this helps make the checkboxes show themselves on the dark color themes QPalette pal = m_curveOptionWidget->sensorSelector->palette(); QPalette newPalette = pal; newPalette.setColor(QPalette::Active, QPalette::Background, pal.text().color() ); m_curveOptionWidget->sensorSelector->setPalette(newPalette); } KisCubicCurve KisCurveOptionWidget::getWidgetCurve() { return m_curveOptionWidget->curveWidget->curve(); } KisCubicCurve KisCurveOptionWidget::getHighlightedSensorCurve() { return m_curveOptionWidget->sensorSelector->currentHighlighted()->curve(); } diff --git a/plugins/paintops/libpaintop/kis_curve_option_widget.h b/plugins/paintops/libpaintop/kis_curve_option_widget.h index 5c2396c1e5..f0affe9143 100644 --- a/plugins/paintops/libpaintop/kis_curve_option_widget.h +++ b/plugins/paintops/libpaintop/kis_curve_option_widget.h @@ -1,90 +1,91 @@ /* This file is part of the KDE project * Copyright (C) 2008 Boudewijn Rempt * Copyright (C) 2009 Sven Langkamp * Copyright (C) 2011 Silvio Heinrich * * 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_CURVE_OPTION_WIDGET_H #define KIS_CURVE_OPTION_WIDGET_H #include class Ui_WdgCurveOption; class KisCurveOption; class QComboBox; #include class PAINTOP_EXPORT KisCurveOptionWidget : public KisPaintOpOption { Q_OBJECT public: KisCurveOptionWidget(KisCurveOption* curveOption, const QString &minLabel, const QString &maxLabel, bool hideSlider = false); ~KisCurveOptionWidget() override; void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; void lodLimitations(KisPaintopLodLimitations *l) const override; bool isCheckable() const override; bool isChecked() const override; void setChecked(bool checked) override; void show(); + void setEnabled(bool enabled); protected: KisCurveOption* curveOption(); QWidget* curveWidget(); private Q_SLOTS: void slotModified(); void slotUseSameCurveChanged(); void updateSensorCurveLabels(KisDynamicSensorSP sensor); void updateCurve(KisDynamicSensorSP sensor); void updateValues(); void updateMode(); void updateLabelsOfCurrentSensor(); void disableWidgets(bool disable); void updateThemedIcons(); // curve shape preset buttons void changeCurveLinear(); void changeCurveReverseLinear(); void changeCurveSShape(); void changeCurveReverseSShape(); void changeCurveJShape(); void changeCurveLShape(); void changeCurveUShape(); void changeCurveArchShape(); private: QWidget* m_widget; Ui_WdgCurveOption* m_curveOptionWidget; QComboBox* m_curveMode; KisCurveOption* m_curveOption; KisCubicCurve getWidgetCurve(); KisCubicCurve getHighlightedSensorCurve(); }; #endif // KIS_CURVE_OPTION_WIDGET_H diff --git a/plugins/paintops/libpaintop/kis_dab_cache.cpp b/plugins/paintops/libpaintop/kis_dab_cache.cpp index 7104688eb1..ff06121e9b 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache.cpp +++ b/plugins/paintops/libpaintop/kis_dab_cache.cpp @@ -1,213 +1,217 @@ /* * Copyright (c) 2012 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_dab_cache.h" #include #include "kis_paint_device.h" #include "kis_brush.h" #include #include "kis_color_source.h" #include "kis_pressure_sharpness_option.h" #include "kis_texture_option.h" #include struct KisDabCache::Private { Private(KisBrushSP brush) : brush(brush) {} KisFixedPaintDeviceSP dab; KisFixedPaintDeviceSP dabOriginal; KisBrushSP brush; KisPaintDeviceSP colorSourceDevice; KisPressureSharpnessOption *sharpnessOption = 0; KisTextureProperties *textureOption = 0; }; KisDabCache::KisDabCache(KisBrushSP brush) : m_d(new Private(brush)) { } KisDabCache::~KisDabCache() { delete m_d; } void KisDabCache::setSharpnessPostprocessing(KisPressureSharpnessOption *option) { m_d->sharpnessOption = option; } void KisDabCache::setTexturePostprocessing(KisTextureProperties *option) { m_d->textureOption = option; } bool KisDabCache::needSeparateOriginal() const { return KisDabCacheBase::needSeparateOriginal(m_d->textureOption, m_d->sharpnessOption); } KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs, KisColorSource *colorSource, const QPointF &cursorPoint, KisDabShape const& shape, const KisPaintInformation& info, qreal softnessFactor, - QRect *dstDabRect) + QRect *dstDabRect, + qreal lightnessStrength) { return fetchDabCommon(cs, colorSource, KoColor(), cursorPoint, shape, info, softnessFactor, dstDabRect); } KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs, const KoColor& color, const QPointF &cursorPoint, KisDabShape const& shape, const KisPaintInformation& info, qreal softnessFactor, - QRect *dstDabRect) + QRect *dstDabRect, + qreal lightnessStrength) { return fetchDabCommon(cs, 0, color, cursorPoint, shape, info, softnessFactor, - dstDabRect); + dstDabRect, + lightnessStrength); } inline KisFixedPaintDeviceSP KisDabCache::fetchFromCache(KisDabCacheUtils::DabRenderingResources *resources, const KisPaintInformation& info, QRect *dstDabRect) { if (needSeparateOriginal()) { *m_d->dab = *m_d->dabOriginal; *dstDabRect = KisDabCacheUtils::correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size()); KisDabCacheUtils::postProcessDab(m_d->dab, dstDabRect->topLeft(), info, resources); } else { *dstDabRect = KisDabCacheUtils::correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size()); } resources->brush->notifyCachedDabPainted(info); return m_d->dab; } /** * A special hack class that allows creation of temporary object with resources * without taking ownershop over the option classes */ struct TemporaryResourcesWithoutOwning : public KisDabCacheUtils::DabRenderingResources { ~TemporaryResourcesWithoutOwning() override { // we do not own these resources, so just // release them before destruction colorSource.take(); sharpnessOption.take(); textureOption.take(); } }; inline KisFixedPaintDeviceSP KisDabCache::fetchDabCommon(const KoColorSpace *cs, KisColorSource *colorSource, const KoColor& color, const QPointF &cursorPoint, KisDabShape shape, const KisPaintInformation& info, qreal softnessFactor, - QRect *dstDabRect) + QRect *dstDabRect, + qreal lightnessStrength) { Q_ASSERT(dstDabRect); bool hasDabInCache = true; if (!m_d->dab || *m_d->dab->colorSpace() != *cs) { m_d->dab = new KisFixedPaintDevice(cs); hasDabInCache = false; } using namespace KisDabCacheUtils; // 1. Calculate new dab parameters and whether we can reuse the cache TemporaryResourcesWithoutOwning resources; resources.brush = m_d->brush; resources.colorSourceDevice = m_d->colorSourceDevice; // NOTE: we use a special subclass of resources that will NOT // delete options on destruction! resources.colorSource.reset(colorSource); resources.sharpnessOption.reset(m_d->sharpnessOption); resources.textureOption.reset(m_d->textureOption); DabGenerationInfo di; bool shouldUseCache = false; fetchDabGenerationInfo(hasDabInCache, &resources, DabRequestInfo( color, cursorPoint, shape, info, softnessFactor), &di, &shouldUseCache); *dstDabRect = di.dstDabRect; // 2. Try return a saved dab from the cache if (shouldUseCache) { return fetchFromCache(&resources, info, dstDabRect); } // 3. Generate new dab generateDab(di, &resources, &m_d->dab); // 4. Do postprocessing if (di.needsPostprocessing) { if (!m_d->dabOriginal || *cs != *m_d->dabOriginal->colorSpace()) { m_d->dabOriginal = new KisFixedPaintDevice(cs); } *m_d->dabOriginal = *m_d->dab; postProcessDab(m_d->dab, di.dstDabRect.topLeft(), info, &resources); } return m_d->dab; } diff --git a/plugins/paintops/libpaintop/kis_dab_cache.h b/plugins/paintops/libpaintop/kis_dab_cache.h index 0a7c610ad6..12dfe1fe2a 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache.h +++ b/plugins/paintops/libpaintop/kis_dab_cache.h @@ -1,103 +1,106 @@ /* * Copyright (c) 2012 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_DAB_CACHE_H #define __KIS_DAB_CACHE_H #include "kritapaintop_export.h" #include "kis_dab_cache_base.h" #include "kis_brush.h" class KisColorSource; class KisPressureSharpnessOption; class KisTextureProperties; class KisPressureMirrorOption; class KisPrecisionOption; struct MirrorProperties; /** * @brief The KisDabCache class provides caching for dabs into the brush paintop * * This class adds caching of the dabs to the paintop system of Krita. * Such cache makes the execution of the benchmarks up to 2 times faster. * Subjectively, the real painting becomes much faster, especially with * huge brushes. Artists report up to 20% speed gain while painting. * * Of course, such caching makes the painting a bit less precise: we need * to tolerate subpixel differences to allow the cache to work. Sometimes * small difference in the size of a dab can also be acceptable. That is * why I introduced levels of precision. They are graded from 1 to 5: from * the fastest and less precise to the slowest, but with the best quality. * You can see the slider in the paintop settings dialog. The ToolTip text * explains which features of the brush are sacrificed on each precision * level. * * The texturing and mirroring problems are solved. */ class PAINTOP_EXPORT KisDabCache : public KisDabCacheBase { public: KisDabCache(KisBrushSP brush); ~KisDabCache(); KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs, KisColorSource *colorSource, const QPointF &cursorPoint, KisDabShape const&, const KisPaintInformation& info, qreal softnessFactor, - QRect *dstDabRect); + QRect *dstDabRect, + qreal lightnessStrength = 1.0); KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs, const KoColor& color, const QPointF &cursorPoint, KisDabShape const&, const KisPaintInformation& info, qreal softnessFactor, - QRect *dstDabRect); + QRect *dstDabRect, + qreal lightnessStrength = 1.0); void setSharpnessPostprocessing(KisPressureSharpnessOption *option); void setTexturePostprocessing(KisTextureProperties *option); bool needSeparateOriginal() const; private: inline KisFixedPaintDeviceSP fetchFromCache(KisDabCacheUtils::DabRenderingResources *resources, const KisPaintInformation& info, QRect *dstDabRect); inline KisFixedPaintDeviceSP fetchDabCommon(const KoColorSpace *cs, KisColorSource *colorSource, const KoColor& color, const QPointF &cursorPoint, KisDabShape, const KisPaintInformation& info, qreal softnessFactor, - QRect *dstDabRect); + QRect *dstDabRect, + qreal lightnessStrength = 1.0); private: struct Private; Private * const m_d; }; #endif /* __KIS_DAB_CACHE_H */ diff --git a/plugins/paintops/libpaintop/kis_dab_cache_base.cpp b/plugins/paintops/libpaintop/kis_dab_cache_base.cpp index d3d3adceb9..10f3b2ffc2 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache_base.cpp +++ b/plugins/paintops/libpaintop/kis_dab_cache_base.cpp @@ -1,288 +1,295 @@ /* * Copyright (c) 2012 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_dab_cache_base.h" #include #include "kis_color_source.h" #include "kis_paint_device.h" #include "kis_brush.h" #include #include #include #include #include #include #include struct PrecisionValues { qreal angle; qreal sizeFrac; qreal subPixel; qreal softnessFactor; + qreal lightnessStrength; qreal ratio; }; const qreal eps = 1e-6; static const PrecisionValues precisionLevels[] = { - {M_PI / 180, 0.05, 1, 0.01, 0.05}, - {M_PI / 180, 0.01, 1, 0.01, 0.01}, - {M_PI / 180, 0, 1, 0.01, eps}, - {M_PI / 180, 0, 0.5, 0.01, eps}, - { eps, 0, eps, eps, eps} + {M_PI / 180, 0.05, 1, 0.01, 0.01, 0.05}, + {M_PI / 180, 0.01, 1, 0.01, 0.01, 0.01}, + {M_PI / 180, 0, 1, 0.01, 0.01, eps}, + {M_PI / 180, 0, 0.5, 0.01, 0.01, eps}, + { eps, 0, eps, eps, eps, eps} }; struct KisDabCacheBase::SavedDabParameters { KoColor color; qreal angle; int width; int height; qreal subPixelX; qreal subPixelY; qreal softnessFactor; + qreal lightnessStrength; qreal ratio; int index; MirrorProperties mirrorProperties; bool compare(const SavedDabParameters &rhs, int precisionLevel) const { const PrecisionValues &prec = precisionLevels[precisionLevel]; return color == rhs.color && qAbs(angle - rhs.angle) <= prec.angle && qAbs(width - rhs.width) <= (int)(prec.sizeFrac * width) && qAbs(height - rhs.height) <= (int)(prec.sizeFrac * height) && qAbs(subPixelX - rhs.subPixelX) <= prec.subPixel && qAbs(subPixelY - rhs.subPixelY) <= prec.subPixel && qAbs(softnessFactor - rhs.softnessFactor) <= prec.softnessFactor && + qAbs(lightnessStrength - rhs.lightnessStrength) <= prec.lightnessStrength && qAbs(ratio - rhs.ratio) <= prec.ratio && index == rhs.index && mirrorProperties.horizontalMirror == rhs.mirrorProperties.horizontalMirror && mirrorProperties.verticalMirror == rhs.mirrorProperties.verticalMirror; } }; struct KisDabCacheBase::Private { Private() : mirrorOption(0), precisionOption(0), subPixelPrecisionDisabled(false) {} KisPressureMirrorOption *mirrorOption; KisPrecisionOption *precisionOption; bool subPixelPrecisionDisabled; SavedDabParameters lastSavedDabParameters; static qreal positiveFraction(qreal x); }; KisDabCacheBase::KisDabCacheBase() : m_d(new Private()) { } KisDabCacheBase::~KisDabCacheBase() { delete m_d; } void KisDabCacheBase::setMirrorPostprocessing(KisPressureMirrorOption *option) { m_d->mirrorOption = option; } void KisDabCacheBase::setPrecisionOption(KisPrecisionOption *option) { m_d->precisionOption = option; } void KisDabCacheBase::disableSubpixelPrecision() { m_d->subPixelPrecisionDisabled = true; } inline KisDabCacheBase::SavedDabParameters KisDabCacheBase::getDabParameters(KisBrushSP brush, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, + qreal lightnessStrength, MirrorProperties mirrorProperties) { SavedDabParameters params; params.color = color; params.angle = shape.rotation(); params.width = brush->maskWidth(shape, subPixelX, subPixelY, info); params.height = brush->maskHeight(shape, subPixelX, subPixelY, info); params.subPixelX = subPixelX; params.subPixelY = subPixelY; params.softnessFactor = softnessFactor; + params.lightnessStrength = lightnessStrength; params.index = brush->brushIndex(info); params.mirrorProperties = mirrorProperties; params.ratio = shape.ratio(); return params; } bool KisDabCacheBase::needSeparateOriginal(KisTextureProperties *textureOption, KisPressureSharpnessOption *sharpnessOption) const { return (textureOption && textureOption->m_enabled) || (sharpnessOption && sharpnessOption->isChecked()); } struct KisDabCacheBase::DabPosition { DabPosition(const QRect &_rect, const QPointF &_subPixel, qreal _realAngle) : rect(_rect), subPixel(_subPixel), realAngle(_realAngle) { } QRect rect; QPointF subPixel; qreal realAngle; }; qreal KisDabCacheBase::Private::positiveFraction(qreal x) { qint32 unused = 0; qreal fraction = 0.0; KisPaintOp::splitCoordinate(x, &unused, &fraction); return fraction; } inline KisDabCacheBase::DabPosition KisDabCacheBase::calculateDabRect(KisBrushSP brush, const QPointF &cursorPoint, KisDabShape shape, const KisPaintInformation& info, const MirrorProperties &mirrorProperties, KisPressureSharpnessOption *sharpnessOption) { qint32 x = 0, y = 0; qreal subPixelX = 0.0, subPixelY = 0.0; if (mirrorProperties.coordinateSystemFlipped) { shape = KisDabShape(shape.scale(), shape.ratio(), 2 * M_PI - shape.rotation()); } QPointF hotSpot = brush->hotSpot(shape, info); QPointF pt = cursorPoint - hotSpot; if (sharpnessOption) { sharpnessOption->apply(info, pt, x, y, subPixelX, subPixelY); } else { KisPaintOp::splitCoordinate(pt.x(), &x, &subPixelX); KisPaintOp::splitCoordinate(pt.y(), &y, &subPixelY); } if (m_d->subPixelPrecisionDisabled) { subPixelX = 0; subPixelY = 0; } if (qIsNaN(subPixelX)) { subPixelX = 0; } if (qIsNaN(subPixelY)) { subPixelY = 0; } int width = brush->maskWidth(shape, subPixelX, subPixelY, info); int height = brush->maskHeight(shape, subPixelX, subPixelY, info); if (mirrorProperties.horizontalMirror) { subPixelX = Private::positiveFraction(-(cursorPoint.x() + hotSpot.x())); width = brush->maskWidth(shape, subPixelX, subPixelY, info); x = qRound(cursorPoint.x() + subPixelX + hotSpot.x()) - width; } if (mirrorProperties.verticalMirror) { subPixelY = Private::positiveFraction(-(cursorPoint.y() + hotSpot.y())); height = brush->maskHeight(shape, subPixelX, subPixelY, info); y = qRound(cursorPoint.y() + subPixelY + hotSpot.y()) - height; } return DabPosition(QRect(x, y, width, height), QPointF(subPixelX, subPixelY), shape.rotation()); } void KisDabCacheBase::fetchDabGenerationInfo(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache) { di->info = request.info; di->softnessFactor = request.softnessFactor; + di->lightnessStrength = request.lightnessStrength; if (m_d->mirrorOption) { di->mirrorProperties = m_d->mirrorOption->apply(request.info); } DabPosition position = calculateDabRect(resources->brush, request.cursorPoint, request.shape, request.info, di->mirrorProperties, resources->sharpnessOption.data()); di->shape = KisDabShape(request.shape.scale(), request.shape.ratio(), position.realAngle); di->dstDabRect = position.rect; di->subPixel = position.subPixel; di->solidColorFill = !resources->colorSource || resources->colorSource->isUniformColor(); di->paintColor = resources->colorSource && resources->colorSource->isUniformColor() ? resources->colorSource->uniformColor() : request.color; SavedDabParameters newParams = getDabParameters(resources->brush, di->paintColor, di->shape, di->info, di->subPixel.x(), di->subPixel.y(), di->softnessFactor, + di->lightnessStrength, di->mirrorProperties); int precisionLevel = 4; if (m_d->precisionOption) { const int effectiveDabSize = qMin(newParams.width, newParams.height); precisionLevel = m_d->precisionOption->effectivePrecisionLevel(effectiveDabSize) - 1; } *shouldUseCache = hasDabInCache && di->solidColorFill && newParams.compare(m_d->lastSavedDabParameters, precisionLevel); if (!*shouldUseCache) { m_d->lastSavedDabParameters = newParams; } di->needsPostprocessing = needSeparateOriginal(resources->textureOption.data(), resources->sharpnessOption.data()); } diff --git a/plugins/paintops/libpaintop/kis_dab_cache_base.h b/plugins/paintops/libpaintop/kis_dab_cache_base.h index 3c0d6bc0ba..4018856f32 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache_base.h +++ b/plugins/paintops/libpaintop/kis_dab_cache_base.h @@ -1,123 +1,124 @@ /* * Copyright (c) 2012 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_DAB_CACHE_BASE_H #define __KIS_DAB_CACHE_BASE_H #include "kritapaintop_export.h" #include "kis_brush.h" #include "KisDabCacheUtils.h" class KisColorSource; class KisPressureSharpnessOption; class KisTextureProperties; class KisPressureMirrorOption; class KisPrecisionOption; struct MirrorProperties; /** * @brief The KisDabCacheBase class provides caching for dabs into the brush paintop * * This class adds caching of the dabs to the paintop system of Krita. * Such cache makes the execution of the benchmarks up to 2 times faster. * Subjectively, the real painting becomes much faster, especially with * huge brushes. Artists report up to 20% speed gain while painting. * * Of course, such caching makes the painting a bit less precise: we need * to tolerate subpixel differences to allow the cache to work. Sometimes * small difference in the size of a dab can also be acceptable. That is * why I introduced levels of precision. They are graded from 1 to 5: from * the fastest and less precise to the slowest, but with the best quality. * You can see the slider in the paintop settings dialog. The ToolTip text * explains which features of the brush are sacrificed on each precision * level. * * The texturing and mirroring problems are solved. */ class PAINTOP_EXPORT KisDabCacheBase { public: KisDabCacheBase(); ~KisDabCacheBase(); void setMirrorPostprocessing(KisPressureMirrorOption *option); void setPrecisionOption(KisPrecisionOption *option); /** * Disables handling of the subPixelX and subPixelY values, this * is needed at least for the Color Smudge paint op, which reads * aligned areas from image, so additional offsets generated by * the subpixel precision should be avoided */ void disableSubpixelPrecision(); /** * Return true if the dab needs postprocessing by special options * like 'texture' or 'sharpness' */ bool needSeparateOriginal(KisTextureProperties *textureOption, KisPressureSharpnessOption *sharpnessOption) const; protected: /** * Fetches all the necessary information for dab generation and * tells if the caller must should reuse the preciously returned dab. * * Please note that KisDabCacheBase has an internal state, that keeps the * parameters of the previously generated (on a cache-miss) dab. This function * automatically updates this state when 'shouldUseCache == false'. Therefore, the * caller *must* generate the dab if and only if when 'shouldUseCache == false'. * Otherwise the internal state will become inconsistent. * * @param hasDabInCache shows if the caller has something in its cache * @param resources rendering resources available for this dab * @param request the request information * @param di (OUT) calculated dab generation information * @param shouldUseCache (OUT) shows whether the caller *must* use cache or not */ void fetchDabGenerationInfo(bool hasDabInCache, KisDabCacheUtils::DabRenderingResources *resources, const KisDabCacheUtils::DabRequestInfo &request, /* out */ KisDabCacheUtils::DabGenerationInfo *di, bool *shouldUseCache); private: struct SavedDabParameters; struct DabPosition; private: inline SavedDabParameters getDabParameters(KisBrushSP brush, const KoColor& color, KisDabShape const&, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, + qreal lightnessStrength, MirrorProperties mirrorProperties); inline KisDabCacheBase::DabPosition calculateDabRect(KisBrushSP brush, const QPointF &cursorPoint, KisDabShape, const KisPaintInformation& info, const MirrorProperties &mirrorProperties, KisPressureSharpnessOption *sharpnessOption); private: struct Private; Private * const m_d; }; #endif /* __KIS_DAB_CACHE_BASE_H */ diff --git a/plugins/paintops/libpaintop/kis_pressure_lightness_strength_option.cpp b/plugins/paintops/libpaintop/kis_pressure_lightness_strength_option.cpp new file mode 100644 index 0000000000..02a7728868 --- /dev/null +++ b/plugins/paintops/libpaintop/kis_pressure_lightness_strength_option.cpp @@ -0,0 +1,30 @@ +/* This file is part of the KDE project + * Copyright (C) Boudewijn Rempt , (C) 2008 + * + * 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_lightness_strength_option.h" + +KisPressureLightnessStrengthOption::KisPressureLightnessStrengthOption() + : KisCurveOption("LightnessStrength", KisPaintOpOption::GENERAL, false) +{ +} + +double KisPressureLightnessStrengthOption::apply(const KisPaintInformation& info) const +{ + if (!isChecked()) return 1.0; + return computeSizeLikeValue(info); +} \ No newline at end of file diff --git a/plugins/paintops/libpaintop/kis_pressure_lightness_strength_option.h b/plugins/paintops/libpaintop/kis_pressure_lightness_strength_option.h new file mode 100644 index 0000000000..1d5c12c7ef --- /dev/null +++ b/plugins/paintops/libpaintop/kis_pressure_lightness_strength_option.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + * Copyright (C) Boudewijn Rempt , (C) 2008 + * + * 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_LIGHTNESS_STRENGTH_OPTION_H +#define KIS_PRESSURE_LIGHTNESS_STRENGTH_OPTION_H + +#include "kis_curve_option.h" +#include + + +/** + * The lightness strength option defines a curve that is used to + * calculate the effect of pressure on the strength of the lightness overlay + */ +class PAINTOP_EXPORT KisPressureLightnessStrengthOption : public KisCurveOption +{ +public: + KisPressureLightnessStrengthOption(); + double apply(const KisPaintInformation & info) const; + +}; + +#endif