diff --git a/libs/brush/kis_auto_brush.cpp b/libs/brush/kis_auto_brush.cpp index d67d6f4373..d91f232081 100644 --- a/libs/brush/kis_auto_brush.cpp +++ b/libs/brush/kis_auto_brush.cpp @@ -1,378 +1,388 @@ /* * 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 #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() { } KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs) : KisBrush(rhs), d(new Private(*rhs.d)) { } KisBrush* KisAutoBrush::clone() const { return new KisAutoBrush(*this); } +qint32 KisAutoBrush::maskHeight( + KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const +{ + Q_UNUSED(info); + /* It's difficult to predict the mask height when exaclty when there are + * more than 2 spikes, so we return an upperbound instead. */ + return KisBrush::maskHeight(KisDabShape(shape.scale(), 1.0, shape.rotation()), + subPixelX, subPixelY, info); +} + 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, - double scaleX, double scaleY, double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) const { Q_UNUSED(info); // 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(scaleX, angle, subPixelX, subPixelY, info); - int dstHeight = maskHeight(scaleY, angle, subPixelX, subPixelY, info); - QPointF hotSpot = this->hotSpot(scaleX, scaleY, angle, info); + 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 - angle += KisBrush::angle(); + 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) { // old bounds QRect oldBounds = dst->bounds(); // new bounds. we don't care if there is some extra memory occcupied. dst->setRect(QRect(0, 0, dstWidth, dstHeight)); if (dstWidth * dstHeight <= oldBounds.width() * oldBounds.height()) { // just clear the data in dst, memset(dst->data(), OPACITY_TRANSPARENT_U8, dstWidth * dstHeight * dst->pixelSize()); } else { // enlarge the data dst->initialize(); } } else { if (dst->data() == 0 || dst->bounds().isEmpty()) { warnKrita << "Creating a default black dab: no coloring info and no initialized paint device to mask"; dst->clear(QRect(0, 0, dstWidth, dstHeight)); } Q_ASSERT(dst->bounds().width() >= dstWidth && dst->bounds().height() >= dstHeight); } quint8* dabPointer = dst->data(); quint8* color = 0; if (coloringInformation) { 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->setScale(scaleX, scaleY); + d->shape->setScale(shape.scaleX(), shape.scaleY()); d->shape->setSoftness(softnessFactor); if (coloringInformation) { if (color && pixelSize == 4) { fillPixelOptimized_4bytes(color, dabPointer, dstWidth * dstHeight); } else if (color) { fillPixelOptimized_general(color, dabPointer, dstWidth * dstHeight, pixelSize); } else { 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, d->randomness, d->density, centerX, centerY, angle); KisBrushMaskApplicatorBase *applicator = d->shape->applicator(); applicator->initializeData(&data); int jobs = d->idealThreadCountCached; if (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() { srand(0); srand48(0); - int width = maskWidth(1.0, 0.0, 0.0, 0.0, KisPaintInformation()); - int height = maskHeight(1.0, 0.0, 0.0, 0.0, KisPaintInformation()); + 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()), 1.0, 1.0, 0.0, info); + 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 0542d0a4ec..cda35e2f94 100644 --- a/libs/brush/kis_auto_brush.h +++ b/libs/brush/kis_auto_brush.h @@ -1,94 +1,97 @@ /* * 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; virtual ~KisAutoBrush(); public: + qint32 maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, + const KisPaintInformation& info) const Q_DECL_OVERRIDE; + virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace*, - double, double, + KisDabShape const&, const KisPaintInformation&, - double = 0, double = 0) const { + double = 0, double = 0) const Q_DECL_OVERRIDE { return 0; // The autobrush does NOT support images! } virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* src, - double scaleX, double scaleY, double angle, + KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, - qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; + qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const Q_DECL_OVERRIDE; virtual QPainterPath outline() const; public: bool load() { return false; } virtual bool loadFromDevice(QIODevice *) { return false; } bool save() { return false; } bool saveToDevice(QIODevice*) const { return false; } void toXML(QDomDocument& , QDomElement&) const; const KisMaskGenerator* maskGenerator() const; qreal randomness() const; qreal density() const; void lodLimitations(KisPaintopLodLimitations *l) const; 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 55c079b446..12f58acd4b 100644 --- a/libs/brush/kis_brush.cpp +++ b/libs/brush/kis_brush.cpp @@ -1,639 +1,639 @@ /* * 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 "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 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) , brushType(INVALID) , autoSpacingActive(false) , autoSpacingCoeff(1.0) {} ~Private() { delete boundary; } mutable KisBoundary* boundary; qreal angle; qreal scale; bool hasColor; enumBrushType brushType; qint32 width; qint32 height; double spacing; QPointF hotSpot; mutable QSharedPointer brushPyramid; QImage brushTipImage; bool autoSpacingActive; qreal autoSpacingCoeff; }; KisBrush::KisBrush() : KoResource("") , d(new Private) { } KisBrush::KisBrush(const QString& filename) : KoResource(filename) , d(new Private) { } KisBrush::KisBrush(const KisBrush& rhs) : KoResource("") , 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->angle = rhs.d->angle; d->scale = rhs.d->scale; d->autoSpacingActive = rhs.d->autoSpacingActive; d->autoSpacingCoeff = rhs.d->autoSpacingCoeff; setFilename(rhs.filename()); /** * Be careful! The pyramid is shared between two brush objects, * therefore you cannot change it, only recreate! That i sthe * 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() { clearBrushPyramid(); 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(double scaleX, double scaleY, double rotation, const KisPaintInformation& info) const +QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const { Q_UNUSED(info); - QSizeF metric = characteristicSize(scaleX, scaleY, rotation); + QSizeF metric = characteristicSize(shape.scaleX(), shape.scaleY(), shape.rotation()); 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::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) { //Q_ASSERT(!image.isNull()); 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())); } void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const { element.setAttribute("BrushVersion", "2"); } KisBrushSP KisBrush::fromXML(const QDomElement& element, bool forceCopy) { KisBrushSP brush = KisBrushRegistry::instance()->getOrCreateBrush(element, forceCopy); if (brush && element.attribute("BrushVersion", "1") == "1") { brush->setScale(brush->scale() * 2.0); } return brush; } QSizeF KisBrush::characteristicSize(double scaleX, double scaleY, double rotation) const { Q_UNUSED(scaleY); qreal angle = normalizeAngle(rotation + d->angle); qreal scale = scaleX * d->scale; - return KisQImagePyramid::characteristicSize(QSize(width(), height()), - scale, angle); + return KisQImagePyramid::characteristicSize( + QSize(width(), height()), KisDabShape(scale, 1.0, angle)); } -qint32 KisBrush::maskWidth(double scale, double angle, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const +qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); - angle = normalizeAngle(angle + d->angle); - scale *= d->scale; + qreal angle = normalizeAngle(shape.rotation() + d->angle); + qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), - scale, angle, + KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).width(); } -qint32 KisBrush::maskHeight(double scale, double angle, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const +qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); - angle = normalizeAngle(angle + d->angle); - scale *= d->scale; + qreal angle = normalizeAngle(shape.rotation() + d->angle); + qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), - scale, angle, + 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::prepareBrushPyramid() const { if (!d->brushPyramid) { d->brushPyramid = toQShared(new KisQImagePyramid(brushTipImage())); } } void KisBrush::clearBrushPyramid() { d->brushPyramid.clear(); } -void KisBrush::mask(KisFixedPaintDeviceSP dst, double scaleX, double scaleY, double angle, const KisPaintInformation& info , double subPixelX, double subPixelY, qreal softnessFactor) const +void KisBrush::mask(KisFixedPaintDeviceSP dst, KisDabShape const& shape, const KisPaintInformation& info , double subPixelX, double subPixelY, qreal softnessFactor) const { - generateMaskAndApplyMaskOrCreateDab(dst, 0, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + generateMaskAndApplyMaskOrCreateDab(dst, 0, shape, info, subPixelX, subPixelY, softnessFactor); } -void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, double scaleX, double scaleY, double angle, 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) const { PlainColoringInformation pci(color.data()); - generateMaskAndApplyMaskOrCreateDab(dst, &pci, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor); } -void KisBrush::mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, double scaleX, double scaleY, double angle, 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) const { - PaintDeviceColoringInformation pdci(src, maskWidth(scaleX, angle, subPixelX, subPixelY, info)); - generateMaskAndApplyMaskOrCreateDab(dst, &pdci, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + PaintDeviceColoringInformation pdci(src, maskWidth(shape, subPixelX, subPixelY, info)); + generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor); } void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInformation, - double scaleX, double scaleY, double angle, + KisDabShape const& shape, const KisPaintInformation& info_, double subPixelX, double subPixelY, qreal softnessFactor) const { Q_ASSERT(valid()); Q_UNUSED(info_); Q_UNUSED(softnessFactor); - angle = normalizeAngle(angle + d->angle); - scaleX *= d->scale; - scaleY *= d->scale; - - double scale = 0.5 * (scaleX + scaleY); - prepareBrushPyramid(); - QImage outputImage = d->brushPyramid->createImage(scale, -angle, subPixelX, subPixelY); + QImage outputImage = d->brushPyramid->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->initialize(); quint8* color = 0; if (coloringInformation) { if (dynamic_cast(coloringInformation)) { color = const_cast(coloringInformation->color()); } } const KoColorSpace *cs = dst->colorSpace(); qint32 pixelSize = cs->pixelSize(); quint8 *dabPointer = dst->data(); quint8 *rowPointer = dabPointer; quint8 *alphaArray = new quint8[maskWidth]; bool hasColor = this->hasColor(); for (int y = 0; y < maskHeight; y++) { const quint8* maskPointer = outputImage.constScanLine(y); if (coloringInformation) { for (int x = 0; x < maskWidth; x++) { if (color) { memcpy(dabPointer, color, pixelSize); } else { memcpy(dabPointer, coloringInformation->color(), pixelSize); coloringInformation->nextColumn(); } dabPointer += pixelSize; } } if (hasColor) { const quint8 *src = maskPointer; quint8 *dst = alphaArray; for (int x = 0; x < maskWidth; x++) { const QRgb *c = reinterpret_cast(src); *dst = KoColorSpaceMaths::multiply(255 - qGray(*c), qAlpha(*c)); src += 4; dst++; } } else { const quint8 *src = maskPointer; quint8 *dst = alphaArray; for (int x = 0; x < maskWidth; x++) { const QRgb *c = reinterpret_cast(src); *dst = KoColorSpaceMaths::multiply(255 - *src, qAlpha(*c)); src += 4; dst++; } } cs->applyAlphaU8Mask(rowPointer, alphaArray, maskWidth); rowPointer += maskWidth * pixelSize; dabPointer = rowPointer; if (!color && coloringInformation) { coloringInformation->nextRow(); } } delete[] alphaArray; } KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace, - double scale, double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { Q_ASSERT(valid()); Q_UNUSED(info); - angle = normalizeAngle(angle + d->angle); - scale *= d->scale; + double angle = normalizeAngle(shape.rotation() + d->angle); + double scale = shape.scale() * d->scale; prepareBrushPyramid(); - QImage outputImage = d->brushPyramid->createImage(scale, -angle, subPixelX, subPixelY); + QImage outputImage = d->brushPyramid->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(), 1.0 / scale(), -angle(), KisPaintInformation()); + dev = paintDevice(KoColorSpaceRegistry::instance()->rgb8(), + inverseTransform, KisPaintInformation()); } else { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); dev = new KisFixedPaintDevice(cs); - mask(dev, KoColor(Qt::black, cs) , 1.0 / scale(), 1.0 / scale(), -angle(), KisPaintInformation()); + 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 b535a520ae..4ded24a333 100644 --- a/libs/brush/kis_brush.h +++ b/libs/brush/kis_brush.h @@ -1,379 +1,379 @@ /* * 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; 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); virtual ~PlainColoringInformation(); virtual const quint8* color() const ; virtual void nextColumn(); virtual void nextRow(); private: const quint8* m_color; }; class PaintDeviceColoringInformation : public ColoringInformation { public: PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width); virtual ~PaintDeviceColoringInformation(); virtual const quint8* color() const ; virtual void nextColumn(); virtual void nextRow(); private: const KisPaintDeviceSP m_source; KisHLineConstIteratorSP m_iterator; }; public: KisBrush(); KisBrush(const QString& filename); virtual ~KisBrush(); virtual bool load() { return false; } virtual bool loadFromDevice(QIODevice *) { return false; } virtual bool save() { return false; } virtual bool saveToDevice(QIODevice* ) const { 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(double scale, double angle, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const; + 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(double scale, double angle, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const; + 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. */ QSizeF characteristicSize(double scaleX, double scaleY, double rotation) 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(double scaleX, double scaleY, double rotation, const KisPaintInformation& info) 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. * * Currently, this is used by pipe'd brushes to implement * incremental and random parasites */ virtual void notifyCachedDabPainted(const KisPaintInformation& info); /** * Return a fixed paint device that contains a correctly scaled image dab. */ virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, - double scale, double angle, + KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0) const; /** * Apply the brush mask to the pixels in dst. Dst should be big enough! */ void mask(KisFixedPaintDeviceSP dst, - double scaleX, double scaleY, double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; /** * clear dst fill it with a mask colored with KoColor */ void mask(KisFixedPaintDeviceSP dst, const KoColor& color, - double scaleX, double scaleY, double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; /** * clear dst and fill it with a mask colored with the corresponding colors of src */ void mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, - double scaleX, double scaleY, double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; virtual bool hasColor() const; /** * 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 scale a scale applied on the alpha mask * @param angle a rotation 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) * * @return a mask computed from the grey-level values of the * pixels in the brush. */ virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInfo, - double scaleX, double scaleY, double angle, + 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, bool forceCopy = false); 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 prepareBrushPyramid() 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); /** * 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); /** * XXX */ virtual void setBrushType(enumBrushType type); friend class KisBrushTest; virtual void setHasColor(bool hasColor); /** * 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: friend class KisImagePipeBrushTest; // 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 d56f3a20a8..458770b586 100644 --- a/libs/brush/kis_brushes_pipe.h +++ b/libs/brush/kis_brushes_pipe.h @@ -1,175 +1,175 @@ /* * 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) { return !m_brushes.isEmpty() ? m_brushes.at(chooseNextBrush(info)) : 0; } int brushIndex(const KisPaintInformation& info) { return chooseNextBrush(info); } - qint32 maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) { + qint32 maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) { BrushType *brush = currentBrush(info); - return brush ? brush->maskWidth(scale, angle, subPixelX, subPixelY, info) : 0; + return brush ? brush->maskWidth(shape, subPixelX, subPixelY, info) : 0; } - qint32 maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) { + qint32 maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) { BrushType *brush = currentBrush(info); - return brush ? brush->maskHeight(scale, angle, subPixelX, subPixelY, info) : 0; + 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 notifyCachedDabPainted(const KisPaintInformation& info) { updateBrushIndexes(info); } void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, - double scaleX, double scaleY, double angle, const KisPaintInformation& info, + KisDabShape const& shape, + const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) { BrushType *brush = currentBrush(info); if (!brush) return; - brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); updateBrushIndexes(info); } KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, - double scale, double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) { BrushType *brush = currentBrush(info); if (!brush) return 0; - - KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, scale, angle, info, subPixelX, subPixelY); + KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, shape, info, subPixelX, subPixelY); updateBrushIndexes(info); return device; } QVector brushes() { return m_brushes; } void testingSelectNextBrush(const KisPaintInformation& info) { (void) chooseNextBrush(info); updateBrushIndexes(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); } /** * Returns the index of the brush that corresponds to the current * values of \p info. 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 chooseNextBrush(const KisPaintInformation& info) = 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. */ virtual void updateBrushIndexes(const KisPaintInformation& info) = 0; protected: QVector m_brushes; }; #endif /* __KIS_BRUSHES_PIPE_H */ diff --git a/libs/brush/tests/kis_auto_brush_test.h b/libs/brush/kis_dab_shape.h similarity index 52% copy from libs/brush/tests/kis_auto_brush_test.h copy to libs/brush/kis_dab_shape.h index 73e30ae7d3..04dbedb861 100644 --- a/libs/brush/tests/kis_auto_brush_test.h +++ b/libs/brush/kis_dab_shape.h @@ -1,38 +1,44 @@ /* - * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org + * Copyright (c) 2016 Nishant Rodrigues * * 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_TEST_H -#define KIS_AUTOBRUSH_RESOURCE_TEST_H +#pragma once +#include -#include +class KisDabShape { + qreal m_scale; + qreal m_ratio; + qreal m_rotation; -class KisAutoBrushTest : public QObject -{ - Q_OBJECT -private Q_SLOTS: - - void testCreation(); - void testMaskGeneration(); - void testSizeRotation(); - void testCopyMasking(); - - void testClone(); +public: + KisDabShape() + : m_scale(1.0) + , m_ratio(1.0) + , m_rotation(0.0) + {} + KisDabShape(qreal scale, qreal ratio, qreal rotation) + : m_scale(scale) + , m_ratio(ratio) + , m_rotation(rotation) + {} + qreal scale() const { return m_scale; } + qreal scaleX() const { return scale(); } + qreal scaleY() const { return m_scale * m_ratio; } + qreal ratio() const { return m_ratio; } + qreal rotation() const { return m_rotation; } }; - -#endif diff --git a/libs/brush/kis_imagepipe_brush.cpp b/libs/brush/kis_imagepipe_brush.cpp index f85f312d65..e8ff371d34 100644 --- a/libs/brush/kis_imagepipe_brush.cpp +++ b/libs/brush/kis_imagepipe_brush.cpp @@ -1,498 +1,502 @@ /* * 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_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; 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_d->PI_2 to be compatible with the gimp angle = info.drawingAngle() + M_PI_2; 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; default: warnImage << "Parasite" << mode << "is not implemented"; index = 0; } return index; } static int selectPost(KisParasite::SelectionMode mode, int index, int rank, const KisPaintInformation& info) { switch (mode) { case KisParasite::Constant: break; case KisParasite::Incremental: index = (index + 1) % rank; break; case KisParasite::Random: index = info.randomSource()->generate(0, rank); break; case KisParasite::Pressure: case KisParasite::Angular: break; case KisParasite::TiltX: case KisParasite::TiltY: break; default: warnImage << "Parasite" << mode << "is not implemented"; index = 0; } return index; } int chooseNextBrush(const KisPaintInformation& info) { 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); 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 %= m_brushes.size(); return brushIndex; } void updateBrushIndexes(const KisPaintInformation& info) { 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); } } public: using KisBrushesPipe::addBrush; 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 makeMaskImage() { Q_FOREACH (KisGbrBrush * brush, m_brushes) { brush->makeMaskImage(); } } bool saveToDevice(QIODevice* dev) const { Q_FOREACH (KisGbrBrush * brush, m_brushes) { if (!brush->saveToDevice(dev)) { return false; } } return true; } void notifyStrokeStarted() { m_isInitialized = false; } private: KisPipeBrushParasite m_parasite; 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("") , 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 (data[i] != '\n' && i < data.size()) { 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 (data[i] != '\n' && i < data.size()) { 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(); m_d->brushesPipe.setParasite(parasite); i++; // Skip past the second newline for (int brushIndex = 0; 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 != 1) { 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::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, - double scaleX, double scaleY, double angle, const KisPaintInformation& info, + KisDabShape const& shape, + const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) const { - m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + m_d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); } QVector KisImagePipeBrush::brushes() const { return m_d->brushesPipe.brushes(); } -KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice(const KoColorSpace * colorSpace, double scale, double angle, const KisPaintInformation& info, double subPixelX, double subPixelY) const +KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice( + const KoColorSpace * colorSpace, + KisDabShape const& shape, + const KisPaintInformation& info, double subPixelX, double subPixelY) const { - return m_d->brushesPipe.paintDevice(colorSpace, scale, angle, info, subPixelX, subPixelY); + return m_d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY); } enumBrushType KisImagePipeBrush::brushType() const { return !hasColor() || useColorAsMask() ? PIPE_MASK : PIPE_IMAGE; } bool KisImagePipeBrush::hasColor() const { return m_d->brushesPipe.hasColor(); } void KisImagePipeBrush::makeMaskImage() { m_d->brushesPipe.makeMaskImage(); setUseColorAsMask(false); } void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask) { KisGbrBrush::setUseColorAsMask(useColorAsMask); m_d->brushesPipe.setUseColorAsMask(useColorAsMask); } 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(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const +qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { - return m_d->brushesPipe.maskWidth(scale, angle, subPixelX, subPixelY, info); + return m_d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info); } -qint32 KisImagePipeBrush::maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const +qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { - return m_d->brushesPipe.maskHeight(scale, angle, subPixelX, subPixelY, info); + 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 } 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 8b9f5d607d..710d444ab8 100644 --- a/libs/brush/kis_imagepipe_brush.h +++ b/libs/brush/kis_imagepipe_brush.h @@ -1,142 +1,142 @@ /* * 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); virtual ~KisImagePipeBrush(); virtual bool load(); virtual bool loadFromDevice(QIODevice *dev); virtual bool save(); virtual bool saveToDevice(QIODevice* dev) const; /** * @return the next image in the pipe. */ virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, - double scale, double angle, + KisDabShape const&, const KisPaintInformation& info, - double subPixelX = 0, double subPixelY = 0) const; + double subPixelX = 0, double subPixelY = 0) const Q_DECL_OVERRIDE; virtual void setUseColorAsMask(bool useColorAsMask); virtual bool hasColor() const; virtual enumBrushType brushType() const; virtual const KisBoundary* boundary() const; virtual bool canPaintFor(const KisPaintInformation& info); virtual void makeMaskImage(); virtual KisBrush* clone() const; virtual QString defaultFileExtension() const; void setAngle(qreal _angle); void setScale(qreal _scale); void setSpacing(double _spacing); quint32 brushIndex(const KisPaintInformation& info) const; - qint32 maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const; - qint32 maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const; + qint32 maskWidth(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const Q_DECL_OVERRIDE; + qint32 maskHeight(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const Q_DECL_OVERRIDE; void notifyStrokeStarted(); void notifyCachedDabPainted(const KisPaintInformation& info); void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, - double scaleX, double scaleY, double angle, + KisDabShape const&, 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) const Q_DECL_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); void setHasColor(bool hasColor); /// 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); private: struct Private; Private * const m_d; }; #endif // KIS_IMAGEPIPE_BRUSH_ diff --git a/libs/brush/kis_qimage_pyramid.cpp b/libs/brush/kis_qimage_pyramid.cpp index 997cfa2d2a..6dcb2b40c5 100644 --- a/libs/brush/kis_qimage_pyramid.cpp +++ b/libs/brush/kis_qimage_pyramid.cpp @@ -1,311 +1,306 @@ /* * Copyright (c) 2013 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_qimage_pyramid.h" #include #include #include #define MIPMAP_SIZE_THRESHOLD 512 #define MAX_MIPMAP_SCALE 8.0 #define QPAINTER_WORKAROUND_BORDER 1 KisQImagePyramid::KisQImagePyramid(const QImage &baseImage) { Q_ASSERT(!baseImage.isNull()); m_originalSize = baseImage.size(); qreal scale = MAX_MIPMAP_SCALE; while (scale > 1.0) { QSize scaledSize = m_originalSize * scale; if (scaledSize.width() <= MIPMAP_SIZE_THRESHOLD || scaledSize.height() <= MIPMAP_SIZE_THRESHOLD) { if (m_levels.isEmpty()) { m_baseScale = scale; } appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } scale *= 0.5; } if (m_levels.isEmpty()) { m_baseScale = 1.0; } appendPyramidLevel(baseImage); scale = 0.5; while (true) { QSize scaledSize = m_originalSize * scale; if (scaledSize.width() == 0 || scaledSize.height() == 0) break; appendPyramidLevel(baseImage.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); scale *= 0.5; } } KisQImagePyramid::~KisQImagePyramid() { } int KisQImagePyramid::findNearestLevel(qreal scale, qreal *baseScale) const { const qreal scale_epsilon = 1e-6; qreal levelScale = m_baseScale; int level = 0; int lastLevel = m_levels.size() - 1; while ((0.5 * levelScale > scale || qAbs(0.5 * levelScale - scale) < scale_epsilon) && level < lastLevel) { levelScale *= 0.5; level++; } *baseScale = levelScale; return level; } inline QRect roundRect(const QRectF &rc) { /** * This is an analog of toAlignedRect() with the only difference * that it ensures the rect position will never be below zero. * * Warning: be *very* careful with using bottom()/right() values * of a pure QRect (we don't use it here for the dangers * it can lead to). */ QRectF rect(rc); KIS_ASSERT_RECOVER_NOOP(rect.x() > -1e-6); KIS_ASSERT_RECOVER_NOOP(rect.y() > -1e-6); if (rect.x() < 0.0) { rect.setLeft(0.0); } if (rect.y() < 0.0) { rect.setTop(0.0); } return rect.toAlignedRect(); } -QTransform baseBrushTransform(qreal scaleX, qreal scaleY, - qreal rotation, +QTransform baseBrushTransform(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const QRectF &baseBounds) { QTransform transform; - if (!qFuzzyCompare(rotation, 0)) { + if (!qFuzzyCompare(shape.rotation(), 0)) { QTransform rotationTransform; - rotationTransform.rotateRadians(rotation); + rotationTransform.rotateRadians(shape.rotation()); QRectF rotatedBounds = rotationTransform.mapRect(baseBounds); transform = rotationTransform * QTransform::fromTranslate(-rotatedBounds.x(), -rotatedBounds.y()); } return transform * - QTransform::fromScale(scaleX, scaleY) * + QTransform::fromScale(shape.scaleX(), shape.scaleY()) * QTransform::fromTranslate(subPixelX, subPixelY); } -void KisQImagePyramid::calculateParams(qreal scale, qreal rotation, +void KisQImagePyramid::calculateParams(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const QSize &originalSize, QTransform *outputTransform, QSize *outputSize) { - calculateParams(scale, rotation, + calculateParams(shape, subPixelX, subPixelY, originalSize, 1.0, originalSize, outputTransform, outputSize); } -void KisQImagePyramid::calculateParams(qreal scale, qreal rotation, +void KisQImagePyramid::calculateParams(KisDabShape shape, qreal subPixelX, qreal subPixelY, const QSize &originalSize, qreal baseScale, const QSize &baseSize, QTransform *outputTransform, QSize *outputSize) { Q_UNUSED(baseScale); QRectF originalBounds = QRectF(QPointF(), originalSize); QTransform originalTransform = - baseBrushTransform(scale, scale, - rotation, - subPixelX, subPixelY, + baseBrushTransform(shape, subPixelX, subPixelY, originalBounds); qreal realBaseScaleX = qreal(baseSize.width()) / originalSize.width(); qreal realBaseScaleY = qreal(baseSize.height()) / originalSize.height(); - - qreal scaleX = scale / realBaseScaleX; - qreal scaleY = scale / realBaseScaleY; + qreal scaleX = shape.scaleX() / realBaseScaleX; + qreal scaleY = shape.scaleY() / realBaseScaleY; + shape = KisDabShape(scaleX, scaleY/scaleX, shape.rotation()); QRectF baseBounds = QRectF(QPointF(), baseSize); QTransform transform = - baseBrushTransform(scaleX, scaleY, - rotation, + baseBrushTransform(shape, subPixelX, subPixelY, baseBounds); QRect expectedDstRect = roundRect(originalTransform.mapRect(originalBounds)); #if 0 // Only enable when debugging; users shouldn't see this warning { QRect testingRect = roundRect(transform.mapRect(baseBounds)); if (testingRect != expectedDstRect) { warnKrita << "WARNING: expected and real dab rects do not coincide!"; warnKrita << " expected rect:" << expectedDstRect; warnKrita << " real rect: " << testingRect; } } #endif KIS_ASSERT_RECOVER_NOOP(expectedDstRect.x() >= 0); KIS_ASSERT_RECOVER_NOOP(expectedDstRect.y() >= 0); int width = expectedDstRect.x() + expectedDstRect.width(); int height = expectedDstRect.y() + expectedDstRect.height(); // we should not return invalid image, so adjust the image to be // at least 1 px in size. width = qMax(1, width); height = qMax(1, height); *outputTransform = transform; *outputSize = QSize(width, height); } QSize KisQImagePyramid::imageSize(const QSize &originalSize, - qreal scale, qreal rotation, + KisDabShape const& shape, qreal subPixelX, qreal subPixelY) { QTransform transform; QSize dstSize; - calculateParams(scale, rotation, subPixelX, subPixelY, + calculateParams(shape, subPixelX, subPixelY, originalSize, &transform, &dstSize); return dstSize; } QSizeF KisQImagePyramid::characteristicSize(const QSize &originalSize, - qreal scale, qreal rotation) + KisDabShape const& shape) { QRectF originalRect(QPointF(), originalSize); - QTransform transform = baseBrushTransform(scale, scale, - rotation, + QTransform transform = baseBrushTransform(shape, 0.0, 0.0, originalRect); return transform.mapRect(originalRect).size(); } void KisQImagePyramid::appendPyramidLevel(const QImage &image) { /** * QPainter has a bug: when doing a transformation it decides that * all the pixels outside of the image (source rect) are equal to * the border pixels (CLAMP in terms of openGL). This means that * there will be no smooth scaling on the border of the image when * it is rotated. To workaround this bug we need to add one pixel * wide border to the image, so that it transforms smoothly. * * See a unittest in: KisBrushTest::testQPainterTransformationBorder */ QSize levelSize = image.size(); QImage tmp = image.convertToFormat(QImage::Format_ARGB32); tmp = tmp.copy(-QPAINTER_WORKAROUND_BORDER, -QPAINTER_WORKAROUND_BORDER, image.width() + 2 * QPAINTER_WORKAROUND_BORDER, image.height() + 2 * QPAINTER_WORKAROUND_BORDER); m_levels.append(PyramidLevel(tmp, levelSize)); } -QImage KisQImagePyramid::createImage(qreal scale, qreal rotation, +QImage KisQImagePyramid::createImage(KisDabShape const& shape, qreal subPixelX, qreal subPixelY) const { qreal baseScale = -1.0; - int level = findNearestLevel(scale, &baseScale); + int level = findNearestLevel(shape.scale(), &baseScale); const QImage &srcImage = m_levels[level].image; QTransform transform; QSize dstSize; - calculateParams(scale, rotation, subPixelX, subPixelY, + calculateParams(shape, subPixelX, subPixelY, m_originalSize, baseScale, m_levels[level].size, &transform, &dstSize); if (transform.isIdentity() && srcImage.format() == QImage::Format_ARGB32) { return srcImage.copy(QPAINTER_WORKAROUND_BORDER, QPAINTER_WORKAROUND_BORDER, srcImage.width() - 2 * QPAINTER_WORKAROUND_BORDER, srcImage.height() - 2 * QPAINTER_WORKAROUND_BORDER); } QImage dstImage(dstSize, QImage::Format_ARGB32); dstImage.fill(0); /** * QPainter has one more bug: when a QTransform is TxTranslate, it * does wrong sampling (probably, Nearest Neighbour) even though * we tell it directly that we need SmoothPixmapTransform. * * So here is a workaround: we set a negligible scale to convince * Qt we use a non-only-translating transform. */ while (transform.type() == QTransform::TxTranslate) { const qreal scale = transform.m11(); const qreal fakeScale = scale - 10 * std::numeric_limits::epsilon(); transform *= QTransform::fromScale(fakeScale, fakeScale); } QPainter gc(&dstImage); gc.setTransform( QTransform::fromTranslate(-QPAINTER_WORKAROUND_BORDER, -QPAINTER_WORKAROUND_BORDER) * transform); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QPointF(), srcImage); gc.end(); return dstImage; } diff --git a/libs/brush/kis_qimage_pyramid.h b/libs/brush/kis_qimage_pyramid.h index 6f3394dfb7..57b0d25929 100644 --- a/libs/brush/kis_qimage_pyramid.h +++ b/libs/brush/kis_qimage_pyramid.h @@ -1,74 +1,74 @@ /* * Copyright (c) 2013 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_QIMAGE_PYRAMID_H #define __KIS_QIMAGE_PYRAMID_H #include #include +#include #include class BRUSH_EXPORT KisQImagePyramid { public: KisQImagePyramid(const QImage &baseImage); ~KisQImagePyramid(); static QSize imageSize(const QSize &originalSize, - qreal scale, qreal rotation, + KisDabShape const&, qreal subPixelX, qreal subPixelY); - static QSizeF characteristicSize(const QSize &originalSize, - qreal scale, qreal rotation); + static QSizeF characteristicSize(const QSize &originalSize, KisDabShape const&); - QImage createImage(qreal scale, qreal rotation, + QImage createImage(KisDabShape const&, qreal subPixelX, qreal subPixelY) const; private: friend class KisBrushTest; int findNearestLevel(qreal scale, qreal *baseScale) const; void appendPyramidLevel(const QImage &image); - static void calculateParams(qreal scale, qreal rotation, + static void calculateParams(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const QSize &originalSize, QTransform *outputTransform, QSize *outputSize); - static void calculateParams(qreal scale, qreal rotation, + static void calculateParams(KisDabShape shape, qreal subPixelX, qreal subPixelY, const QSize &originalSize, qreal baseScale, const QSize &baseSize, QTransform *outputTransform, QSize *outputSize); private: QSize m_originalSize; qreal m_baseScale; struct PyramidLevel { PyramidLevel() {} PyramidLevel(QImage _image, QSize _size) : image(_image), size(_size) {} QImage image; QSize size; }; QVector m_levels; }; #endif /* __KIS_QIMAGE_PYRAMID_H */ diff --git a/libs/brush/kis_text_brush.cpp b/libs/brush/kis_text_brush.cpp index d28acfd8b7..e0630cd58f 100644 --- a/libs/brush/kis_text_brush.cpp +++ b/libs/brush/kis_text_brush.cpp @@ -1,305 +1,310 @@ /* * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_text_brush.h" #include #include #include #include #include "kis_gbr_brush.h" #include "kis_brushes_pipe.h" #include #include #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND #include #include #include #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ class KisTextBrushesPipe : public KisBrushesPipe { public: KisTextBrushesPipe() { m_charIndex = 0; m_currentBrushIndex = 0; } KisTextBrushesPipe(const KisTextBrushesPipe &rhs) : KisBrushesPipe(rhs) { m_brushesMap.clear(); QMapIterator iter(rhs.m_brushesMap); while (iter.hasNext()) { iter.next(); m_brushesMap.insert(iter.key(), iter.value()); } } void setText(const QString &text, const QFont &font) { m_text = text; m_charIndex = 0; clear(); for (int i = 0; i < m_text.length(); i++) { QChar letter = m_text.at(i); QImage image = renderChar(letter, font); KisGbrBrush *brush = new KisGbrBrush(image, letter); brush->setSpacing(0.1); // support for letter spacing? brush->makeMaskImage(); m_brushesMap.insert(letter, brush); KisBrushesPipe::addBrush(brush); } } static QImage renderChar(const QString& text, const QFont &font) { #ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND QWidget *focusWidget = qApp->focusWidget(); if (focusWidget) { QThread *guiThread = focusWidget->thread(); if (guiThread != QThread::currentThread()) { warnKrita << "WARNING: Rendering text in non-GUI thread!" << "That may lead to hangups and crashes on some" << "versions of X11/Qt!"; } } #endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */ QFontMetrics metric(font); QRect rect = metric.boundingRect(text); if (rect.isEmpty()) { rect = QRect(0, 0, 1, 1); // paint at least something } QRect paintingRect = rect.translated(-rect.x(), -rect.y()); QImage renderedChar(paintingRect.size(), QImage::Format_ARGB32); QPainter p; p.begin(&renderedChar); p.setFont(font); p.fillRect(paintingRect, Qt::white); p.setPen(Qt::black); p.drawText(-rect.x(), -rect.y(), text); p.end(); return renderedChar; } void clear() { m_brushesMap.clear(); KisBrushesPipe::clear(); } KisGbrBrush* firstBrush() const { Q_ASSERT(m_text.size() > 0); Q_ASSERT(m_brushesMap.size() > 0); return m_brushesMap.value(m_text.at(0)); } void notifyStrokeStarted() { m_charIndex = 0; updateBrushIndexesImpl(); } protected: int chooseNextBrush(const KisPaintInformation& info) { Q_UNUSED(info); return m_currentBrushIndex; } void updateBrushIndexes(const KisPaintInformation& info) { Q_UNUSED(info); m_charIndex++; updateBrushIndexesImpl(); } private: void updateBrushIndexesImpl() { if (m_text.isEmpty()) return; if (m_charIndex >= m_text.size()) { m_charIndex = 0; } QChar letter = m_text.at(m_charIndex); Q_ASSERT(m_brushesMap.contains(letter)); m_currentBrushIndex = m_brushes.indexOf(m_brushesMap.value(letter)); } private: QMap m_brushesMap; QString m_text; int m_charIndex; int m_currentBrushIndex; }; KisTextBrush::KisTextBrush() : m_brushesPipe(new KisTextBrushesPipe()) { setPipeMode(false); } KisTextBrush::KisTextBrush(const KisTextBrush &rhs) : KisBrush(rhs), m_brushesPipe(new KisTextBrushesPipe(*rhs.m_brushesPipe)) { } KisTextBrush::~KisTextBrush() { delete m_brushesPipe; } void KisTextBrush::setPipeMode(bool pipe) { setBrushType(pipe ? PIPE_MASK : MASK); } bool KisTextBrush::pipeMode() const { return brushType() == PIPE_MASK; } void KisTextBrush::setText(const QString& txt) { m_text = txt; } QString KisTextBrush::text(void) const { return m_text; } void KisTextBrush::setFont(const QFont& font) { m_font = font; } QFont KisTextBrush::font() { return m_font; } void KisTextBrush::notifyStrokeStarted() { m_brushesPipe->notifyStrokeStarted(); } void KisTextBrush::notifyCachedDabPainted(const KisPaintInformation& info) { m_brushesPipe->notifyCachedDabPainted(info); } -void KisTextBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, double scaleX, double scaleY, double angle, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const +void KisTextBrush::generateMaskAndApplyMaskOrCreateDab( + KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, + KisDabShape const& shape, + const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const { if (brushType() == MASK) { - KisBrush::generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + KisBrush::generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); } else { /* if (brushType() == PIPE_MASK)*/ - m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, scaleX, scaleY, angle, info, subPixelX, subPixelY, softnessFactor); + m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); } } -KisFixedPaintDeviceSP KisTextBrush::paintDevice(const KoColorSpace * colorSpace, double scale, double angle, const KisPaintInformation& info, double subPixelX, double subPixelY) const +KisFixedPaintDeviceSP KisTextBrush::paintDevice(const KoColorSpace * colorSpace, + KisDabShape const& shape, + const KisPaintInformation& info, double subPixelX, double subPixelY) const { if (brushType() == MASK) { - return KisBrush::paintDevice(colorSpace, scale, angle, info, subPixelX, subPixelY); + return KisBrush::paintDevice(colorSpace, shape, info, subPixelX, subPixelY); } else { /* if (brushType() == PIPE_MASK)*/ - return m_brushesPipe->paintDevice(colorSpace, scale, angle, info, subPixelX, subPixelY); + return m_brushesPipe->paintDevice(colorSpace, shape, info, subPixelX, subPixelY); } } void KisTextBrush::toXML(QDomDocument& doc, QDomElement& e) const { Q_UNUSED(doc); e.setAttribute("type", "kis_text_brush"); e.setAttribute("spacing", KisDomUtils::toString(spacing())); e.setAttribute("text", m_text); e.setAttribute("font", m_font.toString()); e.setAttribute("pipe", (brushType() == PIPE_MASK) ? "true" : "false"); KisBrush::toXML(doc, e); } void KisTextBrush::updateBrush() { Q_ASSERT((brushType() == PIPE_MASK) || (brushType() == MASK)); if (brushType() == PIPE_MASK) { m_brushesPipe->setText(m_text, m_font); setBrushTipImage(m_brushesPipe->firstBrush()->brushTipImage()); } else { /* if (brushType() == MASK)*/ setBrushTipImage(KisTextBrushesPipe::renderChar(m_text, m_font)); } resetBoundary(); setValid(true); } quint32 KisTextBrush::brushIndex(const KisPaintInformation& info) const { return brushType() == MASK ? 0 : 1 + m_brushesPipe->brushIndex(info); } -qint32 KisTextBrush::maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const +qint32 KisTextBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return brushType() == MASK ? - KisBrush::maskWidth(scale, angle, subPixelX, subPixelY, info) : - m_brushesPipe->maskWidth(scale, angle, subPixelX, subPixelY, info); + KisBrush::maskWidth(shape, subPixelX, subPixelY, info) : + m_brushesPipe->maskWidth(shape, subPixelX, subPixelY, info); } -qint32 KisTextBrush::maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const +qint32 KisTextBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return brushType() == MASK ? - KisBrush::maskHeight(scale, angle, subPixelX, subPixelY, info) : - m_brushesPipe->maskHeight(scale, angle, subPixelX, subPixelY, info); + KisBrush::maskHeight(shape, subPixelX, subPixelY, info) : + m_brushesPipe->maskHeight(shape, subPixelX, subPixelY, info); } void KisTextBrush::setAngle(qreal _angle) { KisBrush::setAngle(_angle); m_brushesPipe->setAngle(_angle); } void KisTextBrush::setScale(qreal _scale) { KisBrush::setScale(_scale); m_brushesPipe->setScale(_scale); } void KisTextBrush::setSpacing(double _spacing) { KisBrush::setSpacing(_spacing); m_brushesPipe->setSpacing(_spacing); } KisBrush* KisTextBrush::clone() const { return new KisTextBrush(*this); } diff --git a/libs/brush/kis_text_brush.h b/libs/brush/kis_text_brush.h index 2116b47f89..d91f0d1d84 100644 --- a/libs/brush/kis_text_brush.h +++ b/libs/brush/kis_text_brush.h @@ -1,94 +1,95 @@ /* * Copyright (c) 2004 Cyrille Berger * Copyright (c) 2011 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_TEXT_BRUSH_H_ #define _KIS_TEXT_BRUSH_H_ #include #include "kis_brush.h" #include "kritabrush_export.h" class KisTextBrushesPipe; class BRUSH_EXPORT KisTextBrush : public KisBrush { public: KisTextBrush(); KisTextBrush(const KisTextBrush &rhs); virtual ~KisTextBrush(); void notifyStrokeStarted(); void notifyCachedDabPainted(const KisPaintInformation& info); void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, - double scaleX, double scaleY, double angle, + KisDabShape const&, 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) const Q_DECL_OVERRIDE; - KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, double scale, double angle, const KisPaintInformation& info, double subPixelX, double subPixelY) const; + KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, + KisDabShape const&, const KisPaintInformation& info, double subPixelX, double subPixelY) const Q_DECL_OVERRIDE; bool load() { return false; } virtual bool loadFromDevice(QIODevice *) { return false; } bool save() { return false; } bool saveToDevice(QIODevice* ) const { return false; } void setText(const QString& txt); QString text(void) const; QFont font(); void setFont(const QFont& font); void setPipeMode(bool pipe); bool pipeMode() const; void updateBrush(); void toXML(QDomDocument& , QDomElement&) const; quint32 brushIndex(const KisPaintInformation& info) const; - qint32 maskWidth(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const; - qint32 maskHeight(double scale, double angle, double subPixelX, double subPixelY, const KisPaintInformation& info) const; + qint32 maskWidth(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const Q_DECL_OVERRIDE; + qint32 maskHeight(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const Q_DECL_OVERRIDE; void setAngle(qreal _angle); void setScale(qreal _scale); void setSpacing(double _spacing); KisBrush* clone() const; private: QFont m_font; QString m_text; private: KisTextBrushesPipe *m_brushesPipe; }; #endif diff --git a/libs/brush/tests/kis_auto_brush_test.cpp b/libs/brush/tests/kis_auto_brush_test.cpp index 77ba8de5f5..e8ec39a070 100644 --- a/libs/brush/tests/kis_auto_brush_test.cpp +++ b/libs/brush/tests/kis_auto_brush_test.cpp @@ -1,196 +1,196 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * Copyright (c) 2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include "kis_auto_brush_test.h" #include #include #include "../kis_auto_brush.h" #include "kis_mask_generator.h" #include "kis_paint_device.h" #include "kis_fill_painter.h" #include #include #include #include #include #include void KisAutoBrushTest::testCreation() { KisCircleMaskGenerator circle(10, 1.0, 1.0, 1.0, 2, true); KisRectangleMaskGenerator rect(10, 1.0, 1.0, 1.0, 2, true); } void KisAutoBrushTest::testMaskGeneration() { KisCircleMaskGenerator* circle = new KisCircleMaskGenerator(10, 1.0, 1.0, 1.0, 2, false); KisBrushSP a = new KisAutoBrush(circle, 0.0, 0.0); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); // check masking an existing paint device KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs); fdev->setRect(QRect(0, 0, 100, 100)); fdev->initialize(); cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100); QPoint errpoint; QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "result_autobrush_1.png"); QImage image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_autobrush_test_1.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } // Check creating a mask dab with a single color fdev = new KisFixedPaintDevice(cs); - a->mask(fdev, KoColor(Qt::black, cs), 1.0, 1.0, 0.0, info); + a->mask(fdev, KoColor(Qt::black, cs), KisDabShape(), info); result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_autobrush_3.png"); image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_autobrush_test_3.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } // Check creating a mask dab with a color taken from a paint device KoColor red(Qt::red, cs); cs->setOpacity(red.data(), quint8(128), 1); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 100, 100, red.data()); fdev = new KisFixedPaintDevice(cs); - a->mask(fdev, dev, 1.0, 1.0, 0.0, info); + a->mask(fdev, dev, KisDabShape(), info); result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_autobrush_4.png"); image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_autobrush_test_4.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } -void KisAutoBrushTest::testSizeRotation() +static void dabSizeHelper(KisBrushSP const& brush, + QString const& name, KisDabShape const& shape, int expectedWidth, int expectedHeight) { - { - KisCircleMaskGenerator* circle = new KisCircleMaskGenerator(10, 0.5, 1.0, 1.0, 2, false); - KisBrushSP a = new KisAutoBrush(circle, 0.0, 0.0); - QCOMPARE(a->width(), 10); - QCOMPARE(a->height(), 5); - QCOMPARE(a->maskWidth(1.0, 0.0, 0.0, 0.0, KisPaintInformation()), 10); - QCOMPARE(a->maskHeight(1.0, 0.0, 0.0, 0.0, KisPaintInformation()), 5); - QCOMPARE(a->maskWidth(2.0, 0.0, 0.0, 0.0, KisPaintInformation()), 20); - QCOMPARE(a->maskHeight(2.0, 0.0, 0.0, 0.0, KisPaintInformation()), 10); - QCOMPARE(a->maskWidth(0.5, 0.0, 0.0, 0.0, KisPaintInformation()), 5); - QCOMPARE(a->maskHeight(0.5, 0.0, 0.0, 0.0, KisPaintInformation()), 3); - QCOMPARE(a->maskWidth(1.0, M_PI, 0.0, 0.0, KisPaintInformation()), 10); - QCOMPARE(a->maskHeight(1.0, M_PI, 0.0, 0.0, KisPaintInformation()), 5); - QCOMPARE(a->maskWidth(1.0, M_PI_2, 0.0, 0.0, KisPaintInformation()), 6); // ceil-rule - QCOMPARE(a->maskHeight(1.0, M_PI_2, 0.0, 0.0, KisPaintInformation()), 10); - QCOMPARE(a->maskWidth(1.0, -M_PI_2, 0.0, 0.0, KisPaintInformation()), 6); // ceil rule - QCOMPARE(a->maskHeight(1.0, -M_PI_2, 0.0, 0.0, KisPaintInformation()), 11); // ceil rule - QCOMPARE(a->maskWidth(1.0, 0.25 * M_PI, 0.0, 0.0, KisPaintInformation()), 11); - QCOMPARE(a->maskHeight(1.0, 0.25 * M_PI, 0.0, 0.0, KisPaintInformation()), 11); - QCOMPARE(a->maskWidth(2.0, 0.25 * M_PI, 0.0, 0.0, KisPaintInformation()), 22); // ceil rule - QCOMPARE(a->maskHeight(2.0, 0.25 * M_PI, 0.0, 0.0, KisPaintInformation()), 22); // ceil rule - QCOMPARE(a->maskWidth(0.5, 0.25 * M_PI, 0.0, 0.0, KisPaintInformation()), 6); // ceil rule - QCOMPARE(a->maskHeight(0.5, 0.25 * M_PI, 0.0, 0.0, KisPaintInformation()), 6); // ceil rule - } + qDebug() << name; + QCOMPARE(brush->maskWidth(shape, 0.0, 0.0, KisPaintInformation()), expectedWidth); + QCOMPARE(brush->maskHeight(shape, 0.0, 0.0, KisPaintInformation()), expectedHeight); +} + +void KisAutoBrushTest::testDabSize() +{ + KisCircleMaskGenerator* circle = new KisCircleMaskGenerator(10, 0.5, 1.0, 1.0, 2, false); + KisBrushSP a = new KisAutoBrush(circle, 0.0, 0.0); + QCOMPARE(a->width(), 10); + QCOMPARE(a->height(), 5); + + dabSizeHelper(a, "Identity", KisDabShape(), 10, 5); + dabSizeHelper(a, "Double", KisDabShape(2.0, 1.0, 0.0), 20, 10); + dabSizeHelper(a, "Halve", KisDabShape(0.5, 1.0, 0.0), 5, 3); + dabSizeHelper(a, "180 deg", KisDabShape(1.0, 1.0, M_PI), 10, 5); + dabSizeHelper(a, "90 deg", KisDabShape(1.0, 1.0, M_PI_2), 6, 10); // ceil rule + dabSizeHelper(a, "-90 deg", KisDabShape(1.0, 1.0, -M_PI_2), 6, 11); // ceil rule + dabSizeHelper(a, "45 deg", KisDabShape(1.0, 1.0, 0.25 * M_PI), 11, 11); + dabSizeHelper(a, "2x, 45d", KisDabShape(2.0, 1.0, 0.25 * M_PI), 22, 22); + dabSizeHelper(a, "0.5x, 45d", KisDabShape(0.5, 1.0, 0.25 * M_PI), 6, 6); + dabSizeHelper(a, "0.5x, 45d", KisDabShape(0.5, 1.0, 0.25 * M_PI), 6, 6); + dabSizeHelper(a, "0.5y", KisDabShape(1.0, 0.5, 0.0), 10, 3); } //#define SAVE_OUTPUT_IMAGES void KisAutoBrushTest::testCopyMasking() { int w = 64; int h = 64; int x = 0; int y = 0; QRect rc(x, y, w, h); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor black(Qt::black, cs); KoColor red(Qt::red, cs); KisPaintDeviceSP tempDev = new KisPaintDevice(cs); tempDev->fill(0, 0, w, h, red.data()); #ifdef SAVE_OUTPUT_IMAGES tempDev->convertToQImage(0).save("tempDev.png"); #endif KisCircleMaskGenerator * mask = new KisCircleMaskGenerator(w, 1.0, 0.5, 0.5, 2, true); KisAutoBrush brush(mask, 0, 0); KisFixedPaintDeviceSP maskDab = new KisFixedPaintDevice(cs); - brush.mask(maskDab, black, 1, 1, 0, KisPaintInformation()); + brush.mask(maskDab, black, KisDabShape(), KisPaintInformation()); maskDab->convertTo(KoColorSpaceRegistry::instance()->alpha8()); #ifdef SAVE_OUTPUT_IMAGES maskDab->convertToQImage(0, 0, 0, 64, 64).save("maskDab.png"); #endif QCOMPARE(tempDev->exactBounds(), rc); QCOMPARE(maskDab->bounds(), rc); KisFixedPaintDeviceSP dev2fixed = new KisFixedPaintDevice(cs); dev2fixed->setRect(rc); dev2fixed->initialize(); tempDev->readBytes(dev2fixed->data(), rc); dev2fixed->convertToQImage(0).save("converted-tempDev-to-fixed.png"); KisPaintDeviceSP dev = new KisPaintDevice(cs); KisPainter painter(dev); painter.setCompositeOp(COMPOSITE_COPY); painter.bltFixedWithFixedSelection(x, y, dev2fixed, maskDab, 0, 0, 0, 0, rc.width(), rc.height()); //painter.bitBltWithFixedSelection(x, y, tempDev, maskDab, 0, 0, 0, 0, rc.width(), rc.height()); #ifdef SAVE_OUTPUT_IMAGES dev->convertToQImage(0).save("final.png"); #endif } void KisAutoBrushTest::testClone() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisCircleMaskGenerator* circle = new KisCircleMaskGenerator(10, 0.7, 0.85, 0.5, 2, true); KisBrushSP brush = new KisAutoBrush(circle, 0.5, 0.0); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); KisFixedPaintDeviceSP fdev1 = new KisFixedPaintDevice(cs); - brush->mask(fdev1, KoColor(Qt::black, cs), 0.8, 0.8, 8.0, info); + brush->mask(fdev1, KoColor(Qt::black, cs), KisDabShape(0.8, 1.0, 8.0), info); QImage res1 = fdev1->convertToQImage(0); KisBrushSP clone = brush->clone(); KisFixedPaintDeviceSP fdev2 = new KisFixedPaintDevice(cs); - clone->mask(fdev2, KoColor(Qt::black, cs), 0.8, 0.8, 8.0, info); + clone->mask(fdev2, KoColor(Qt::black, cs), KisDabShape(0.8, 1.0, 8.0), info); QImage res2 = fdev2->convertToQImage(0); QCOMPARE(res1, res2); } QTEST_MAIN(KisAutoBrushTest) diff --git a/libs/brush/tests/kis_auto_brush_test.h b/libs/brush/tests/kis_auto_brush_test.h index 73e30ae7d3..eb7f34a362 100644 --- a/libs/brush/tests/kis_auto_brush_test.h +++ b/libs/brush/tests/kis_auto_brush_test.h @@ -1,38 +1,37 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_TEST_H #define KIS_AUTOBRUSH_RESOURCE_TEST_H #include class KisAutoBrushTest : public QObject { Q_OBJECT private Q_SLOTS: void testCreation(); void testMaskGeneration(); - void testSizeRotation(); + void testDabSize(); void testCopyMasking(); - void testClone(); }; #endif diff --git a/libs/brush/tests/kis_brush_test.cpp b/libs/brush/tests/kis_brush_test.cpp index 3c4b9276ab..9aedb451fa 100644 --- a/libs/brush/tests/kis_brush_test.cpp +++ b/libs/brush/tests/kis_brush_test.cpp @@ -1,345 +1,345 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_test.h" #include #include #include #include #include #include #include "testutil.h" #include "../kis_gbr_brush.h" #include "kis_types.h" #include "kis_paint_device.h" #include "brushengine/kis_paint_information.h" #include #include "kis_qimage_pyramid.h" void KisBrushTest::testMaskGenerationNoColor() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr"); brush->load(); Q_ASSERT(brush->valid()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); // check masking an existing paint device KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs); fdev->setRect(QRect(0, 0, 100, 100)); fdev->initialize(); cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100); QPoint errpoint; QImage result(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_1.png"); QImage image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_brush_test_1.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } - brush->mask(fdev, 1.0, 1.0, 0.0, info); + brush->mask(fdev, KisDabShape(), info); result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_2.png"); image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_brush_test_2.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisBrushTest::testMaskGenerationSingleColor() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr"); brush->load(); Q_ASSERT(brush->valid()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); // check masking an existing paint device KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs); fdev->setRect(QRect(0, 0, 100, 100)); fdev->initialize(); cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100); // Check creating a mask dab with a single color fdev = new KisFixedPaintDevice(cs); - brush->mask(fdev, KoColor(Qt::black, cs), 1.0, 1.0, 0.0, info); + brush->mask(fdev, KoColor(Qt::black, cs), KisDabShape(), info); QPoint errpoint; QImage result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_3.png"); QImage image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_brush_test_3.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisBrushTest::testMaskGenerationDevColor() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr"); brush->load(); Q_ASSERT(brush->valid()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); // check masking an existing paint device KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs); fdev->setRect(QRect(0, 0, 100, 100)); fdev->initialize(); cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100); // Check creating a mask dab with a color taken from a paint device KoColor red(Qt::red, cs); cs->setOpacity(red.data(), quint8(128), 1); KisPaintDeviceSP dev = new KisPaintDevice(cs); dev->fill(0, 0, 100, 100, red.data()); fdev = new KisFixedPaintDevice(cs); - brush->mask(fdev, dev, 1.0, 1.0, 0.0, info); + brush->mask(fdev, dev, KisDabShape(), info); QPoint errpoint; QImage result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_4.png"); QImage image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_brush_test_4.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } void KisBrushTest::testMaskGenerationDefaultColor() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "brush.gbr"); brush->load(); Q_ASSERT(brush->valid()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); // check masking an existing paint device KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(cs); fdev->setRect(QRect(0, 0, 100, 100)); fdev->initialize(); cs->setOpacity(fdev->data(), OPACITY_OPAQUE_U8, 100 * 100); // check creating a mask dab with a default color fdev = new KisFixedPaintDevice(cs); - brush->mask(fdev, 1.0, 1.0, 0.0, info); + brush->mask(fdev, KisDabShape(), info); QPoint errpoint; QImage result = QImage(QString(FILES_DATA_DIR) + QDir::separator() + "result_brush_3.png"); QImage image = fdev->convertToQImage(0); if (!TestUtil::compareQImages(errpoint, image, result)) { image.save("kis_brush_test_5.png"); QFAIL(QString("Failed to create identical image, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } delete brush; } void KisBrushTest::testImageGeneration() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"); bool res = brush->load(); Q_UNUSED(res); Q_ASSERT(res); QVERIFY(!brush->brushTipImage().isNull()); brush->prepareBrushPyramid(); qsrand(1); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); KisFixedPaintDeviceSP dab; for (int i = 0; i < 200; i++) { qreal scale = qreal(qrand()) / RAND_MAX * 2.0; qreal rotation = qreal(qrand()) / RAND_MAX * 2 * M_PI; qreal subPixelX = qreal(qrand()) / RAND_MAX * 0.5; QString testName = QString("brush_%1_sc_%2_rot_%3_sub_%4") .arg(i).arg(scale).arg(rotation).arg(subPixelX); - dab = brush->paintDevice(cs, scale, rotation, info, subPixelX); + dab = brush->paintDevice(cs, KisDabShape(scale, 1.0, rotation), info, subPixelX); /** * Compare first 10 images. Others are tested for asserts only */ if (i < 10) { QImage result = dab->convertToQImage(0); TestUtil::checkQImage(result, "brush_masks", "", testName); } } } void KisBrushTest::benchmarkPyramidCreation() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"); brush->load(); QVERIFY(!brush->brushTipImage().isNull()); QBENCHMARK { brush->prepareBrushPyramid(); brush->clearBrushPyramid(); } } void KisBrushTest::benchmarkScaling() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"); brush->load(); QVERIFY(!brush->brushTipImage().isNull()); brush->prepareBrushPyramid(); qsrand(1); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); KisFixedPaintDeviceSP dab; QBENCHMARK { - dab = brush->paintDevice(cs, qreal(qrand()) / RAND_MAX * 2.0, 0.0, info); + dab = brush->paintDevice(cs, KisDabShape(qreal(qrand()) / RAND_MAX * 2.0, 1.0, 0.0), info); //dab->convertToQImage(0).save(QString("dab_%1_new_smooth.png").arg(i++)); } } void KisBrushTest::benchmarkRotation() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"); brush->load(); QVERIFY(!brush->brushTipImage().isNull()); brush->prepareBrushPyramid(); qsrand(1); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); KisFixedPaintDeviceSP dab; QBENCHMARK { - dab = brush->paintDevice(cs, 1.0, qreal(qrand()) / RAND_MAX * 2 * M_PI, info); + dab = brush->paintDevice(cs, KisDabShape(1.0, 1.0, qreal(qrand()) / RAND_MAX * 2 * M_PI), info); } } void KisBrushTest::benchmarkMaskScaling() { KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"); brush->load(); QVERIFY(!brush->brushTipImage().isNull()); brush->prepareBrushPyramid(); qsrand(1); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisPaintInformation info(QPointF(100.0, 100.0), 0.5); KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(cs); QBENCHMARK { KoColor c(Qt::black, cs); qreal scale = qreal(qrand()) / RAND_MAX * 2.0; - brush->mask(dab, c, scale, scale, 0.0, info, 0.0, 0.0, 1.0); + brush->mask(dab, c, KisDabShape(scale, 1.0, 0.0), info, 0.0, 0.0, 1.0); } } void KisBrushTest::testPyramidLevelRounding() { QSize imageSize(41, 41); QImage image(imageSize, QImage::Format_ARGB32); image.fill(0); KisQImagePyramid pyramid(image); qreal baseScale; int baseLevel; baseLevel = pyramid.findNearestLevel(1.0, &baseScale); QCOMPARE(baseScale, 1.0); QCOMPARE(baseLevel, 3); baseLevel = pyramid.findNearestLevel(2.0, &baseScale); QCOMPARE(baseScale, 2.0); QCOMPARE(baseLevel, 2); baseLevel = pyramid.findNearestLevel(4.0, &baseScale); QCOMPARE(baseScale, 4.0); QCOMPARE(baseLevel, 1); baseLevel = pyramid.findNearestLevel(0.5, &baseScale); QCOMPARE(baseScale, 0.5); QCOMPARE(baseLevel, 4); baseLevel = pyramid.findNearestLevel(0.25, &baseScale); QCOMPARE(baseScale, 0.25); QCOMPARE(baseLevel, 5); baseLevel = pyramid.findNearestLevel(0.25 + 1e-7, &baseScale); QCOMPARE(baseScale, 0.25); QCOMPARE(baseLevel, 5); } // see comment in KisQImagePyramid::appendPyramidLevel void KisBrushTest::testQPainterTransformationBorder() { QImage image1(10, 10, QImage::Format_ARGB32); QImage image2(12, 12, QImage::Format_ARGB32); image1.fill(0); image2.fill(0); { QPainter gc(&image1); gc.fillRect(QRect(0, 0, 10, 10), Qt::black); } { QPainter gc(&image2); gc.fillRect(QRect(1, 1, 10, 10), Qt::black); } image1.save("src1.png"); image2.save("src2.png"); { QImage canvas(100, 100, QImage::Format_ARGB32); canvas.fill(0); QPainter gc(&canvas); QTransform transform; transform.rotate(15); gc.setTransform(transform); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QPointF(50, 50), image1); gc.end(); canvas.save("canvas1.png"); } { QImage canvas(100, 100, QImage::Format_ARGB32); canvas.fill(0); QPainter gc(&canvas); QTransform transform; transform.rotate(15); gc.setTransform(transform); gc.setRenderHints(QPainter::SmoothPixmapTransform); gc.drawImage(QPointF(50, 50), image2); gc.end(); canvas.save("canvas2.png"); } } QTEST_MAIN(KisBrushTest) diff --git a/libs/brush/tests/kis_imagepipe_brush_test.cpp b/libs/brush/tests/kis_imagepipe_brush_test.cpp index 89a69c50cb..ea47af1816 100644 --- a/libs/brush/tests/kis_imagepipe_brush_test.cpp +++ b/libs/brush/tests/kis_imagepipe_brush_test.cpp @@ -1,277 +1,278 @@ /* * 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_imagepipe_brush_test.h" #include #include #include #include #include #include #include #include #include "kis_imagepipe_brush.h" #include #include #define COMPARE_ALL(brush, method) \ Q_FOREACH (KisGbrBrush *child, brush->brushes()) { \ if(brush->method() != child->method()) { \ dbgKrita << "Failing method:" << #method \ << "brush index:" \ << brush->brushes().indexOf(child); \ QCOMPARE(brush->method(), child->method()); \ } \ } inline void KisImagePipeBrushTest::checkConsistency(KisImagePipeBrush *brush) { qreal scale = 0.5; Q_UNUSED(scale); KisGbrBrush *firstBrush = brush->brushes().first(); /** * This set of values is supposed to be constant, so * it is just set to the corresponding values of the * first brush */ QCOMPARE(brush->width(), firstBrush->width()); QCOMPARE(brush->height(), firstBrush->height()); QCOMPARE(brush->boundary(), firstBrush->boundary()); /** * These values should be spread over the children brushes */ COMPARE_ALL(brush, maskAngle); COMPARE_ALL(brush, scale); COMPARE_ALL(brush, angle); COMPARE_ALL(brush, spacing); /** * Check mask size values, they depend on current brush */ KisPaintInformation info; KisBrush *oldBrush = brush->testingGetCurrentBrush(info); QVERIFY(oldBrush); qreal realScale = 1; qreal realAngle = 0; qreal subPixelX = 0; qreal subPixelY = 0; - int maskWidth = brush->maskWidth(realScale, realAngle, subPixelX, subPixelY, info); - int maskHeight = brush->maskHeight(realScale, realAngle, subPixelX, subPixelY, info); + int maskWidth = brush->maskWidth(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info); + int maskHeight = brush->maskHeight(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info); const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); - KisFixedPaintDeviceSP dev = brush->testingGetCurrentBrush(info)->paintDevice(cs, realScale, realAngle, info, subPixelX, subPixelY); + KisFixedPaintDeviceSP dev = brush->testingGetCurrentBrush(info)->paintDevice( + cs, KisDabShape(realScale, 1.0, realAngle), info, subPixelX, subPixelY); QCOMPARE(maskWidth, dev->bounds().width()); QCOMPARE(maskHeight, dev->bounds().height()); KisBrush *newBrush = brush->testingGetCurrentBrush(info); QCOMPARE(oldBrush, newBrush); } void KisImagePipeBrushTest::testLoading() { KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih"); brush->load(); QVERIFY(brush->valid()); checkConsistency(brush); delete brush; } void KisImagePipeBrushTest::testChangingBrushes() { KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih"); brush->load(); QVERIFY(brush->valid()); qreal rotation = 0; KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation); for (int i = 0; i < 100; i++) { checkConsistency(brush); brush->testingSelectNextBrush(info); } delete brush; } void checkIncrementalPainting(KisBrush *brush, const QString &prefix) { qreal realScale = 1; qreal realAngle = 0; const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KoColor fillColor(Qt::red, cs); KisFixedPaintDeviceSP fixedDab = new KisFixedPaintDevice(cs); qreal rotation = 0; qreal subPixelX = 0.0; qreal subPixelY = 0.0; KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation); for (int i = 0; i < 20; i++) { - int maskWidth = brush->maskWidth(realScale, realAngle, subPixelX, subPixelY, info); - int maskHeight = brush->maskHeight(realScale, realAngle, subPixelX, subPixelY, info); + int maskWidth = brush->maskWidth(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info); + int maskHeight = brush->maskHeight(KisDabShape(realScale, 1.0, realAngle), subPixelX, subPixelY, info); QRect fillRect(0, 0, maskWidth, maskHeight); fixedDab->setRect(fillRect); fixedDab->initialize(); fixedDab->fill(fillRect.x(), fillRect.y(), fillRect.width(), fillRect.height(), fillColor.data()); - brush->mask(fixedDab, realScale, realScale, realAngle, info); + brush->mask(fixedDab, KisDabShape(realScale, 1.0, realAngle), info); QCOMPARE(fixedDab->bounds(), fillRect); QImage result = fixedDab->convertToQImage(0); result.save(QString("fixed_dab_%1_%2.png").arg(prefix).arg(i)); } } void KisImagePipeBrushTest::testSimpleDabApplication() { KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih"); brush->load(); QVERIFY(brush->valid()); checkConsistency(brush); checkIncrementalPainting(brush, "simple"); delete brush; } void KisImagePipeBrushTest::testColoredDab() { KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "G_Sparks.gih"); brush->load(); QVERIFY(brush->valid()); checkConsistency(brush); QCOMPARE(brush->useColorAsMask(), false); QCOMPARE(brush->hasColor(), true); QCOMPARE(brush->brushType(), PIPE_IMAGE); // let it be the mask (should be revertible) brush->setUseColorAsMask(true); QCOMPARE(brush->useColorAsMask(), true); QCOMPARE(brush->hasColor(), true); QCOMPARE(brush->brushType(), PIPE_MASK); // revert back brush->setUseColorAsMask(false); QCOMPARE(brush->useColorAsMask(), false); QCOMPARE(brush->hasColor(), true); QCOMPARE(brush->brushType(), PIPE_IMAGE); // convert to the mask (irreversible) brush->makeMaskImage(); QCOMPARE(brush->useColorAsMask(), false); QCOMPARE(brush->hasColor(), false); QCOMPARE(brush->brushType(), PIPE_MASK); checkConsistency(brush); delete brush; } void KisImagePipeBrushTest::testColoredDabWash() { KisImagePipeBrush *brush = new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "G_Sparks.gih"); brush->load(); QVERIFY(brush->valid()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); qreal rotation = 0; KisPaintInformation info(QPointF(100.0, 100.0), 0.5, 0, 0, rotation); KisPaintDeviceSP layer = new KisPaintDevice(cs); KisPainter painter(layer); painter.setCompositeOp(COMPOSITE_ALPHA_DARKEN); const QVector gbrs = brush->brushes(); - KisFixedPaintDeviceSP dab = gbrs.at(0)->paintDevice(cs, 2.0, 0.0, info); + KisFixedPaintDeviceSP dab = gbrs.at(0)->paintDevice(cs, KisDabShape(2.0, 1.0, 0.0), info); painter.bltFixed(0, 0, dab, 0, 0, dab->bounds().width(), dab->bounds().height()); painter.bltFixed(80, 60, dab, 0, 0, dab->bounds().width(), dab->bounds().height()); painter.end(); QRect rc = layer->exactBounds(); QImage result = layer->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height()); #if 0 // if you want to see the result on white background, set #if 1 QImage bg(result.size(), result.format()); bg.fill(Qt::white); QPainter qPainter(&bg); qPainter.drawImage(0, 0, result); result = bg; #endif result.save("z_spark_alpha_darken.png"); delete brush; } #include "kis_text_brush.h" void KisImagePipeBrushTest::testTextBrushNoPipes() { KisTextBrush *brush = new KisTextBrush(); brush->setPipeMode(false); brush->setFont(QApplication::font()); brush->setText("The_Quick_Brown_Fox_Jumps_Over_The_Lazy_Dog"); brush->updateBrush(); checkIncrementalPainting(brush, "text_no_incremental"); delete brush; } void KisImagePipeBrushTest::testTextBrushPiped() { KisTextBrush *brush = new KisTextBrush(); brush->setPipeMode(true); brush->setFont(QApplication::font()); brush->setText("The_Quick_Brown_Fox_Jumps_Over_The_Lazy_Dog"); brush->updateBrush(); checkIncrementalPainting(brush, "text_incremental"); delete brush; } QTEST_MAIN(KisImagePipeBrushTest) diff --git a/libs/image/brushengine/kis_paintop_settings.cpp b/libs/image/brushengine/kis_paintop_settings.cpp index a5bb0c58d6..fade0b4462 100644 --- a/libs/image/brushengine/kis_paintop_settings.cpp +++ b/libs/image/brushengine/kis_paintop_settings.cpp @@ -1,351 +1,361 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Lukáš Tvrdý * Copyright (c) 2014 Mohit Goyal * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "kis_paint_layer.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_paint_device.h" #include "kis_paintop_registry.h" #include #include "kis_paintop_config_widget.h" #include #include #include struct Q_DECL_HIDDEN KisPaintOpSettings::Private { Private() : disableDirtyNotifications(false) {} QPointer settingsWidget; QString modelName; KisPaintOpPresetWSP preset; bool disableDirtyNotifications; class DirtyNotificationsLocker { public: DirtyNotificationsLocker(KisPaintOpSettings::Private *d) : m_d(d), m_oldNotificationsState(d->disableDirtyNotifications) { m_d->disableDirtyNotifications = true; } ~DirtyNotificationsLocker() { m_d->disableDirtyNotifications = m_oldNotificationsState; } private: KisPaintOpSettings::Private *m_d; bool m_oldNotificationsState; Q_DISABLE_COPY(DirtyNotificationsLocker) }; }; KisPaintOpSettings::KisPaintOpSettings() : d(new Private) { d->preset = 0; } KisPaintOpSettings::~KisPaintOpSettings() { } void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget) { d->settingsWidget = widget; } void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset) { d->preset = preset; } KisPaintOpPresetWSP KisPaintOpSettings::preset() const { return d->preset; } bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &pos, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { Q_UNUSED(pos); Q_UNUSED(modifiers); Q_UNUSED(currentNode); setRandomOffset(); return true; // ignore the event by default } void KisPaintOpSettings::setRandomOffset() { srand(time(0)); bool isRandomOffsetX = KisPropertiesConfiguration::getBool("Texture/Pattern/isRandomOffsetX"); bool isRandomOffsetY = KisPropertiesConfiguration::getBool("Texture/Pattern/isRandomOffsetY"); int offsetX = KisPropertiesConfiguration::getInt("Texture/Pattern/OffsetX"); int offsetY = KisPropertiesConfiguration::getInt("Texture/Pattern/OffsetY"); if (KisPropertiesConfiguration::getBool("Texture/Pattern/Enabled")) { if (isRandomOffsetX) { offsetX = rand() % KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX"); KisPropertiesConfiguration::setProperty("Texture/Pattern/OffsetX", offsetX); offsetX = KisPropertiesConfiguration::getInt("Texture/Pattern/OffsetX"); } if (isRandomOffsetY) { offsetY = rand() % KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY"); KisPropertiesConfiguration::setProperty("Texture/Pattern/OffsetY", offsetY); offsetY = KisPropertiesConfiguration::getInt("Texture/Pattern/OffsetY"); } } } KisPaintOpSettingsSP KisPaintOpSettings::clone() const { QString paintopID = getString("paintop"); if (paintopID.isEmpty()) return 0; KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID, "")); QMapIterator i(getProperties()); while (i.hasNext()) { i.next(); settings->setProperty(i.key(), QVariant(i.value())); } settings->setPreset(this->preset()); return settings; } void KisPaintOpSettings::activate() { } void KisPaintOpSettings::changePaintOpSize(qreal x, qreal y) { if (!d->settingsWidget.isNull()) { d->settingsWidget.data()->changePaintOpSize(x, y); d->settingsWidget.data()->writeConfiguration(this); } } QSizeF KisPaintOpSettings::paintOpSize() const { if (!d->settingsWidget.isNull()) { return d->settingsWidget.data()->paintOpSize(); } return QSizeF(1.0, 1.0); } void KisPaintOpSettings::setPaintOpOpacity(qreal value) { setProperty("OpacityValue", value); } void KisPaintOpSettings::setPaintOpFlow(qreal value) { setProperty("FlowValue", value); } void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value) { setProperty("CompositeOp", value); } qreal KisPaintOpSettings::paintOpOpacity() const { return getDouble("OpacityValue", 1.0); } qreal KisPaintOpSettings::paintOpFlow() const { return getDouble("FlowValue", 1.0); } QString KisPaintOpSettings::paintOpCompositeOp() const { return getString("CompositeOp", COMPOSITE_OVER); } void KisPaintOpSettings::setEraserMode(bool value) { setProperty("EraserMode", value); } bool KisPaintOpSettings::eraserMode() const { return getBool("EraserMode", false); } QString KisPaintOpSettings::effectivePaintOpCompositeOp() const { return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE; } qreal KisPaintOpSettings::savedEraserSize() const { return getDouble("SavedEraserSize", 0.0); } void KisPaintOpSettings::setSavedEraserSize(qreal value) { setProperty("SavedEraserSize", value); setPropertyNotSaved("SavedEraserSize"); } qreal KisPaintOpSettings::savedBrushSize() const { return getDouble("SavedBrushSize", 0.0); } void KisPaintOpSettings::setSavedBrushSize(qreal value) { setProperty("SavedBrushSize", value); setPropertyNotSaved("SavedBrushSize"); } QString KisPaintOpSettings::modelName() const { return d->modelName; } void KisPaintOpSettings::setModelName(const QString & modelName) { d->modelName = modelName; } KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const { if (d->settingsWidget.isNull()) return 0; return d->settingsWidget.data(); } bool KisPaintOpSettings::isValid() const { return true; } bool KisPaintOpSettings::isLoadable() { return isValid(); } QString KisPaintOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_ALPHA_DARKEN; } QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { path = ellipseOutline(10, 10, 1.0, 0); if (mode == CursorTiltOutline) { - QPainterPath tiltLine; - QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,3.0)); - tiltAngle.setLength(50.0 * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-2.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+2.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); - path.addPath(tiltLine); + path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0)); } path.translate(info.pos()); } return path; } -QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) const +QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) { QPainterPath path; QRectF ellipse(0, 0, width * scale, height * scale); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); QTransform m; m.reset(); m.rotate(rotation); path = m.map(path); return path; } +QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info, + QPointF const& start, qreal maxLength, qreal angle) +{ + if (maxLength == 0.0) maxLength = 50.0; + maxLength = qMax(maxLength, 50.0); + qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true)); + qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0); + + QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle); + guideLine.translate(start); + QPainterPath ret; + ret.moveTo(guideLine.p1()); + ret.lineTo(guideLine.p2()); + guideLine.setAngle(baseAngle - angle); + ret.lineTo(guideLine.p2()); + ret.lineTo(guideLine.p1()); + return ret; +} + void KisPaintOpSettings::setCanvasRotation(qreal angle) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasRotation", angle); setPropertyNotSaved("runtimeCanvasRotation"); } void KisPaintOpSettings::setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored) { Private::DirtyNotificationsLocker locker(d.data()); setProperty("runtimeCanvasMirroredX", xAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredX"); setProperty("runtimeCanvasMirroredY", yAxisMirrored); setPropertyNotSaved("runtimeCanvasMirroredY"); } void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value) { if (value != KisPropertiesConfiguration::getProperty(name) && !d->disableDirtyNotifications && this->preset()) { this->preset()->setPresetDirty(true); } KisPropertiesConfiguration::setProperty(name, value); onPropertyChanged(); } void KisPaintOpSettings::onPropertyChanged() { } bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfiguration *config) { return config->getBool("lodUserAllowed", true); } void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfiguration *config, bool value) { config->setProperty("lodUserAllowed", value); } diff --git a/libs/image/brushengine/kis_paintop_settings.h b/libs/image/brushengine/kis_paintop_settings.h index 46d1a630b6..bcbc48f4a2 100644 --- a/libs/image/brushengine/kis_paintop_settings.h +++ b/libs/image/brushengine/kis_paintop_settings.h @@ -1,265 +1,276 @@ /* * Copyright (c) 2007 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_PAINTOP_SETTINGS_H_ #define KIS_PAINTOP_SETTINGS_H_ #include "kis_types.h" #include "kritaimage_export.h" #include #include #include "kis_shared.h" #include "kis_properties_configuration.h" #include class KisPaintOpConfigWidget; /** * This class is used to cache the settings for a paintop * between two creations. There is one KisPaintOpSettings per input device (mouse, tablet, * etc...). * * The settings may be stored in a preset or a recorded brush stroke. Note that if your * paintop's settings subclass has data that is not stored as a property, that data is not * saved and restored. * * The object also contains a pointer to its parent KisPaintOpPreset object.This is to control the DirtyPreset * property of KisPaintOpPreset. Whenever the settings are changed/modified from the original -- the preset is * set to dirty. */ class KRITAIMAGE_EXPORT KisPaintOpSettings : public KisPropertiesConfiguration, public KisShared { public: KisPaintOpSettings(); virtual ~KisPaintOpSettings(); /** * */ virtual void setOptionsWidget(KisPaintOpConfigWidget* widget); /** * This function is called by a tool when the mouse is pressed. It's useful if * the paintop needs mouse interaction for instance in the case of the clone op. * If the tool is supposed to ignore the event, the paint op should return false * and if the tool is supposed to use the event, return true. */ virtual bool mousePressEvent(const KisPaintInformation &pos, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode); /** * This function is called to set random offsets to the brush whenever the mouse is clicked. It is * specific to when the pattern option is set. * */ virtual void setRandomOffset(); /** * Clone the current settings object. Override this if your settings instance doesn't * store everything as properties. */ virtual KisPaintOpSettingsSP clone() const; /** * @return the node the paintop is working on. */ KisNodeSP node() const; /** * Call this function when the paint op is selected or the tool is activated */ virtual void activate(); /** * XXX: Remove this after 2.0, when the paint operation (incremental/non incremental) will * be completely handled in the paintop, not in the tool. This is a filthy hack to move * the option to the right place, at least. * @return true if we paint incrementally, false if we paint like Photoshop. By default, paintops * do not support non-incremental. */ virtual bool paintIncremental() { return true; } /** * @return the composite op it to which the indirect painting device * should be initialized to. This is used by clone op to reset * the composite op to COMPOSITE_COPY */ virtual QString indirectPaintingCompositeOp() const; /** * Whether this paintop wants to deposit paint even when not moving, i.e. the * tool needs to activate its timer. */ virtual bool isAirbrushing() const { return false; } /** * If this paintop deposit the paint even when not moving, the tool needs to know the rate of it in miliseconds */ virtual int rate() const { return 100; } /** * This enum defines the current mode for painting an outline. */ enum OutlineMode { CursorIsOutline = 1, ///< When this mode is set, an outline is painted around the cursor CursorIsCircleOutline, CursorNoOutline, CursorTiltOutline, CursorColorOutline }; /** * Returns the brush outline in pixel coordinates. Tool is responsible for conversion into view coordinates. * Outline mode has to be passed to the paintop which builds the outline as some paintops have to paint outline * always like clone paintop indicating the duplicate position */ virtual QPainterPath brushOutline(const KisPaintInformation &info, OutlineMode mode) const; /** - * Useful for simple elliptical brush outline. + * Helpers for drawing the brush outline */ - QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation) const; + static QPainterPath ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation); + + /** + * Helper for drawing a triangle representing the tilt of the stylus. + * + * @param start is the offset from the brush's outline's bounding box + * @param lengthScale is used for deciding the size of the triangle. + * Brush diameter or width are common choices for this. + * @param angle is the angle between the two sides of the triangle. + */ + static QPainterPath makeTiltIndicator(KisPaintInformation const& info, + QPointF const& start, qreal lengthScale, qreal angle); /** * The behaviour might be different per paintop. Most of the time * the brush diameter is increased by x pixels, y ignored * * @param x is add to the diameter or radius (according the paintop) * It might be also negative, to decrease the value of the brush diameter/radius. * x is in pixels * @param y is unused, it supposed to be used to change some different attribute * of the brush like softness or density */ virtual void changePaintOpSize(qreal x, qreal y); /** * @return The width and the height of the brush mask/dab in pixels */ virtual QSizeF paintOpSize() const; /** * Set paintop opacity directly in the properties */ void setPaintOpOpacity(qreal value); /** * Set paintop flow directly in the properties */ void setPaintOpFlow(qreal value); /** * Set paintop composite mode directly in the properties */ void setPaintOpCompositeOp(const QString &value); /** * @return opacity saved in the properties */ qreal paintOpOpacity() const; /** * @return flow saved in the properties */ qreal paintOpFlow() const; /** * @return composite mode saved in the properties */ QString paintOpCompositeOp() const; void setEraserMode(bool value); bool eraserMode() const; qreal savedEraserSize() const; void setSavedEraserSize(qreal value); qreal savedBrushSize() const; void setSavedBrushSize(qreal value); QString effectivePaintOpCompositeOp() const; void setPreset(KisPaintOpPresetWSP preset); KisPaintOpPresetWSP preset() const; /** * @return filename of the 3D brush model, empty if no brush is set */ virtual QString modelName() const; /** * Set filename of 3D brush model. By default no brush is set */ void setModelName(const QString & modelName); /// Check if the settings are valid, setting might be invalid through missing brushes etc /// Overwrite if the settings of a paintop can be invalid /// @return state of the settings, default implementation is true virtual bool isValid() const; /// Check if the settings are loadable, that might the case if we can fallback to something /// Overwrite if the settings can do some kind of fallback /// @return loadable state of the settings, by default implementation return the same as isValid() virtual bool isLoadable(); /** * These methods are populating properties with runtime * information about canvas rotation/mirroring. This information * is set directly by KisToolFreehand. Later the data is accessed * by the pressure options to make a final decision. */ void setCanvasRotation(qreal angle); void setCanvasMirroring(bool xAxisMirrored, bool yAxisMirrored); /** * Overrides the method in KisPropertiesCofiguration to allow * onPropertyChanged() callback */ void setProperty(const QString & name, const QVariant & value); static bool isLodUserAllowed(const KisPropertiesConfiguration *config); static void setLodUserAllowed(KisPropertiesConfiguration *config, bool value); protected: /** * @return the option widget of the paintop (can be 0 is no option widgets is set) */ KisPaintOpConfigWidget* optionsWidget() const; /** * The callback is called every time when a property changes */ virtual void onPropertyChanged(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/libs/image/kis_base_mask_generator.cpp b/libs/image/kis_base_mask_generator.cpp index 13821749eb..14f8adb904 100644 --- a/libs/image/kis_base_mask_generator.cpp +++ b/libs/image/kis_base_mask_generator.cpp @@ -1,332 +1,332 @@ /* * Copyright (c) 2004,2007-2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2011 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 //vc.h must come first #include "kis_brush_mask_applicator_factories.h" #include "kis_mask_generator.h" #include "kis_brush_mask_applicator_base.h" #include #include "kis_fast_math.h" #include #include "kis_circle_mask_generator.h" #include "kis_rect_mask_generator.h" #include "kis_gauss_circle_mask_generator.h" #include "kis_gauss_rect_mask_generator.h" #include "kis_cubic_curve.h" #include "kis_curve_circle_mask_generator.h" #include "kis_curve_rect_mask_generator.h" #include struct KisMaskGenerator::Private { Private() : diameter(1.0), ratio(1.0), softness(1.0), fh(1.0), fv(1.0), cs(0.0), ss(0.0), cachedSpikesAngle(0.0), spikes(2), empty(true), antialiasEdges(false), type(CIRCLE), scaleX(1.0), scaleY(1.0) { } Private(const Private &rhs) : diameter(rhs.diameter), ratio(rhs.ratio), softness(rhs.softness), fh(rhs.fh), fv(rhs.fv), cs(rhs.cs), ss(rhs.ss), cachedSpikesAngle(rhs.cachedSpikesAngle), spikes(rhs.spikes), empty(rhs.empty), antialiasEdges(rhs.antialiasEdges), type(rhs.type), curveString(rhs.curveString), scaleX(rhs.scaleX), scaleY(rhs.scaleY) { } qreal diameter, ratio; qreal softness; qreal fh, fv; qreal cs, ss; qreal cachedSpikesAngle; int spikes; bool empty; bool antialiasEdges; Type type; QString curveString; qreal scaleX; qreal scaleY; QScopedPointer defaultMaskProcessor; }; KisMaskGenerator::KisMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges, Type type, const KoID& id) : d(new Private), m_id(id) { d->diameter = diameter; d->ratio = ratio; d->fh = 0.5 * fh; d->fv = 0.5 * fv; d->softness = 1.0; // by default don't change fade/softness/hardness d->spikes = spikes; d->cachedSpikesAngle = M_PI / d->spikes; d->type = type; d->antialiasEdges = antialiasEdges; d->scaleX = 1.0; d->scaleY = 1.0; init(); } KisMaskGenerator::~KisMaskGenerator() { } KisMaskGenerator::KisMaskGenerator(const KisMaskGenerator &rhs) : d(new Private(*rhs.d)), m_id(rhs.m_id) { } void KisMaskGenerator::init() { d->cs = cos(- 2 * M_PI / d->spikes); d->ss = sin(- 2 * M_PI / d->spikes); d->empty = (d->ratio == 0.0 || d->diameter == 0.0); } bool KisMaskGenerator::shouldSupersample() const { return false; } bool KisMaskGenerator::shouldVectorize() const { return false; } bool KisMaskGenerator::isEmpty() const { return d->empty; } KisBrushMaskApplicatorBase* KisMaskGenerator::applicator() { if (!d->defaultMaskProcessor) { d->defaultMaskProcessor.reset( createOptimizedClass >(this)); } return d->defaultMaskProcessor.data(); } void KisMaskGenerator::toXML(QDomDocument& doc, QDomElement& e) const { Q_UNUSED(doc); //e.setAttribute("radius", d->radius); e.setAttribute("diameter", QString::number(d->diameter)); e.setAttribute("ratio", QString::number(d->ratio)); e.setAttribute("hfade", QString::number(horizontalFade())); e.setAttribute("vfade", QString::number(verticalFade())); e.setAttribute("spikes", d->spikes); e.setAttribute("type", d->type == CIRCLE ? "circle" : "rect"); e.setAttribute("antialiasEdges", d->antialiasEdges); e.setAttribute("id", id()); } KisMaskGenerator* KisMaskGenerator::fromXML(const QDomElement& elt) { double diameter = 1.0; // backward compatibility -- it was mistakenly named radius for 2.2 if (elt.hasAttribute("radius")){ diameter = KisDomUtils::toDouble(elt.attribute("radius", "1.0")); } else /*if (elt.hasAttribute("diameter"))*/{ diameter = KisDomUtils::toDouble(elt.attribute("diameter", "1.0")); } double ratio = KisDomUtils::toDouble(elt.attribute("ratio", "1.0")); double hfade = KisDomUtils::toDouble(elt.attribute("hfade", "0.0")); double vfade = KisDomUtils::toDouble(elt.attribute("vfade", "0.0")); int spikes = elt.attribute("spikes", "2").toInt(); QString typeShape = elt.attribute("type", "circle"); QString id = elt.attribute("id", DefaultId.id()); bool antialiasEdges = elt.attribute("antialiasEdges", "0").toInt(); if (id == DefaultId.id()) { if (typeShape == "circle") { return new KisCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges); } else { return new KisRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges); } } if (id == SoftId.id()) { KisCubicCurve curve; curve.fromString(elt.attribute("softness_curve","0,0;1,1")); if (typeShape == "circle") { return new KisCurveCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, curve, antialiasEdges); } else { return new KisCurveRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, curve, antialiasEdges); } } if (id == GaussId.id()) { if (typeShape == "circle") { return new KisGaussCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges); } else { return new KisGaussRectangleMaskGenerator(diameter, ratio, hfade, vfade, spikes, antialiasEdges); } } // if unknown return new KisCircleMaskGenerator(diameter, ratio, hfade, vfade, spikes, true); } qreal KisMaskGenerator::width() const { return d->diameter; } qreal KisMaskGenerator::height() const { if (d->spikes == 2) { return d->diameter * d->ratio; } return d->diameter; } qreal KisMaskGenerator::effectiveSrcWidth() const { return d->diameter * d->scaleX; } qreal KisMaskGenerator::effectiveSrcHeight() const { /** * This height is related to the source of the brush mask, so we * don't take spikes into account, they will be generated from * this data. */ - return d->diameter * d->ratio * d->scaleX; + return d->diameter * d->ratio * d->scaleY; } qreal KisMaskGenerator::diameter() const { return d->diameter; } qreal KisMaskGenerator::ratio() const { return d->ratio; } qreal KisMaskGenerator::softness() const { return d->softness; } void KisMaskGenerator::setSoftness(qreal softness) { d->softness = softness; } qreal KisMaskGenerator::horizontalFade() const { return 2.0 * d->fh; // 'cause in init we divide it again } qreal KisMaskGenerator::verticalFade() const { return 2.0 * d->fv; // 'cause in init we divide it again } int KisMaskGenerator::spikes() const { return d->spikes; } KisMaskGenerator::Type KisMaskGenerator::type() const { return d->type; } QList< KoID > KisMaskGenerator::maskGeneratorIds() { QList ids; ids << DefaultId << SoftId << GaussId; return ids; } QString KisMaskGenerator::curveString() const { return d->curveString; } void KisMaskGenerator::setCurveString(const QString& curveString) { d->curveString = curveString; } bool KisMaskGenerator::antialiasEdges() const { return d->antialiasEdges; } void KisMaskGenerator::setScale(qreal scaleX, qreal scaleY) { d->scaleX = scaleX; d->scaleY = scaleY; } void KisMaskGenerator::fixRotation(qreal &xr, qreal &yr) const { if (d->spikes > 2) { double angle = (KisFastMath::atan2(yr, xr)); while (angle > d->cachedSpikesAngle){ double sx = xr; double sy = yr; xr = d->cs * sx - d->ss * sy; yr = d->ss * sx + d->cs * sy; angle -= 2 * d->cachedSpikesAngle; } } } diff --git a/libs/image/kis_distance_information.h b/libs/image/kis_distance_information.h index 2cfeccdc62..461a284fb8 100644 --- a/libs/image/kis_distance_information.h +++ b/libs/image/kis_distance_information.h @@ -1,132 +1,128 @@ /* * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2013 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_DISTANCE_INFORMATION_H_ #define _KIS_DISTANCE_INFORMATION_H_ #include #include #include "kritaimage_export.h" class KisPaintInformation; /** * This structure contains information about the desired spacing * requested by the paintAt call */ class KisSpacingInformation { public: explicit KisSpacingInformation() : m_spacing(0.0, 0.0) - , m_isIsotropic(true) , m_rotation(0.0) { } explicit KisSpacingInformation(qreal isotropicSpacing) : m_spacing(isotropicSpacing, isotropicSpacing) - , m_isIsotropic(true) , m_rotation(0.0) { } explicit KisSpacingInformation(const QPointF &anisotropicSpacing, qreal rotation) : m_spacing(anisotropicSpacing) - , m_isIsotropic(anisotropicSpacing.x() == anisotropicSpacing.y()) , m_rotation(rotation) { } inline QPointF spacing() const { return m_spacing; } inline bool isIsotropic() const { - return m_isIsotropic; + return m_spacing.x() == m_spacing.y(); } inline qreal scalarApprox() const { - return m_isIsotropic ? m_spacing.x() : QVector2D(m_spacing).length(); + return isIsotropic() ? m_spacing.x() : QVector2D(m_spacing).length(); } inline qreal rotation() const { return m_rotation; } private: QPointF m_spacing; - bool m_isIsotropic; qreal m_rotation; }; /** * This structure is used as return value of paintLine to contain * information that is needed to be passed for the next call. */ class KRITAIMAGE_EXPORT KisDistanceInformation { public: KisDistanceInformation(); KisDistanceInformation(const QPointF &lastPosition, qreal lastTime); KisDistanceInformation(const KisDistanceInformation &rhs); KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail); KisDistanceInformation& operator=(const KisDistanceInformation &rhs); ~KisDistanceInformation(); const KisSpacingInformation& currentSpacing() const; bool hasLastDabInformation() const; QPointF lastPosition() const; qreal lastTime() const; qreal lastDrawingAngle() const; bool hasLastPaintInformation() const; const KisPaintInformation& lastPaintInformation() const; void registerPaintedDab(const KisPaintInformation &info, const KisSpacingInformation &spacing); qreal getNextPointPosition(const QPointF &start, const QPointF &end); /** * \return true if at least one dab has been painted with this * distance information */ bool isStarted() const; bool hasLockedDrawingAngle() const; qreal lockedDrawingAngle() const; void setLockedDrawingAngle(qreal angle); qreal scalarDistanceApprox() const; void overrideLastValues(const QPointF &lastPosition, qreal lastTime); private: qreal getNextPointPositionIsotropic(const QPointF &start, const QPointF &end); qreal getNextPointPositionAnisotropic(const QPointF &start, const QPointF &end); private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/kis_gauss_circle_mask_generator.cpp b/libs/image/kis_gauss_circle_mask_generator.cpp index 3af92fc085..50977c95f6 100644 --- a/libs/image/kis_gauss_circle_mask_generator.cpp +++ b/libs/image/kis_gauss_circle_mask_generator.cpp @@ -1,126 +1,127 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2011 Geoffry Song * * 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 #include #include #include #include #include "kis_fast_math.h" #include "kis_base_mask_generator.h" #include "kis_gauss_circle_mask_generator.h" #include "kis_antialiasing_fade_maker.h" #define M_SQRT_2 1.41421356237309504880 #ifdef Q_OS_WIN // on windows we get our erf() from boost #include #define erf(x) boost::math::erf(x) #endif struct Q_DECL_HIDDEN KisGaussCircleMaskGenerator::Private { Private(bool enableAntialiasing) : fadeMaker(*this, enableAntialiasing) { } Private(const Private &rhs) : ycoef(rhs.ycoef), fade(rhs.fade), center(rhs.center), distfactor(rhs.distfactor), alphafactor(rhs.alphafactor), fadeMaker(rhs.fadeMaker, *this) { } qreal ycoef; qreal fade; qreal center, distfactor, alphafactor; KisAntialiasingFadeMaker1D fadeMaker; inline quint8 value(qreal dist) const; }; KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(qreal diameter, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(diameter, ratio, fh, fv, spikes, antialiasEdges, CIRCLE, GaussId), d(new Private(antialiasEdges)) { d->ycoef = 1.0 / ratio; d->fade = 1.0 - (fh + fv) / 2.0; if (d->fade == 0.0) d->fade = 1e-6; else if (d->fade == 1.0) d->fade = 1.0 - 1e-6; // would become undefined for fade == 0 or 1 d->center = (2.5 * (6761.0*d->fade-10000.0))/(M_SQRT_2*6761.0*d->fade); d->alphafactor = 255.0 / (2.0 * erf(d->center)); } KisGaussCircleMaskGenerator::KisGaussCircleMaskGenerator(const KisGaussCircleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { } KisMaskGenerator* KisGaussCircleMaskGenerator::clone() const { return new KisGaussCircleMaskGenerator(*this); } void KisGaussCircleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); + d->ycoef = scaleX / (scaleY * ratio()); d->distfactor = M_SQRT_2 * 12500.0 / (6761.0 * d->fade * effectiveSrcWidth() / 2.0); d->fadeMaker.setRadius(0.5 * effectiveSrcWidth()); } KisGaussCircleMaskGenerator::~KisGaussCircleMaskGenerator() { } inline quint8 KisGaussCircleMaskGenerator::Private::value(qreal dist) const { dist *= distfactor; quint8 ret = alphafactor * (erf(dist + center) - erf(dist - center)); return (quint8) 255 - ret; } quint8 KisGaussCircleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = x; qreal yr = qAbs(y); fixRotation(xr, yr); qreal dist = sqrt(norme(xr, yr * d->ycoef)); quint8 value; if (d->fadeMaker.needFade(dist, &value)) { return value; } return d->value(dist); } diff --git a/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp b/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp index f6cfd67232..8f2f60d11b 100644 --- a/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp +++ b/plugins/paintops/chalk/kis_chalk_paintop_settings.cpp @@ -1,69 +1,59 @@ /* * Copyright (c) 2008 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_chalk_paintop_settings.h" #include #include #include KisChalkPaintOpSettings::KisChalkPaintOpSettings() { } bool KisChalkPaintOpSettings::paintIncremental() { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } bool KisChalkPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED); } int KisChalkPaintOpSettings::rate() const { return getInt(AIRBRUSH_RATE); } QPainterPath KisChalkPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { qreal size = getInt(CHALK_RADIUS) * 2 + 1; path = ellipseOutline(size, size, 1.0, 0.0); - QPainterPath tiltLine; - QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,size)); - tiltAngle.setLength(qMax(size*qreal(0.5), qreal(50.0)) * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); - if (mode == CursorTiltOutline) { - path.addPath(tiltLine); + path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), size * 0.5, 3.0)); } path.translate(info.pos()); } return path; } diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp index ae45a1bdda..74d0dd82ac 100644 --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp @@ -1,289 +1,287 @@ /* * Copyright (C) 2011 Silvio Heinrich * * 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_colorsmudgeop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisColorSmudgeOp::KisColorSmudgeOp(const KisBrushBasedPaintOpSettings* settings, KisPainter* painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_firstRun(true) , m_image(image) , m_tempDev(painter->device()->createCompositionSourceDevice()) , m_backgroundPainter(new KisPainter(m_tempDev)) , m_smudgePainter(new KisPainter(m_tempDev)) , m_colorRatePainter(new KisPainter(m_tempDev)) , m_smudgeRateOption() , m_colorRateOption("ColorRate", KisPaintOpOption::GENERAL, false) , m_smudgeRadiusOption() { Q_UNUSED(node); Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_smudgeRateOption.readOptionSetting(settings); m_colorRateOption.readOptionSetting(settings); m_smudgeRadiusOption.readOptionSetting(settings); m_overlayModeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_gradientOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_smudgeRateOption.resetAllSensors(); m_colorRateOption.resetAllSensors(); m_smudgeRadiusOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_gradientOption.resetAllSensors(); m_gradient = painter->gradient(); m_backgroundPainter->setCompositeOp(COMPOSITE_COPY); // Smudge Painter works in default COMPOSITE_OVER mode m_colorRatePainter->setCompositeOp(painter->compositeOp()->id()); m_rotationOption.applyFanCornersInfo(this); } KisColorSmudgeOp::~KisColorSmudgeOp() { delete m_backgroundPainter; delete m_colorRatePainter; delete m_smudgePainter; } void KisColorSmudgeOp::updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint) { static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); m_maskDab = m_dabCache->fetchDab(cs, color, cursorPoint, - scale, scale, - rotation, + KisDabShape(scale, 1.0, rotation), info, 1.0, &m_dstDabRect); // sanity check KIS_ASSERT_RECOVER_NOOP(m_dstDabRect.size() == m_maskDab->bounds().size()); } inline void KisColorSmudgeOp::getTopLeftAligned(const QPointF &pos, const QPointF &hotSpot, qint32 *x, qint32 *y) { QPointF topLeft = pos - hotSpot; qreal xFraction, yFraction; // will not be used splitCoordinate(topLeft.x(), x, &xFraction); splitCoordinate(topLeft.y(), y, &yFraction); } KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info) { KisBrushSP brush = m_brush; // Simple error catching if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { /** * Disable handling of the subpixel precision. In the smudge op we * should read from the aligned areas of the image, so having * additional internal offsets, created by the subpixel precision, * will worsen the quality (at least because * QRectF(m_dstDabRect).center() will not point to the real center * of the brush anymore). * Of course, this only really matters with smearing_mode (bug:327235), * and you only notice the lack of subpixel precision in the dulling methods. */ m_dabCache->disableSubpixelPrecision(); } #if 0 //if precision KoColor colorSpaceChanger = painter()->paintColor(); const KoColorSpace* preciseColorSpace = colorSpaceChanger.colorSpace(); if (colorSpaceChanger.colorSpace()->colorDepthId().id() == "U8") { preciseColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceChanger.colorSpace()->colorModelId().id(), "U16", colorSpaceChanger.profile() ); colorSpaceChanger.convertTo(preciseColorSpace); } painter()->setPaintColor(colorSpaceChanger); #endif // get the scaling factor calculated by the size option qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(info); - if (checkSizeTooSmall(scale)) return KisSpacingInformation(); - + KisDabShape shape(scale, 1.0, rotation); QPointF scatteredPos = m_scatterOption.apply(info, - brush->maskWidth(scale, rotation, 0, 0, info), - brush->maskHeight(scale, rotation, 0, 0, info)); + brush->maskWidth(shape, 0, 0, info), + brush->maskHeight(shape, 0, 0, info)); - QPointF hotSpot = brush->hotSpot(scale, scale, rotation, info); + QPointF hotSpot = brush->hotSpot(shape, info); /** * Update the brush mask. * * Upon leaving the function: * o m_maskDab stores the new mask * o m_maskBounds stores the extents of the mask paint device * o m_dstDabRect stores the destination rect where the mask is going * to be written to */ updateMask(info, scale, rotation, scatteredPos); QPointF newCenterPos = QRectF(m_dstDabRect).center(); /** * Save the center of the current dab to know where to read the * data during the next pass. We do not save scatteredPos here, * because it may differ slightly from the real center of the * brush (due to rounding effects), which will result in a * really weird quality. */ QRect srcDabRect = m_dstDabRect.translated((m_lastPaintPos - newCenterPos).toPoint()); m_lastPaintPos = newCenterPos; KisSpacingInformation spacingInfo = effectiveSpacing(scale, rotation, m_spacingOption, info); if (m_firstRun) { m_firstRun = false; return spacingInfo; } // save the old opacity value and composite mode quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); qreal fpOpacity = (qreal(oldOpacity) / 255.0) * m_opacityOption.getOpacityf(info); if (m_image && m_overlayModeOption.isChecked()) { m_image->blockUpdates(); m_backgroundPainter->bitBlt(QPoint(), m_image->projection(), srcDabRect); m_image->unblockUpdates(); } else { // IMPORTANT: clear the temporary painting device to color black with zero opacity: // it will only clear the extents of the brush. m_tempDev->clear(QRect(QPoint(), m_dstDabRect.size())); } if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) { m_smudgePainter->bitBlt(QPoint(), painter()->device(), srcDabRect); } else { QPoint pt = (srcDabRect.topLeft() + hotSpot).toPoint(); if (m_smudgeRadiusOption.isChecked()) { qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height()); m_smudgeRadiusOption.apply(*m_smudgePainter, info, effectiveSize, pt.x(), pt.y(), painter()->device()); KoColor color2 = m_smudgePainter->paintColor(); m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color2); } else { KoColor color = painter()->paintColor(); // get the pixel on the canvas that lies beneath the hot spot // of the dab and fill the temporary paint device with that color KisCrossDeviceColorPickerInt colorPicker(painter()->device(), color); colorPicker.pickColor(pt.x(), pt.y(), color.data()); m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); } } // if the user selected the color smudge option, // we will mix some color into the temporary painting device (m_tempDev) if (m_colorRateOption.isChecked()) { // this will apply the opacity (selected by the user) to copyPainter // (but fit the rate inbetween the range 0.0 to (1.0-SmudgeRate)) qreal maxColorRate = qMax(1.0 - m_smudgeRateOption.getRate(), 0.2); m_colorRateOption.apply(*m_colorRatePainter, info, 0.0, maxColorRate, fpOpacity); // paint a rectangle with the current color (foreground color) // or a gradient color (if enabled) // into the temporary painting device and use the user selected // composite mode KoColor color = painter()->paintColor(); m_gradientOption.apply(color, m_gradient, info); m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); } // if color is disabled (only smudge) and "overlay mode" is enabled // then first blit the region under the brush from the image projection // to the painting device to prevent a rapid build up of alpha value // if the color to be smudged is semi transparent. if (m_image && m_overlayModeOption.isChecked() && !m_colorRateOption.isChecked()) { painter()->setCompositeOp(COMPOSITE_COPY); painter()->setOpacity(OPACITY_OPAQUE_U8); m_image->blockUpdates(); painter()->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); m_image->unblockUpdates(); } // set opacity calculated by the rate option m_smudgeRateOption.apply(*painter(), info, 0.0, 1.0, fpOpacity); // then blit the temporary painting device on the canvas at the current brush position // the alpha mask (maskDab) will be used here to only blit the pixels that are in the area (shape) of the brush painter()->setCompositeOp(COMPOSITE_COPY); painter()->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacy and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); return spacingInfo; } diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp index 40334edefd..3ab6f688a7 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -1,189 +1,191 @@ /* * 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 #include KisBrushOp::KisBrushOp(const KisBrushBasedPaintOpSettings *settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter), m_opacityOption(node), m_hsvTransformation(0) { Q_UNUSED(image); Q_ASSERT(settings); KisColorSourceOption colorSourceOption; colorSourceOption.readOptionSetting(settings); m_colorSource = colorSourceOption.createColorSource(painter); m_hsvOptions.append(KisPressureHSVOption::createHueOption()); m_hsvOptions.append(KisPressureHSVOption::createSaturationOption()); m_hsvOptions.append(KisPressureHSVOption::createValueOption()); Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) { option->readOptionSetting(settings); option->resetAllSensors(); if (option->isChecked() && !m_hsvTransformation) { m_hsvTransformation = painter->backgroundColor().colorSpace()->createColorTransformation("hsv_adjustment", QHash()); } } m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); + m_ratioOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_darkenOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_mixOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_sizeOption.resetAllSensors(); + m_ratioOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_darkenOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisBrushOp::~KisBrushOp() { qDeleteAll(m_hsvOptions); delete m_colorSource; delete m_hsvTransformation; } 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); KisPaintDeviceSP device = painter()->device(); - qreal rotation = m_rotationOption.apply(info); - + KisDabShape shape(scale, ratio, rotation); QPointF cursorPos = m_scatterOption.apply(info, - brush->maskWidth(scale, rotation, 0, 0, info), - brush->maskHeight(scale, rotation, 0, 0, info)); + brush->maskWidth(shape, 0, 0, info), + brush->maskHeight(shape, 0, 0, info)); quint8 origOpacity = painter()->opacity(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); m_colorSource->selectColor(m_mixOption.apply(info), info); m_darkenOption.apply(m_colorSource, info); if (m_hsvTransformation) { Q_FOREACH (KisPressureHSVOption * option, m_hsvOptions) { option->apply(m_hsvTransformation, info); } m_colorSource->applyColorTransformation(m_hsvTransformation); } QRect dabRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(device->compositionSourceColorSpace(), m_colorSource, cursorPos, - scale, scale, - rotation, + shape, info, m_softnessOption.apply(info), &dabRect); // sanity check for the size calculation code if (dab->bounds().size() != dabRect.size()) { warnKrita << "KisBrushOp: dab bounds is not dab rect. See bug 327156" << dab->bounds().size() << dabRect.size(); } painter()->bltFixed(dabRect.topLeft(), dab, dab->bounds()); painter()->renderMirrorMaskSafe(dabRect, dab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale, rotation, m_spacingOption, 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 05278a441f..44b7340b16 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h @@ -1,77 +1,78 @@ /* * 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 - class KisBrushBasedPaintOpSettings; class KisPainter; class KisColorSource; class KisBrushOp : public KisBrushBasedPaintOp { public: KisBrushOp(const KisBrushBasedPaintOpSettings *settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisBrushOp(); KisSpacingInformation paintAt(const KisPaintInformation& info); void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance); private: KisColorSource *m_colorSource; KisPressureSizeOption m_sizeOption; + KisPressureRatioOption m_ratioOption; KisPressureSpacingOption m_spacingOption; KisPressureFlowOption m_flowOption; KisFlowOpacityOption m_opacityOption; KisPressureSoftnessOption m_softnessOption; KisPressureSharpnessOption m_sharpnessOption; KisPressureDarkenOption m_darkenOption; KisPressureRotationOption m_rotationOption; KisPressureMixOption m_mixOption; KisPressureScatterOption m_scatterOption; QList m_hsvOptions; KoColorTransformation *m_hsvTransformation; KisPaintDeviceSP m_lineCacheDevice; KisPaintDeviceSP m_colorSourceDevice; }; #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 ffe1a4ddd3..0443d33a97 100644 --- a/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop_settings_widget.cpp @@ -1,95 +1,97 @@ /* * 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 "kis_texture_option.h" #include "kis_curve_option_widget.h" #include #include "kis_pressure_texture_strength_option.h" KisBrushOpSettingsWidget::KisBrushOpSettingsWidget(QWidget* parent) : KisBrushBasedPaintopOptionWidget(parent) { setObjectName("brush option widget"); setPrecisionEnabled(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")); 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()), i18n("Value")); addPaintOpOption(new KisAirbrushOption(false), i18n("Airbrush")); addPaintOpOption(new KisPaintActionTypeOption(), i18n("Painting Mode")); addPaintOpOption(new KisTextureOption(), i18n("Pattern")); addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength")); } KisBrushOpSettingsWidget::~KisBrushOpSettingsWidget() { } KisPropertiesConfiguration* KisBrushOpSettingsWidget::configuration() const { KisBrushBasedPaintOpSettings *config = new KisBrushBasedPaintOpSettings(); config->setOptionsWidget(const_cast(this)); config->setProperty("paintop", "paintbrush"); // XXX: make this a const id string writeConfiguration(config); return config; } diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp index e515c173a1..3fc43ad72a 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp @@ -1,282 +1,283 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004,2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_duplicateop.h" #include "kis_duplicateop_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_duplicateop_settings.h" #include "kis_duplicateop_settings_widget.h" #include "kis_duplicateop_option.h" KisDuplicateOp::KisDuplicateOp(const KisDuplicateOpSettings *settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) , m_node(node) , m_settings(settings) { Q_ASSERT(settings); Q_ASSERT(painter); m_sizeOption.readOptionSetting(settings); m_healing = settings->getBool(DUPLICATE_HEALING); m_perspectiveCorrection = settings->getBool(DUPLICATE_CORRECT_PERSPECTIVE); m_moveSourcePoint = settings->getBool(DUPLICATE_MOVE_SOURCE_POINT); m_cloneFromProjection = settings->getBool(DUPLICATE_CLONE_FROM_PROJECTION); m_srcdev = source()->createCompositionSourceDevice(); } KisDuplicateOp::~KisDuplicateOp() { } #define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x))) KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return KisSpacingInformation(1.0); KisBrushSP brush = m_brush; if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); if (!m_duplicateStartIsSet) { m_duplicateStartIsSet = true; m_duplicateStart = info.pos(); } KisPaintDeviceSP realSourceDevice; if (m_cloneFromProjection && m_image) { realSourceDevice = m_image->projection(); } else { KisNodeSP externalSourceNode = m_settings->sourceNode(); /** * The saved layer might have been deleted by then, so check if it * still belongs to a graph */ if (!externalSourceNode || !externalSourceNode->graphListener()) { externalSourceNode = m_node; } realSourceDevice = externalSourceNode->projection(); } qreal scale = m_sizeOption.apply(info); if (checkSizeTooSmall(scale)) return KisSpacingInformation(); + KisDabShape shape(scale, 1.0, 0.0); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), - scale, scale, 0.0, + shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QPoint srcPoint; if (m_moveSourcePoint) { srcPoint = (dstRect.topLeft() - m_settings->offset()).toPoint(); } else { - QPointF hotSpot = brush->hotSpot(scale, scale, 0, info); + QPointF hotSpot = brush->hotSpot(shape, info); srcPoint = (m_settings->position() - hotSpot).toPoint(); } qint32 sw = dstRect.width(); qint32 sh = dstRect.height(); // Perspective correction ? // if (m_perspectiveCorrection && m_image && m_image->perspectiveGrid()->countSubGrids() == 1) { // Matrix3qreal startM = Matrix3qreal::Identity(); // Matrix3qreal endM = Matrix3qreal::Identity(); // // First look for the grid corresponding to the start point // KisSubPerspectiveGrid* subGridStart = *m_image->perspectiveGrid()->begin(); // QRect r = QRect(0, 0, m_image->width(), m_image->height()); // if (subGridStart) { // startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight()); // } // // Second look for the grid corresponding to the end point // KisSubPerspectiveGrid* subGridEnd = *m_image->perspectiveGrid()->begin(); // if (subGridEnd) { // endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r); // } // // Compute the translation in the perspective transformation space: // QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart)); // QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(m_settings->offset())); // QPointF translat = duplicateStartPositionT - positionStartPaintingT; // KisSequentialIterator dstIt(m_srcdev, QRect(0, 0, sw, sh)); // KisRandomSubAccessorSP srcAcc = realSourceDevice->createRandomSubAccessor(); // //Action // do { // QPointF p = KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + dstRect.x(), dstIt.y() + dstRect.y())) + translat); // srcAcc->moveTo(p); // srcAcc->sampledOldRawData(dstIt.rawData()); // } while (dstIt.nextPixel()); // } // else { KisPainter copyPainter(m_srcdev); copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bitBltOldData(0, 0, realSourceDevice, srcPoint.x(), srcPoint.y(), sw, sh); copyPainter.end(); } // heal ? if (m_healing) { QRect healRect(dstRect); const bool smallWidth = healRect.width() < 3; const bool smallHeight = healRect.height() < 3; if (smallWidth || smallHeight) { healRect.adjust(-smallWidth, -smallHeight, smallWidth, smallHeight); } const int healSW = healRect.width(); const int healSH = healRect.height(); quint16 srcData[4]; quint16 tmpData[4]; QScopedArrayPointer matrix(new qreal[ 3 * healSW * healSH ]); // First divide const KoColorSpace* srcCs = realSourceDevice->colorSpace(); const KoColorSpace* tmpCs = m_srcdev->colorSpace(); KisHLineConstIteratorSP srcIt = realSourceDevice->createHLineConstIteratorNG(healRect.x(), healRect.y() , healSW); KisHLineIteratorSP tmpIt = m_srcdev->createHLineIteratorNG(0, 0, healSW); qreal* matrixIt = matrix.data(); for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { srcCs->toLabA16(srcIt->oldRawData(), (quint8*)srcData, 1); tmpCs->toLabA16(tmpIt->rawData(), (quint8*)tmpData, 1); // Division for (int k = 0; k < 3; k++) { matrixIt[k] = srcData[k] / (qreal)qMax((int)tmpData [k], 1); } srcIt->nextPixel(); tmpIt->nextPixel(); matrixIt += 3; } srcIt->nextRow(); tmpIt->nextRow(); } // Minimize energy { int iter = 0; qreal err; QScopedArrayPointer solution(new qreal[ 3 * healSW * healSH ]); do { err = DuplicateOpUtils::minimizeEnergy(matrix.data(), solution.data(), healSW, healSH); solution.swap(matrix); iter++; } while (err > 0.00001 && iter < 100); } // Finally multiply KisHLineIteratorSP tmpIt2 = m_srcdev->createHLineIteratorNG(0, 0, healSW); matrixIt = &matrix[0]; for (int j = 0; j < healSH; j++) { for (int i = 0; i < healSW; i++) { tmpCs->toLabA16(tmpIt2->rawData(), (quint8*)tmpData, 1); // Multiplication for (int k = 0; k < 3; k++) { tmpData[k] = (int)CLAMP(matrixIt[k] * qMax((int) tmpData[k], 1), 0, 65535); } tmpCs->fromLabA16((quint8*)tmpData, tmpIt2->rawData(), 1); tmpIt2->nextPixel(); matrixIt += 3; } tmpIt2->nextRow(); } } painter()->bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_srcdev, dab, dstRect.width(), dstRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale, 0.0); } diff --git a/plugins/paintops/deform/deform_brush.cpp b/plugins/paintops/deform/deform_brush.cpp index aece16ddbd..c25c432754 100644 --- a/plugins/paintops/deform/deform_brush.cpp +++ b/plugins/paintops/deform/deform_brush.cpp @@ -1,296 +1,294 @@ /* * Copyright (c) 2008,2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "deform_brush.h" #include "kis_painter.h" #include "kis_fixed_paint_device.h" #include #include #include #include #include #include #include #include #include const qreal degToRad = M_PI / 180.0; DeformBrush::DeformBrush() { m_firstPaint = false; m_counter = 1; m_deformAction = 0; } DeformBrush::~DeformBrush() { delete m_deformAction; } void DeformBrush::initDeformAction() { DeformModes mode = DeformModes(m_properties->action - 1); switch (mode) { case GROW: case SHRINK: { m_deformAction = new DeformScale(); break; } case SWIRL_CW: case SWIRL_CCW: { m_deformAction = new DeformRotation(); break; } case MOVE: { m_deformAction = new DeformMove(); static_cast(m_deformAction)->setFactor(m_properties->deformAmount); break; } case LENS_IN: case LENS_OUT: { m_deformAction = new DeformLens(); static_cast(m_deformAction)->setLensFactor(m_properties->deformAmount, 0.0); static_cast(m_deformAction)->setMode(mode == LENS_OUT); break; } case DEFORM_COLOR: { m_deformAction = new DeformColor(); static_cast(m_deformAction)->setFactor(m_properties->deformAmount); break; } default: { m_deformAction = new DeformBase(); break; } } } bool DeformBrush::setupAction( DeformModes mode, const QPointF& pos, QTransform const& rotation) { switch (mode) { case GROW: case SHRINK: { // grow or shrink, the sign decide qreal sign = (mode == GROW) ? 1.0 : -1.0; qreal factor; if (m_properties->useCounter) { factor = (1.0 + sign * (m_counter * m_counter / 100.0)); } else { factor = (1.0 + sign * (m_properties->deformAmount)); } dynamic_cast(m_deformAction)->setFactor(factor); break; } case SWIRL_CW: case SWIRL_CCW: { // CW or CCW, the sign decide qreal sign = (mode == SWIRL_CW) ? 1.0 : -1.0; qreal factor; if (m_properties->useCounter) { factor = m_counter * sign * degToRad; } else { factor = (360 * m_properties->deformAmount * 0.5) * sign * degToRad; } dynamic_cast(m_deformAction)->setAlpha(factor); break; } case MOVE: { if (m_firstPaint == false) { m_prevX = pos.x(); m_prevY = pos.y(); static_cast(m_deformAction)->setDistance(0.0, 0.0); m_firstPaint = true; return false; } else { qreal xDistance = pos.x() - m_prevX; qreal yDistance = pos.y() - m_prevY; rotation.map(xDistance, yDistance, &xDistance, &yDistance); static_cast(m_deformAction)->setDistance(xDistance, yDistance); m_prevX = pos.x(); m_prevY = pos.y(); } break; } case LENS_IN: case LENS_OUT: { static_cast(m_deformAction)->setMaxDistance(m_sizeProperties->diameter * 0.5, m_sizeProperties->diameter * 0.5); break; } case DEFORM_COLOR: { // no run-time setup break; } default: { break; } } return true; } KisFixedPaintDeviceSP DeformBrush::paintMask(KisFixedPaintDeviceSP dab, KisPaintDeviceSP layer, qreal scale, qreal rotation, QPointF pos, qreal subPixelX, qreal subPixelY, int dabX, int dabY) { KisFixedPaintDeviceSP mask = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); KisCrossDeviceColorPicker colorPicker(layer, dab); qreal fWidth = maskWidth(scale); qreal fHeight = maskHeight(scale); int dstWidth = qRound(m_maskRect.width()); int dstHeight = qRound(m_maskRect.height()); // clear if (dab->bounds().width() != dstWidth || dab->bounds().height() != dstHeight) { dab->setRect(m_maskRect.toRect()); dab->initialize(); } else { dab->clear(m_maskRect.toRect()); } qreal const centerX = dstWidth * 0.5 + subPixelX; qreal const centerY = dstHeight * 0.5 + subPixelY; qreal const majorAxis = 2.0 / fWidth; qreal const minorAxis = 2.0 / fHeight; qreal distance; QTransform forwardRotationMatrix; forwardRotationMatrix.rotateRadians(-rotation); QTransform reverseRotationMatrix; reverseRotationMatrix.rotateRadians(rotation); // if can't paint, stop if (!setupAction(DeformModes(m_properties->action - 1), pos, forwardRotationMatrix)) { return 0; } mask->setRect(dab->bounds()); mask->initialize(); quint8* maskPointer = mask->data(); qint8 maskPixelSize = mask->pixelSize(); quint8* dabPointer = dab->data(); int dabPixelSize = dab->colorSpace()->pixelSize(); for (int y = 0; y < dstHeight; y++) { for (int x = 0; x < dstWidth; x++) { qreal maskX = x - centerX; qreal maskY = y - centerY; - forwardRotationMatrix.map(maskX, maskY, &maskX, &maskY); - forwardRotationMatrix.map(maskX, maskY, &maskX, &maskY); distance = norme(maskX * majorAxis, maskY * minorAxis); if (distance > 1.0) { // leave there OPACITY TRANSPARENT pixel (default pixel) colorPicker.pickOldColor(x + dabX, y + dabY, dabPointer); dabPointer += dabPixelSize; *maskPointer = OPACITY_TRANSPARENT_U8; maskPointer += maskPixelSize; continue; } if (m_sizeProperties->density != 1.0) { if (m_sizeProperties->density < drand48()) { dabPointer += dabPixelSize; *maskPointer = OPACITY_TRANSPARENT_U8; maskPointer += maskPixelSize; continue; } } m_deformAction->transform(&maskX, &maskY, distance); reverseRotationMatrix.map(maskX, maskY, &maskX, &maskY); maskX += pos.x(); maskY += pos.y(); if (!m_properties->useBilinear) { maskX = qRound(maskX); maskY = qRound(maskY); } if (m_properties->useOldData) { colorPicker.pickOldColor(maskX, maskY, dabPointer); } else { colorPicker.pickColor(maskX, maskY, dabPointer); } dabPointer += dabPixelSize; *maskPointer = OPACITY_OPAQUE_U8; maskPointer += maskPixelSize; } } m_counter++; return mask; } void DeformBrush::debugColor(const quint8* data, KoColorSpace * cs) { QColor rgbcolor; cs->toQColor(data, &rgbcolor); dbgPlugins << "RGBA: (" << rgbcolor.red() << ", " << rgbcolor.green() << ", " << rgbcolor.blue() << ", " << rgbcolor.alpha() << ")"; } QPointF DeformBrush::hotSpot(qreal scale, qreal rotation) { qreal fWidth = maskWidth(scale); qreal fHeight = maskHeight(scale); QTransform m; m.reset(); m.rotateRadians(rotation); m_maskRect = QRect(0, 0, fWidth, fHeight); m_maskRect.translate(-m_maskRect.center()); m_maskRect = m.mapRect(m_maskRect); m_maskRect.translate(-m_maskRect.topLeft()); return m_maskRect.center(); } diff --git a/plugins/paintops/deform/kis_deform_paintop_settings.cpp b/plugins/paintops/deform/kis_deform_paintop_settings.cpp index 87f1e572dd..302f848b18 100644 --- a/plugins/paintops/deform/kis_deform_paintop_settings.cpp +++ b/plugins/paintops/deform/kis_deform_paintop_settings.cpp @@ -1,84 +1,74 @@ /* * Copyright (c) 2008,2009,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 #include #include #include #include KisDeformPaintOpSettings::KisDeformPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::SIZE_OPTION | KisCurrentOutlineFetcher::ROTATION_OPTION) { } bool KisDeformPaintOpSettings::paintIncremental() { return true; } bool KisDeformPaintOpSettings::isAirbrushing() const { // version 2.3 if (hasProperty(AIRBRUSH_ENABLED)) { return getBool(AIRBRUSH_ENABLED); } else { return getBool(DEFORM_USE_MOVEMENT_PAINT); } } int KisDeformPaintOpSettings::rate() const { if (hasProperty(AIRBRUSH_RATE)) { return getInt(AIRBRUSH_RATE); } else { return KisPaintOpSettings::rate(); } } QPainterPath KisDeformPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { qreal width = getInt(BRUSH_DIAMETER); qreal height = getInt(BRUSH_DIAMETER) * getDouble(BRUSH_ASPECT); path = ellipseOutline(width, height, getDouble(BRUSH_SCALE), getDouble(BRUSH_ROTATION)); - - QPainterPath tiltLine; - QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,width)); - tiltAngle.setLength(qMax(width*0.5, 50.0) * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); - path = outlineFetcher()->fetchOutline(info, this, path); - + if (mode == CursorTiltOutline) { + QPainterPath tiltLine = makeTiltIndicator(info, QPointF(0.0, 0.0), width * 0.5, 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, 1.0, 0.0, true, 0, 0)); } } return path; } diff --git a/plugins/paintops/experiment/kis_experiment_paintop_settings.cpp b/plugins/paintops/experiment/kis_experiment_paintop_settings.cpp index b2bad5d25f..6ba83141e1 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop_settings.cpp +++ b/plugins/paintops/experiment/kis_experiment_paintop_settings.cpp @@ -1,63 +1,50 @@ /* * Copyright (c) 2009,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_experiment_paintop_settings.h" #include "kis_current_outline_fetcher.h" bool KisExperimentPaintOpSettings::paintIncremental() { /** * The experiment brush supports working in the * WASH mode only! */ return false; } QPainterPath KisExperimentPaintOpSettings::brushOutline(const KisPaintInformation &info, KisPaintOpSettings::OutlineMode mode) const { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { QRectF ellipse(0, 0, 3, 3); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); - - ellipse.setRect(0,0, 12, 12); ellipse.translate(-ellipse.center()); path.addEllipse(ellipse); - QPainterPath tiltLine; - QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,3.0)); - tiltAngle.setLength(50.0 * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); - if (mode == CursorTiltOutline) { - path.addPath(tiltLine); + path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 3.0)); } path.translate(info.pos()); - } return path; } diff --git a/plugins/paintops/filterop/kis_filterop.cpp b/plugins/paintops/filterop/kis_filterop.cpp index bae2059083..2b2746e944 100644 --- a/plugins/paintops/filterop/kis_filterop.cpp +++ b/plugins/paintops/filterop/kis_filterop.cpp @@ -1,147 +1,145 @@ /* * 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_filterop.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisFilterOp::KisFilterOp(const KisFilterOpSettings *settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_filterConfiguration(0) { Q_UNUSED(node); Q_UNUSED(image); Q_ASSERT(settings); Q_ASSERT(painter); m_tmpDevice = source()->createCompositionSourceDevice(); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_filter = KisFilterRegistry::instance()->get(settings->getString(FILTER_ID)); m_filterConfiguration = settings->filterConfig(); m_smudgeMode = settings->getBool(FILTER_SMUDGE_MODE); m_rotationOption.applyFanCornersInfo(this); } KisFilterOp::~KisFilterOp() { } KisSpacingInformation KisFilterOp::paintAt(const KisPaintInformation& info) { if (!painter()) { return KisSpacingInformation(1.0); } if (!m_filter) { return KisSpacingInformation(1.0); } if (!source()) { return KisSpacingInformation(1.0); } KisBrushSP brush = m_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); + KisDabShape shape(scale, 1.0, rotation); static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP dab = m_dabCache->fetchDab(cs, color, info.pos(), - scale, scale, rotation, + shape, info, 1.0, &dstRect); if (dstRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = dab->bounds(); // sanity check Q_ASSERT(dstRect.size() == dabRect.size()); // Filter the paint device QRect neededRect = m_filter->neededRect(dstRect, m_filterConfiguration, painter()->device()->defaultBounds()->currentLevelOfDetail()); KisPainter p(m_tmpDevice); if (!m_smudgeMode) { p.setCompositeOp(COMPOSITE_COPY); } p.bitBltOldData(neededRect.topLeft() - dstRect.topLeft(), source(), neededRect); KisTransaction transaction(m_tmpDevice); m_filter->process(m_tmpDevice, dabRect, m_filterConfiguration, 0); transaction.end(); painter()-> bitBltWithFixedSelection(dstRect.x(), dstRect.y(), m_tmpDevice, dab, 0, 0, dabRect.x(), dabRect.y(), dabRect.width(), dabRect.height()); painter()->renderMirrorMaskSafe(dstRect, m_tmpDevice, 0, 0, dab, !m_dabCache->needSeparateOriginal()); return effectiveSpacing(scale, rotation); } diff --git a/plugins/paintops/gridbrush/kis_grid_paintop_settings.cpp b/plugins/paintops/gridbrush/kis_grid_paintop_settings.cpp index 7abb6affa6..85f452e465 100644 --- a/plugins/paintops/gridbrush/kis_grid_paintop_settings.cpp +++ b/plugins/paintops/gridbrush/kis_grid_paintop_settings.cpp @@ -1,65 +1,55 @@ /* * Copyright (c) 2009,2010 Lukáš Tvrdý (lukast.dev@gmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_grid_paintop_settings.h" #include "kis_grid_paintop_settings_widget.h" #include "kis_gridop_option.h" #include "kis_grid_shape_option.h" #include KisGridPaintOpSettings::KisGridPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::NO_OPTION) { } bool KisGridPaintOpSettings::paintIncremental() { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } QPainterPath KisGridPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { qreal sizex = getInt(GRID_WIDTH) * getDouble(GRID_SCALE); qreal sizey = getInt(GRID_HEIGHT) * getDouble(GRID_SCALE); QRectF rc(0, 0, sizex, sizey); rc.translate(-rc.center()); path.addRect(rc); - - QPainterPath tiltLine; - QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,sizex)); - tiltAngle.setLength(qMax(sizex*0.5, 50.0) * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); - path = outlineFetcher()->fetchOutline(info, this, path); if (mode == CursorTiltOutline) { + QPainterPath tiltLine = makeTiltIndicator(info, QPointF(0.0, 0.0), sizex * 0.5, 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, 1.0, 0.0, true, 0, 0)); } } return path; } diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp index d49edf3aa0..90f2f9d01e 100644 --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -1,139 +1,139 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hairy_paintop.h" #include "kis_hairy_paintop_settings.h" #include #include #include #include #include "kis_paint_device.h" #include "kis_painter.h" #include #include #include #include #include #include #include #include #include "kis_brush.h" KisHairyPaintOp::KisHairyPaintOp(const KisBrushBasedPaintOpSettings *settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image) Q_ASSERT(settings); m_dev = node ? node->paintDevice() : 0; KisBrushOption brushOption; brushOption.readOptionSetting(settings, true); KisBrushSP brush = brushOption.brush(); KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { - dab = brush->paintDevice(source()->colorSpace(), 1.0, 0.0, KisPaintInformation()); + dab = brush->paintDevice(source()->colorSpace(), KisDabShape(), KisPaintInformation()); } else { - brush->mask(dab, painter->paintColor(), 1.0, 1.0, 0.0, KisPaintInformation()); + brush->mask(dab, painter->paintColor(), KisDabShape(), KisPaintInformation()); } m_brush.fromDabWithDensity(dab, settings->getDouble(HAIRY_BRISTLE_DENSITY) * 0.01); m_brush.setInkColor(painter->paintColor()); loadSettings(settings); m_brush.setProperties(&m_properties); m_rotationOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } void KisHairyPaintOp::loadSettings(const KisBrushBasedPaintOpSettings* settings) { m_properties.inkAmount = settings->getInt(HAIRY_INK_AMOUNT); //TODO: wait for the transfer function with variable size m_properties.inkDepletionCurve = settings->getCubicCurve(HAIRY_INK_DEPLETION_CURVE).floatTransfer(m_properties.inkAmount); m_properties.inkDepletionEnabled = settings->getBool(HAIRY_INK_DEPLETION_ENABLED); m_properties.useSaturation = settings->getBool(HAIRY_INK_USE_SATURATION); m_properties.useOpacity = settings->getBool(HAIRY_INK_USE_OPACITY); m_properties.useWeights = settings->getBool(HAIRY_INK_USE_WEIGHTS); m_properties.pressureWeight = settings->getDouble(HAIRY_INK_PRESSURE_WEIGHT) / 100.0; m_properties.bristleLengthWeight = settings->getDouble(HAIRY_INK_BRISTLE_LENGTH_WEIGHT) / 100.0; m_properties.bristleInkAmountWeight = settings->getDouble(HAIRY_INK_BRISTLE_INK_AMOUNT_WEIGHT) / 100.0; m_properties.inkDepletionWeight = settings->getDouble(HAIRY_INK_DEPLETION_WEIGHT); m_properties.useSoakInk = settings->getBool(HAIRY_INK_SOAK); m_properties.useMousePressure = settings->getBool(HAIRY_BRISTLE_USE_MOUSEPRESSURE); m_properties.shearFactor = settings->getDouble(HAIRY_BRISTLE_SHEAR); m_properties.randomFactor = settings->getDouble(HAIRY_BRISTLE_RANDOM); m_properties.scaleFactor = settings->getDouble(HAIRY_BRISTLE_SCALE); m_properties.threshold = settings->getBool(HAIRY_BRISTLE_THRESHOLD); m_properties.antialias = settings->getBool(HAIRY_BRISTLE_ANTI_ALIASING); m_properties.useCompositing = settings->getBool(HAIRY_BRISTLE_USE_COMPOSITING); m_properties.connectedPath = settings->getBool(HAIRY_BRISTLE_CONNECTED); } KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info) { Q_UNUSED(info); return KisSpacingInformation(0.5); } void KisHairyPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); } else { m_dab->clear(); } // Hairy Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed qreal scale = m_sizeOption.apply(pi2); scale *= KisLodTransform::lodToScale(painter()->device()); qreal rotation = m_rotationOption.apply(pi2); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); m_brush.paintLine(m_dab, m_dev, pi1, pi2, scale * m_properties.scaleFactor, rotation); //QRect rc = m_dab->exactBounds(); QRect rc = m_dab->extent(); painter()->bitBlt(rc.topLeft(), m_dab, rc); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } diff --git a/plugins/paintops/hatching/kis_hatching_paintop.cpp b/plugins/paintops/hatching/kis_hatching_paintop.cpp index 578e011b5f..553671b442 100644 --- a/plugins/paintops/hatching/kis_hatching_paintop.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop.cpp @@ -1,203 +1,203 @@ /* * Copyright (c) 2008,2009 Lukáš Tvrdý * Copyright (c) 2010 José Luis Vergara * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_hatching_paintop.h" #include "kis_hatching_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KisHatchingPaintOp::KisHatchingPaintOp(const KisHatchingPaintOpSettings *settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisBrushBasedPaintOp(settings, painter) , m_image(image) { Q_UNUSED(node); m_settings = new KisHatchingPaintOpSettings(); settings->initializeTwin(m_settings); m_hatchingBrush = new HatchingBrush(m_settings); m_crosshatchingOption.readOptionSetting(settings); m_separationOption.readOptionSetting(settings); m_thicknessOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_crosshatchingOption.resetAllSensors(); m_separationOption.resetAllSensors(); m_thicknessOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); } KisHatchingPaintOp::~KisHatchingPaintOp() { delete m_hatchingBrush; } KisSpacingInformation KisHatchingPaintOp::paintAt(const KisPaintInformation& info) { //------START SIMPLE ERROR CATCHING------- if (!painter()->device()) return KisSpacingInformation(1.0); if (!m_hatchedDab) m_hatchedDab = source()->createCompositionSourceDevice(); else m_hatchedDab->clear(); //Simple convenience renaming, I'm thinking of removing these inherited quirks KisBrushSP brush = m_brush; KisPaintDeviceSP device = painter()->device(); //Macro to catch errors Q_ASSERT(brush); //----------SIMPLE error catching code, maybe it's not even needed------ if (!brush) return KisSpacingInformation(1.0); if (!brush->canPaintFor(info)) return KisSpacingInformation(1.0); //SENSOR-depending settings m_settings->crosshatchingsensorvalue = m_crosshatchingOption.apply(info); m_settings->separationsensorvalue = m_separationOption.apply(info); m_settings->thicknesssensorvalue = m_thicknessOption.apply(info); const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); const double scale = additionalScale * m_sizeOption.apply(info); if ((scale * brush->width()) <= 0.01 || (scale * brush->height()) <= 0.01) return KisSpacingInformation(1.0); - + KisDabShape shape(scale, 1.0, 0.0); quint8 origOpacity = m_opacityOption.apply(painter(), info); /*----Fetch the Dab----*/ static const KoColorSpace *cs = KoColorSpaceRegistry::instance()->alpha8(); static KoColor color(Qt::black, cs); QRect dstRect; KisFixedPaintDeviceSP maskDab = m_dabCache->fetchDab(cs, color, info.pos(), - scale, scale, 0.0, + shape, info, 1.0, &dstRect); // sanity check KIS_ASSERT_RECOVER_NOOP(dstRect.size() == maskDab->bounds().size()); /*-----Convenient renaming for the limits of the maskDab, this will be used to hatch a dab of just the right size------*/ qint32 x, y, sw, sh; dstRect.getRect(&x, &y, &sw, &sh); //------This If_block pre-fills the future m_hatchedDab with a pretty backgroundColor if (m_settings->opaquebackground) { KoColor aersh = painter()->backgroundColor(); m_hatchedDab->fill(0, 0, (sw - 1), (sh - 1), aersh.data()); //this plus yellow background = french fry brush } // Trick for moire pattern to look better bool donotbasehatch = false; /* If block describing how to stack hatching passes to generate crosshatching according to user specifications */ if (m_settings->enabledcurvecrosshatching) { if (m_settings->perpendicular) { if (m_settings->crosshatchingsensorvalue > 0.5) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { if (m_settings->crosshatchingsensorvalue > 0.33) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); if (m_settings->crosshatchingsensorvalue > 0.67) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle((m_settings->crosshatchingsensorvalue) * 180), painter()->paintColor(), additionalScale); donotbasehatch = true; } } else { if (m_settings->perpendicular) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(90), painter()->paintColor(), additionalScale); } else if (m_settings->minusthenplus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); } else if (m_settings->plusthenminus) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(45), painter()->paintColor(), additionalScale); m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-45), painter()->paintColor(), additionalScale); } else if (m_settings->moirepattern) { m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, spinAngle(-10), painter()->paintColor(), additionalScale); } } if (!donotbasehatch) m_hatchingBrush->hatch(m_hatchedDab, x, y, sw, sh, m_settings->angle, painter()->paintColor(), additionalScale); // The most important line, the one that paints to the screen. painter()->bitBltWithFixedSelection(x, y, m_hatchedDab, maskDab, sw, sh); painter()->renderMirrorMaskSafe(QRect(QPoint(x, y), QSize(sw, sh)), m_hatchedDab, 0, 0, maskDab, !m_dabCache->needSeparateOriginal()); painter()->setOpacity(origOpacity); return effectiveSpacing(scale, 0.0); } double KisHatchingPaintOp::spinAngle(double spin) { double tempangle = m_settings->angle + spin; qint8 factor = 1; if (tempangle < 0) factor = -1; tempangle = fabs(fmod(tempangle, 180)); if ((tempangle >= 0) && (tempangle <= 90)) return factor * tempangle; else if ((tempangle > 90) && (tempangle <= 180)) return factor * -(180 - tempangle); return 0; // this should never be executed except if NAN } diff --git a/plugins/paintops/libpaintop/CMakeLists.txt b/plugins/paintops/libpaintop/CMakeLists.txt index 37c63befbd..c116d4b2b6 100644 --- a/plugins/paintops/libpaintop/CMakeLists.txt +++ b/plugins/paintops/libpaintop/CMakeLists.txt @@ -1,96 +1,97 @@ set(kritalibpaintop_LIB_SRCS kis_airbrush_option.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_custom_brush_widget.cpp kis_clipboard_brush_widget.cpp kis_dynamic_sensor.cc 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_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_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_pressure_texture_strength_option.cpp kis_embedded_pattern_manager.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/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/kis_brush_based_paintop_settings.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp index c3f98228e1..ca6232962b 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp @@ -1,130 +1,125 @@ /* * Copyright (c) 2010 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_based_paintop_settings.h" #include #include #include "kis_brush_based_paintop_options_widget.h" #include #include "kis_brush_server.h" #include KisBrushBasedPaintOpSettings::KisBrushBasedPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::SIZE_OPTION | KisCurrentOutlineFetcher::ROTATION_OPTION | KisCurrentOutlineFetcher::MIRROR_OPTION) { } bool KisBrushBasedPaintOpSettings::paintIncremental() { if (hasProperty("PaintOpAction")) { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } return true; } bool KisBrushBasedPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED); } int KisBrushBasedPaintOpSettings::rate() const { return getInt(AIRBRUSH_RATE); } KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const { KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy::clone(); KisBrushBasedPaintOpSettings *settings = dynamic_cast(_settings.data()); settings->m_savedBrush = this->brush(); return settings; } KisBrushSP KisBrushBasedPaintOpSettings::brush() const { KisBrushBasedPaintopOptionWidget *widget = dynamic_cast(optionsWidget()); return widget ? widget->brush() : m_savedBrush; } QPainterPath KisBrushBasedPaintOpSettings::brushOutlineImpl(const KisPaintInformation &info, OutlineMode mode, qreal additionalScale, bool forceOutline) const { QPainterPath path; if (forceOutline || mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { KisBrushSP brush = this->brush(); qreal finalScale = brush->scale() * additionalScale; QPainterPath realOutline = brush->outline(); - QPainterPath tiltLine; - QLineF tiltAngle(realOutline.boundingRect().center(), realOutline.boundingRect().topLeft()); - tiltAngle.setLength(qMax(realOutline.boundingRect().width()*qreal(0.5), qreal(50.0)) * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); if (mode == CursorIsCircleOutline || mode == CursorTiltOutline || (forceOutline && mode == CursorNoOutline)) { QPainterPath ellipse; ellipse.addEllipse(realOutline.boundingRect()); realOutline = ellipse; } path = outlineFetcher()->fetchOutline(info, this, realOutline, finalScale, brush->angle()); if (mode == CursorTiltOutline) { + QPainterPath tiltLine = makeTiltIndicator(info, + realOutline.boundingRect().center(), + realOutline.boundingRect().width() * 0.5, + 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, finalScale, 0.0, true, realOutline.boundingRect().center().x(), realOutline.boundingRect().center().y())); } } return path; } QPainterPath KisBrushBasedPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { return brushOutlineImpl(info, mode, 1.0); } bool KisBrushBasedPaintOpSettings::isValid() const { QString filename = getString("requiredBrushFile", ""); if (!filename.isEmpty()) { KisBrushSP brush = KisBrushServer::instance()->brushServer()->resourceByFilename(filename); if (!brush) { return false; } } return true; } bool KisBrushBasedPaintOpSettings::isLoadable() { return (KisBrushServer::instance()->brushServer()->resources().count() > 0); } diff --git a/plugins/paintops/libpaintop/kis_dab_cache.cpp b/plugins/paintops/libpaintop/kis_dab_cache.cpp index 8479bc0db6..9316061281 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache.cpp +++ b/plugins/paintops/libpaintop/kis_dab_cache.cpp @@ -1,428 +1,421 @@ /* * 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_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; }; const qreal eps = 1e-6; static const PrecisionValues precisionLevels[] = { {M_PI / 180, 0.05, 1, 0.01}, {M_PI / 180, 0.01, 1, 0.01}, {M_PI / 180, 0, 1, 0.01}, {M_PI / 180, 0, 0.5, 0.01}, {eps, 0, eps, eps} }; struct KisDabCache::SavedDabParameters { KoColor color; qreal angle; int width; int height; qreal subPixelX; qreal subPixelY; qreal softnessFactor; 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 && index == rhs.index && mirrorProperties.horizontalMirror == rhs.mirrorProperties.horizontalMirror && mirrorProperties.verticalMirror == rhs.mirrorProperties.verticalMirror; } }; struct KisDabCache::Private { Private(KisBrushSP brush) : brush(brush), mirrorOption(0), sharpnessOption(0), textureOption(0), precisionOption(0), subPixelPrecisionDisabled(false), cachedDabParameters(new SavedDabParameters) {} KisFixedPaintDeviceSP dab; KisFixedPaintDeviceSP dabOriginal; KisBrushSP brush; KisPaintDeviceSP colorSourceDevice; KisPressureMirrorOption *mirrorOption; KisPressureSharpnessOption *sharpnessOption; KisTextureProperties *textureOption; KisPrecisionOption *precisionOption; bool subPixelPrecisionDisabled; SavedDabParameters *cachedDabParameters; }; KisDabCache::KisDabCache(KisBrushSP brush) : m_d(new Private(brush)) { } KisDabCache::~KisDabCache() { delete m_d->cachedDabParameters; delete m_d; } void KisDabCache::setMirrorPostprocessing(KisPressureMirrorOption *option) { m_d->mirrorOption = option; } void KisDabCache::setSharpnessPostprocessing(KisPressureSharpnessOption *option) { m_d->sharpnessOption = option; } void KisDabCache::setTexturePostprocessing(KisTextureProperties *option) { m_d->textureOption = option; } void KisDabCache::setPrecisionOption(KisPrecisionOption *option) { m_d->precisionOption = option; } void KisDabCache::disableSubpixelPrecision() { m_d->subPixelPrecisionDisabled = true; } inline KisDabCache::SavedDabParameters KisDabCache::getDabParameters(const KoColor& color, - double scaleX, double scaleY, - double angle, + KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, MirrorProperties mirrorProperties) { SavedDabParameters params; params.color = color; - params.angle = angle; - params.width = m_d->brush->maskWidth(scaleX, angle, subPixelX, subPixelY, info); - params.height = m_d->brush->maskHeight(scaleY, angle, subPixelX, subPixelY, info); + params.angle = shape.rotation(); + params.width = m_d->brush->maskWidth(shape, subPixelX, subPixelY, info); + params.height = m_d->brush->maskHeight(shape, subPixelX, subPixelY, info); params.subPixelX = subPixelX; params.subPixelY = subPixelY; params.softnessFactor = softnessFactor; params.index = m_d->brush->brushIndex(info); params.mirrorProperties = mirrorProperties; return params; } KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs, const KisColorSource *colorSource, const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape const& shape, const KisPaintInformation& info, qreal softnessFactor, QRect *dstDabRect) { return fetchDabCommon(cs, colorSource, KoColor(), cursorPoint, - scaleX, scaleY, angle, + shape, info, softnessFactor, dstDabRect); } KisFixedPaintDeviceSP KisDabCache::fetchDab(const KoColorSpace *cs, const KoColor& color, const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape const& shape, const KisPaintInformation& info, qreal softnessFactor, QRect *dstDabRect) { return fetchDabCommon(cs, 0, color, cursorPoint, - scaleX, scaleY, angle, + shape, info, softnessFactor, dstDabRect); } bool KisDabCache::needSeparateOriginal() { return (m_d->textureOption && m_d->textureOption->m_enabled) || (m_d->sharpnessOption && m_d->sharpnessOption->isChecked()); } struct KisDabCache::DabPosition { DabPosition(const QRect &_rect, const QPointF &_subPixel, qreal _realAngle) : rect(_rect), subPixel(_subPixel), realAngle(_realAngle) { } QRect rect; QPointF subPixel; qreal realAngle; }; inline QRect KisDabCache::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()); } inline KisFixedPaintDeviceSP KisDabCache::tryFetchFromCache(const SavedDabParameters ¶ms, const KisPaintInformation& info, QRect *dstDabRect) { int precisionLevel = m_d->precisionOption ? m_d->precisionOption->precisionLevel() - 1 : 3; if (!params.compare(*m_d->cachedDabParameters, precisionLevel)) { return 0; } if (needSeparateOriginal()) { *m_d->dab = *m_d->dabOriginal; *dstDabRect = correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size()); postProcessDab(m_d->dab, dstDabRect->topLeft(), info); } else { *dstDabRect = correctDabRectWhenFetchedFromCache(*dstDabRect, m_d->dab->bounds().size()); } m_d->brush->notifyCachedDabPainted(info); return m_d->dab; } qreal positiveFraction(qreal x) { qint32 unused = 0; qreal fraction = 0.0; KisPaintOp::splitCoordinate(x, &unused, &fraction); return fraction; } inline KisDabCache::DabPosition KisDabCache::calculateDabRect(const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape shape, const KisPaintInformation& info, const MirrorProperties &mirrorProperties) { qint32 x = 0, y = 0; qreal subPixelX = 0.0, subPixelY = 0.0; if (mirrorProperties.coordinateSystemFlipped) { - angle = 2 * M_PI - angle; + shape = KisDabShape(shape.scale(), shape.ratio(), 2 * M_PI - shape.rotation()); } - QPointF hotSpot = m_d->brush->hotSpot(scaleX, scaleY, angle, info); + QPointF hotSpot = m_d->brush->hotSpot(shape, info); QPointF pt = cursorPoint - hotSpot; if (m_d->sharpnessOption) { m_d->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 = m_d->brush->maskWidth(scaleX, angle, subPixelX, subPixelY, info); - int height = m_d->brush->maskHeight(scaleY, angle, subPixelX, subPixelY, info); + int width = m_d->brush->maskWidth(shape, subPixelX, subPixelY, info); + int height = m_d->brush->maskHeight(shape, subPixelX, subPixelY, info); if (mirrorProperties.horizontalMirror) { subPixelX = positiveFraction(-(cursorPoint.x() + hotSpot.x())); - width = m_d->brush->maskWidth(scaleX, angle, subPixelX, subPixelY, info); + width = m_d->brush->maskWidth(shape, subPixelX, subPixelY, info); x = qRound(cursorPoint.x() + subPixelX + hotSpot.x()) - width; } if (mirrorProperties.verticalMirror) { subPixelY = positiveFraction(-(cursorPoint.y() + hotSpot.y())); - height = m_d->brush->maskHeight(scaleY, angle, subPixelX, subPixelY, info); + height = m_d->brush->maskHeight(shape, subPixelX, subPixelY, info); y = qRound(cursorPoint.y() + subPixelY + hotSpot.y()) - height; } return DabPosition(QRect(x, y, width, height), QPointF(subPixelX, subPixelY), - angle); + shape.rotation()); } inline KisFixedPaintDeviceSP KisDabCache::fetchDabCommon(const KoColorSpace *cs, const KisColorSource *colorSource, const KoColor& color, const QPointF &cursorPoint, - double scaleX, double scaleY, - double initialAngle, + KisDabShape shape, const KisPaintInformation& info, qreal softnessFactor, QRect *dstDabRect) { Q_ASSERT(dstDabRect); MirrorProperties mirrorProperties; if (m_d->mirrorOption) { mirrorProperties = m_d->mirrorOption->apply(info); } DabPosition position = calculateDabRect(cursorPoint, - scaleX, scaleY, - initialAngle, + shape, info, mirrorProperties); - + shape = KisDabShape(shape.scale(), shape.ratio(), position.realAngle); *dstDabRect = position.rect; bool cachingIsPossible = !colorSource || colorSource->isUniformColor(); KoColor paintColor = colorSource && colorSource->isUniformColor() ? colorSource->uniformColor() : color; SavedDabParameters newParams = getDabParameters(paintColor, - scaleX, scaleY, - position.realAngle, info, + shape, info, position.subPixel.x(), position.subPixel.y(), softnessFactor, mirrorProperties); if (!m_d->dab || !(*m_d->dab->colorSpace() == *cs)) { m_d->dab = new KisFixedPaintDevice(cs); } else if (cachingIsPossible) { KisFixedPaintDeviceSP cachedDab = tryFetchFromCache(newParams, info, dstDabRect); if (cachedDab) return cachedDab; } if (m_d->brush->brushType() == IMAGE || m_d->brush->brushType() == PIPE_IMAGE) { - m_d->dab = m_d->brush->paintDevice(cs, scaleX, position.realAngle, info, + m_d->dab = m_d->brush->paintDevice(cs, shape, info, position.subPixel.x(), position.subPixel.y()); } else if (cachingIsPossible) { *m_d->cachedDabParameters = newParams; - m_d->brush->mask(m_d->dab, paintColor, scaleX, scaleY, position.realAngle, + m_d->brush->mask(m_d->dab, paintColor, shape, info, position.subPixel.x(), position.subPixel.y(), softnessFactor); } else { if (!m_d->colorSourceDevice || !(*cs == *m_d->colorSourceDevice->colorSpace())) { m_d->colorSourceDevice = new KisPaintDevice(cs); } else { m_d->colorSourceDevice->clear(); } QRect maskRect(QPoint(), position.rect.size()); colorSource->colorize(m_d->colorSourceDevice, maskRect, info.pos().toPoint()); delete m_d->colorSourceDevice->convertTo(cs); - m_d->brush->mask(m_d->dab, m_d->colorSourceDevice, scaleX, scaleY, position.realAngle, + m_d->brush->mask(m_d->dab, m_d->colorSourceDevice, shape, info, position.subPixel.x(), position.subPixel.y(), softnessFactor); } if (!mirrorProperties.isEmpty()) { m_d->dab->mirror(mirrorProperties.horizontalMirror, mirrorProperties.verticalMirror); } if (needSeparateOriginal()) { 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, position.rect.topLeft(), info); return m_d->dab; } void KisDabCache::postProcessDab(KisFixedPaintDeviceSP dab, const QPoint &dabTopLeft, const KisPaintInformation& info) { if (m_d->sharpnessOption) { m_d->sharpnessOption->applyThreshold(dab); } if (m_d->textureOption) { m_d->textureOption->apply(dab, dabTopLeft, info); } } diff --git a/plugins/paintops/libpaintop/kis_dab_cache.h b/plugins/paintops/libpaintop/kis_dab_cache.h index 3581831bd9..442543e475 100644 --- a/plugins/paintops/libpaintop/kis_dab_cache.h +++ b/plugins/paintops/libpaintop/kis_dab_cache.h @@ -1,139 +1,134 @@ /* * 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_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: KisDabCache(KisBrushSP brush); ~KisDabCache(); void setMirrorPostprocessing(KisPressureMirrorOption *option); void setSharpnessPostprocessing(KisPressureSharpnessOption *option); void setTexturePostprocessing(KisTextureProperties *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(); bool needSeparateOriginal(); KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs, const KisColorSource *colorSource, const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape const&, const KisPaintInformation& info, qreal softnessFactor, QRect *dstDabRect); KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs, const KoColor& color, const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape const&, const KisPaintInformation& info, qreal softnessFactor, QRect *dstDabRect); private: struct SavedDabParameters; struct DabPosition; private: inline SavedDabParameters getDabParameters(const KoColor& color, - double scaleX, double scaleY, - double angle, + KisDabShape const&, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor, MirrorProperties mirrorProperties); inline KisDabCache::DabPosition calculateDabRect(const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape, const KisPaintInformation& info, const MirrorProperties &mirrorProperties); inline QRect correctDabRectWhenFetchedFromCache(const QRect &dabRect, const QSize &realDabSize); inline KisFixedPaintDeviceSP tryFetchFromCache(const SavedDabParameters ¶ms, const KisPaintInformation& info, QRect *dstDabRect); inline KisFixedPaintDeviceSP fetchDabCommon(const KoColorSpace *cs, const KisColorSource *colorSource, const KoColor& color, const QPointF &cursorPoint, - double scaleX, double scaleY, - double angle, + KisDabShape, const KisPaintInformation& info, qreal softnessFactor, QRect *dstDabRect); void postProcessDab(KisFixedPaintDeviceSP dab, const QPoint &dabTopLeft, const KisPaintInformation& info); private: struct Private; Private * const m_d; }; #endif /* __KIS_DAB_CACHE_H */ diff --git a/plugins/paintops/libpaintop/kis_pressure_ratio_option.cpp b/plugins/paintops/libpaintop/kis_pressure_ratio_option.cpp index 58a50da1ed..e7f476ef70 100644 --- a/plugins/paintops/libpaintop/kis_pressure_ratio_option.cpp +++ b/plugins/paintops/libpaintop/kis_pressure_ratio_option.cpp @@ -1,32 +1,32 @@ /* This file is part of the KDE project * Copyright (C) Nishant Rodrigues , (C) 2016 * * 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_ratio_option.h" KisPressureRatioOption::KisPressureRatioOption() : KisCurveOption("Ratio", KisPaintOpOption::GENERAL, true) { } double KisPressureRatioOption::apply(const KisPaintInformation & info) const { if (!isChecked()) return 1.0; - return computeSizeLikeValue(); + return computeSizeLikeValue(info); } diff --git a/plugins/paintops/sketch/kis_sketch_paintop.cpp b/plugins/paintops/sketch/kis_sketch_paintop.cpp index e7cac4d3d0..bd1331a51e 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop.cpp +++ b/plugins/paintops/sketch/kis_sketch_paintop.cpp @@ -1,300 +1,300 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2010 Ricardo Cabello * * 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_sketch_paintop.h" #include "kis_sketch_paintop_settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_lod_transform.h" #include /* * Based on Harmony project http://github.com/mrdoob/harmony/ */ // chrome : diff 0.2, sketchy : 0.3, fur: 0.5 // fur : distance / thresholdDistance // shaded: opacity per line :/ // ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0 // chrome: color per line :/ //this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )"; // long fur // from: count + offset * -random // to: i point - (offset * -random) + random * 2 // probability distance / thresholdDistnace // shaded: probabity : paint always - 0.0 density KisSketchPaintOp::KisSketchPaintOp(const KisSketchPaintOpSettings *settings, KisPainter * painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_opacityOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_sketchProperties.readOptionSetting(settings); m_brushOption.readOptionSetting(settings, true); m_densityOption.readOptionSetting(settings); m_lineWidthOption.readOptionSetting(settings); m_offsetScaleOption.readOptionSetting(settings); m_brush = m_brushOption.brush(); m_dabCache = new KisDabCache(m_brush); m_opacityOption.resetAllSensors(); m_sizeOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_painter = 0; m_count = 0; } KisSketchPaintOp::~KisSketchPaintOp() { delete m_painter; delete m_dabCache; } void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth) { if (lineWidth == 1.0) { m_painter->drawThickLine(start, end, lineWidth, lineWidth); } else { m_painter->drawLine(start, end, lineWidth, true); } } void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation) { QRect dstRect; m_maskDab = m_dabCache->fetchDab(m_dab->colorSpace(), painter()->paintColor(), info.pos(), - scale, scale, rotation, + KisDabShape(scale, 1.0, rotation), info, 1.0, &dstRect); m_brushBoundingBox = dstRect; m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(), 0.5 * m_brushBoundingBox.height()); } void KisSketchPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!m_brush || !painter()) return; if (!m_dab) { m_dab = source()->createCompositionSourceDevice(); m_painter = new KisPainter(m_dab); m_painter->setPaintColor(painter()->paintColor()); } else { m_dab->clear(); } QPointF prevMouse = pi1.pos(); QPointF mousePosition = pi2.pos(); m_points.append(mousePosition); const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2); if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return; const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2, m_sketchProperties.lineWidth)); const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2, m_sketchProperties.offset); const double rotation = m_rotationOption.apply(pi2); const double currentProbability = m_densityOption.apply(pi2, m_sketchProperties.probability); // shaded: does not draw this line, chrome does, fur does if (m_sketchProperties.makeConnection) { drawConnection(prevMouse, mousePosition, currentLineWidth); } qreal thresholdDistance = 0.0; // update the mask for simple mode only once // determine the radius if (m_count == 0 && m_sketchProperties.simpleMode) { updateBrushMask(pi2, 1.0, 0.0); //m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5; m_radius = 0.5 * qMax(m_brush->width(), m_brush->height()); } if (!m_sketchProperties.simpleMode) { updateBrushMask(pi2, scale, rotation); m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5; thresholdDistance = pow(m_radius, 2); } if (m_sketchProperties.simpleMode) { // update the radius according scale in simple mode thresholdDistance = pow(m_radius * scale, 2); } // determine density const qreal density = thresholdDistance * currentProbability; // probability behaviour qreal probability = 1.0 - currentProbability; QColor painterColor = painter()->paintColor().toQColor(); QColor randomColor; KoColor color(m_dab->colorSpace()); int w = m_maskDab->bounds().width(); quint8 opacityU8 = 0; quint8 * pixel; qreal distance; QPoint positionInMask; QPointF diff; int size = m_points.size(); // MAIN LOOP for (int i = 0; i < size; i++) { diff = m_points.at(i) - mousePosition; distance = diff.x() * diff.x() + diff.y() * diff.y(); // circle test bool makeConnection = false; if (m_sketchProperties.simpleMode) { if (distance < thresholdDistance) { makeConnection = true; } // mask test } else { if (m_brushBoundingBox.contains(m_points.at(i))) { positionInMask = (diff + m_hotSpot).toPoint(); uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize()); if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) { pixel = m_maskDab->data() + pos; opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel); if (opacityU8 != 0) { makeConnection = true; } } } } if (!makeConnection) { // check next point continue; } if (m_sketchProperties.distanceDensity) { probability = distance / density; } KisRandomSourceSP randomSource = pi2.randomSource(); // density check if (randomSource->generateNormalized() >= probability) { QPointF offsetPt = diff * currentOffsetScale; if (m_sketchProperties.randomRGB) { /** * Since the order of calculation of function * parameters is not defined by C++ standard, we * should generate values in an external code snippet * which has a definite order of execution. */ qreal r1 = randomSource->generateNormalized(); qreal r2 = randomSource->generateNormalized(); qreal r3 = randomSource->generateNormalized(); // some color transformation per line goes here randomColor.setRgbF(r1 * painterColor.redF(), r2 * painterColor.greenF(), r3 * painterColor.blueF()); color.fromQColor(randomColor); m_painter->setPaintColor(color); } // distance based opacity quint8 opacity = OPACITY_OPAQUE_U8; if (m_sketchProperties.distanceOpacity) { opacity *= qRound((1.0 - (distance / thresholdDistance))); } if (m_sketchProperties.randomOpacity) { opacity *= randomSource->generateNormalized(); } m_painter->setOpacity(opacity); if (m_sketchProperties.magnetify) { drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth); } else { drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth); } } }// end of MAIN LOOP m_count++; QRect rc = m_dab->extent(); quint8 origOpacity = m_opacityOption.apply(painter(), pi2); painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height()); painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); } KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info) { KisDistanceInformation di; paintLine(info, info, &di); return di.currentSpacing(); } diff --git a/plugins/paintops/sketch/kis_sketch_paintop_settings.cpp b/plugins/paintops/sketch/kis_sketch_paintop_settings.cpp index c7f16b3528..da5ba34a80 100644 --- a/plugins/paintops/sketch/kis_sketch_paintop_settings.cpp +++ b/plugins/paintops/sketch/kis_sketch_paintop_settings.cpp @@ -1,84 +1,74 @@ /* * 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_sketch_paintop_settings.h" #include #include #include #include "kis_current_outline_fetcher.h" KisSketchPaintOpSettings::KisSketchPaintOpSettings() { } bool KisSketchPaintOpSettings::paintIncremental() { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } bool KisSketchPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED); } int KisSketchPaintOpSettings::rate() const { return getInt(AIRBRUSH_RATE); } QPainterPath KisSketchPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { bool isSimpleMode = getBool(SKETCH_USE_SIMPLE_MODE); if (!isSimpleMode) { return KisBrushBasedPaintOpSettings::brushOutline(info, mode); } KisBrushBasedPaintopOptionWidget *widget = dynamic_cast(optionsWidget()); QPainterPath path; if (widget && (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline)) { KisBrushSP brush = widget->brush(); // just circle supported qreal diameter = qMax(brush->width(), brush->height()); path = ellipseOutline(diameter, diameter, 1.0, 0.0/*brush->scale(), brush->angle()*/); - QPainterPath tiltLine; - QLineF tiltAngle(path.boundingRect().center(), path.boundingRect().topLeft()); - tiltAngle.setLength(qMax(diameter*qreal(0.5), qreal(50.0)) * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); + QPainterPath tiltLine = + makeTiltIndicator(info, path.boundingRect().center(), diameter * 0.5, 3.0); path = outlineFetcher()->fetchOutline(info, this, path); if (mode == CursorTiltOutline) { - QPainterPath realOutline = brush->outline(); - tiltLine.translate(info.pos()); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, 1.0, 0.0, true, path.boundingRect().center().x(), path.boundingRect().center().y())); } } return path; } diff --git a/plugins/paintops/spray/kis_spray_paintop_settings.cpp b/plugins/paintops/spray/kis_spray_paintop_settings.cpp index 34bc95a96b..50f382c449 100644 --- a/plugins/paintops/spray/kis_spray_paintop_settings.cpp +++ b/plugins/paintops/spray/kis_spray_paintop_settings.cpp @@ -1,76 +1,67 @@ /* * Copyright (c) 2008,2009,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 #include #include #include "kis_spray_paintop_settings.h" #include "kis_sprayop_option.h" #include "kis_spray_shape_option.h" #include KisSprayPaintOpSettings::KisSprayPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::SIZE_OPTION | KisCurrentOutlineFetcher::ROTATION_OPTION) { } bool KisSprayPaintOpSettings::paintIncremental() { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } bool KisSprayPaintOpSettings::isAirbrushing() const { return getBool(AIRBRUSH_ENABLED); } int KisSprayPaintOpSettings::rate() const { return getInt(AIRBRUSH_RATE); } QPainterPath KisSprayPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode) const { QPainterPath path; if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) { qreal width = getInt(SPRAY_DIAMETER); qreal height = getInt(SPRAY_DIAMETER) * getDouble(SPRAY_ASPECT); path = ellipseOutline(width, height, getDouble(SPRAY_SCALE), getDouble(SPRAY_ROTATION)); - - QPainterPath tiltLine; - QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,width)); - tiltAngle.setLength(qMax(width*0.5, 50.0) * (1 - info.tiltElevation(info, 60.0, 60.0, true))); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0); - tiltLine.moveTo(tiltAngle.p1()); - tiltLine.lineTo(tiltAngle.p2()); - tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0); - tiltLine.lineTo(tiltAngle.p2()); - tiltLine.lineTo(tiltAngle.p1()); - path = outlineFetcher()->fetchOutline(info, this, path); - + if (mode == CursorTiltOutline) { + QPainterPath tiltLine = + makeTiltIndicator(info, QPointF(0.0, 0.0), width * 0.5, 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, 1.0, 0.0, true, 0, 0)); } } return path; } diff --git a/plugins/paintops/spray/spray_brush.cpp b/plugins/paintops/spray/spray_brush.cpp index bb35c5de18..4449215e65 100644 --- a/plugins/paintops/spray/spray_brush.cpp +++ b/plugins/paintops/spray/spray_brush.cpp @@ -1,535 +1,531 @@ /* * Copyright (c) 2008-2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "spray_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_spray_paintop_settings.h" #include #include #include "random_gauss.h" #include SprayBrush::SprayBrush() { m_painter = 0; m_transfo = 0; m_rand = new RandomGauss(time(0)); } SprayBrush::~SprayBrush() { delete m_painter; delete m_transfo; delete m_rand; } void SprayBrush::setProperties(KisSprayProperties * properties, KisColorProperties * colorProperties, KisShapeProperties * shapeProperties, KisShapeDynamicsProperties * shapeDynamicsProperties, KisBrushSP brush) { m_properties = properties; m_colorProperties = colorProperties; m_shapeProperties = shapeProperties; m_shapeDynamicsProperties = shapeDynamicsProperties; m_brush = brush; if (m_brush) { m_brush->notifyStrokeStarted(); } } qreal SprayBrush::rotationAngle(KisRandomSourceSP randomSource) { qreal rotation = 0.0; if (m_shapeDynamicsProperties->fixedRotation) { rotation = deg2rad(m_shapeDynamicsProperties->fixedAngle); } if (m_shapeDynamicsProperties->randomRotation) { if (m_properties->gaussian) { rotation = linearInterpolation(rotation , M_PI * 2.0 * qBound(0.0, m_rand->nextGaussian(0.0, 0.50) , 1.0), m_shapeDynamicsProperties->randomRotationWeight); } else { rotation = linearInterpolation(rotation, M_PI * 2.0 * randomSource->generateNormalized(), m_shapeDynamicsProperties->randomRotationWeight); } } return rotation; } void SprayBrush::paint(KisPaintDeviceSP dab, KisPaintDeviceSP source, const KisPaintInformation& info, qreal rotation, qreal scale, qreal additionalScale, const KoColor &color, const KoColor &bgColor) { KisRandomSourceSP randomSource = info.randomSource(); // initializing painter if (!m_painter) { m_painter = new KisPainter(dab); m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); m_painter->setMaskImageSize(m_shapeProperties->width, m_shapeProperties->height); m_dabPixelSize = dab->colorSpace()->pixelSize(); if (m_colorProperties->useRandomHSV) { m_transfo = dab->colorSpace()->createColorTransformation("hsv_adjustment", QHash()); } m_brushQImage = m_shapeProperties->image; if (!m_brushQImage.isNull()) { m_brushQImage = m_brushQImage.scaled(m_shapeProperties->width, m_shapeProperties->height); } m_imageDevice = new KisPaintDevice(dab->colorSpace()); } qreal x = info.pos().x(); qreal y = info.pos().y(); KisRandomAccessorSP accessor = dab->createRandomAccessorNG(qRound(x), qRound(y)); Q_ASSERT(color.colorSpace()->pixelSize() == dab->pixelSize()); m_inkColor = color; KisCrossDeviceColorPicker colorPicker(source, m_inkColor); // apply size sensor m_radius = m_properties->radius * scale * additionalScale; // jitter movement if (m_properties->jitterMovement) { x = x + ((2 * m_radius * randomSource->generateNormalized()) - m_radius) * m_properties->amount; y = y + ((2 * m_radius * randomSource->generateNormalized()) - m_radius) * m_properties->amount; } // this is wrong for every shape except pixel and anti-aliased pixel if (m_properties->useDensity) { m_particlesCount = (m_properties->coverage * (M_PI * pow2(m_radius)) / pow2(additionalScale)); } else { m_particlesCount = m_properties->particleCount; } QHash params; qreal nx, ny; int ix, iy; qreal angle; qreal length; qreal rotationZ = 0.0; qreal particleScale = 1.0; bool shouldColor = true; if (m_colorProperties->fillBackground) { m_painter->setPaintColor(bgColor); paintCircle(m_painter, x, y, m_radius); } QTransform m; m.reset(); m.rotateRadians(-rotation + deg2rad(m_properties->brushRotation)); m.scale(m_properties->scale, m_properties->scale); for (quint32 i = 0; i < m_particlesCount; i++) { // generate random angle angle = randomSource->generateNormalized() * M_PI * 2; // generate random length if (m_properties->gaussian) { length = qBound(0.0, m_rand->nextGaussian(0.0, 0.50) , 1.0); } else { length = randomSource->generateNormalized(); } if (m_shapeDynamicsProperties->enabled) { // rotation rotationZ = rotationAngle(randomSource); if (m_shapeDynamicsProperties->followCursor) { rotationZ = linearInterpolation(rotationZ, angle, m_shapeDynamicsProperties->followCursorWeigth); } if (m_shapeDynamicsProperties->followDrawingAngle) { rotationZ = linearInterpolation(rotationZ, info.drawingAngle(), m_shapeDynamicsProperties->followDrawingAngleWeight); } // random size - scale if (m_shapeDynamicsProperties->randomSize) { particleScale = randomSource->generateNormalized(); } } // generate polar coordinate nx = (m_radius * cos(angle) * length); ny = (m_radius * sin(angle) * length); // compute the height of the ellipse ny *= m_properties->aspect; // transform m.map(nx, ny, &nx, &ny); // color transformation if (shouldColor) { if (m_colorProperties->sampleInputColor) { colorPicker.pickOldColor(nx + x, ny + y, m_inkColor.data()); } // mix the color with background color if (m_colorProperties->mixBgColor) { KoMixColorsOp * mixOp = dab->colorSpace()->mixColorsOp(); const quint8 *colors[2]; colors[0] = m_inkColor.data(); colors[1] = bgColor.data(); qint16 colorWeights[2]; int MAX_16BIT = 255; qreal blend = info.pressure(); colorWeights[0] = static_cast(blend * MAX_16BIT); colorWeights[1] = static_cast((1.0 - blend) * MAX_16BIT); mixOp->mixColors(colors, colorWeights, 2, m_inkColor.data()); } if (m_colorProperties->useRandomHSV && m_transfo) { params["h"] = (m_colorProperties->hue / 180.0) * randomSource->generateNormalized(); params["s"] = (m_colorProperties->saturation / 100.0) * randomSource->generateNormalized(); params["v"] = (m_colorProperties->value / 100.0) * randomSource->generateNormalized(); m_transfo->setParameters(params); m_transfo->setParameter(3, 1);//sets the type to HSV. For some reason 0 is not an option. m_transfo->setParameter(4, false);//sets the colorize to false. m_transfo->transform(m_inkColor.data(), m_inkColor.data() , 1); } if (m_colorProperties->useRandomOpacity) { quint8 alpha = qRound(randomSource->generateNormalized() * OPACITY_OPAQUE_U8); m_inkColor.setOpacity(alpha); m_painter->setOpacity(alpha); } if (!m_colorProperties->colorPerParticle) { shouldColor = false; } m_painter->setPaintColor(m_inkColor); } qreal jitteredWidth = qMax(1.0 * additionalScale, m_shapeProperties->width * particleScale * additionalScale); qreal jitteredHeight = qMax(1.0 * additionalScale, m_shapeProperties->height * particleScale * additionalScale); if (m_shapeProperties->enabled){ switch (m_shapeProperties->shape){ // ellipse case 0: { if (m_shapeProperties->width == m_shapeProperties->height){ paintCircle(m_painter, nx + x, ny + y, jitteredWidth * 0.5); } else { paintEllipse(m_painter, nx + x, ny + y, jitteredWidth * 0.5 , jitteredHeight * 0.5, rotationZ); } break; } // rectangle case 1: { paintRectangle(m_painter, nx + x, ny + y, qRound(jitteredWidth) , qRound(jitteredHeight), rotationZ); break; } // wu-particle case 2: { paintParticle(accessor, m_inkColor, nx + x, ny + y); break; } // pixel case 3: { ix = qRound(nx + x); iy = qRound(ny + y); accessor->moveTo(ix, iy); memcpy(accessor->rawData(), m_inkColor.data(), m_dabPixelSize); break; } case 4: { if (!m_brushQImage.isNull()) { QTransform m; m.rotate(rad2deg(rotationZ)); m.scale(additionalScale, additionalScale); if (m_shapeDynamicsProperties->randomSize) { m.scale(particleScale, particleScale); } m_transformed = m_brushQImage.transformed(m, Qt::SmoothTransformation); m_imageDevice->convertFromQImage(m_transformed, 0); KisRandomAccessorSP ac = m_imageDevice->createRandomAccessorNG(0, 0); QRect rc = m_transformed.rect(); if (m_colorProperties->useRandomHSV && m_transfo) { for (int y = rc.y(); y < rc.y() + rc.height(); y++) { for (int x = rc.x(); x < rc.x() + rc.width(); x++) { ac->moveTo(x, y); m_transfo->transform(ac->rawData(), ac->rawData() , 1); } } } ix = qRound(nx + x - rc.width() * 0.5); iy = qRound(ny + y - rc.height() * 0.5); m_painter->bitBlt(QPoint(ix, iy), m_imageDevice, rc); m_imageDevice->clear(); break; } } } // Auto-brush } else { - QPointF hotSpot = m_brush->hotSpot(particleScale * additionalScale, - particleScale * additionalScale, - -rotationZ, info); + KisDabShape shape(particleScale * additionalScale, 1.0, -rotationZ); + QPointF hotSpot = m_brush->hotSpot(shape, info); QPointF pos(nx + x, ny + y); QPointF pt = pos - hotSpot; qint32 ix; qreal xFraction; qint32 iy; qreal yFraction; KisPaintOp::splitCoordinate(pt.x(), &ix, &xFraction); KisPaintOp::splitCoordinate(pt.y(), &iy, &yFraction); //KisFixedPaintDeviceSP dab; if (m_brush->brushType() == IMAGE || m_brush->brushType() == PIPE_IMAGE) { m_fixedDab = m_brush->paintDevice(m_fixedDab->colorSpace(), - particleScale * additionalScale, - -rotationZ, info, xFraction, yFraction); + shape, info, xFraction, yFraction); if (m_colorProperties->useRandomHSV && m_transfo) { quint8 * dabPointer = m_fixedDab->data(); int pixelCount = m_fixedDab->bounds().width() * m_fixedDab->bounds().height(); m_transfo->transform(dabPointer, dabPointer, pixelCount); } } else { - m_brush->mask(m_fixedDab, m_inkColor, - particleScale * additionalScale, - particleScale * additionalScale, - -rotationZ, info, xFraction, yFraction); + m_brush->mask(m_fixedDab, m_inkColor, shape, + info, xFraction, yFraction); } m_painter->bltFixed(QPoint(ix, iy), m_fixedDab, m_fixedDab->bounds()); } if (m_colorProperties->colorPerParticle){ m_inkColor=color;//reset color// } } // recover from jittering of color, // m_inkColor.opacity is recovered with every paint } void SprayBrush::paintParticle(KisRandomAccessorSP &writeAccessor, const KoColor &color, qreal rx, qreal ry) { // opacity top left, right, bottom left, right KoColor pcolor(color); //int opacity = pcolor.opacityU8(); int ipx = int (rx); int ipy = int (ry); qreal fx = rx - ipx; qreal fy = ry - ipy; qreal btl = (1 - fx) * (1 - fy); qreal btr = (fx) * (1 - fy); qreal bbl = (1 - fx) * (fy); qreal bbr = (fx) * (fy); // this version overwrite pixels, e.g. when it sprays two particle next // to each other, the pixel with lower opacity can override other pixel. // Maybe some kind of compositing using here would be cool pcolor.setOpacity(btl); writeAccessor->moveTo(ipx , ipy); memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); pcolor.setOpacity(btr); writeAccessor->moveTo(ipx + 1, ipy); memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); pcolor.setOpacity(bbl); writeAccessor->moveTo(ipx, ipy + 1); memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); pcolor.setOpacity(bbr); writeAccessor->moveTo(ipx + 1, ipy + 1); memcpy(writeAccessor->rawData(), pcolor.data(), m_dabPixelSize); } void SprayBrush::paintCircle(KisPainter* painter, qreal x, qreal y, qreal radius) { QPainterPath path; path.addEllipse(QPointF(x,y),radius,radius); painter->fillPainterPath(path); } void SprayBrush::paintEllipse(KisPainter* painter, qreal x, qreal y, qreal a, qreal b, qreal angle) { QPainterPath path; path.addEllipse(QPointF(), a, b); QTransform t; t.translate(x, y); t.rotateRadians(angle); path = t.map(path); painter->fillPainterPath(path); } void SprayBrush::paintRectangle(KisPainter* painter, qreal x, qreal y, qreal width, qreal height, qreal angle) { QPainterPath path; path.addRect(QRectF(-0.5 * width, -0.5 * height, width, height)); QTransform t; t.translate(x, y); t.rotateRadians(angle); path = t.map(path); painter->fillPainterPath(path); } void SprayBrush::paintOutline(KisPaintDeviceSP dev , const KoColor &outlineColor, qreal posX, qreal posY, qreal radius) { QList antiPixels; KisRandomAccessorSP accessor = dev->createRandomAccessorNG(qRound(posX), qRound(posY)); for (int y = -radius + posY; y <= radius + posY; y++) { for (int x = -radius + posX; x <= radius + posX; x++) { accessor->moveTo(x, y); qreal alpha = dev->colorSpace()->opacityU8(accessor->rawData()); if (alpha != 0) { // top left accessor->moveTo(x - 1, y - 1); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x - 1, y - 1)); //continue; } // top accessor->moveTo(x, y - 1); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x, y - 1)); //continue; } // top right accessor->moveTo(x + 1, y - 1); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x + 1, y - 1)); //continue; } //left accessor->moveTo(x - 1, y); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x - 1, y)); //continue; } //right accessor->moveTo(x + 1, y); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x + 1, y)); //continue; } // bottom left accessor->moveTo(x - 1, y + 1); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x - 1, y + 1)); //continue; } // bottom accessor->moveTo(x, y + 1); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x, y + 1)); //continue; } // bottom right accessor->moveTo(x + 1, y + 1); if (dev->colorSpace()->opacityU8(accessor->rawData()) == 0) { antiPixels.append(QPointF(x + 1, y + 1)); //continue; } } } } // anti-alias it int size = antiPixels.size(); for (int i = 0; i < size; i++) { accessor->moveTo(antiPixels[i].x(), antiPixels[i].y()); memcpy(accessor->rawData(), outlineColor.data(), dev->colorSpace()->pixelSize()); } } void SprayBrush::setFixedDab(KisFixedPaintDeviceSP dab) { m_fixedDab = dab; } diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp index d038b34696..8a0e9ac7da 100644 --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp @@ -1,233 +1,232 @@ /* * Copyright (C) 2015 Wolthera van Hövell tot Westerflier * * 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_tangent_normal_paintop.h" #include #include #include #include #include #include #include #include #include #include #include KisTangentNormalPaintOp::KisTangentNormalPaintOp(const KisBrushBasedPaintOpSettings* settings, KisPainter* painter, KisNodeSP node, KisImageSP image): KisBrushBasedPaintOp(settings, painter), m_opacityOption(node), m_tempDev(painter->device()->createCompositionSourceDevice()) { Q_UNUSED(image); //Init, read settings, etc// m_tangentTiltOption.readOptionSetting(settings); m_sizeOption.readOptionSetting(settings); m_opacityOption.readOptionSetting(settings); m_flowOption.readOptionSetting(settings); m_spacingOption.readOptionSetting(settings); m_softnessOption.readOptionSetting(settings); m_sharpnessOption.readOptionSetting(settings); m_rotationOption.readOptionSetting(settings); m_scatterOption.readOptionSetting(settings); m_sizeOption.resetAllSensors(); m_opacityOption.resetAllSensors(); m_flowOption.resetAllSensors(); m_spacingOption.resetAllSensors(); m_softnessOption.resetAllSensors(); m_sharpnessOption.resetAllSensors(); m_rotationOption.resetAllSensors(); m_scatterOption.resetAllSensors(); m_dabCache->setSharpnessPostprocessing(&m_sharpnessOption); m_rotationOption.applyFanCornersInfo(this); } KisTangentNormalPaintOp::~KisTangentNormalPaintOp() { //destroy things here// } KisSpacingInformation KisTangentNormalPaintOp::paintAt(const KisPaintInformation& info) { /* * For the color, the precision of tilt is only 60x60, and the precision of direction and rotation are 360 and 360*90. * You can't get more precise than 8bit. Therefore, we will check if the current space is RGB, * if so we request a profile with that space and 8bit bit depth, if not, just sRGB */ KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(info, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace);//Should be default RGB(0.5,0.5,1.0) //draw stuff here, return kisspacinginformation. KisBrushSP brush = m_brush; if (!painter()->device() || !brush || !brush->canPaintFor(info)) { return KisSpacingInformation(1.0); } qreal scale = m_sizeOption.apply(info); scale *= KisLodTransform::lodToScale(painter()->device()); - qreal rotation = m_rotationOption.apply(info); - if (checkSizeTooSmall(scale)) return KisSpacingInformation(); + KisDabShape shape(scale, 1.0, rotation); QPointF cursorPos = m_scatterOption.apply(info, - brush->maskWidth(scale, rotation, 0, 0, info), - brush->maskHeight(scale, rotation, 0, 0, info)); + brush->maskWidth(shape, 0, 0, info), + brush->maskHeight(shape, 0, 0, info)); m_maskDab = m_dabCache->fetchDab(rgbColorSpace, color, cursorPos, - scale, scale, rotation, + shape, info, m_softnessOption.apply(info), &m_dstDabRect); if (m_dstDabRect.isEmpty()) return KisSpacingInformation(1.0); QRect dabRect = m_maskDab->bounds(); // sanity check Q_ASSERT(m_dstDabRect.size() == dabRect.size()); Q_UNUSED(dabRect); quint8 oldOpacity = painter()->opacity(); QString oldCompositeOpId = painter()->compositeOp()->id(); m_opacityOption.setFlow(m_flowOption.apply(info)); m_opacityOption.apply(painter(), info); //paint with the default color? Copied this from color smudge.// //painter()->setCompositeOp(COMPOSITE_COPY); //painter()->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); painter()->bltFixed(m_dstDabRect.topLeft(), m_maskDab, m_maskDab->bounds()); painter()->renderMirrorMaskSafe(m_dstDabRect, m_maskDab, !m_dabCache->needSeparateOriginal()); // restore orginal opacity and composite mode values painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); return effectiveSpacing(scale, rotation, m_spacingOption, info); } void KisTangentNormalPaintOp::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 = m_tempDev; } else { m_lineCacheDevice->clear(); } KisPainter p(m_lineCacheDevice); KoColor currentColor = painter()->paintColor(); QString currentSpace = currentColor.colorSpace()->colorModelId().id(); const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); if (currentSpace != "RGBA") { rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { rgbColorSpace = currentColor.colorSpace(); } QVector channelValues(4); qreal r, g, b; if (currentColor.colorSpace()->colorDepthId().id()=="F16" || currentColor.colorSpace()->colorDepthId().id()=="F32"){ channelValues[0] = 0.5;//red channelValues[1] = 0.5;//green channelValues[2] = 1.0;//blue channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(pi2, &r, &g, &b); channelValues[0] = r;//red channelValues[1] = g;//green channelValues[2] = b;//blue } else { channelValues[0] = 1.0;//blue channelValues[1] = 0.5;//green channelValues[2] = 0.5;//red channelValues[3] = 1.0;//alpha, leave alone. m_tangentTiltOption.apply(pi2, &r, &g, &b); channelValues[0] = b;//blue channelValues[1] = g;//green channelValues[2] = r;//red } quint8 data[4]; rgbColorSpace->fromNormalisedChannelsValue(data, channelValues); KoColor color(data, rgbColorSpace); p.setPaintColor(color); 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()); painter()->renderMirrorMask(rc, m_lineCacheDevice); } else { KisPaintOp::paintLine(pi1, pi2, currentDistance); } }