diff --git a/benchmarks/kis_mask_generator_benchmark.cpp b/benchmarks/kis_mask_generator_benchmark.cpp index da3709a01e..e9971a8201 100644 --- a/benchmarks/kis_mask_generator_benchmark.cpp +++ b/benchmarks/kis_mask_generator_benchmark.cpp @@ -1,111 +1,111 @@ /* * Copyright (c) 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 #ifdef HAVE_VC #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #endif #include #include "kis_mask_generator_benchmark.h" #include "kis_circle_mask_generator.h" #include "kis_rect_mask_generator.h" void KisMaskGeneratorBenchmark::benchmarkCircle() { KisCircleMaskGenerator gen(1000, 0.5, 0.5, 0.5, 3, true); QBENCHMARK{ for(int i = -600; i < 600; ++i) { for(int j = -600; j < 600; ++j) { gen.valueAt(i, j); } } } } #include #include #include "kis_fixed_paint_device.h" #include "kis_types.h" #include "kis_brush_mask_applicator_base.h" #include "krita_utils.h" void benchmarkSIMD(qreal fade) { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisFixedPaintDeviceSP dev = new KisFixedPaintDevice(cs); dev->setRect(QRect(0, 0, 1000, 1000)); dev->initialize(); - MaskProcessingData data(dev, cs, + MaskProcessingData data(dev, cs, nullptr, 0.0, 1.0, 500, 500, 0); KisCircleMaskGenerator gen(1000, 1.0, fade, fade, 2, false); KisBrushMaskApplicatorBase *applicator = gen.applicator(); applicator->initializeData(&data); QVector rects = KritaUtils::splitRectIntoPatches(dev->bounds(), QSize(63, 63)); QBENCHMARK{ Q_FOREACH (const QRect &rc, rects) { applicator->process(rc); } } } void KisMaskGeneratorBenchmark::benchmarkSIMD_SharpBrush() { benchmarkSIMD(1.0); } void KisMaskGeneratorBenchmark::benchmarkSIMD_FadedBrush() { benchmarkSIMD(0.5); } void KisMaskGeneratorBenchmark::benchmarkSquare() { KisRectangleMaskGenerator gen(1000, 0.5, 0.5, 0.5, 3, true); QBENCHMARK{ for(int i = -600; i < 600; ++i) { for(int j = -600; j < 600; ++j) { gen.valueAt(i, j); } } } } QTEST_MAIN(KisMaskGeneratorBenchmark) diff --git a/libs/brush/CMakeLists.txt b/libs/brush/CMakeLists.txt index a6f851515d..a7ef3a88ed 100644 --- a/libs/brush/CMakeLists.txt +++ b/libs/brush/CMakeLists.txt @@ -1,51 +1,52 @@ add_subdirectory( tests ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ) set(kritalibbrush_LIB_SRCS kis_predefined_brush_factory.cpp kis_auto_brush.cpp kis_boundary.cc kis_brush.cpp kis_scaling_size_brush.cpp kis_brush_registry.cpp KisBrushServerProvider.cpp kis_gbr_brush.cpp kis_abr_brush.cpp kis_abr_brush_collection.cpp kis_imagepipe_brush.cpp kis_pipebrush_parasite.cpp kis_png_brush.cpp kis_svg_brush.cpp kis_qimage_pyramid.cpp KisSharedQImagePyramid.cpp kis_text_brush.cpp kis_auto_brush_factory.cpp kis_text_brush_factory.cpp KisAbrStorage.cpp + KisColorfulBrush.cpp ) add_library(kritalibbrush SHARED ${kritalibbrush_LIB_SRCS} ) generate_export_header(kritalibbrush BASE_NAME kritabrush EXPORT_MACRO_NAME BRUSH_EXPORT) if (WIN32) target_link_libraries(kritalibbrush kritaimage Qt5::Svg ${WIN32_PLATFORM_NET_LIBS}) else () target_link_libraries(kritalibbrush kritaimage Qt5::Svg) endif () if(HAVE_VC) include_directories(SYSTEM ${Vc_INCLUDE_DIR}) target_link_libraries(kritalibbrush ${Vc_LIBRARIES}) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}") endif() set_target_properties(kritalibbrush PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritalibbrush ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/libs/brush/KisColorfulBrush.cpp b/libs/brush/KisColorfulBrush.cpp new file mode 100644 index 0000000000..3bcb21184c --- /dev/null +++ b/libs/brush/KisColorfulBrush.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020 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 "KisColorfulBrush.h" + + +KisColorfulBrush::KisColorfulBrush(const QString &filename) + : KisScalingSizeBrush(filename) +{ +} + +void KisColorfulBrush::setUseColorAsMask(bool useColorAsMask) +{ + /** + * WARNING: There is a problem in the brush server, since it + * returns not copies of brushes, but direct pointers to them. It + * means that the brushes are shared among all the currently + * present paintops, which might be a problem for e.g. Multihand + * Brush Tool. + * + * Right now, all the instances of Multihand Brush Tool share the + * same brush, so there is no problem in this sharing, unless we + * reset the internal state of the brush on our way. + */ + + if (useColorAsMask != m_useColorAsMask) { + m_useColorAsMask = useColorAsMask; + resetBoundary(); + clearBrushPyramid(); + } +} + +bool KisColorfulBrush::useColorAsMask() const +{ + return m_useColorAsMask; +} + +#include + +QImage KisColorfulBrush::brushTipImage() const +{ + QImage image = KisBrush::brushTipImage(); + if (hasColor() && useColorAsMask()) { + if (m_adjustmentMidPoint != 127 || + !qFuzzyIsNull(m_brightnessAdjustment) || + !qFuzzyIsNull(m_contrastAdjustment)) { + + const int half = KoColorSpaceMathsTraits::halfValue; + const int unit = KoColorSpaceMathsTraits::unitValue; + + const qreal midX = m_adjustmentMidPoint; + const qreal midY = m_brightnessAdjustment > 0 ? + KoColorSpaceMaths::blend(unit, half, m_brightnessAdjustment) : + KoColorSpaceMaths::blend(0, half, -m_brightnessAdjustment); + + qreal loA = 0.0; + qreal hiA = 0.0; + + qreal loB = 0.0; + qreal hiB = 255.0; + + if (!qFuzzyCompare(m_contrastAdjustment, 1.0)) { + loA = midY / (1.0 - m_contrastAdjustment) / midX; + hiA = (unit - midY) / (1.0 - m_contrastAdjustment) / (unit - midX); + + loB = midY - midX * loA; + hiB = midY - midX * hiA; + } + + for (int y = 0; y < image.height(); y++) { + QRgb *pixel = reinterpret_cast(image.scanLine(y)); + for (int x = 0; x < image.width(); x++) { + QRgb c = pixel[x]; + + int v = qGray(c); + + if (v >= midX) { + v = qMin(unit, qRound(hiA * v + hiB)); + } else { + v = qMax(0, qRound(loA * v + loB)); + } + + pixel[x] = qRgba(v, v, v, qAlpha(c)); + } + } + } else { + for (int y = 0; y < image.height(); y++) { + QRgb *pixel = reinterpret_cast(image.scanLine(y)); + for (int x = 0; x < image.width(); x++) { + QRgb c = pixel[x]; + + int v = qGray(c); + pixel[x] = qRgba(v, v, v, qAlpha(c)); + } + } + } + } + return image; +} + +void KisColorfulBrush::setAdjustmentMidPoint(quint8 value) +{ + m_adjustmentMidPoint = value; +} + +void KisColorfulBrush::setBrightnessAdjustment(qreal value) +{ + m_brightnessAdjustment = value; +} + +void KisColorfulBrush::setContrastAdjustment(qreal value) +{ + m_contrastAdjustment = value; +} + +quint8 KisColorfulBrush::adjustmentMidPoint() const +{ + return m_adjustmentMidPoint; +} + +qreal KisColorfulBrush::brightnessAdjustment() const +{ + return m_brightnessAdjustment; +} + +qreal KisColorfulBrush::contrastAdjustment() const +{ + return m_contrastAdjustment; +} + +#include + +void KisColorfulBrush::toXML(QDomDocument& d, QDomElement& e) const +{ + e.setAttribute("ColorAsMask", QString::number((int)useColorAsMask())); + e.setAttribute("AdjustmentMidPoint", QString::number(m_adjustmentMidPoint)); + e.setAttribute("BrightnessAdjustment", QString::number(m_brightnessAdjustment)); + e.setAttribute("ContrastAdjustment", QString::number(m_contrastAdjustment)); + KisBrush::toXML(d, e); +} diff --git a/libs/brush/KisColorfulBrush.h b/libs/brush/KisColorfulBrush.h new file mode 100644 index 0000000000..fdbfea65ab --- /dev/null +++ b/libs/brush/KisColorfulBrush.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020 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 KISCOLORFULBRUSH_H +#define KISCOLORFULBRUSH_H + +#include "kis_scaling_size_brush.h" + + +class BRUSH_EXPORT KisColorfulBrush : public KisScalingSizeBrush +{ +public: + KisColorfulBrush() = default; + KisColorfulBrush(const QString& filename); + KisColorfulBrush(const KisColorfulBrush &rhs) = default; + + /** + * If the brush image data are colorful (e.g. you created the brush from the canvas with custom brush) + * and you want to paint with it as with masks, set to true. + */ + virtual void setUseColorAsMask(bool useColorAsMask); + bool useColorAsMask() const; + + QImage brushTipImage() const override; + + virtual void setAdjustmentMidPoint(quint8 value); + virtual void setBrightnessAdjustment(qreal value); + virtual void setContrastAdjustment(qreal value); + + quint8 adjustmentMidPoint() const; + qreal brightnessAdjustment() const; + qreal contrastAdjustment() const; + + void toXML(QDomDocument& d, QDomElement& e) const override; + +private: + bool m_useColorAsMask = false; + quint8 m_adjustmentMidPoint = 127; + qreal m_brightnessAdjustment = 0.0; + qreal m_contrastAdjustment = 0.0; +}; + +#endif // KISCOLORFULBRUSH_H diff --git a/libs/brush/kis_auto_brush.cpp b/libs/brush/kis_auto_brush.cpp index e8d60b8f12..c190ef3540 100644 --- a/libs/brush/kis_auto_brush.cpp +++ b/libs/brush/kis_auto_brush.cpp @@ -1,403 +1,396 @@ /* * Copyright (c) 2004,2007-2009 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2012 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include "kis_auto_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(_WIN32) || defined(_WIN64) #include #define srand48 srand inline double drand48() { return double(rand()) / RAND_MAX; } #endif struct KisAutoBrush::Private { Private() : randomness(0) , density(1.0) , idealThreadCountCached(1) {} Private(const Private &rhs) : shape(rhs.shape->clone()) , randomness(rhs.randomness) , density(rhs.density) , idealThreadCountCached(rhs.idealThreadCountCached) { } QScopedPointer shape; qreal randomness; qreal density; int idealThreadCountCached; }; KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density) : KoEphemeralResource(), d(new Private) { d->shape.reset(as); d->randomness = randomness; d->density = density; d->idealThreadCountCached = QThread::idealThreadCount(); setBrushType(MASK); setWidth(qMax(qreal(1.0), d->shape->width())); setHeight(qMax(qreal(1.0), d->shape->height())); QImage image = createBrushPreview(); setBrushTipImage(image); // Set angle here so brush tip image is generated unrotated setAngle(angle); image = createBrushPreview(); setImage(image); } KisAutoBrush::~KisAutoBrush() { } qreal KisAutoBrush::userEffectiveSize() const { return d->shape->diameter(); } void KisAutoBrush::setUserEffectiveSize(qreal value) { d->shape->setDiameter(value); } KisAutoBrush::KisAutoBrush(const KisAutoBrush& rhs) : KoEphemeralResource(rhs) , d(new Private(*rhs.d)) { } KoResourceSP KisAutoBrush::clone() const { return KoResourceSP(new KisAutoBrush(*this)); } /* It's difficult to predict the mask height exactly when there are * more than 2 spikes, so we return an upperbound instead. */ static KisDabShape lieAboutDabShape(KisDabShape const& shape, int spikes) { return spikes > 2 ? KisDabShape(shape.scale(), 1.0, shape.rotation()) : shape; } qint32 KisAutoBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { return KisBrush::maskHeight( lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info); } qint32 KisAutoBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { return KisBrush::maskWidth( lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info); } QSizeF KisAutoBrush::characteristicSize(KisDabShape const& shape) const { return KisBrush::characteristicSize(lieAboutDabShape(shape, maskGenerator()->spikes())); } inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size) { /** * This version of filling uses low granularity of data transfers * (32-bit chunks) and internal processor's parallelism. It reaches * 25% better performance in KisStrokeBenchmark in comparison to * per-pixel memcpy version (tested on Sandy Bridge). */ int block1 = size / 8; int block2 = size % 8; quint32 *src = reinterpret_cast(color); quint32 *dst = reinterpret_cast(buf); // check whether all buffers are 4 bytes aligned // (uncomment if experience some problems) // Q_ASSERT(((qint64)src & 3) == 0); // Q_ASSERT(((qint64)dst & 3) == 0); for (int i = 0; i < block1; i++) { *dst = *src; *(dst + 1) = *src; *(dst + 2) = *src; *(dst + 3) = *src; *(dst + 4) = *src; *(dst + 5) = *src; *(dst + 6) = *src; *(dst + 7) = *src; dst += 8; } for (int i = 0; i < block2; i++) { *dst = *src; dst++; } } inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize) { /** * This version uses internal processor's parallelism and gives * 20% better performance in KisStrokeBenchmark in comparison to * per-pixel memcpy version (tested on Sandy Bridge (+20%) and * on Merom (+10%)). */ int block1 = size / 8; int block2 = size % 8; for (int i = 0; i < block1; i++) { quint8 *d1 = buf; quint8 *d2 = buf + pixelSize; quint8 *d3 = buf + 2 * pixelSize; quint8 *d4 = buf + 3 * pixelSize; quint8 *d5 = buf + 4 * pixelSize; quint8 *d6 = buf + 5 * pixelSize; quint8 *d7 = buf + 6 * pixelSize; quint8 *d8 = buf + 7 * pixelSize; for (int j = 0; j < pixelSize; j++) { *(d1 + j) = color[j]; *(d2 + j) = color[j]; *(d3 + j) = color[j]; *(d4 + j) = color[j]; *(d5 + j) = color[j]; *(d6 + j) = color[j]; *(d7 + j) = color[j]; *(d8 + j) = color[j]; } buf += 8 * pixelSize; } for (int i = 0; i < block2; i++) { memcpy(buf, color, pixelSize); buf += pixelSize; } } void KisAutoBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) const { 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(shape, subPixelX, subPixelY, info); int dstHeight = maskHeight(shape, subPixelX, subPixelY, info); QPointF hotSpot = this->hotSpot(shape, info); // mask size and hotSpot function take the KisBrush rotation into account qreal angle = shape.rotation() + KisBrush::angle(); // if there's coloring information, we merely change the alpha: in that case, // the dab should be big enough! if (coloringInformation) { // new bounds. we don't care if there is some extra memory occcupied. dst->setRect(QRect(0, 0, dstWidth, dstHeight)); dst->lazyGrowBufferWithoutInitialization(); } else { KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth && dst->bounds().height() >= dstHeight); } + KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation); + quint8* dabPointer = dst->data(); quint8* color = 0; - if (coloringInformation) { - if (dynamic_cast(coloringInformation)) { - color = const_cast(coloringInformation->color()); - } + if (dynamic_cast(coloringInformation)) { + color = const_cast(coloringInformation->color()); } double centerX = hotSpot.x() - 0.5 + subPixelX; double centerY = hotSpot.y() - 0.5 + subPixelY; d->shape->setSoftness(softnessFactor); // softness must be set first d->shape->setScale(shape.scaleX(), shape.scaleY()); - if (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(); + if (!color) { + for (int y = 0; y < dstHeight; y++) { + for (int x = 0; x < dstWidth; x++) { + memcpy(dabPointer, coloringInformation->color(), pixelSize); + coloringInformation->nextColumn(); + dabPointer += pixelSize; } + coloringInformation->nextRow(); } } - MaskProcessingData data(dst, cs, d->randomness, d->density, + MaskProcessingData data(dst, cs, color, + d->randomness, d->density, centerX, centerY, angle); KisBrushMaskApplicatorBase *applicator = d->shape->applicator(); applicator->initializeData(&data); int jobs = d->idealThreadCountCached; if (threadingAllowed() && dstHeight > 100 && jobs >= 4) { int splitter = dstHeight / jobs; QVector rects; for (int i = 0; i < jobs - 1; i++) { rects << QRect(0, i * splitter, dstWidth, splitter); } rects << QRect(0, (jobs - 1)*splitter, dstWidth, dstHeight - (jobs - 1)*splitter); OperatorWrapper wrapper(applicator); QtConcurrent::blockingMap(rects, wrapper); } else { QRect rect(0, 0, dstWidth, dstHeight); applicator->process(rect); } } void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const { QDomElement shapeElt = doc.createElement("MaskGenerator"); d->shape->toXML(doc, shapeElt); e.appendChild(shapeElt); e.setAttribute("type", "auto_brush"); e.setAttribute("spacing", QString::number(spacing())); e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); e.setAttribute("angle", QString::number(KisBrush::angle())); e.setAttribute("randomness", QString::number(d->randomness)); e.setAttribute("density", QString::number(d->density)); KisBrush::toXML(doc, e); } QImage KisAutoBrush::createBrushPreview() { int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation()); int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation()); KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0); KisFixedPaintDeviceSP fdev = new KisFixedPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); fdev->setRect(QRect(0, 0, width, height)); fdev->initialize(); mask(fdev, KoColor(Qt::black, fdev->colorSpace()), KisDabShape(), info); return fdev->convertToQImage(0); } const KisMaskGenerator* KisAutoBrush::maskGenerator() const { return d->shape.data(); } qreal KisAutoBrush::density() const { return d->density; } qreal KisAutoBrush::randomness() const { return d->randomness; } QPainterPath KisAutoBrush::outline() const { bool simpleOutline = (d->density < 1.0); if (simpleOutline) { QPainterPath path; QRectF brushBoundingbox(0, 0, width(), height()); if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) { path.addEllipse(brushBoundingbox); } else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE) path.addRect(brushBoundingbox); } return path; } return KisBrush::boundary()->path(); } void KisAutoBrush::lodLimitations(KisPaintopLodLimitations *l) const { KisBrush::lodLimitations(l); if (!qFuzzyCompare(density(), 1.0)) { l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0")); } if (!qFuzzyCompare(randomness(), 0.0)) { l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0")); } } diff --git a/libs/brush/kis_brush.cpp b/libs/brush/kis_brush.cpp index fcd3dd7a35..cc72a1b1f6 100644 --- a/libs/brush/kis_brush.cpp +++ b/libs/brush/kis_brush.cpp @@ -1,637 +1,653 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Adrian Page * Copyright (c) 2005 Bart Coppens * Copyright (c) 2007 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_boundary.h" #include "kis_image.h" #include "kis_iterator_ng.h" #include "kis_brush_registry.h" #include #include #include #include #include KisBrush::ColoringInformation::~ColoringInformation() { } KisBrush::PlainColoringInformation::PlainColoringInformation(const quint8* color) : m_color(color) { } KisBrush::PlainColoringInformation::~PlainColoringInformation() { } const quint8* KisBrush::PlainColoringInformation::color() const { return m_color; } void KisBrush::PlainColoringInformation::nextColumn() { } void KisBrush::PlainColoringInformation::nextRow() { } KisBrush::PaintDeviceColoringInformation::PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width) : m_source(source) , m_iterator(m_source->createHLineConstIteratorNG(0, 0, width)) { } KisBrush::PaintDeviceColoringInformation::~PaintDeviceColoringInformation() { } const quint8* KisBrush::PaintDeviceColoringInformation::color() const { return m_iterator->oldRawData(); } void KisBrush::PaintDeviceColoringInformation::nextColumn() { m_iterator->nextPixel(); } void KisBrush::PaintDeviceColoringInformation::nextRow() { m_iterator->nextRow(); } struct KisBrush::Private { Private() : brushType(INVALID) , hasColor(false) + , preserveLightness(false) , angle(0) , scale(1.0) , autoSpacingActive(false) , autoSpacingCoeff(1.0) , threadingAllowed(true) {} Private(const Private &rhs) : brushType(rhs.brushType), width(rhs.width), height(rhs.height), spacing(rhs.spacing), hotSpot(rhs.hotSpot), hasColor(rhs.hasColor), + preserveLightness(rhs.preserveLightness), angle(rhs.angle), scale(rhs.scale), autoSpacingActive(rhs.autoSpacingActive), autoSpacingCoeff(rhs.autoSpacingCoeff), threadingAllowed(rhs.threadingAllowed), brushTipImage(rhs.brushTipImage), /** * Be careful! The pyramid is shared between two brush objects, * therefore you cannot change it, only recreate! That is the * reason why it is defined as const! */ brushPyramid(rhs.brushPyramid) { // don't copy the boundary, it will be regenerated -- see bug 291910 } ~Private() { } mutable QScopedPointer boundary; enumBrushType brushType; qint32 width; qint32 height; double spacing; QPointF hotSpot; bool hasColor; + bool preserveLightness; qreal angle; qreal scale; bool autoSpacingActive; qreal autoSpacingCoeff; bool threadingAllowed; QImage brushTipImage; mutable QSharedPointer brushPyramid; }; KisBrush::KisBrush() : KoResource(QString()) , d(new Private) { } KisBrush::KisBrush(const QString& filename) : KoResource(filename) , d(new Private) { } KisBrush::KisBrush(const KisBrush& rhs) : KoResource(rhs) , d(new Private(*rhs.d)) { } KisBrush::~KisBrush() { delete d; } QImage KisBrush::brushTipImage() const { KIS_SAFE_ASSERT_RECOVER_NOOP(!d->brushTipImage.isNull()); return d->brushTipImage; } qint32 KisBrush::width() const { return d->width; } void KisBrush::setWidth(qint32 width) { d->width = width; } qint32 KisBrush::height() const { return d->height; } void KisBrush::setHeight(qint32 height) { d->height = height; } void KisBrush::setHotSpot(QPointF pt) { double x = pt.x(); double y = pt.y(); if (x < 0) x = 0; else if (x >= width()) x = width() - 1; if (y < 0) y = 0; else if (y >= height()) y = height() - 1; d->hotSpot = QPointF(x, y); } QPointF KisBrush::hotSpot(KisDabShape const& shape, const KisPaintInformation& info) const { Q_UNUSED(info); QSizeF metric = characteristicSize(shape); qreal w = metric.width(); qreal h = metric.height(); // The smallest brush we can produce is a single pixel. if (w < 1) { w = 1; } if (h < 1) { h = 1; } // XXX: This should take d->hotSpot into account, though it // isn't specified by gimp brushes so it would default to the center // anyway. QPointF p(w / 2, h / 2); return p; } bool KisBrush::hasColor() const { return d->hasColor; } void KisBrush::setHasColor(bool hasColor) { d->hasColor = hasColor; } +bool KisBrush::preserveLightness() const +{ + return d->preserveLightness; +} + +void KisBrush::setPreserveLightness(bool preserveLightness) +{ + d->preserveLightness = preserveLightness; +} + + bool KisBrush::isPiercedApprox() const { QImage image = brushTipImage(); qreal w = image.width(); qreal h = image.height(); qreal xPortion = qMin(0.1, 5.0 / w); qreal yPortion = qMin(0.1, 5.0 / h); int x0 = std::floor((0.5 - xPortion) * w); int x1 = std::ceil((0.5 + xPortion) * w); int y0 = std::floor((0.5 - yPortion) * h); int y1 = std::ceil((0.5 + yPortion) * h); const int maxNumSamples = (x1 - x0 + 1) * (y1 - y0 + 1); const int failedPixelsThreshold = 0.1 * maxNumSamples; const int thresholdValue = 0.95 * 255; int failedPixels = 0; for (int y = y0; y <= y1; y++) { for (int x = x0; x <= x1; x++) { QRgb pixel = image.pixel(x,y); if (qRed(pixel) > thresholdValue) { failedPixels++; } } } return failedPixels > failedPixelsThreshold; } bool KisBrush::canPaintFor(const KisPaintInformation& /*info*/) { return true; } void KisBrush::setBrushTipImage(const QImage& image) { d->brushTipImage = image; if (!image.isNull()) { if (image.width() > 128 || image.height() > 128) { KoResource::setImage(image.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } else { KoResource::setImage(image); } setWidth(image.width()); setHeight(image.height()); } clearBrushPyramid(); } void KisBrush::setBrushType(enumBrushType type) { d->brushType = type; } enumBrushType KisBrush::brushType() const { return d->brushType; } void KisBrush::predefinedBrushToXML(const QString &type, QDomElement& e) const { e.setAttribute("type", type); e.setAttribute("filename", filename()); e.setAttribute("spacing", QString::number(spacing())); e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive())); e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff())); e.setAttribute("angle", QString::number(angle())); e.setAttribute("scale", QString::number(scale())); + e.setAttribute("preserveLightness", QString::number((int)preserveLightness())); } void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const { element.setAttribute("BrushVersion", "2"); } KisBrushSP KisBrush::fromXML(const QDomElement& element, KisResourcesInterfaceSP resourcesInterface) { KisBrushSP brush = KisBrushRegistry::instance()->createBrush(element, resourcesInterface); if (brush && element.attribute("BrushVersion", "1") == "1") { brush->setScale(brush->scale() * 2.0); } return brush; } QSizeF KisBrush::characteristicSize(KisDabShape const& shape) const { KisDabShape normalizedShape( shape.scale() * d->scale, shape.ratio(), normalizeAngle(shape.rotation() + d->angle)); return KisQImagePyramid::characteristicSize( QSize(width(), height()), normalizedShape); } qint32 KisBrush::maskWidth(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); qreal angle = normalizeAngle(shape.rotation() + d->angle); qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).width(); } qint32 KisBrush::maskHeight(KisDabShape const& shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const { Q_UNUSED(info); qreal angle = normalizeAngle(shape.rotation() + d->angle); qreal scale = shape.scale() * d->scale; return KisQImagePyramid::imageSize(QSize(width(), height()), KisDabShape(scale, shape.ratio(), angle), subPixelX, subPixelY).height(); } double KisBrush::maskAngle(double angle) const { return normalizeAngle(angle + d->angle); } quint32 KisBrush::brushIndex(const KisPaintInformation& info) const { Q_UNUSED(info); return 0; } void KisBrush::setSpacing(double s) { if (s < 0.02) s = 0.02; d->spacing = s; } double KisBrush::spacing() const { return d->spacing; } void KisBrush::setAutoSpacing(bool active, qreal coeff) { d->autoSpacingCoeff = coeff; d->autoSpacingActive = active; } bool KisBrush::autoSpacingActive() const { return d->autoSpacingActive; } qreal KisBrush::autoSpacingCoeff() const { return d->autoSpacingCoeff; } void KisBrush::notifyStrokeStarted() { } void KisBrush::notifyCachedDabPainted(const KisPaintInformation& info) { Q_UNUSED(info); } void KisBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo) { Q_UNUSED(info); Q_UNUSED(seqNo); } void KisBrush::setThreadingAllowed(bool value) { d->threadingAllowed = value; } bool KisBrush::threadingAllowed() const { return d->threadingAllowed; } void KisBrush::clearBrushPyramid() { d->brushPyramid.reset(new KisSharedQImagePyramid()); } void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const { PlainColoringInformation pci(color.data()); generateMaskAndApplyMaskOrCreateDab(dst, &pci, shape, info, subPixelX, subPixelY, softnessFactor); } 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(shape, subPixelX, subPixelY, info)); generateMaskAndApplyMaskOrCreateDab(dst, &pdci, shape, info, subPixelX, subPixelY, softnessFactor); } +namespace { +void fetchPremultipliedRed(const QRgb* src, quint8 *dst, int maskWidth) +{ + for (int x = 0; x < maskWidth; x++) { + *dst = KoColorSpaceMaths::multiply(255 - *src, qAlpha(*src)); + src++; + dst++; + } +} +} + +//#define SANITY_CHECK void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info_, double subPixelX, double subPixelY, qreal softnessFactor) const { KIS_SAFE_ASSERT_RECOVER_RETURN(valid()); Q_UNUSED(info_); Q_UNUSED(softnessFactor); QImage outputImage = d->brushPyramid->pyramid(this)->createImage(KisDabShape( shape.scale() * d->scale, shape.ratio(), -normalizeAngle(shape.rotation() + d->angle)), subPixelX, subPixelY); qint32 maskWidth = outputImage.width(); qint32 maskHeight = outputImage.height(); dst->setRect(QRect(0, 0, maskWidth, maskHeight)); dst->lazyGrowBufferWithoutInitialization(); - quint8* color = 0; + KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation); - if (coloringInformation) { - if (dynamic_cast(coloringInformation)) { - color = const_cast(coloringInformation->color()); - } + quint8* color = 0; + if (dynamic_cast(coloringInformation)) { + color = const_cast(coloringInformation->color()); } +#ifdef SANITY_CHECK + // we expect that brushTipImage() has removed image color for + // us already + KIS_SAFE_ASSERT_RECOVER_NOOP(outputImage.allGray()); +#endif + 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(); + const quint32 pixelSize = cs->pixelSize(); + quint8 *rowPointer = dst->data(); + + const bool preserveLightness = this->preserveLightness(); for (int y = 0; y < maskHeight; y++) { const quint8* maskPointer = outputImage.constScanLine(y); - if (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 (color) { + if (preserveLightness) { + cs->fillGrayBrushWithColorAndLightnessOverlay(rowPointer, reinterpret_cast(maskPointer), color, maskWidth); } - } - - 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 { + cs->fillGrayBrushWithColor(rowPointer, reinterpret_cast(maskPointer), color, maskWidth); } } 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++; + { + quint8 *dst = rowPointer; + for (int x = 0; x < maskWidth; x++) { + memcpy(dst, coloringInformation->color(), pixelSize); + coloringInformation->nextColumn(); + dst += pixelSize; + } } + + QScopedArrayPointer alphaArray(new quint8[maskWidth]); + fetchPremultipliedRed(reinterpret_cast(maskPointer), alphaArray.data(), maskWidth); + cs->applyAlphaU8Mask(rowPointer, alphaArray.data(), maskWidth); } - cs->applyAlphaU8Mask(rowPointer, alphaArray, maskWidth); rowPointer += maskWidth * pixelSize; - dabPointer = rowPointer; - if (!color && coloringInformation) { + if (!color) { coloringInformation->nextRow(); } } - delete[] alphaArray; + } KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { Q_ASSERT(valid()); Q_UNUSED(info); double angle = normalizeAngle(shape.rotation() + d->angle); double scale = shape.scale() * d->scale; QImage outputImage = d->brushPyramid->pyramid(this)->createImage( KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY); KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace); Q_CHECK_PTR(dab); dab->convertFromQImage(outputImage, ""); return dab; } void KisBrush::resetBoundary() { d->boundary.reset(); } void KisBrush::generateBoundary() const { KisFixedPaintDeviceSP dev; KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle()); if (brushType() == IMAGE || brushType() == PIPE_IMAGE) { dev = paintDevice(KoColorSpaceRegistry::instance()->rgb8(), inverseTransform, KisPaintInformation()); } else { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); dev = new KisFixedPaintDevice(cs); mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation()); } d->boundary.reset(new KisBoundary(dev)); d->boundary->generateBoundary(); } const KisBoundary* KisBrush::boundary() const { if (!d->boundary) generateBoundary(); return d->boundary.data(); } 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 2f914c3f16..53995e88cb 100644 --- a/libs/brush/kis_brush.h +++ b/libs/brush/kis_brush.h @@ -1,380 +1,389 @@ /* * 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 QSharedPointer 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: class ColoringInformation { public: virtual ~ColoringInformation(); virtual const quint8* color() const = 0; virtual void nextColumn() = 0; virtual void nextRow() = 0; }; protected: class PlainColoringInformation : public ColoringInformation { public: PlainColoringInformation(const quint8* color); ~PlainColoringInformation() override; const quint8* color() const override ; void nextColumn() override; void nextRow() override; private: const quint8* m_color; }; class PaintDeviceColoringInformation : public ColoringInformation { public: PaintDeviceColoringInformation(const KisPaintDeviceSP source, int width); ~PaintDeviceColoringInformation() override; const quint8* color() const override ; void nextColumn() override; void nextRow() override; private: const KisPaintDeviceSP m_source; KisHLineConstIteratorSP m_iterator; }; public: KisBrush(); KisBrush(const QString& filename); ~KisBrush() override; KisBrush(const KisBrush &rhs); KisBrush &operator=(const KisBrush &rhs) = delete; virtual qreal userEffectiveSize() const = 0; virtual void setUserEffectiveSize(qreal value) = 0; QPair resourceType() const override { return QPair(ResourceType::Brushes, ""); } /** * @brief brushImage the image the brush tip can paint with. Not all brush types have a single * image. * @return a valid QImage. */ virtual QImage brushTipImage() const; /** * Change the spacing of the brush. * @param spacing a spacing of 1.0 means that strokes will be separated from one time the size * of the brush. */ virtual void setSpacing(double spacing); /** * @return the spacing between two strokes for this brush */ double spacing() const; void setAutoSpacing(bool active, qreal coeff); bool autoSpacingActive() const; qreal autoSpacingCoeff() const; /** * @return the width (for scale == 1.0) */ qint32 width() const; /** * @return the height (for scale == 1.0) */ qint32 height() const; /** * @return the width of the mask for the given scale and angle */ virtual qint32 maskWidth(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const; /** * @return the height of the mask for the given scale and angle */ virtual qint32 maskHeight(KisDabShape const&, qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const; /** * @return the logical size of the brush, that is the size measured * in floating point value. * * This value should not be used for calculating future dab sizes * because it doesn't take any rounding into account. The only use * of this metric is calculation of brush-size derivatives like * hotspots and spacing. */ virtual QSizeF characteristicSize(KisDabShape const&) const; /** * @return the angle of the mask adding the given angle */ double maskAngle(double angle = 0) const; /** * @return the index of the brush * if the brush consists of multiple images */ virtual quint32 brushIndex(const KisPaintInformation& info) const; /** * The brush type defines how the brush is used. */ virtual enumBrushType brushType() const; QPointF hotSpot(KisDabShape const&, const KisPaintInformation& info) const; /** * Returns true if this brush can return something useful for the info. This is used * by Pipe Brushes that can't paint sometimes **/ virtual bool canPaintFor(const KisPaintInformation& /*info*/); /** * Is called by the paint op when a paintop starts a stroke. The * point is that we store brushes a server while the paint ops are * are recreated all the time. Is means that upon a stroke start * the brushes may need to clear its state. */ virtual void notifyStrokeStarted(); /** * Is called by the cache, when cache hit has happened. * Having got this notification the brush can update the counters * of dabs, generate some new random values if needed. * * * NOTE: one should use **either** notifyCachedDabPainted() or prepareForSeqNo() * * Currently, this is used by pipe'd brushes to implement * incremental and random parasites */ virtual void notifyCachedDabPainted(const KisPaintInformation& info); /** * Is called by the multithreaded queue to prepare a specific brush * tip for the particular seqNo. * * NOTE: one should use **either** notifyCachedDabPainted() or prepareForSeqNo() * * Currently, this is used by pipe'd brushes to implement * incremental and random parasites */ virtual void prepareForSeqNo(const KisPaintInformation& info, int seqNo); /** * Notify the brush if it can use QtConcurrent's threading capabilities in its * internal routines. By default it is allowed, but some paintops (who do their * own multithreading) may ask the brush to avoid internal threading. */ void setThreadingAllowed(bool value); /** * \see setThreadingAllowed() for details */ bool threadingAllowed() const; /** * Return a fixed paint device that contains a correctly scaled image dab. */ virtual KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0) const; /** * clear dst fill it with a mask colored with KoColor */ void mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; /** * clear dst and fill it with a mask colored with the corresponding colors of src */ void mask(KisFixedPaintDeviceSP dst, const KisPaintDeviceSP src, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; virtual bool hasColor() const; + virtual bool preserveLightness() const; + + /** + * If the brush image data are colorful (e.g. you created the brush from the canvas with custom brush) + * and you want to paint with it as with masks, but preserve Lightness (Value), set to true. + */ + virtual void setPreserveLightness(bool preserveLightness); + + /** * Create a mask and either mask dst (that is, change all alpha values of the * existing pixels to those of the mask) or, if coloringInfo is present, clear * dst and fill dst with pixels according to coloringInfo, masked according to the * generated mask. * * @param dst the destination that will be draw on the image, and this function * will edit its alpha channel * @param coloringInfo coloring information that will be copied on the dab, it can be null * @param shape a shape applied on the alpha mask * @param info the painting information (this is only and should only be used by * KisImagePipeBrush and only to be backward compatible with the Gimp, * KisImagePipeBrush is ignoring scale and angle information) * @param subPixelX sub position of the brush (contained between 0.0 and 1.0) * @param subPixelY sub position of the brush (contained between 0.0 and 1.0) * @param softnessFactor softness factor of the brush * * @return a mask computed from the grey-level values of the * pixels in the brush. */ virtual void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, ColoringInformation* coloringInfo, KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const; /** * Serialize this brush to XML. */ virtual void toXML(QDomDocument& , QDomElement&) const; static KisBrushSP fromXML(const QDomElement& element, KisResourcesInterfaceSP resourcesInterface); virtual const KisBoundary* boundary() const; virtual QPainterPath outline() const; virtual void setScale(qreal _scale); qreal scale() const; virtual void setAngle(qreal _angle); qreal angle() const; void clearBrushPyramid(); virtual void lodLimitations(KisPaintopLodLimitations *l) const; protected: void setWidth(qint32 width); void setHeight(qint32 height); void setHotSpot(QPointF); /** * XXX */ virtual void setBrushType(enumBrushType type); virtual void setHasColor(bool hasColor); public: /** * The image is used to represent the brush in the gui, and may also, depending on the brush type * be used to define the actual brush instance. */ virtual void setBrushTipImage(const QImage& image); /** * Returns true if the brush has a bunch of pixels almost * fully transparent in the very center. If the brush is pierced, * then dulling mode may not work correctly due to empty samples. * * WARNING: this method is relatively expensive since it iterates * up to 100 pixels of the brush. */ bool isPiercedApprox() const; protected: void resetBoundary(); void predefinedBrushToXML(const QString &type, QDomElement& e) const; private: // Initialize our boundary void generateBoundary() const; struct Private; Private* const d; }; #endif // KIS_BRUSH_ diff --git a/libs/brush/kis_brushes_pipe.h b/libs/brush/kis_brushes_pipe.h index 804fef40e6..27e98cac98 100644 --- a/libs/brush/kis_brushes_pipe.h +++ b/libs/brush/kis_brushes_pipe.h @@ -1,194 +1,200 @@ /* * 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 #include template class KisBrushesPipe { public: KisBrushesPipe() { } KisBrushesPipe(const KisBrushesPipe &rhs) { m_brushes.clear(); Q_FOREACH (QSharedPointer brush, rhs.m_brushes) { KoResourceSP clonedBrush = brush->clone(); QSharedPointer actualClonedBrush = clonedBrush.dynamicCast(); m_brushes.append(actualClonedBrush ); KIS_ASSERT_RECOVER(clonedBrush) {continue;} } } virtual ~KisBrushesPipe() { } virtual void clear() { m_brushes.clear(); } QSharedPointer firstBrush() const { return m_brushes.first(); } QSharedPointer lastBrush() const { return m_brushes.last(); } QSharedPointer currentBrush(const KisPaintInformation& info) { Q_UNUSED(info); return !m_brushes.isEmpty() ? m_brushes.at(currentBrushIndex()) : 0; } int brushIndex(const KisPaintInformation& info) { return chooseNextBrush(info); } qint32 maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) { QSharedPointer brush = currentBrush(info); return brush ? brush->maskWidth(shape, subPixelX, subPixelY, info) : 0; } qint32 maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) { QSharedPointer brush = currentBrush(info); return brush ? brush->maskHeight(shape, subPixelX, subPixelY, info) : 0; } void setAngle(qreal angle) { Q_FOREACH (QSharedPointer brush, m_brushes) { brush->setAngle(angle); } } void setScale(qreal scale) { Q_FOREACH (QSharedPointer brush, m_brushes) { brush->setScale(scale); } } void setSpacing(double spacing) { Q_FOREACH (QSharedPointer brush, m_brushes) { brush->setSpacing(spacing); } } bool hasColor() const { Q_FOREACH (QSharedPointer brush, m_brushes) { if (brush->hasColor()) return true; } return false; } + void setPreserveLightness(bool preserveLightness) const { + Q_FOREACH(QSharedPointer brush, m_brushes) { + brush->setPreserveLightness(preserveLightness); + } + } + void notifyCachedDabPainted(const KisPaintInformation& info) { updateBrushIndexes(info, -1); } void prepareForSeqNo(const KisPaintInformation& info, int seqNo) { updateBrushIndexes(info, seqNo); } void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) { QSharedPointer brush = currentBrush(info); if (!brush) return; brush->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); notifyCachedDabPainted(info); } KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) { QSharedPointer brush = currentBrush(info); if (!brush) return 0; KisFixedPaintDeviceSP device = brush->paintDevice(colorSpace, shape, info, subPixelX, subPixelY); notifyCachedDabPainted(info); return device; } QVector> brushes() { return m_brushes; } void testingSelectNextBrush(const KisPaintInformation& info) { (void) chooseNextBrush(info); notifyCachedDabPainted(info); } /** * Is called by the paint op when a paintop starts a stroke. The * brushes are shared among different strokes, so sometimes the * brush should be reset. */ virtual void notifyStrokeStarted() = 0; protected: void addBrush(QSharedPointer brush) { m_brushes.append(brush); } int sizeBrush() { return m_brushes.size(); } /** * Returns the index of the next brush that corresponds to the current * values of \p info. This method is called *before* the dab is * actually painted. * */ virtual int chooseNextBrush(const KisPaintInformation& info) = 0; /** * Returns the current index of the brush * This method is called *before* the dab is * actually painted. * * The method is const, so no internal counters of the brush should * change during its execution */ virtual int currentBrushIndex() = 0; /** * Updates internal counters of the brush *after* a dab has been * painted on the canvas. Some incremental switching of the brushes * may me implemented in this method. * * If \p seqNo is equal or greater than zero, then incremental iteration is * overridden by this seqNo value */ virtual void updateBrushIndexes(const KisPaintInformation& info, int seqNo) = 0; protected: QVector> m_brushes; }; #endif /* __KIS_BRUSHES_PIPE_H */ diff --git a/libs/brush/kis_gbr_brush.cpp b/libs/brush/kis_gbr_brush.cpp index 5ed0763684..f8c86df831 100644 --- a/libs/brush/kis_gbr_brush.cpp +++ b/libs/brush/kis_gbr_brush.cpp @@ -1,494 +1,449 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2003 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2004 Adrian Page * Copyright (c) 2005 Bart Coppens * Copyright (c) 2007 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 #include #include "kis_gbr_brush.h" #include #include #include #include #include #include #include #include #include "kis_datamanager.h" #include "kis_paint_device.h" #include "kis_global.h" #include "kis_image.h" struct GimpBrushV1Header { quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ quint32 version; /* brush file version # */ quint32 width; /* width of brush */ quint32 height; /* height of brush */ quint32 bytes; /* depth of brush in bytes */ }; /// All fields are in MSB on disk! struct GimpBrushHeader { quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ quint32 version; /* brush file version # */ quint32 width; /* width of brush */ quint32 height; /* height of brush */ quint32 bytes; /* depth of brush in bytes */ /* The following are only defined in version 2 */ quint32 magic_number; /* GIMP brush magic number */ quint32 spacing; /* brush spacing as % of width & height, 0 - 1000 */ }; // Needed, or the GIMP won't open it! quint32 const GimpV2BrushMagic = ('G' << 24) + ('I' << 16) + ('M' << 8) + ('P' << 0); struct KisGbrBrush::Private { QByteArray data; - bool useColorAsMask; - quint32 header_size; /* header_size = sizeof (BrushHeader) + brush name */ quint32 version; /* brush file version # */ quint32 bytes; /* depth of brush in bytes */ quint32 magic_number; /* GIMP brush magic number */ }; #define DEFAULT_SPACING 0.25 KisGbrBrush::KisGbrBrush(const QString& filename) - : KisScalingSizeBrush(filename) + : KisColorfulBrush(filename) , d(new Private) { - d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); } -KisGbrBrush::KisGbrBrush(const QString &filename, - const QByteArray &data, - qint32 &dataPos) - : KisScalingSizeBrush(filename) +KisGbrBrush::KisGbrBrush(const QString& filename, + const QByteArray& data, + qint32 & dataPos) + : KisColorfulBrush(filename) , d(new Private) { - d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); d->data = QByteArray::fromRawData(data.data() + dataPos, data.size() - dataPos); init(); d->data.clear(); dataPos += d->header_size + (width() * height() * d->bytes); } KisGbrBrush::KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h) - : KisScalingSizeBrush() + : KisColorfulBrush() , d(new Private) { - d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); initFromPaintDev(image, x, y, w, h); } KisGbrBrush::KisGbrBrush(const QImage& image, const QString& name) - : KisScalingSizeBrush() + : KisColorfulBrush() , d(new Private) { - d->useColorAsMask = false; setHasColor(false); setSpacing(DEFAULT_SPACING); setBrushTipImage(image); setName(name); } KisGbrBrush::KisGbrBrush(const KisGbrBrush& rhs) - : KisScalingSizeBrush(rhs) + : KisColorfulBrush(rhs) , d(new Private(*rhs.d)) { d->data = QByteArray(); } KoResourceSP KisGbrBrush::clone() const { return KoResourceSP(new KisGbrBrush(*this)); } KisGbrBrush::~KisGbrBrush() { delete d; } bool KisGbrBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); d->data = dev->readAll(); return init(); } bool KisGbrBrush::init() { GimpBrushHeader bh; if (sizeof(GimpBrushHeader) > (uint)d->data.size()) { qWarning() << filename() << "GBR could not be loaded: expected header size larger than bytearray size. Header Size:" << sizeof(GimpBrushHeader) << "Byte array size" << d->data.size(); return false; } memcpy(&bh, d->data, sizeof(GimpBrushHeader)); bh.header_size = qFromBigEndian(bh.header_size); d->header_size = bh.header_size; bh.version = qFromBigEndian(bh.version); d->version = bh.version; bh.width = qFromBigEndian(bh.width); bh.height = qFromBigEndian(bh.height); bh.bytes = qFromBigEndian(bh.bytes); d->bytes = bh.bytes; bh.magic_number = qFromBigEndian(bh.magic_number); d->magic_number = bh.magic_number; if (bh.version == 1) { // No spacing in version 1 files so use Gimp default bh.spacing = static_cast(DEFAULT_SPACING * 100); } else { bh.spacing = qFromBigEndian(bh.spacing); if (bh.spacing > 1000) { qWarning() << filename() << "GBR could not be loaded, spacing above 1000. Spacing:" << bh.spacing; return false; } } setSpacing(bh.spacing / 100.0); if (bh.header_size > (uint)d->data.size() || bh.header_size == 0) { qWarning() << "GBR could not be loaded: header size larger than bytearray size. Header Size:" << bh.header_size << "Byte array size" << d->data.size(); return false; } QString name; if (bh.version == 1) { // Version 1 has no magic number or spacing, so the name // is at a different offset. Character encoding is undefined. const char *text = d->data.constData() + sizeof(GimpBrushV1Header); name = QString::fromLatin1(text, bh.header_size - sizeof(GimpBrushV1Header) - 1); } else { // ### Version = 3->cinepaint; may be float16 data! // Version >=2: UTF-8 encoding is used name = QString::fromUtf8(d->data.constData() + sizeof(GimpBrushHeader), bh.header_size - sizeof(GimpBrushHeader) - 1); } setName(name); if (bh.width == 0 || bh.height == 0) { qWarning() << filename() << "GBR loading failed: width or height is 0" << bh.width << bh.height; return false; } QImage::Format imageFormat; if (bh.bytes == 1) { imageFormat = QImage::Format_Indexed8; } else { imageFormat = QImage::Format_ARGB32; } QImage image(QImage(bh.width, bh.height, imageFormat)); if (image.isNull()) { qWarning() << filename() << "GBR loading failed; image could not be created from following dimensions" << bh.width << bh.height << "QImage::Format" << imageFormat; return false; } qint32 k = bh.header_size; if (bh.bytes == 1) { QVector table; for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i)); image.setColorTable(table); // Grayscale if (static_cast(k + bh.width * bh.height) > d->data.size()) { qWarning() << filename() << "GBR file dimensions bigger than bytearray size. Header:"<< k << "Width:" << bh.width << "height" << bh.height << "expected byte array size:" << (k + (bh.width * bh.height)) << "actual byte array size" << d->data.size(); return false; } setHasColor(false); for (quint32 y = 0; y < bh.height; y++) { uchar *pixel = reinterpret_cast(image.scanLine(y)); for (quint32 x = 0; x < bh.width; x++, k++) { qint32 val = 255 - static_cast(d->data[k]); *pixel = val; ++pixel; } } } else if (bh.bytes == 4) { // RGBA if (static_cast(k + (bh.width * bh.height * 4)) > d->data.size()) { qWarning() << filename() << "GBR file dimensions bigger than bytearray size. Header:"<< k << "Width:" << bh.width << "height" << bh.height << "expected byte array size:" << (k + (bh.width * bh.height * 4)) << "actual byte array size" << d->data.size(); return false; } setHasColor(true); + setPreserveLightness(false); for (quint32 y = 0; y < bh.height; y++) { QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (quint32 x = 0; x < bh.width; x++, k += 4) { *pixel = qRgba(d->data[k], d->data[k + 1], d->data[k + 2], d->data[k + 3]); ++pixel; } } } else { warnKrita << filename() << "WARNING: loading of GBR brushes with" << bh.bytes << "bytes per pixel is not supported"; return false; } setWidth(image.width()); setHeight(image.height()); if (!d->data.isEmpty()) { d->data.resize(0); // Save some memory, we're using enough of it as it is. } setValid(image.width() != 0 && image.height() != 0); setBrushTipImage(image); return true; } bool KisGbrBrush::initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h) { // Forcefully convert to RGBA8 // XXX profile and exposure? setBrushTipImage(image->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags())); setName(image->objectName()); setHasColor(true); + setPreserveLightness(false); return true; } bool KisGbrBrush::saveToDevice(QIODevice* dev) const { if (!valid() || brushTipImage().isNull()) { qWarning() << "this brush is not valid, set a brush tip image" << filename(); return false; } GimpBrushHeader bh; QByteArray utf8Name = name().toUtf8(); // Names in v2 brushes are in UTF-8 char const* name = utf8Name.data(); int nameLength = qstrlen(name); int wrote; bh.header_size = qToBigEndian((quint32)sizeof(GimpBrushHeader) + nameLength + 1); bh.version = qToBigEndian((quint32)2); // Only RGBA8 data needed atm, no cinepaint stuff bh.width = qToBigEndian((quint32)width()); bh.height = qToBigEndian((quint32)height()); // Hardcoded, 4 bytes RGBA or 1 byte GREY if (!hasColor()) { bh.bytes = qToBigEndian((quint32)1); } else { bh.bytes = qToBigEndian((quint32)4); } bh.magic_number = qToBigEndian((quint32)GimpV2BrushMagic); bh.spacing = qToBigEndian(static_cast(spacing() * 100.0)); // Write header: first bh, then the name QByteArray bytes = QByteArray::fromRawData(reinterpret_cast(&bh), sizeof(GimpBrushHeader)); wrote = dev->write(bytes); bytes.clear(); if (wrote == -1) { return false; } wrote = dev->write(name, nameLength + 1); if (wrote == -1) { return false; } int k = 0; QImage image = brushTipImage(); if (!hasColor()) { bytes.resize(width() * height()); for (qint32 y = 0; y < height(); y++) { for (qint32 x = 0; x < width(); x++) { QRgb c = image.pixel(x, y); bytes[k++] = static_cast(255 - qRed(c)); // red == blue == green } } } else { bytes.resize(width() * height() * 4); for (qint32 y = 0; y < height(); y++) { for (qint32 x = 0; x < width(); x++) { // order for gimp brushes, v2 is: RGBA QRgb pixel = image.pixel(x, y); bytes[k++] = static_cast(qRed(pixel)); bytes[k++] = static_cast(qGreen(pixel)); bytes[k++] = static_cast(qBlue(pixel)); bytes[k++] = static_cast(qAlpha(pixel)); } } } wrote = dev->write(bytes); if (wrote == -1) { return false; } KoResource::saveToDevice(dev); return true; } -QImage KisGbrBrush::brushTipImage() const -{ - QImage image = KisBrush::brushTipImage(); - if (hasColor() && useColorAsMask() && !image.isNull()) { - for (int y = 0; y < image.height(); y++) { - QRgb *pixel = reinterpret_cast(image.scanLine(y)); - for (int x = 0; x < image.width(); x++) { - QRgb c = pixel[x]; - float alpha = qAlpha(c) / 255.0f; - int a = 255 + int(alpha * (qGray(c) - 255)); - pixel[x] = qRgba(a, a, a, 255); - } - } - } - return image; -} - - enumBrushType KisGbrBrush::brushType() const { return !hasColor() || useColorAsMask() ? MASK : IMAGE; } void KisGbrBrush::setBrushType(enumBrushType type) { Q_UNUSED(type); qFatal("FATAL: protected member setBrushType has no meaning for KisGbrBrush"); } void KisGbrBrush::setBrushTipImage(const QImage& image) { KisBrush::setBrushTipImage(image); setValid(true); } -void KisGbrBrush::makeMaskImage() +void KisGbrBrush::makeMaskImage(bool preserveAlpha) { if (!hasColor()) { return; } QImage brushTip = brushTipImage(); - if (brushTip.width() == width() && brushTip.height() == height()) { - int imageWidth = brushTip.width(); - int imageHeight = brushTip.height(); + if (!preserveAlpha && brushTip.width() == width() && brushTip.height() == height()) { + int imageWidth = width(); + int imageHeight = height(); QImage image(imageWidth, imageHeight, QImage::Format_Indexed8); QVector table; for (int i = 0; i < 256; ++i) { table.append(qRgb(i, i, i)); } image.setColorTable(table); for (int y = 0; y < imageHeight; y++) { QRgb *pixel = reinterpret_cast(brushTip.scanLine(y)); uchar * dstPixel = image.scanLine(y); for (int x = 0; x < imageWidth; x++) { QRgb c = pixel[x]; float alpha = qAlpha(c) / 255.0f; // linear interpolation with maximum gray value which is transparent in the mask //int a = (qGray(c) * alpha) + ((1.0 - alpha) * 255); // single multiplication version int a = 255 + int(alpha * (qGray(c) - 255)); dstPixel[x] = (uchar)a; } } setBrushTipImage(image); } + else { + setBrushTipImage(brushTip); + } - setHasColor(false); - setUseColorAsMask(false); + setHasColor(preserveAlpha); + setUseColorAsMask(preserveAlpha); resetBoundary(); clearBrushPyramid(); } void KisGbrBrush::toXML(QDomDocument& d, QDomElement& e) const { predefinedBrushToXML("gbr_brush", e); - e.setAttribute("ColorAsMask", QString::number((int)useColorAsMask())); - KisBrush::toXML(d, e); -} - -void KisGbrBrush::setUseColorAsMask(bool useColorAsMask) -{ - /** - * WARNING: There is a problem in the brush server, since it - * returns not copies of brushes, but direct pointers to them. It - * means that the brushes are shared among all the currently - * present paintops, which might be a problem for e.g. Multihand - * Brush Tool. - * - * Right now, all the instances of Multihand Brush Tool share the - * same brush, so there is no problem in this sharing, unless we - * reset the internal state of the brush on our way. - */ - - if (useColorAsMask != d->useColorAsMask) { - d->useColorAsMask = useColorAsMask; - resetBoundary(); - clearBrushPyramid(); - } -} -bool KisGbrBrush::useColorAsMask() const -{ - return d->useColorAsMask; + KisColorfulBrush::toXML(d, e); } QString KisGbrBrush::defaultFileExtension() const { return QString(".gbr"); } diff --git a/libs/brush/kis_gbr_brush.h b/libs/brush/kis_gbr_brush.h index e2bab1975a..6cb2ddae5d 100644 --- a/libs/brush/kis_gbr_brush.h +++ b/libs/brush/kis_gbr_brush.h @@ -1,126 +1,117 @@ /* * 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_GBR_BRUSH_ #define KIS_GBR_BRUSH_ #include #include -#include "kis_scaling_size_brush.h" +#include "KisColorfulBrush.h" #include #include #include #include "kritabrush_export.h" class KisQImagemask; typedef KisSharedPtr KisQImagemaskSP; class QString; class QIODevice; -class BRUSH_EXPORT KisGbrBrush : public KisScalingSizeBrush +class BRUSH_EXPORT KisGbrBrush : public KisColorfulBrush { protected: public: /// Construct brush to load filename later as brush KisGbrBrush(const QString& filename); /// Load brush from the specified data, at position dataPos, and set the filename KisGbrBrush(const QString& filename, const QByteArray & data, qint32 & dataPos); /// Load brush from the specified paint device, in the specified region KisGbrBrush(KisPaintDeviceSP image, int x, int y, int w, int h); /// Load brush as a copy from the specified QImage (handy when you need to copy a brush!) KisGbrBrush(const QImage& image, const QString& name = QString()); ~KisGbrBrush() override; KisGbrBrush(const KisGbrBrush& rhs); KoResourceSP clone() const override; KisGbrBrush &operator=(const KisGbrBrush &rhs); bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; bool saveToDevice(QIODevice* dev) const override; QPair resourceType() const override { return QPair(ResourceType::Brushes, ResourceSubType::GbrBrushes); } - /** - * @return a preview of the brush - */ - QImage brushTipImage() const override; - /** - * If the brush image data are colorful (e.g. you created the brush from the canvas with custom brush) - * and you want to paint with it as with masks, set to true. - */ - virtual void setUseColorAsMask(bool useColorAsMask); - - virtual bool useColorAsMask() const; - /** * Convert the mask to inverted gray scale, so it is alpha mask. - * It can be used as MASK brush type. This operates on the date of the brush, - * so it destruct the original brush data + * It can be used as MASK brush type. This operates on the data of the brush, + * so it destruct the original brush data. + * + * @param preserveAlpha convert to grayscale, but save as full RGBA format, to allow + * preserving lightness option */ - virtual void makeMaskImage(); + virtual void makeMaskImage(bool preserveAlpha); enumBrushType brushType() const override; /** * @return default file extension for saving the brush */ QString defaultFileExtension() const override; protected: /** * save the content of this brush to an IO device */ friend class KisImageBrushesPipe; friend class KisBrushExport; void setBrushType(enumBrushType type) override; void setBrushTipImage(const QImage& image) override; void toXML(QDomDocument& d, QDomElement& e) const override; private: bool init(); bool initFromPaintDev(KisPaintDeviceSP image, int x, int y, int w, int h); struct Private; Private* const d; }; typedef QSharedPointer KisGbrBrushSP; #endif // KIS_GBR_BRUSH_ diff --git a/libs/brush/kis_imagepipe_brush.cpp b/libs/brush/kis_imagepipe_brush.cpp index 2c3c919b02..e9f8e29b45 100644 --- a/libs/brush/kis_imagepipe_brush.cpp +++ b/libs/brush/kis_imagepipe_brush.cpp @@ -1,522 +1,565 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_imagepipe_brush.h" #include "kis_pipebrush_parasite.h" #include "kis_brushes_pipe.h" class KisImageBrushesPipe : public KisBrushesPipe { public: KisImageBrushesPipe() : m_currentBrushIndex(0) , m_isInitialized(false) { } /* pre and post are split because: 21:12:20 < dmitryK> boud: i guess it was somehow related to the fact that the maskWidth/maskHeight should correspond to the size of the mask returned by paintDevice() 21:13:33 < dmitryK> boud: the random stuff is called once per brush->paintDevice() call, after the device is returned to the paint op, that is "preparing the randomness for the next call" 21:14:16 < dmitryK> boud: and brushesPipe->currentBrush() always returning the same brush for any particular paintInfo. */ protected: static int selectPre(KisParasite::SelectionMode mode, int index, int rank, const KisPaintInformation& info) { qreal angle; qreal velocity; qreal capSpeed = 3; switch (mode) { case KisParasite::Constant: case KisParasite::Incremental: case KisParasite::Random: break; case KisParasite::Pressure: index = static_cast(info.pressure() * (rank - 1) + 0.5); break; case KisParasite::Angular: // + M_PI_2 + M_PI_4 to be compatible with the gimp angle = info.drawingAngle() + M_PI_2 + M_PI_4; angle = normalizeAngle(angle); index = static_cast(angle / (2.0 * M_PI) * rank); break; case KisParasite::TiltX: index = qRound(info.xTilt() / 2.0 * rank) + rank / 2; break; case KisParasite::TiltY: index = qRound(info.yTilt() / 2.0 * rank) + rank / 2; break; case KisParasite::Velocity: // log is slow, but allows for nicer dab transition velocity = log(info.drawingSpeed() + 1); if (velocity > capSpeed) { velocity = capSpeed; } velocity /= capSpeed; velocity *= (rank - 1) + 0.5; index = qRound(velocity); break; default: warnImage << "Parasite" << mode << "is not implemented"; index = 0; } return index; } static int selectPost(KisParasite::SelectionMode mode, int index, int rank, const KisPaintInformation& info, int seqNo) { switch (mode) { case KisParasite::Constant: break; case KisParasite::Incremental: index = (seqNo >= 0 ? seqNo : (index + 1)) % rank; break; case KisParasite::Random: index = info.randomSource()->generate(0, rank-1); break; case KisParasite::Pressure: case KisParasite::Angular: break; case KisParasite::TiltX: case KisParasite::TiltY: case KisParasite::Velocity: break; default: warnImage << "Parasite" << mode << "is not implemented"; index = 0; } return index; } int chooseNextBrush(const KisPaintInformation& info) override { quint32 brushIndex = 0; if (!m_isInitialized) { /** * Reset all the indexes to the initial values and do the * generation based on parameters. */ for (int i = 0; i < m_parasite.dim; i++) { m_parasite.index[i] = 0; } updateBrushIndexes(info, 0); m_isInitialized = true; } for (int i = 0; i < m_parasite.dim; i++) { int index = selectPre(m_parasite.selection[i], m_parasite.index[i], m_parasite.rank[i], info); brushIndex += m_parasite.brushesCount[i] * index; } brushIndex %= (quint32)m_brushes.size(); m_currentBrushIndex = brushIndex; return brushIndex; } int currentBrushIndex() override { return m_currentBrushIndex; } void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override { for (int i = 0; i < m_parasite.dim; i++) { m_parasite.index[i] = selectPost(m_parasite.selection[i], m_parasite.index[i], m_parasite.rank[i], info, seqNo); } } public: using KisBrushesPipe::addBrush; using KisBrushesPipe::sizeBrush; void setParasite(const KisPipeBrushParasite& parasite) { m_parasite = parasite; } const KisPipeBrushParasite& parasite() const { return m_parasite; } void setUseColorAsMask(bool useColorAsMask) { Q_FOREACH (KisGbrBrushSP brush, m_brushes) { brush->setUseColorAsMask(useColorAsMask); } } - void makeMaskImage() { + void setAdjustmentMidPoint(quint8 value) { Q_FOREACH (KisGbrBrushSP brush, m_brushes) { - brush->makeMaskImage(); + brush->setAdjustmentMidPoint(value); + } + } + + void setBrightnessAdjustment(qreal value) { + Q_FOREACH (KisGbrBrushSP brush, m_brushes) { + brush->setBrightnessAdjustment(value); + } + } + + void setContrastAdjustment(qreal value) { + Q_FOREACH (KisGbrBrushSP brush, m_brushes) { + brush->setContrastAdjustment(value); + } + } + + void makeMaskImage(bool preserveAlpha) { + Q_FOREACH (KisGbrBrushSP brush, m_brushes) { + brush->makeMaskImage(preserveAlpha); } } bool saveToDevice(QIODevice* dev) const { Q_FOREACH (KisGbrBrushSP brush, m_brushes) { if (!brush->saveToDevice(dev)) { return false; } } return true; } void notifyStrokeStarted() override { m_isInitialized = false; } private: KisPipeBrushParasite m_parasite; int m_currentBrushIndex; bool m_isInitialized; }; struct KisImagePipeBrush::Private { public: KisImageBrushesPipe brushesPipe; }; KisImagePipeBrush::KisImagePipeBrush(const QString& filename) : KisGbrBrush(filename) , d(new Private()) { } KisImagePipeBrush::KisImagePipeBrush(const QString& name, int w, int h, QVector< QVector > devices, QVector modes) : KisGbrBrush(QString()) , 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(d->brushesPipe.firstBrush()->brushTipImage()); } KisImagePipeBrush::KisImagePipeBrush(const KisImagePipeBrush& rhs) : KisGbrBrush(rhs), d(new Private(*rhs.d)) { } KoResourceSP KisImagePipeBrush::clone() const { return KoResourceSP(new KisImagePipeBrush(*this)); } KisImagePipeBrush::~KisImagePipeBrush() { delete d; } bool KisImagePipeBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); 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(); parasiteSelectionString = parasite.selectionMode; // selection mode to return to UI d->brushesPipe.setParasite(parasite); i++; // Skip past the second newline for (int brushIndex = d->brushesPipe.sizeBrush(); brushIndex < numOfBrushes && i < data.size(); brushIndex++) { KisGbrBrushSP brush = KisGbrBrushSP(new KisGbrBrush(name() + '_' + QString().setNum(brushIndex), data, i)); d->brushesPipe.addBrush(brush); } if (numOfBrushes > 0) { setValid(true); setSpacing(d->brushesPipe.lastBrush()->spacing()); setWidth(d->brushesPipe.firstBrush()->width()); setHeight(d->brushesPipe.firstBrush()->height()); setBrushTipImage(d->brushesPipe.firstBrush()->brushTipImage()); } return true; } 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 (d->brushesPipe.parasite().dim >= KisPipeBrushParasite::MaxDim) { warnImage << "Save to file for pipe brushes with dim != not yet supported!"; return false; } // Save this pipe brush: first the header, and then all individual brushes consecutively // XXX: this needs some care for when we have > 1 dimension) // Gimp Pipe Brush header format: Name\n \n // The name\n if (dev->write(name, len) == -1) return false; if (!dev->putChar('\n')) return false; // Write the parasite (also writes number of brushes) if (!d->brushesPipe.parasite().saveToDevice(dev)) return false; if (!dev->putChar('\n')) return false; KoResource::saveToDevice(dev); // return d->brushesPipe.saveToDevice(dev); } void KisImagePipeBrush::notifyStrokeStarted() { d->brushesPipe.notifyStrokeStarted(); } void KisImagePipeBrush::notifyCachedDabPainted(const KisPaintInformation& info) { d->brushesPipe.notifyCachedDabPainted(info); } void KisImagePipeBrush::prepareForSeqNo(const KisPaintInformation &info, int seqNo) { d->brushesPipe.prepareForSeqNo(info, seqNo); } void KisImagePipeBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX , double subPixelY, qreal softnessFactor) const { d->brushesPipe.generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); } QVector KisImagePipeBrush::brushes() const { return d->brushesPipe.brushes(); } KisFixedPaintDeviceSP KisImagePipeBrush::paintDevice( const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { return d->brushesPipe.paintDevice(colorSpace, shape, info, subPixelX, subPixelY); } enumBrushType KisImagePipeBrush::brushType() const { return !hasColor() || useColorAsMask() ? PIPE_MASK : PIPE_IMAGE; } QString KisImagePipeBrush::parasiteSelection() { return parasiteSelectionString; } bool KisImagePipeBrush::hasColor() const { return d->brushesPipe.hasColor(); } -void KisImagePipeBrush::makeMaskImage() +void KisImagePipeBrush::makeMaskImage(bool preserveAlpha) { - d->brushesPipe.makeMaskImage(); - setUseColorAsMask(false); + d->brushesPipe.makeMaskImage(preserveAlpha); + setUseColorAsMask(true); } void KisImagePipeBrush::setUseColorAsMask(bool useColorAsMask) { KisGbrBrush::setUseColorAsMask(useColorAsMask); d->brushesPipe.setUseColorAsMask(useColorAsMask); } +void KisImagePipeBrush::setAdjustmentMidPoint(quint8 value) +{ + KisGbrBrush::setAdjustmentMidPoint(value); + d->brushesPipe.setAdjustmentMidPoint(value); +} + +void KisImagePipeBrush::setBrightnessAdjustment(qreal value) +{ + KisGbrBrush::setBrightnessAdjustment(value); + d->brushesPipe.setBrightnessAdjustment(value); +} + +void KisImagePipeBrush::setContrastAdjustment(qreal value) +{ + KisGbrBrush::setContrastAdjustment(value); + d->brushesPipe.setContrastAdjustment(value); +} + const KisBoundary* KisImagePipeBrush::boundary() const { KisGbrBrushSP brush = d->brushesPipe.firstBrush(); Q_ASSERT(brush); return brush->boundary(); } bool KisImagePipeBrush::canPaintFor(const KisPaintInformation& info) { return (!d->brushesPipe.parasite().needsMovement || info.drawingDistance() >= 0.5); } QString KisImagePipeBrush::defaultFileExtension() const { return QString(".gih"); } quint32 KisImagePipeBrush::brushIndex(const KisPaintInformation& info) const { return d->brushesPipe.brushIndex(info); } qint32 KisImagePipeBrush::maskWidth(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return d->brushesPipe.maskWidth(shape, subPixelX, subPixelY, info); } qint32 KisImagePipeBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return d->brushesPipe.maskHeight(shape, subPixelX, subPixelY, info); } void KisImagePipeBrush::setAngle(qreal _angle) { KisGbrBrush::setAngle(_angle); d->brushesPipe.setAngle(_angle); } void KisImagePipeBrush::setScale(qreal _scale) { KisGbrBrush::setScale(_scale); d->brushesPipe.setScale(_scale); } void KisImagePipeBrush::setSpacing(double _spacing) { KisGbrBrush::setSpacing(_spacing); d->brushesPipe.setSpacing(_spacing); } void KisImagePipeBrush::setBrushType(enumBrushType type) { Q_UNUSED(type); qFatal("FATAL: protected member setBrushType has no meaning for KisImagePipeBrush"); // brushType() is a function of hasColor() and useColorAsMask() } void KisImagePipeBrush::setHasColor(bool hasColor) { Q_UNUSED(hasColor); qFatal("FATAL: protected member setHasColor has no meaning for KisImagePipeBrush"); // hasColor() is a function of the underlying brushes } +void KisImagePipeBrush::setPreserveLightness(bool preserveLightness) +{ + //Set all underlying brushes to preserve lightness + KisGbrBrush::setPreserveLightness(preserveLightness); + d->brushesPipe.setPreserveLightness(preserveLightness); +} + KisGbrBrushSP KisImagePipeBrush::testingGetCurrentBrush(const KisPaintInformation& info) const { return d->brushesPipe.currentBrush(info); } void KisImagePipeBrush::testingSelectNextBrush(const KisPaintInformation& info) const { return d->brushesPipe.testingSelectNextBrush(info); } const KisPipeBrushParasite& KisImagePipeBrush::parasite() const { return d->brushesPipe.parasite(); } void KisImagePipeBrush::setParasite(const KisPipeBrushParasite ¶site) { d->brushesPipe.setParasite(parasite); } void KisImagePipeBrush::setDevices(QVector > devices, int w, int h) { for (int i = 0; i < devices.at(0).count(); i++) { d->brushesPipe.addBrush(KisGbrBrushSP(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 7b257ddc54..5a1ac58988 100644 --- a/libs/brush/kis_imagepipe_brush.h +++ b/libs/brush/kis_imagepipe_brush.h @@ -1,143 +1,150 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 Bart Coppens * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMAGEPIPE_BRUSH_ #define KIS_IMAGEPIPE_BRUSH_ #include #include #include #include #include "kis_gbr_brush.h" #include "kis_global.h" class KisPipeBrushParasite; /** * Velocity won't be supported, atm Tilt isn't either, * but have chances of implementation */ namespace KisParasite { enum SelectionMode { Constant, Incremental, Angular, Velocity, Random, Pressure, TiltX, TiltY }; } class BRUSH_EXPORT KisImagePipeBrush : public KisGbrBrush { public: KisImagePipeBrush(const QString& filename); /** * Specialized constructor that makes a new pipe brush from a sequence of samesize * devices. The fact that it's a vector of a vector, is to support multidimensional * brushes (not yet supported!) */ KisImagePipeBrush(const QString& name, int w, int h, QVector< QVector > devices, QVector modes); ~KisImagePipeBrush() override; /// Will call KisBrush's saveToDevice as well KisImagePipeBrush(const KisImagePipeBrush& rhs); KisImagePipeBrush &operator=(const KisImagePipeBrush &rhs) = delete; KoResourceSP clone() const override; bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; bool saveToDevice(QIODevice* dev) const override; /** * @return the next image in the pipe. */ KisFixedPaintDeviceSP paintDevice(const KoColorSpace * colorSpace, KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0) const override; void setUseColorAsMask(bool useColorAsMask) override; bool hasColor() const override; + void setAdjustmentMidPoint(quint8 value) override; + void setBrightnessAdjustment(qreal value) override; + void setContrastAdjustment(qreal value) override; + enumBrushType brushType() const override; QString parasiteSelection(); // returns random, constant, etc const KisBoundary* boundary() const override; bool canPaintFor(const KisPaintInformation& info) override; - void makeMaskImage() override; + void makeMaskImage(bool preserveAlpha) override; QString defaultFileExtension() const override; void setAngle(qreal _angle) override; void setScale(qreal _scale) override; void setSpacing(double _spacing) override; quint32 brushIndex(const KisPaintInformation& info) const override; qint32 maskWidth(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override; qint32 maskHeight(KisDabShape const&, double subPixelX, double subPixelY, const KisPaintInformation& info) const override; void notifyStrokeStarted() override; void notifyCachedDabPainted(const KisPaintInformation& info) override; void prepareForSeqNo(const KisPaintInformation& info, int seqNo) override; void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation* coloringInformation, KisDabShape const&, const KisPaintInformation& info, double subPixelX = 0, double subPixelY = 0, qreal softnessFactor = DEFAULT_SOFTNESS_FACTOR) const override; QVector brushes() const; const KisPipeBrushParasite ¶site() const; void setParasite(const KisPipeBrushParasite& parasite); void setDevices(QVector< QVector > devices, int w, int h); protected: void setBrushType(enumBrushType type) override; void setHasColor(bool hasColor) override; + void setPreserveLightness(bool preserveLightness) override; + /// Will call KisBrush's saveToDevice as well + private: friend class KisImagePipeBrushTest; KisGbrBrushSP testingGetCurrentBrush(const KisPaintInformation& info) const; void testingSelectNextBrush(const KisPaintInformation& info) const; bool initFromData(const QByteArray &data); QString parasiteSelectionString; // incremental, random, etc. private: struct Private; Private * const d; }; typedef QSharedPointer KisImagePipeBrushSP; #endif // KIS_IMAGEPIPE_BRUSH_ diff --git a/libs/brush/kis_png_brush.cpp b/libs/brush/kis_png_brush.cpp index 8e484e1c47..514f0c7462 100644 --- a/libs/brush/kis_png_brush.cpp +++ b/libs/brush/kis_png_brush.cpp @@ -1,142 +1,138 @@ /* * Copyright (c) 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_png_brush.h" #include #include #include #include #include #include #include KisPngBrush::KisPngBrush(const QString& filename) - : KisScalingSizeBrush(filename) + : KisColorfulBrush(filename) { setBrushType(INVALID); setSpacing(0.25); setHasColor(false); } KisPngBrush::KisPngBrush(const KisPngBrush &rhs) - : KisScalingSizeBrush(rhs) + : KisColorfulBrush(rhs) { - setSpacing(rhs.spacing()); - if (brushTipImage().isGrayscale()) { - setBrushType(MASK); - setHasColor(false); - } - else { - setBrushType(IMAGE); - setHasColor(true); - } } KoResourceSP KisPngBrush::clone() const { return KoResourceSP(new KisPngBrush(*this)); } bool KisPngBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = dev->readAll(); QBuffer buf(&data); buf.open(QIODevice::ReadOnly); QImageReader reader(&buf, "PNG"); if (!reader.canRead()) { dbgKrita << "Could not read brush" << filename() << ". Error:" << reader.errorString(); setValid(false); return false; } if (reader.textKeys().contains("brush_spacing")) { setSpacing(KisDomUtils::toDouble(reader.text("brush_spacing"))); } if (reader.textKeys().contains("brush_name")) { setName(reader.text("brush_name")); } else { QFileInfo info(filename()); setName(info.completeBaseName()); } QImage image = reader.read(); if (image.isNull()) { dbgKrita << "Could not create image for" << filename() << ". Error:" << reader.errorString(); setValid(false); return false; } setValid(true); if (image.allGray()) { // Make sure brush tips all have a white background QImage base(image.size(), image.format()); if ((int)base.format() < (int)QImage::Format_RGB32) { base = base.convertToFormat(QImage::Format_ARGB32); } QPainter gc(&base); gc.fillRect(base.rect(), Qt::white); gc.drawImage(0, 0, image); gc.end(); QImage converted = base.convertToFormat(QImage::Format_Grayscale8); setBrushTipImage(converted); setBrushType(MASK); setHasColor(false); } else { setBrushTipImage(image); setBrushType(IMAGE); setHasColor(true); } setWidth(brushTipImage().width()); setHeight(brushTipImage().height()); return valid(); } bool KisPngBrush::saveToDevice(QIODevice *dev) const { if(brushTipImage().save(dev, "PNG")) { KoResource::saveToDevice(dev); return true; } return false; } +enumBrushType KisPngBrush::brushType() const +{ + return !hasColor() || useColorAsMask() ? MASK : IMAGE; +} + QString KisPngBrush::defaultFileExtension() const { return QString(".png"); } void KisPngBrush::toXML(QDomDocument& d, QDomElement& e) const { predefinedBrushToXML("png_brush", e); - KisBrush::toXML(d, e); + KisColorfulBrush::toXML(d, e); } diff --git a/libs/brush/kis_png_brush.h b/libs/brush/kis_png_brush.h index 9476ba306e..cc87d7684b 100644 --- a/libs/brush/kis_png_brush.h +++ b/libs/brush/kis_png_brush.h @@ -1,44 +1,47 @@ /* * Copyright (c) 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. */ #ifndef KIS_PNG_BRUSH_ #define KIS_PNG_BRUSH_ -#include "kis_scaling_size_brush.h" +#include "KisColorfulBrush.h" -class BRUSH_EXPORT KisPngBrush : public KisScalingSizeBrush +class BRUSH_EXPORT KisPngBrush : public KisColorfulBrush { public: /// Construct brush to load filename later as brush KisPngBrush(const QString& filename); KisPngBrush(const KisPngBrush &rhs); KoResourceSP clone() const override; KisPngBrush &operator=(const KisPngBrush &rhs) = delete; bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; bool saveToDevice(QIODevice *dev) const override; + + enumBrushType brushType() const override; + QString defaultFileExtension() const override; void toXML(QDomDocument& d, QDomElement& e) const override; QPair resourceType() const override { return QPair(ResourceType::Brushes, ResourceSubType::PngBrushes); } }; #endif diff --git a/libs/brush/kis_predefined_brush_factory.cpp b/libs/brush/kis_predefined_brush_factory.cpp index 263ea07356..9f0a643ed2 100644 --- a/libs/brush/kis_predefined_brush_factory.cpp +++ b/libs/brush/kis_predefined_brush_factory.cpp @@ -1,83 +1,88 @@ /* * 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_predefined_brush_factory.h" #include -#include "KisBrushServerProvider.h" +#include #include "kis_gbr_brush.h" +#include "kis_png_brush.h" #include #include KisPredefinedBrushFactory::KisPredefinedBrushFactory(const QString &brushType) : m_id(brushType) { } QString KisPredefinedBrushFactory::id() const { return m_id; } KisBrushSP KisPredefinedBrushFactory::createBrush(const QDomElement& brushDefinition, KisResourcesInterfaceSP resourcesInterface) { auto resourceSourceAdapter = resourcesInterface->source(ResourceType::Brushes); const QString brushFileName = brushDefinition.attribute("filename", ""); KisBrushSP brush = resourceSourceAdapter.resourceForFilename(brushFileName); //Fallback for files that still use the old format if (!brush) { QFileInfo info(brushFileName); brush = resourceSourceAdapter.resourceForFilename(info.fileName()); } if (!brush) { brush = resourceSourceAdapter.fallbackResource(); } KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(brush, 0); // we always return a copy of the brush! brush = brush->clone().dynamicCast(); double spacing = KisDomUtils::toDouble(brushDefinition.attribute("spacing", "0.25")); brush->setSpacing(spacing); bool useAutoSpacing = KisDomUtils::toInt(brushDefinition.attribute("useAutoSpacing", "0")); qreal autoSpacingCoeff = KisDomUtils::toDouble(brushDefinition.attribute("autoSpacingCoeff", "1.0")); brush->setAutoSpacing(useAutoSpacing, autoSpacingCoeff); double angle = KisDomUtils::toDouble(brushDefinition.attribute("angle", "0.0")); brush->setAngle(angle); double scale = KisDomUtils::toDouble(brushDefinition.attribute("scale", "1.0")); brush->setScale(scale); - if (m_id == "gbr_brush") { - KisGbrBrush *gbrbrush = dynamic_cast(brush.data()); - if (gbrbrush) { - /** - * WARNING: see comment in KisGbrBrush::setUseColorAsMask() - */ - gbrbrush->setUseColorAsMask((bool)brushDefinition.attribute("ColorAsMask").toInt()); - } + int preserveLightness = KisDomUtils::toInt(brushDefinition.attribute("preserveLightness", "0")); + brush->setPreserveLightness(preserveLightness); + + KisColorfulBrush *colorfulBrush = dynamic_cast(brush.data()); + if (colorfulBrush) { + /** + * WARNING: see comment in KisGbrBrush::setUseColorAsMask() + */ + colorfulBrush->setUseColorAsMask((bool)brushDefinition.attribute("ColorAsMask").toInt()); + colorfulBrush->setAdjustmentMidPoint(brushDefinition.attribute("AdjustmentMidPoint", "127").toInt()); + colorfulBrush->setBrightnessAdjustment(brushDefinition.attribute("BrightnessAdjustment").toDouble()); + colorfulBrush->setContrastAdjustment(brushDefinition.attribute("ContrastAdjustment").toDouble()); } return brush; } diff --git a/libs/brush/kis_scaling_size_brush.cpp b/libs/brush/kis_scaling_size_brush.cpp index 17109c5bc3..5acfdaf95a 100644 --- a/libs/brush/kis_scaling_size_brush.cpp +++ b/libs/brush/kis_scaling_size_brush.cpp @@ -1,46 +1,48 @@ /* * Copyright (c) 2016 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_scaling_size_brush.h" KisScalingSizeBrush::KisScalingSizeBrush() : KisBrush() { } KisScalingSizeBrush::KisScalingSizeBrush(const QString &filename) : KisBrush(filename) { } KisScalingSizeBrush::KisScalingSizeBrush(const KisScalingSizeBrush &rhs) : KisBrush(rhs) { setName(rhs.name()); setValid(rhs.valid()); } qreal KisScalingSizeBrush::userEffectiveSize() const { return this->width() * this->scale(); } void KisScalingSizeBrush::setUserEffectiveSize(qreal value) { this->setScale(value / this->width()); } + + diff --git a/libs/brush/kis_text_brush.cpp b/libs/brush/kis_text_brush.cpp index 69778bbe86..d926fba200 100644 --- a/libs/brush/kis_text_brush.cpp +++ b/libs/brush/kis_text_brush.cpp @@ -1,341 +1,341 @@ /* * 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(), // no copy here! m_text(rhs.m_text), m_charIndex(rhs.m_charIndex), m_currentBrushIndex(rhs.m_currentBrushIndex) { m_brushesMap.clear(); QMapIterator iter(rhs.m_brushesMap); while (iter.hasNext()) { iter.next(); KisGbrBrushSP brush(new KisGbrBrush(*iter.value())); m_brushesMap.insert(iter.key(), brush); KisBrushesPipe::addBrush(brush); } } void setText(const QString &text, const QFont &font) { m_text = text; m_charIndex = 0; clear(); for (int i = 0; i < m_text.length(); i++) { const QChar letter = m_text.at(i); // skip letters that are already present in the brushes pipe if (m_brushesMap.contains(letter)) continue; QImage image = renderChar(letter, font); KisGbrBrushSP brush(new KisGbrBrush(image, letter)); brush->setSpacing(0.1); // support for letter spacing? - brush->makeMaskImage(); + brush->makeMaskImage(false); 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() override { m_brushesMap.clear(); KisBrushesPipe::clear(); } KisGbrBrushSP firstBrush() const { Q_ASSERT(m_text.size() > 0); Q_ASSERT(m_brushesMap.size() > 0); return m_brushesMap.value(m_text.at(0)); } void notifyStrokeStarted() override { m_charIndex = 0; updateBrushIndexesImpl(); } protected: int chooseNextBrush(const KisPaintInformation& info) override { Q_UNUSED(info); return m_currentBrushIndex; } int currentBrushIndex() override { return m_currentBrushIndex; } void updateBrushIndexes(const KisPaintInformation& info, int seqNo) override { Q_UNUSED(info); if (m_text.size()) { m_charIndex = (seqNo >= 0 ? seqNo : (m_charIndex + 1)) % m_text.size(); } else { m_charIndex = 0; } 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) : KoEphemeralResource(rhs), m_font(rhs.m_font), m_text(rhs.m_text), m_brushesPipe(new KisTextBrushesPipe(*rhs.m_brushesPipe)) { } KisTextBrush::~KisTextBrush() { delete m_brushesPipe; } KoResourceSP KisTextBrush::clone() const { return KisBrushSP(new KisTextBrush(*this)); } 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::prepareForSeqNo(const KisPaintInformation &info, int seqNo) { m_brushesPipe->prepareForSeqNo(info, seqNo); } 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, shape, info, subPixelX, subPixelY, softnessFactor); } else { /* if (brushType() == PIPE_MASK)*/ m_brushesPipe->generateMaskAndApplyMaskOrCreateDab(dst, coloringInformation, shape, info, subPixelX, subPixelY, softnessFactor); } } KisFixedPaintDeviceSP KisTextBrush::paintDevice(const KoColorSpace * colorSpace, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY) const { if (brushType() == MASK) { return KisBrush::paintDevice(colorSpace, shape, info, subPixelX, subPixelY); } else { /* if (brushType() == PIPE_MASK)*/ 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(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return brushType() == MASK ? KisBrush::maskWidth(shape, subPixelX, subPixelY, info) : m_brushesPipe->maskWidth(shape, subPixelX, subPixelY, info); } qint32 KisTextBrush::maskHeight(KisDabShape const& shape, double subPixelX, double subPixelY, const KisPaintInformation& info) const { return brushType() == MASK ? 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); } diff --git a/libs/brush/tests/kis_imagepipe_brush_test.cpp b/libs/brush/tests/kis_imagepipe_brush_test.cpp index 9dc0499b4f..16ef07c902 100644 --- a/libs/brush/tests/kis_imagepipe_brush_test.cpp +++ b/libs/brush/tests/kis_imagepipe_brush_test.cpp @@ -1,265 +1,265 @@ /* * 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 #include #define COMPARE_ALL(brush, method) \ Q_FOREACH (KisGbrBrushSP 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(KisImagePipeBrushSP brush) { qreal scale = 0.5; Q_UNUSED(scale); KisGbrBrushSP 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; KisBrushSP oldBrush = brush->testingGetCurrentBrush(info); QVERIFY(oldBrush); qreal realScale = 1; qreal realAngle = 0; qreal subPixelX = 0; qreal subPixelY = 0; 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, KisDabShape(realScale, 1.0, realAngle), info, subPixelX, subPixelY); QCOMPARE(maskWidth, dev->bounds().width()); QCOMPARE(maskHeight, dev->bounds().height()); KisBrushSP newBrush = brush->testingGetCurrentBrush(info); QCOMPARE(oldBrush, newBrush); } void KisImagePipeBrushTest::testLoading() { QSharedPointer brush(new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih")); brush->load(KisGlobalResourcesInterface::instance()); QVERIFY(brush->valid()); checkConsistency(brush); } void KisImagePipeBrushTest::testChangingBrushes() { QSharedPointer brush(new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih")); brush->load(KisGlobalResourcesInterface::instance()); 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); } } void checkIncrementalPainting(KisBrushSP 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(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->lazyGrowBufferWithoutInitialization(); brush->mask(fixedDab, fillColor, 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() { QSharedPointer brush(new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "C_Dirty_Spot.gih")); brush->load(KisGlobalResourcesInterface::instance()); QVERIFY(brush->valid()); checkConsistency(brush); checkIncrementalPainting(brush, "simple"); } void KisImagePipeBrushTest::testColoredDab() { QSharedPointer brush(new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "G_Sparks.gih")); brush->load(KisGlobalResourcesInterface::instance()); 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(); + brush->makeMaskImage(false); QCOMPARE(brush->useColorAsMask(), false); QCOMPARE(brush->hasColor(), false); QCOMPARE(brush->brushType(), PIPE_MASK); checkConsistency(brush); } void KisImagePipeBrushTest::testColoredDabWash() { QSharedPointer brush(new KisImagePipeBrush(QString(FILES_DATA_DIR) + QDir::separator() + "G_Sparks.gih")); brush->load(KisGlobalResourcesInterface::instance()); 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, 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"); } #include "kis_text_brush.h" void KisImagePipeBrushTest::testTextBrushNoPipes() { QSharedPointer 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"); } void KisImagePipeBrushTest::testTextBrushPiped() { QSharedPointer 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"); } QTEST_MAIN(KisImagePipeBrushTest) diff --git a/libs/image/kis_brush_mask_applicator_base.h b/libs/image/kis_brush_mask_applicator_base.h index 9376326ea8..550dc63a3f 100644 --- a/libs/image/kis_brush_mask_applicator_base.h +++ b/libs/image/kis_brush_mask_applicator_base.h @@ -1,87 +1,90 @@ /* * 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_BRUSH_MASK_APPLICATOR_BASE_H #define __KIS_BRUSH_MASK_APPLICATOR_BASE_H #include "kis_types.h" #include "kis_fixed_paint_device.h" #include "math.h" struct MaskProcessingData { MaskProcessingData(KisFixedPaintDeviceSP _device, const KoColorSpace* _colorSpace, + const quint8* _color, qreal _randomness, qreal _density, double _centerX, double _centerY, double _angle) { device = _device; colorSpace = _colorSpace; + color = _color; randomness = _randomness; density = _density; centerX = _centerX; centerY = _centerY; cosa = cos(_angle); sina = sin(_angle); pixelSize = colorSpace->pixelSize(); } KisFixedPaintDeviceSP device; const KoColorSpace* colorSpace; + const quint8* color; qreal randomness; qreal density; double centerX; double centerY; double cosa; double sina; qint32 pixelSize; }; class KisBrushMaskApplicatorBase { public: virtual ~KisBrushMaskApplicatorBase() {} virtual void process(const QRect &rect) = 0; inline void initializeData(const MaskProcessingData *data) { m_d = data; } protected: const MaskProcessingData *m_d; }; struct OperatorWrapper { OperatorWrapper(KisBrushMaskApplicatorBase *applicator) : m_applicator(applicator) {} inline void operator() (const QRect& rect) { m_applicator->process(rect); } KisBrushMaskApplicatorBase *m_applicator; }; #endif /* __KIS_BRUSH_MASK_APPLICATOR_BASE_H */ diff --git a/libs/image/kis_brush_mask_applicators.h b/libs/image/kis_brush_mask_applicators.h index 4ecd348da9..2a7580bda1 100644 --- a/libs/image/kis_brush_mask_applicators.h +++ b/libs/image/kis_brush_mask_applicators.h @@ -1,202 +1,213 @@ /* * Copyright (c) 2012 Sven Langkamp * 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_BRUSH_MASK_APPLICATORS_H #define __KIS_BRUSH_MASK_APPLICATORS_H #include "kis_brush_mask_applicator_base.h" #include "kis_global.h" #include "kis_random_source.h" // 3x3 supersampling #define SUPERSAMPLING 3 template struct KisBrushMaskScalarApplicator : public KisBrushMaskApplicatorBase { KisBrushMaskScalarApplicator(MaskGenerator *maskGenerator) : m_maskGenerator(maskGenerator) { } void process(const QRect &rect) override { processScalar(rect); } protected: void processScalar(const QRect &rect); protected: MaskGenerator *m_maskGenerator; KisRandomSource m_randomSource; // TODO: make it more deterministic for LoD }; #if defined HAVE_VC template struct KisBrushMaskVectorApplicator : public KisBrushMaskScalarApplicator { KisBrushMaskVectorApplicator(MaskGenerator *maskGenerator) : KisBrushMaskScalarApplicator(maskGenerator) { } void process(const QRect &rect) { startProcessing(rect, TypeHelper()); } protected: void processVector(const QRect &rect); private: template struct TypeHelper {}; private: template inline void startProcessing(const QRect &rect, TypeHelper) { KisBrushMaskScalarApplicator::processScalar(rect); } template inline void startProcessing(const QRect &rect, TypeHelper) { MaskGenerator *m_maskGenerator = KisBrushMaskScalarApplicator::m_maskGenerator; if (m_maskGenerator->shouldVectorize()) { processVector(rect); } else { KisBrushMaskScalarApplicator::processScalar(rect); } } }; template void KisBrushMaskVectorApplicator::processVector(const QRect &rect) { const MaskProcessingData *m_d = KisBrushMaskApplicatorBase::m_d; MaskGenerator *m_maskGenerator = KisBrushMaskScalarApplicator::m_maskGenerator; qreal random = 1.0; quint8* dabPointer = m_d->device->data() + rect.y() * rect.width() * m_d->pixelSize; quint8 alphaValue = OPACITY_TRANSPARENT_U8; // this offset is needed when brush size is smaller then fixed device size int offset = (m_d->device->bounds().width() - rect.width()) * m_d->pixelSize; int width = rect.width(); // We need to calculate with a multiple of the width of the simd register int alignOffset = 0; if (width % Vc::float_v::size() != 0) { alignOffset = Vc::float_v::size() - (width % Vc::float_v::size()); } int simdWidth = width + alignOffset; float *buffer = Vc::malloc(simdWidth); typename MaskGenerator::FastRowProcessor processor(m_maskGenerator); for (int y = rect.y(); y < rect.y() + rect.height(); y++) { processor.template process<_impl>(buffer, simdWidth, y, m_d->cosa, m_d->sina, m_d->centerX, m_d->centerY); if (m_d->randomness != 0.0 || m_d->density != 1.0) { for (int x = 0; x < width; x++) { if (m_d->randomness!= 0.0){ random = (1.0 - m_d->randomness) + m_d->randomness * KisBrushMaskScalarApplicator::m_randomSource.generateNormalized(); } alphaValue = quint8( (OPACITY_OPAQUE_U8 - buffer[x]*255) * random); // avoid computation of random numbers if density is full if (m_d->density != 1.0){ // compute density only for visible pixels of the mask if (alphaValue != OPACITY_TRANSPARENT_U8){ if ( !(m_d->density >= KisBrushMaskScalarApplicator::m_randomSource.generateNormalized()) ){ alphaValue = OPACITY_TRANSPARENT_U8; } } } + if (m_d->color) { + memcpy(dabPointer, m_d->color, m_d->pixelSize); + } + m_d->colorSpace->applyAlphaU8Mask(dabPointer, &alphaValue, 1); dabPointer += m_d->pixelSize; } + } else if (m_d->color) { + m_d->colorSpace->fillInverseAlphaNormedFloatMaskWithColor(dabPointer, buffer, m_d->color, width); + dabPointer += width * m_d->pixelSize; } else { m_d->colorSpace->applyInverseNormedFloatMask(dabPointer, buffer, width); dabPointer += width * m_d->pixelSize; }//endfor x dabPointer += offset; }//endfor y Vc::free(buffer); } #endif /* defined HAVE_VC */ template void KisBrushMaskScalarApplicator::processScalar(const QRect &rect) { const MaskProcessingData *m_d = KisBrushMaskApplicatorBase::m_d; MaskGenerator *m_maskGenerator = KisBrushMaskScalarApplicator::m_maskGenerator; qreal random = 1.0; quint8* dabPointer = m_d->device->data() + rect.y() * rect.width() * m_d->pixelSize; quint8 alphaValue = OPACITY_TRANSPARENT_U8; // this offset is needed when brush size is smaller then fixed device size int offset = (m_d->device->bounds().width() - rect.width()) * m_d->pixelSize; int supersample = (m_maskGenerator->shouldSupersample() ? SUPERSAMPLING : 1); double invss = 1.0 / supersample; int samplearea = pow2(supersample); for (int y = rect.y(); y < rect.y() + rect.height(); y++) { for (int x = rect.x(); x < rect.x() + rect.width(); x++) { int value = 0; for (int sy = 0; sy < supersample; sy++) { for (int sx = 0; sx < supersample; sx++) { double x_ = x + sx * invss - m_d->centerX; double y_ = y + sy * invss - m_d->centerY; double maskX = m_d->cosa * x_ - m_d->sina * y_; double maskY = m_d->sina * x_ + m_d->cosa * y_; value += m_maskGenerator->valueAt(maskX, maskY); } } if (supersample != 1) value /= samplearea; if (m_d->randomness!= 0.0){ random = (1.0 - m_d->randomness) + m_d->randomness * m_randomSource.generateNormalized(); } alphaValue = quint8( (OPACITY_OPAQUE_U8 - value) * random); // avoid computation of random numbers if density is full if (m_d->density != 1.0){ // compute density only for visible pixels of the mask if (alphaValue != OPACITY_TRANSPARENT_U8){ if ( !(m_d->density >= m_randomSource.generateNormalized()) ){ alphaValue = OPACITY_TRANSPARENT_U8; } } } + if (m_d->color) { + memcpy(dabPointer, m_d->color, m_d->pixelSize); + } + m_d->colorSpace->applyAlphaU8Mask(dabPointer, &alphaValue, 1); dabPointer += m_d->pixelSize; }//endfor x dabPointer += offset; }//endfor y } #endif /* __KIS_BRUSH_MASK_APPLICATORS_H */ diff --git a/libs/image/tests/KisMaskGeneratorTest.cpp b/libs/image/tests/KisMaskGeneratorTest.cpp index 6b4fa1c070..74967ce7c5 100644 --- a/libs/image/tests/KisMaskGeneratorTest.cpp +++ b/libs/image/tests/KisMaskGeneratorTest.cpp @@ -1,202 +1,202 @@ /* * Copyright (c) 2018 Iván Santa María * * 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 "KisMaskGeneratorTest.h" #include "kis_mask_similarity_test.h" #include "kis_brush_mask_applicator_base.h" #include "kis_mask_generator.h" #include "kis_cubic_curve.h" #include "krita_utils.h" #include "testutil.h" class KisMaskGeneratorTestTester { public: KisMaskGeneratorTestTester(KisBrushMaskApplicatorBase *_applicatorBase, QRect _bounds) : m_bounds(_bounds), applicatorBase(_applicatorBase) { KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); m_paintDev->setRect(m_bounds); m_paintDev->initialize(255); - MaskProcessingData data(m_paintDev, m_colorSpace, + MaskProcessingData data(m_paintDev, m_colorSpace, nullptr, 0.0, 1.0, m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); // Start Benchmark applicatorBase->initializeData(&data); QElapsedTimer maskGenerationTime; maskGenerationTime.start(); QBENCHMARK { applicatorBase->process(m_bounds); } } protected: const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); QRect m_bounds; KisBrushMaskApplicatorBase *applicatorBase; KisFixedPaintDeviceSP m_paintDev; }; void KisMaskGeneratorTest::testDefaultScalarMask() { QRect bounds(0,0,1000,1000); { KisCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorTestTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testDefaultVectorMask() { QRect bounds(0,0,1000,1000); { KisCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); KisMaskGeneratorTestTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorTest::testCircularGaussScalarMask() { QRect bounds(0,0,1000,1000); { KisGaussCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); circScalar.setDiameter(1000); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorTestTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testCircularGaussVectorMask() { QRect bounds(0,0,1000,1000); { KisGaussCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); circVectr.setDiameter(1000); KisMaskGeneratorTestTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorTest::testCircularSoftScalarMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circScalar.setSoftness(0.5); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorTestTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testCircularSoftVectorMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveCircleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circVectr.setSoftness(0.5); KisMaskGeneratorTestTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorTest::testRectangularScalarMask(){ QRect bounds(0,0,1000,1000); { KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true); rectScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorTestTester(rectScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testRectangularVectorMask(){ QRect bounds(0,0,1000,1000); { KisRectangleMaskGenerator rectScalar(1000, 1.0, 0.5, 0.5, 2, true); KisMaskGeneratorTestTester(rectScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testRectangularGaussScalarMask() { QRect bounds(0,0,1000,1000); { KisGaussRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, true); // circScalar.setDiameter(1000); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorTestTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testRectangularGaussVectorMask() { QRect bounds(0,0,1000,1000); { KisGaussRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, true); // circVectr.setDiameter(1000); KisMaskGeneratorTestTester(circVectr.applicator(), bounds); } } void KisMaskGeneratorTest::testRectangularSoftScalarMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveRectangleMaskGenerator circScalar(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); circScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskGeneratorTestTester(circScalar.applicator(), bounds); } } void KisMaskGeneratorTest::testRectangularSoftVectorMask() { QRect bounds(0,0,1000,1000); KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); { KisCurveRectangleMaskGenerator circVectr(1000, 1.0, 0.5, 0.5, 2, pointsCurve, true); KisMaskGeneratorTestTester(circVectr.applicator(), bounds); } } QTEST_MAIN(KisMaskGeneratorTest) diff --git a/libs/image/tests/kis_mask_similarity_test.cpp b/libs/image/tests/kis_mask_similarity_test.cpp index b900727277..bfc7d852e8 100644 --- a/libs/image/tests/kis_mask_similarity_test.cpp +++ b/libs/image/tests/kis_mask_similarity_test.cpp @@ -1,261 +1,262 @@ /* * Copyright (c) 2018 Iván Santa María * * 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_mask_similarity_test.h" #include #include #include #include #include "kis_brush_mask_applicator_base.h" #include "kis_mask_generator.h" #include "kis_cubic_curve.h" #include "krita_utils.h" #include "config-limit-long-tests.h" #ifdef LIMIT_LONG_TESTS #define RATIO_STEP 50 #define FADE_STEP 25 #else /* LIMIT_LONG_TESTS */ #define RATIO_STEP 20 #define FADE_STEP 5 #endif /* LIMIT_LONG_TESTS */ enum MaskType { DEFAULT, CIRC_GAUSS, CIRC_SOFT, RECT, RECT_GAUSS, RECT_SOFT, STAMP }; class KisMaskSimilarityTester { public: KisMaskSimilarityTester(KisBrushMaskApplicatorBase *_legacy, KisBrushMaskApplicatorBase *_vectorized, QRect _bounds, MaskType type) : legacy(_legacy) , vectorized(_vectorized) , m_bounds(_bounds) { KisFixedPaintDeviceSP m_paintDev = new KisFixedPaintDevice(m_colorSpace); m_paintDev->setRect(m_bounds); m_paintDev->initialize(255); MaskProcessingData data(m_paintDev, m_colorSpace, + nullptr, 0.0, 1.0, m_bounds.width() / 2.0, m_bounds.height() / 2.0,0); // Start legacy scalar processing legacy->initializeData(&data); legacy->process(m_bounds); QImage scalarImage(m_paintDev->convertToQImage(m_colorSpace->profile())); scalarImage.invertPixels(); // Make pixel color black // Start vector processing m_paintDev->initialize(255); vectorized->initializeData(&data); vectorized->process(m_bounds); QImage vectorImage(m_paintDev->convertToQImage(m_colorSpace->profile())); vectorImage.invertPixels(); // Make pixel color black // Check for differences, max errors: 0 QPoint tmpPt; if (!TestUtil::compareQImages(tmpPt,scalarImage, vectorImage, 0, 2, 0)) { scalarImage.save(QString(getTypeName(type) + "_scalar_mask.png"),"PNG"); vectorImage.save(QString(getTypeName(type) + "_vector_mask.png"),"PNG"); QFAIL(QString("Masks differ! first different pixel: %1,%2 \n").arg(tmpPt.x()).arg(tmpPt.y()).toLatin1()); } } static bool exahustiveTest(QRect bounds, MaskType type) { // Exahustive test for (size_t i = 0; i <= 100; i += FADE_STEP){ for (size_t j = 0; j <= 100; j += FADE_STEP){ for (size_t k = 0; k <= 100; k += RATIO_STEP){ switch (type) { case CIRC_GAUSS: { KisGaussCircleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); bCircVectr.setDiameter(499.5); KisGaussCircleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; } case CIRC_SOFT: { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveCircleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, pointsCurve, true); bCircVectr.setDiameter(499.5); KisCurveCircleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; } case RECT: { KisRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); KisRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; } case RECT_GAUSS: { KisGaussRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, true); KisGaussRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; } case RECT_SOFT: { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveRectangleMaskGenerator bCircVectr(499.5, k/100.f, i/100.f, j/100.f, 2, pointsCurve, true); KisCurveRectangleMaskGenerator bCircScalar(bCircVectr); bCircScalar.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(bCircScalar.applicator(), bCircVectr.applicator(), bounds,type); break; } default: { break; } } if (QTest::currentTestFailed()) { QWARN(QString("Mask features: Ratio=%1, hfade=%2, vfade=%3 \n") .arg(k/100.f,0,'g',2).arg(i/100.f,0,'g',2).arg(j/100.f,0,'g',2).toLatin1()); return false; } } } } // end for return true; } template static void runMaskGenTest(MaskGenerator& generator, MaskType type) { QRect bounds(0,0,700,700); generator.setDiameter(499.5); MaskGenerator scalarGenerator(generator); scalarGenerator.resetMaskApplicator(true); // Force usage of scalar backend KisMaskSimilarityTester(scalarGenerator.applicator(), generator.applicator(), bounds, type); // KisMaskSimilarityTester::exahustiveTest(bounds,type); } private: QString getTypeName(MaskType type) { QString strName; switch (type) { case CIRC_GAUSS: strName = "CircGauss"; break; case CIRC_SOFT: strName = "CircSoft"; break; case RECT: strName = "Rect"; break; case RECT_GAUSS: strName = "RectGauss"; break; case RECT_SOFT: strName = "RectSoft"; break; case STAMP: strName = "Stamp"; break; default: strName = "Default"; break; } return strName; } protected: const KoColorSpace *m_colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisBrushMaskApplicatorBase *legacy; KisBrushMaskApplicatorBase *vectorized; QRect m_bounds; KisFixedPaintDeviceSP m_paintDev; }; void KisMaskSimilarityTest::testCircleMask() { KisCircleMaskGenerator generator(499.5, 0.2, 0.5, 0.5, 2, true); qDebug() << generator.id() << generator.name(); KisMaskSimilarityTester::runMaskGenTest(generator,DEFAULT); } void KisMaskSimilarityTest::testGaussCircleMask() { KisGaussCircleMaskGenerator generator(499.5, 0.2, 1, 1, 2, true); KisMaskSimilarityTester::runMaskGenTest(generator,CIRC_GAUSS); } void KisMaskSimilarityTest::testSoftCircleMask() { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveCircleMaskGenerator generator(499.5, 0.2, 0.5, 0.5, 2, pointsCurve,true); KisMaskSimilarityTester::runMaskGenTest(generator,CIRC_SOFT); } void KisMaskSimilarityTest::testRectMask() { KisRectangleMaskGenerator generator(499.5, 0.1, 0.5, 0.5, 2, false); KisMaskSimilarityTester::runMaskGenTest(generator,RECT); } void KisMaskSimilarityTest::testGaussRectMask() { KisGaussRectangleMaskGenerator generator(499.5, 0.2, 0.5, 0.2, 2, true); KisMaskSimilarityTester::runMaskGenTest(generator,RECT_GAUSS); } void KisMaskSimilarityTest::testSoftRectMask() { KisCubicCurve pointsCurve; pointsCurve.fromString(QString("0,1;1,0")); KisCurveRectangleMaskGenerator generator(499.5, 0.2, 0.5, 0.2, 2, pointsCurve, true); KisMaskSimilarityTester::runMaskGenTest(generator,RECT_SOFT); } QTEST_MAIN(KisMaskSimilarityTest) diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp index 48ef840a2a..f847452d15 100644 --- a/libs/pigment/KoColorSpace.cpp +++ b/libs/pigment/KoColorSpace.cpp @@ -1,816 +1,833 @@ /* * Copyright (c) 2005 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoColorSpace.h" #include "KoColorSpace_p.h" #include "KoChannelInfo.h" #include "DebugPigment.h" #include "KoCompositeOp.h" #include "KoColorTransformation.h" #include "KoColorTransformationFactory.h" #include "KoColorTransformationFactoryRegistry.h" #include "KoColorConversionCache.h" #include "KoColorConversionSystem.h" #include "KoColorSpaceRegistry.h" #include "KoColorProfile.h" #include "KoCopyColorConversionTransformation.h" #include "KoFallBackColorTransformation.h" #include "KoUniqueNumberForIdServer.h" #include "KoMixColorsOp.h" #include "KoConvolutionOp.h" #include "KoCompositeOpRegistry.h" #include "KoColorSpaceEngine.h" +#include +#include #include #include #include #include #include #include KoColorSpace::KoColorSpace() : d(new Private()) { } KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp) : d(new Private()) { d->id = id; d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id); d->name = name; d->mixColorsOp = mixColorsOp; d->convolutionOp = convolutionOp; d->transfoToRGBA16 = 0; d->transfoFromRGBA16 = 0; d->transfoToLABA16 = 0; d->transfoFromLABA16 = 0; d->gamutXYY = QPolygonF(); d->TRCXYY = QPolygonF(); d->colorants = QVector (0); d->lumaCoefficients = QVector (0); d->iccEngine = 0; d->deletability = NotOwnedByRegistry; } KoColorSpace::~KoColorSpace() { Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete); qDeleteAll(d->compositeOps); Q_FOREACH (KoChannelInfo * channel, d->channels) { delete channel; } if (d->deletability == NotOwnedByRegistry) { KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache(); if (cache) { cache->colorSpaceIsDestroyed(this); } } delete d->mixColorsOp; delete d->convolutionOp; delete d->transfoToRGBA16; delete d->transfoFromRGBA16; delete d->transfoToLABA16; delete d->transfoFromLABA16; delete d; } bool KoColorSpace::operator==(const KoColorSpace& rhs) const { const KoColorProfile* p1 = rhs.profile(); const KoColorProfile* p2 = profile(); return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2)); } QString KoColorSpace::id() const { return d->id; } QString KoColorSpace::name() const { return d->name; } //Color space info stuff. QPolygonF KoColorSpace::gamutXYY() const { if (d->gamutXYY.empty()) { //now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases. //first make a list of colors. qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. max = this->channels()[0]->getUIMax(); } int samples = 5;//amount of samples in our color space. const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32"); quint8 *data = new quint8[pixelSize()]; quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel. //QVector sampleCoordinates(pow(colorChannelCount(),samples)); //sampleCoordinates.fill(0.0); // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for(int x=0;xnormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY << QPointF(x,y); } else { for(int y=0;ynormalisedChannelsValue(data2, channelValuesF); qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } else { channelValuesF[0]=(max/(samples-1))*(x); channelValuesF[1]=(max/(samples-1))*(y); channelValuesF[2]=(max/(samples-1))*(z); channelValuesF[3]=max; if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->gamutXYY<< QPointF(x,y); } } } } } delete[] data; //if we ever implement a boundary-checking thing I'd add it here. return d->gamutXYY; } else { return d->gamutXYY; } } QPolygonF KoColorSpace::estimatedTRCXYY() const { if (d->TRCXYY.empty()){ qreal max = 1.0; if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") { //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general. max = this->channels()[0]->getUIMax(); } const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32"); quint8 *data = new quint8[pixelSize()]; quint8 *data2 = new quint8[xyzColorSpace->pixelSize()]; // This is fixed to 5 since the maximum number of channels are 5 for CMYKA QVector channelValuesF(5);//for getting the coordinates. for (quint32 i=0; i=0; j--){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(4-j)); if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz. fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); } if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(4-j))); } } else { for (int j=0; j<5; j++){ channelValuesF.fill(0.0); channelValuesF[i] = ((max/4)*(j)); fromNormalisedChannelsValue(data, channelValuesF); convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags()); xyzColorSpace->normalisedChannelsValue(data2,channelValuesF); if (j==0) { colorantY = channelValuesF[1]; if (d->colorants.size()<2){ d->colorants.resize(3*colorChannelCount()); d->colorants[i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]); d->colorants[i+2]= channelValuesF[1]; } } d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j))); } } } delete[] data; delete[] data2; return d->TRCXYY; } else { return d->TRCXYY; } } QVector KoColorSpace::lumaCoefficients() const { if (d->lumaCoefficients.size()>1){ return d->lumaCoefficients; } else { d->lumaCoefficients.resize(3); if (colorModelId().id()!="RGBA") { d->lumaCoefficients.fill(0.33); } else { if (d->colorants.size() <= 0) { if (profile() && profile()->hasColorants()) { d->colorants.resize(3 * colorChannelCount()); d->colorants = profile()->getColorantsxyY(); } else { QPolygonF p = estimatedTRCXYY(); Q_UNUSED(p); } } if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) { d->lumaCoefficients[0]=0.2126; d->lumaCoefficients[1]=0.7152; d->lumaCoefficients[2]=0.0722; } else { d->lumaCoefficients[0]=d->colorants[2]; d->lumaCoefficients[1]=d->colorants[5]; d->lumaCoefficients[2]=d->colorants[8]; } } return d->lumaCoefficients; } } QList KoColorSpace::channels() const { return d->channels; } QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const { QBitArray ba(d->channels.size()); if (!color && !alpha) return ba; for (int i = 0; i < d->channels.size(); ++i) { KoChannelInfo * channel = d->channels.at(i); if ((color && channel->channelType() == KoChannelInfo::COLOR) || (alpha && channel->channelType() == KoChannelInfo::ALPHA)) ba.setBit(i, true); } return ba; } void KoColorSpace::addChannel(KoChannelInfo * ci) { d->channels.push_back(ci); } bool KoColorSpace::hasCompositeOp(const QString& id) const { return d->compositeOps.contains(id); } QList KoColorSpace::compositeOps() const { return d->compositeOps.values(); } KoMixColorsOp* KoColorSpace::mixColorsOp() const { return d->mixColorsOp; } KoConvolutionOp* KoColorSpace::convolutionOp() const { return d->convolutionOp; } const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const { const QHash::ConstIterator it = d->compositeOps.constFind(id); if (it != d->compositeOps.constEnd()) { return it.value(); } else { warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER; return d->compositeOps.value(COMPOSITE_OVER); } } void KoColorSpace::addCompositeOp(const KoCompositeOp * op) { if (op->colorSpace()->id() == id()) { d->compositeOps.insert(op->id(), const_cast(op)); } } const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const { if (!d->transfoToLABA16) { d->transfoToLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToLABA16; } const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const { if (!d->transfoFromLABA16) { d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromLABA16; } const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const { if (!d->transfoToRGBA16) { d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoToRGBA16; } const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const { if (!d->transfoFromRGBA16) { d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16() , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ; } return d->transfoFromRGBA16; } void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromLabA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { toRgbA16Converter()->transform(src, dst, nPixels); } void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const { fromRgbA16Converter()->transform(src, dst, nPixels); } KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { return new KoCopyColorConversionTransformation(this); } else { return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags); } } bool KoColorSpace::convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (*this == *dstColorSpace) { if (src != dst) { memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize()); } } else { KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags); cct.transformation()->transform(src, dst, numPixels); } return true; } KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const { if (!d->iccEngine) { d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc"); } if (!d->iccEngine) return 0; return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState); } bool KoColorSpace::proofPixelsTo(const quint8 *src, quint8 *dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const { proofingTransform->transform(src, dst, numPixels); //the transform is deleted in the destructor. return true; } void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { Q_ASSERT_X(*op->colorSpace() == *this, "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1()); if(params.rows <= 0 || params.cols <= 0) return; if(!(*this == *srcSpace)) { if (preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(op->id())) { quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize(); QVector * conversionDstCache = threadLocalConversionCache(params.rows * conversionDstBufferStride); quint8* conversionDstData = conversionDstCache->data(); for(qint32 row=0; rowcompositeOp(op->id()); KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.dstRowStart = conversionDstData; paramInfo.dstRowStride = conversionDstBufferStride; otherOp->composite(paramInfo); for(qint32 row=0; rowconvertPixelsTo(conversionDstData + row * conversionDstBufferStride, params.dstRowStart + row * params.dstRowStride, this, params.cols, renderingIntent, conversionFlags); } } else { quint32 conversionBufferStride = params.cols * pixelSize(); QVector * conversionCache = threadLocalConversionCache(params.rows * conversionBufferStride); quint8* conversionData = conversionCache->data(); for(qint32 row=0; rowconvertPixelsTo(params.srcRowStart + row * params.srcRowStride, conversionData + row * conversionBufferStride, this, params.cols, renderingIntent, conversionFlags); } KoCompositeOp::ParameterInfo paramInfo(params); paramInfo.srcRowStart = conversionData; paramInfo.srcRowStride = conversionBufferStride; op->composite(paramInfo); } } else { op->composite(params); } } QVector * KoColorSpace::threadLocalConversionCache(quint32 size) const { QVector * ba = 0; if (!d->conversionCache.hasLocalData()) { ba = new QVector(size, '0'); d->conversionCache.setLocalData(ba); } else { ba = d->conversionCache.localData(); if ((quint8)ba->size() < size) ba->resize(size); } return ba; } KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash & parameters) const { KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id); if (!factory) return 0; QPair model(colorModelId(), colorDepthId()); QList< QPair > models = factory->supportedModels(); if (models.isEmpty() || models.contains(model)) { return factory->createTransformation(this, parameters); } else { // Find the best solution // TODO use the color conversion cache KoColorConversionTransformation* csToFallBack = 0; KoColorConversionTransformation* fallBackToCs = 0; KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs); Q_ASSERT(csToFallBack); Q_ASSERT(fallBackToCs); KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters); return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo); } } void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{ int channelnumber = channelCount(); QVector channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); luma = qMin(1.0, luma + step); luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = qMin(1.0, luma + step); channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ihasTRC()){ //only linearise and crunch the luma if there's a TRC profile()->linearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); luma = pow(luma, 1/2.2); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } luma = pow(luma, 2.2); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); } else { qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (luma-step<0.0) { luma=0.0; } else { luma -= step; } channelValues = fromHSY(&hue, &sat, &luma); } for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat += step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); sat -= step; sat = qBound(0.0, sat, 1.0); channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue+step>1.0){ hue=(hue+step)- 1.0; } else { hue += step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal hue, sat, luma = 0.0; toHSY(channelValues, &hue, &sat, &luma); if (hue-step<0.0){ hue=1.0-(step-hue); } else { hue -= step; } channelValues = fromHSY(&hue, &sat, &luma); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u += step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); u -= step; u = qBound(0.0, u, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v += step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;i channelValues(channelnumber); QVector channelValuesF(channelnumber); normalisedChannelsValue(pixel, channelValuesF); for (int i=0;ilinearizeFloatValue(channelValues); qreal y, u, v = 0.0; toYUV(channelValues, &y, &u, &v); v -= step; v = qBound(0.0, v, 1.0); channelValues = fromYUV(&y, &u, &v); profile()->delinearizeFloatValue(channelValues); for (int i=0;irgb8(dstProfile); if (data) this->convertPixelsTo(const_cast(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags); return img; } bool KoColorSpace::preferCompositionInSourceColorSpace() const { return false; } + +void KoColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + /// Fallback implementation. All RGB color spaces have their own + /// implementation without any conversions. + + const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel); + QScopedArrayPointer rgbBuffer(new quint8[(nPixels + 1) * rgbPixelSize]); + quint8 *rgbBrushColorBuffer = rgbBuffer.data() + nPixels * rgbPixelSize; + + this->toRgbA16(dst, rgbBuffer.data(), nPixels); + this->toRgbA16(brushColor, rgbBrushColorBuffer, 1); + fillGrayBrushWithColorPreserveLightnessRGB(rgbBuffer.data(), brush, rgbBrushColorBuffer, nPixels); + this->fromRgbA16(rgbBuffer.data(), dst, nPixels); +} diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h index e10bb70673..c5350f5662 100644 --- a/libs/pigment/KoColorSpace.h +++ b/libs/pigment/KoColorSpace.h @@ -1,674 +1,706 @@ /* * Copyright (c) 2005 Boudewijn Rempt * Copyright (c) 2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACE_H #define KOCOLORSPACE_H #include #include #include #include #include #include #include "KoColorSpaceConstants.h" #include "KoColorConversionTransformation.h" #include "KoColorProofingConversionTransformation.h" #include "KoCompositeOp.h" #include #include "kritapigment_export.h" class QDomDocument; class QDomElement; class KoChannelInfo; class KoColorProfile; class KoColorTransformation; class QBitArray; enum Deletability { OwnedByRegistryDoNotDelete, OwnedByRegistryRegistryDeletes, NotOwnedByRegistry }; enum ColorSpaceIndependence { FULLY_INDEPENDENT, TO_LAB16, TO_RGBA8, TO_RGBA16 }; class KoMixColorsOp; class KoConvolutionOp; /** * A KoColorSpace is the definition of a certain color space. * * A color model and a color space are two related concepts. A color * model is more general in that it describes the channels involved and * how they in broad terms combine to describe a color. Examples are * RGB, HSV, CMYK. * * A color space is more specific in that it also describes exactly how * the channels are combined. So for each color model there can be a * number of specific color spaces. So RGB is the model and sRGB, * adobeRGB, etc are colorspaces. * * In Pigment KoColorSpace acts as both a color model and a color space. * You can think of the class definition as the color model, but the * instance of the class as representing a colorspace. * * A third concept is the profile represented by KoColorProfile. It * represents the info needed to specialize a color model into a color * space. * * KoColorSpace is an abstract class serving as an interface. * * Subclasses implement actual color spaces * Some subclasses implement only some parts and are named Traits * */ class KRITAPIGMENT_EXPORT KoColorSpace : public boost::equality_comparable { friend class KoColorSpaceRegistry; friend class KoColorSpaceFactory; protected: /// Only for use by classes that serve as baseclass for real color spaces KoColorSpace(); public: /// Should be called by real color spaces KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp); virtual bool operator==(const KoColorSpace& rhs) const; protected: virtual ~KoColorSpace(); public: //========== Gamut and other basic info ===================================// /* * @returns QPolygonF with 5*channel samples converted to xyY. * maybe convert to 3d space in future? */ QPolygonF gamutXYY() const; /* * @returns a polygon with 5 samples per channel converted to xyY, but unlike * gamutxyY it focuses on the luminance. This then can be used to visualise * the approximate trc of a given colorspace. */ QPolygonF estimatedTRCXYY() const; QVector lumaCoefficients() const; //========== Channels =====================================================// /// Return a list describing all the channels this color model has. The order /// of the channels in the list is the order of channels in the pixel. To find /// out the preferred display position, use KoChannelInfo::displayPosition. QList channels() const; /** * The total number of channels for a single pixel in this color model */ virtual quint32 channelCount() const = 0; /** * Position of the alpha channel in a pixel */ virtual quint32 alphaPos() const = 0; /** * The total number of color channels (excludes alpha) for a single * pixel in this color model. */ virtual quint32 colorChannelCount() const = 0; /** * returns a QBitArray that contains true for the specified * channel types: * * @param color if true, set all color channels to true * @param alpha if true, set all alpha channels to true * * The order of channels is the colorspace descriptive order, * not the pixel order. */ QBitArray channelFlags(bool color = true, bool alpha = false) const; /** * The size in bytes of a single pixel in this color model */ virtual quint32 pixelSize() const = 0; /** * Return a string with the channel's value suitable for display in the gui. */ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a string with the channel's value with integer * channels normalised to the floating point range 0 to 1, if * appropriate. */ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const = 0; /** * Return a QVector of floats with channels' values normalized * to floating point range 0 to 1. */ virtual void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const = 0; /** * Write in the pixel the value from the normalized vector. */ virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const = 0; /** * Convert the value of the channel at the specified position into * an 8-bit value. The position is not the number of bytes, but * the position of the channel as defined in the channel info list. */ virtual quint8 scaleToU8(const quint8 * srcPixel, qint32 channelPos) const = 0; /** * Set dstPixel to the pixel containing only the given channel of srcPixel. The remaining channels * should be set to whatever makes sense for 'empty' channels of this color space, * with the intent being that the pixel should look like it only has the given channel. */ virtual void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const = 0; //========== Identification ===============================================// /** * ID for use in files and internally: unchanging name. As the id must be unique * it is usually the concatenation of the id of the color model and of the color * depth, for instance "RGBA8" or "CMYKA16" or "XYZA32f". */ QString id() const; /** * User visible name which contains the name of the color model and of the color depth. * For instance "RGBA (8-bits)" or "CMYKA (16-bits)". */ QString name() const; /** * @return a string that identify the color model (for instance "RGB" or "CMYK" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorModelId() const = 0; /** * @return a string that identify the bit depth (for instance "U8" or "F16" ...) * @see KoColorModelStandardIds.h */ virtual KoID colorDepthId() const = 0; /** * @return true if the profile given in argument can be used by this color space */ virtual bool profileIsCompatible(const KoColorProfile* profile) const = 0; /** * If false, images in this colorspace will degrade considerably by * functions, tools and filters that have the given measure of colorspace * independence. * * @param independence the measure to which this colorspace will suffer * from the manipulations of the tool or filter asking * @return false if no degradation will take place, true if degradation will * take place */ virtual bool willDegrade(ColorSpaceIndependence independence) const = 0; //========== Capabilities =================================================// /** * Tests if the colorspace offers the specific composite op. */ virtual bool hasCompositeOp(const QString & id) const; /** * Returns the list of user-visible composite ops supported by this colorspace. */ virtual QList compositeOps() const; /** * Retrieve a single composite op from the ones this colorspace offers. * If the requeste composite op does not exist, COMPOSITE_OVER is returned. */ const KoCompositeOp * compositeOp(const QString & id) const; /** * add a composite op to this colorspace. */ virtual void addCompositeOp(const KoCompositeOp * op); /** * Returns true if the colorspace supports channel values outside the * (normalised) range 0 to 1. */ virtual bool hasHighDynamicRange() const = 0; //========== Display profiles =============================================// /** * Return the profile of this color space. */ virtual const KoColorProfile * profile() const = 0; //================= Conversion functions ==================================// /** * The fromQColor methods take a given color defined as an RGB QColor * and fills a byte array with the corresponding color in the * the colorspace managed by this strategy. * * @param color the QColor that will be used to fill dst * @param dst a pointer to a pixel * @param profile the optional profile that describes the color values of QColor */ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const = 0; /** * The toQColor methods take a byte array that is at least pixelSize() long * and converts the contents to a QColor, using the given profile as a source * profile and the optional profile as a destination profile. * * @param src a pointer to the source pixel * @param c the QColor that will be filled with the color at src * @param profile the optional profile that describes the color in c, for instance the monitor profile */ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const = 0; /** * Convert the pixels in data to (8-bit BGRA) QImage using the specified profiles. * * @param data A pointer to a contiguous memory region containing width * height pixels * @param width in pixels * @param height in pixels * @param dstProfile destination profile * @param renderingIntent the rendering intent * @param conversionFlags conversion flags */ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert the specified data to Lab (D50). All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from Lab (D50). to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit lab format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data to sRGB 16 bits. All colorspaces are guaranteed to support this * * @param src the source data * @param dst the destination data * @param nPixels the number of source pixels */ virtual void toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Convert the specified data from sRGB 16 bits. to this colorspace. All colorspaces are * guaranteed to support this. * * @param src the pixels in 16 bit rgb format * @param dst the destination data * @param nPixels the number of pixels in the array */ virtual void fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const; /** * Create a color conversion transformation. */ virtual KoColorConversionTransformation* createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Convert a byte array of srcLen pixels *src to the specified color space * and put the converted bytes into the prepared byte array *dst. * * Returns false if the conversion failed, true if it succeeded * * This function is not thread-safe. If you want to apply multiple conversion * in different threads at the same time, you need to create one color converter * per-thread using createColorConverter. */ virtual bool convertPixelsTo(const quint8 * src, quint8 * dst, const KoColorSpace * dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; virtual KoColorConversionTransformation *createProofingTransform(const KoColorSpace * dstColorSpace, const KoColorSpace * proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const; /** * @brief proofPixelsTo * @param src source * @param dst destination * @param numPixels the amount of pixels. * @param proofingTransform the intent used for proofing. * @return */ virtual bool proofPixelsTo(const quint8 * src, quint8 * dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const; /** * Convert @p nPixels pixels in @p src into their human-visible * visual representation. The channel is shown as grayscale. * * Both buffers are in the same color space. * * @param src source buffer in (*this) color space * @param dst destination buffer in the same color space as @p src * @param nPixels length of the buffers in number of pixels * @param pixelSize stride of each pixel in the destination buffer * @param selectedChannelIndex Index of the selected channel. */ virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const = 0; /** * Convert @p nPixels pixels in @p src into their human-visible * visual representation. The channels are shown as if other channels were null (or, if Lab, L = 1.0, *a = *b = 0.0). * * Both buffers are in the same color space. * * @param src source buffer in (*this) color space * @param dst destination buffer in the same color space as @p src * @param nPixels length of the buffers in number of pixels * @param pixelSize stride of each pixel in the destination buffer * @param selectedChannels Bitmap of selected channels */ virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const = 0; //============================== Manipulation functions ==========================// // // The manipulation functions have default implementations that _convert_ the pixel // to a QColor and back. Reimplement these methods in your color strategy! // /** * Get the alpha value of the given pixel, downscaled to an 8-bit value. */ virtual quint8 opacityU8(const quint8 * pixel) const = 0; virtual qreal opacityF(const quint8 * pixel) const = 0; /** * Set the alpha channel of the given run of pixels to the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; virtual void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const = 0; /** * Multiply the alpha channel of the given run of pixels by the given value. * * pixels -- a pointer to the pixels that will have their alpha set to this value * alpha -- a downscaled 8-bit value for opacity * nPixels -- the number of pixels * */ virtual void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const = 0; /** * Applies the specified 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the inverted 8-bit alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; the alpha values * are assumed to be 8-bits. */ virtual void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const = 0; /** * Applies the specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; /** * Applies the inverted specified float alpha mask to the pixels. We assume that there are just * as many alpha values as pixels but we do not check this; alpha values have to be between 0.0 and 1.0 */ virtual void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const = 0; + /** + * Fills \p pixels with specified \p brushColor and then applies inverted brush + * mask specified in \p alpha. + */ + virtual void fillInverseAlphaNormedFloatMaskWithColor(quint8 * pixels, const float * alpha, const quint8 *brushColor, qint32 nPixels) const = 0; + + /** + * Fills \p dst with specified \p brushColor and then applies inverted brush + * mask specified in \p brush. Premultiplied red channel of the brush is + * used as an alpha channel for destination pixels. + * + * The equation is: + * + * dstC = colorC; + * dstA = qAlpha(brush) * (255 - qRed(brush)) / 255; + */ + virtual void fillGrayBrushWithColor(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const = 0; + + /** + * Fills \p dst with specified \p brushColor and then applies inverted brush + * mask specified in \p brush. Inverted red channel of the brush is used + * as lightness of the destination. Alpha channel of the brush is used as + * alpha of the destination. + * + * The equation is: + * + * lightFactor = 2.0f * (brushL_hsl / 255.0f) - 1.0f; + * dstL_hsl = colorL_hsl + lightFactor; + * dstA = qAlpha(brush); + */ + virtual void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const; + /** * Create an adjustment object for adjusting the brightness and contrast * transferValues is a 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const = 0; /** * Create an adjustment object for adjusting individual channels * transferValues is an array of colorChannelCount number of 256 bins array with values from 0 to 0xFFFF * This function is thread-safe, but you need to create one KoColorTransformation per thread. * * The layout of the channels must be the following: * * 0..N-2 - color channels of the pixel; * N-1 - alpha channel of the pixel (if exists) */ virtual KoColorTransformation *createPerChannelAdjustment(const quint16 * const* transferValues) const = 0; /** * Darken all color channels with the given amount. If compensate is true, * the compensation factor will be used to limit the darkening. * */ virtual KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const = 0; /** * Invert color channels of the given pixels * This function is thread-safe, but you need to create one KoColorTransformation per thread. */ virtual KoColorTransformation *createInvertTransformation() const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). Only completely * opaque and completely transparent are taken into account when computing the difference; * other transparency levels are not regarded when finding the difference. */ virtual quint8 difference(const quint8* src1, const quint8* src2) const = 0; /** * Get the difference between 2 colors, normalized in the range (0,255). This function * takes the Alpha channel of the pixel into account. Alpha channel has the same * weight as Lightness channel. */ virtual quint8 differenceA(const quint8* src1, const quint8* src2) const = 0; /** * @return the mix color operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoMixColorsOp* mixColorsOp() const; /** * @return the convolution operation of this colorspace (do not delete it locally, it's deleted by the colorspace). */ virtual KoConvolutionOp* convolutionOp() const; /** * Calculate the intensity of the given pixel, scaled down to the range 0-255. XXX: Maybe this should be more flexible */ virtual quint8 intensity8(const quint8 * src) const = 0; /* *increase luminosity by step */ virtual void increaseLuminosity(quint8 * pixel, qreal step) const; virtual void decreaseLuminosity(quint8 * pixel, qreal step) const; virtual void increaseSaturation(quint8 * pixel, qreal step) const; virtual void decreaseSaturation(quint8 * pixel, qreal step) const; virtual void increaseHue(quint8 * pixel, qreal step) const; virtual void decreaseHue(quint8 * pixel, qreal step) const; virtual void increaseRed(quint8 * pixel, qreal step) const; virtual void increaseGreen(quint8 * pixel, qreal step) const; virtual void increaseBlue(quint8 * pixel, qreal step) const; virtual void increaseYellow(quint8 * pixel, qreal step) const; virtual void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const = 0; virtual QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const = 0; virtual void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const = 0; virtual QVector fromYUV(qreal *y, qreal *u, qreal *v) const = 0; /** * Compose two arrays of pixels together. If source and target * are not the same color model, the source pixels will be * converted to the target model. We're "dst" -- "dst" pixels are always in _this_ * colorspace. * * @param srcSpace the colorspace of the source pixels that will be composited onto "us" * @param params the information needed for blitting e.g. the source and destination pixel data, * the opacity and flow, ... * @param op the composition operator to use, e.g. COPY_OVER * @param renderingIntent the rendering intent * @param conversionFlags the conversion flags. * */ virtual void bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const; /** * Serialize this color following Create's swatch color specification available * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * This function doesn't create the \ element but rather the \, * \, \ ... elements. It is assumed that colorElt is the \ * element. * * @param pixel buffer to serialized * @param colorElt root element for the serialization, it is assumed that this * element is \ * @param doc is the document containing colorElt */ virtual void colorToXML(const quint8* pixel, QDomDocument& doc, QDomElement& colorElt) const = 0; /** * Unserialize a color following Create's swatch color specification available * at https://web.archive.org/web/20110826002520/http://create.freedesktop.org/wiki/Swatches_-_colour_file_format/Draft * * @param pixel buffer where the color will be unserialized * @param elt the element to unserialize (\, \, \) * @return the unserialize color, or an empty color object if the function failed * to unserialize the color */ virtual void colorFromXML(quint8* pixel, const QDomElement& elt) const = 0; KoColorTransformation* createColorTransformation(const QString & id, const QHash & parameters) const; protected: /** * Use this function in the constructor of your colorspace to add the information about a channel. * @param ci a pointer to the information about a channel */ virtual void addChannel(KoChannelInfo * ci); const KoColorConversionTransformation* toLabA16Converter() const; const KoColorConversionTransformation* fromLabA16Converter() const; const KoColorConversionTransformation* toRgbA16Converter() const; const KoColorConversionTransformation* fromRgbA16Converter() const; /** * Returns the thread-local conversion cache. If it doesn't exist * yet, it is created. If it is currently too small, it is resized. */ QVector * threadLocalConversionCache(quint32 size) const; /** * This function defines the behavior of the bitBlt function * when the composition of pixels in different colorspaces is * requested, that is in case: * * srcCS == any * dstCS == this * * 1) preferCompositionInSourceColorSpace() == false, * * the source pixels are first converted to *this color space * and then composition is performed. * * 2) preferCompositionInSourceColorSpace() == true, * * the destination pixels are first converted into *srcCS color * space, then the composition is done, and the result is finally * converted into *this colorspace. * * This is used by alpha8() color space mostly, because it has * weaker representation of the color, so the composition * should be done in CS with richer functionality. */ virtual bool preferCompositionInSourceColorSpace() const; struct Private; Private * const d; }; inline QDebug operator<<(QDebug dbg, const KoColorSpace *cs) { if (cs) { dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )"; } else { dbg.nospace() << "0x0"; } return dbg.space(); } #endif // KOCOLORSPACE_H diff --git a/libs/pigment/KoColorSpaceAbstract.h b/libs/pigment/KoColorSpaceAbstract.h index fd1a6776ae..397a7a6660 100644 --- a/libs/pigment/KoColorSpaceAbstract.h +++ b/libs/pigment/KoColorSpaceAbstract.h @@ -1,247 +1,255 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2007 Emanuele Tamponi * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOLORSPACEABSTRACT_H #define KOCOLORSPACEABSTRACT_H #include #include #include #include #include #include #include "KoFallBackColorTransformation.h" #include "KoLabDarkenColorTransformation.h" #include "KoMixColorsOpImpl.h" #include "KoConvolutionOpImpl.h" #include "KoInvertColorTransformation.h" /** * This in an implementation of KoColorSpace which can be used as a base for colorspaces with as many * different channels of the same type. * * The template parameters must be a class which inherits KoColorSpaceTrait (or a class with the same signature). * * SOMETYPE is the type of the channel for instance (quint8, quint32...), * SOMENBOFCHANNELS is the number of channels including the alpha channel * SOMEALPHAPOS is the position of the alpha channel in the pixel (can be equal to -1 if no alpha channel). */ template class KoColorSpaceAbstract : public KoColorSpace { public: typedef _CSTrait ColorSpaceTraits; public: KoColorSpaceAbstract(const QString &id, const QString &name) : KoColorSpace(id, name, new KoMixColorsOpImpl< _CSTrait>(), new KoConvolutionOpImpl< _CSTrait>()) { } quint32 colorChannelCount() const override { if (_CSTrait::alpha_pos == -1) return _CSTrait::channels_nb; else return _CSTrait::channels_nb - 1; } quint32 channelCount() const override { return _CSTrait::channels_nb; } quint32 alphaPos() const override { return _CSTrait::alpha_pos; } quint32 pixelSize() const override { return _CSTrait::pixelSize; } QString channelValueText(const quint8 *pixel, quint32 channelIndex) const override { return _CSTrait::channelValueText(pixel, channelIndex); } QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const override { return _CSTrait::normalisedChannelValueText(pixel, channelIndex); } void normalisedChannelsValue(const quint8 *pixel, QVector &channels) const override { return _CSTrait::normalisedChannelsValue(pixel, channels); } void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) const override { return _CSTrait::fromNormalisedChannelsValue(pixel, values); } quint8 scaleToU8(const quint8 * srcPixel, qint32 channelIndex) const override { typename _CSTrait::channels_type c = _CSTrait::nativeArray(srcPixel)[channelIndex]; return KoColorSpaceMaths::scaleToA(c); } void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) const override { _CSTrait::singleChannelPixel(dstPixel, srcPixel, channelIndex); } quint8 opacityU8(const quint8 * U8_pixel) const override { return _CSTrait::opacityU8(U8_pixel); } qreal opacityF(const quint8 * U8_pixel) const override { return _CSTrait::opacityF(U8_pixel); } void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) const override { _CSTrait::setOpacity(pixels, alpha, nPixels); } void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) const override { _CSTrait::setOpacity(pixels, alpha, nPixels); } void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) const override { _CSTrait::multiplyAlpha(pixels, alpha, nPixels); } void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const override { _CSTrait::applyAlphaU8Mask(pixels, alpha, nPixels); } void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) const override { _CSTrait::applyInverseAlphaU8Mask(pixels, alpha, nPixels); } void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const override { _CSTrait::applyAlphaNormedFloatMask(pixels, alpha, nPixels); } void applyInverseNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) const override { _CSTrait::applyInverseAlphaNormedFloatMask(pixels, alpha, nPixels); } + void fillInverseAlphaNormedFloatMaskWithColor(quint8 * pixels, const float * alpha, const quint8 *brushColor, qint32 nPixels) const override { + _CSTrait::fillInverseAlphaNormedFloatMaskWithColor(pixels, alpha, brushColor, nPixels); + } + + void fillGrayBrushWithColor(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override{ + _CSTrait::fillGrayBrushWithColor(dst, brush, brushColor, nPixels); + } + quint8 intensity8(const quint8 * src) const override { QColor c; const_cast *>(this)->toQColor(src, &c); return static_cast(c.red() * 0.30 + c.green() * 0.59 + c.blue() * 0.11); } KoColorTransformation* createInvertTransformation() const override { return KoInvertColorTransformation::getTransformator(this); } KoColorTransformation *createDarkenAdjustment(qint32 shade, bool compensate, qreal compensation) const override { return new KoFallBackColorTransformation(this, KoColorSpaceRegistry::instance()->lab16(""), new KoLabDarkenColorTransformation(shade, compensate, compensation, KoColorSpaceRegistry::instance()->lab16(""))); } bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace *dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const override { // check whether we have the same profile and color model, but only a different bit // depth; in that case we don't convert as such, but scale bool scaleOnly = false; // Note: getting the id() is really, really expensive, so only do that if // we are sure there is a difference between the colorspaces if (!(*this == *dstColorSpace)) { scaleOnly = dstColorSpace->colorModelId().id() == colorModelId().id() && dstColorSpace->colorDepthId().id() != colorDepthId().id() && dstColorSpace->profile()->name() == profile()->name(); } if (scaleOnly && dynamic_cast(dstColorSpace)) { typedef typename _CSTrait::channels_type channels_type; switch(dstColorSpace->channels()[0]->channelValueType()) { case KoChannelInfo::UINT8: scalePixels<_CSTrait::pixelSize, 1, channels_type, quint8>(src, dst, numPixels); return true; // case KoChannelInfo::INT8: // scalePixels<_CSTrait::pixelSize, 1, channels_type, qint8>(src, dst, numPixels); // return true; case KoChannelInfo::UINT16: scalePixels<_CSTrait::pixelSize, 2, channels_type, quint16>(src, dst, numPixels); return true; case KoChannelInfo::INT16: scalePixels<_CSTrait::pixelSize, 2, channels_type, qint16>(src, dst, numPixels); return true; case KoChannelInfo::UINT32: scalePixels<_CSTrait::pixelSize, 4, channels_type, quint32>(src, dst, numPixels); return true; default: break; } } return KoColorSpace::convertPixelsTo(src, dst, dstColorSpace, numPixels, renderingIntent, conversionFlags); } void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const override { qint32 selectedChannelPos = this->channels()[selectedChannelIndex]->pos(); for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { KoChannelInfo *channel = this->channels().at(channelIndex); qint32 channelSize = channel->size(); if (channel->channelType() == KoChannelInfo::COLOR) { memcpy(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * _CSTrait::pixelSize) + selectedChannelPos, channelSize); } else if (channel->channelType() == KoChannelInfo::ALPHA) { memcpy(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), channelSize); } } } } void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const QBitArray selectedChannels) const override { for (uint pixelIndex = 0; pixelIndex < nPixels; ++pixelIndex) { for (uint channelIndex = 0; channelIndex < this->channelCount(); ++channelIndex) { KoChannelInfo *channel = this->channels().at(channelIndex); qint32 channelSize = channel->size(); if (selectedChannels.testBit(channelIndex)) { memcpy(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), src + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize), channelSize); } else { reinterpret_cast(dst + (pixelIndex * _CSTrait::pixelSize) + (channelIndex * channelSize))[0] = _CSTrait::math_trait::zeroValue; } } } } private: template void scalePixels(const quint8* src, quint8* dst, quint32 numPixels) const { qint32 dstPixelSize = dstChannelSize * _CSTrait::channels_nb; for(quint32 i=0; i(src + i * srcPixelSize); TDstChannel* dstPixel = reinterpret_cast(dst + i * dstPixelSize); for(quint32 c=0; c<_CSTrait::channels_nb; ++c) dstPixel[c] = Arithmetic::scale(srcPixel[c]); } } }; #endif // KOCOLORSPACEABSTRACT_H diff --git a/libs/pigment/KoColorSpacePreserveLightnessUtils.h b/libs/pigment/KoColorSpacePreserveLightnessUtils.h new file mode 100644 index 0000000000..4739d9a361 --- /dev/null +++ b/libs/pigment/KoColorSpacePreserveLightnessUtils.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2020 Peter Schatz + * + * 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 KOCOLORSPACEPRESERVELIGHTNESSUTILS_H +#define KOCOLORSPACEPRESERVELIGHTNESSUTILS_H + +#include +#include "kis_global.h" + +template +inline static void fillGrayBrushWithColorPreserveLightnessRGB(quint8 *pixels, const QRgb *brush, quint8 *brushColor, qint32 nPixels) { + using RGBPixel = typename CSTraits::Pixel; + using channels_type = typename CSTraits::channels_type; + static const quint32 pixelSize = CSTraits::pixelSize; + + const RGBPixel *brushColorRGB = reinterpret_cast(brushColor); + + const float brushColorR = KoColorSpaceMaths::scaleToA(brushColorRGB->red); + const float brushColorG = KoColorSpaceMaths::scaleToA(brushColorRGB->green); + const float brushColorB = KoColorSpaceMaths::scaleToA(brushColorRGB->blue); + + /** + * Lightness mixing algorithm is developed by Peter Schatz + * + * We use a formula f(x) where f(0) = 0, f(1) = 1, and f(.5) = z, + * where z is the lightness of the brush color. This can’t be linear unless + * the color chosen is also .5. So we use a quadratic equation: + * + * f(x) = ax^2 + b^x +c + * 0,0 -> 0 = a0^2 + b0 + c -> c = 0 + * 1,1 -> 1 = a1^2 +b1 + c -> 1 = a + b + 0 -> a = 1 - b + * .5,z -> z = a*.5^2 + b*.5 + c -> z = + * = a/4 + b/2 + 0 -> z = + * = 1/4 - b/4 + b/2 -> z = 1/4 + b/4 -> b = 4z - 1 + * + * f(x) = (1 - (4z - 1)) * x^2 + (4z - 1) * x + */ + + const float brushColorL = getLightness(brushColorR, brushColorG, brushColorB); + const float lightnessB = 4 * brushColorL - 1; + const float lightnessA = 1 - lightnessB; + + for (; nPixels > 0; --nPixels, pixels += pixelSize, ++brush) { + RGBPixel *pixelRGB = reinterpret_cast(pixels); + + const float brushMaskL = qRed(*brush) / 255.0f; + const float finalLightness = lightnessA * pow2(brushMaskL) + lightnessB * brushMaskL; + + float pixelR = brushColorR; + float pixelG = brushColorG; + float pixelB = brushColorB; + + setLightness(pixelR, pixelG, pixelB, finalLightness); + + pixelRGB->red = KoColorSpaceMaths::scaleToA(pixelR); + pixelRGB->green = KoColorSpaceMaths::scaleToA(pixelG); + pixelRGB->blue = KoColorSpaceMaths::scaleToA(pixelB); + pixelRGB->alpha = KoColorSpaceMaths::scaleToA(quint8(qAlpha(*brush))); + } +} + + +#endif // KOCOLORSPACEPRESERVELIGHTNESSUTILS_H diff --git a/libs/pigment/KoColorSpaceTraits.h b/libs/pigment/KoColorSpaceTraits.h index f17ae4c51d..16e3f15277 100644 --- a/libs/pigment/KoColorSpaceTraits.h +++ b/libs/pigment/KoColorSpaceTraits.h @@ -1,238 +1,264 @@ /* * Copyright (c) 2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KO_COLORSPACE_TRAITS_H_ #define _KO_COLORSPACE_TRAITS_H_ #include #include "KoColorSpaceConstants.h" #include "KoColorSpaceMaths.h" #include "DebugPigment.h" +#include "kis_global.h" const int MAX_CHANNELS_TYPE_SIZE = sizeof(double); const int MAX_CHANNELS_NB = 5; const int MAX_PIXEL_SIZE = MAX_CHANNELS_NB * MAX_CHANNELS_TYPE_SIZE; /** * This class is the base class to define the main characteristics of a colorspace * which inherits KoColorSpaceAbstract. * * - _channels_type_ is the type of the value use for each channel, for example quint8 for 8bits per channel * color spaces, or quint16 for 16bits integer per channel, float for 32bits per channel * floating point color spaces * - _channels_nb_ is the total number of channels in an image (for example RGB is 3 but RGBA is four) * - _alpha_pos_ is the position of the alpha channel among the channels, if there is no alpha channel, * then _alpha_pos_ is set to -1 * * For instance a colorspace of three color channels and alpha channel in 16bits, * will be defined as KoColorSpaceTrait\. The same without the alpha * channel is KoColorSpaceTrait\ * */ template struct KoColorSpaceTrait { static_assert(sizeof(_channels_type_) <= MAX_CHANNELS_TYPE_SIZE, "MAX_CHANNELS_TYPE_SIZE too small"); static_assert(_channels_nb_ <= MAX_CHANNELS_NB, "MAX_CHANNELS_NB too small"); /// the type of the value of the channels of this color space typedef _channels_type_ channels_type; /// the number of channels in this color space static const quint32 channels_nb = _channels_nb_; /// the position of the alpha channel in the channels of the pixel (or -1 if no alpha /// channel. static const qint32 alpha_pos = _alpha_pos_; /// the number of bit for each channel static const int depth = KoColorSpaceMathsTraits<_channels_type_>::bits; /// the associated math class typedef KoColorSpaceMathsTraits<_channels_type_> math_trait; /** * @return the size in byte of one pixel */ static const quint32 pixelSize = channels_nb * sizeof(channels_type); /** * @return the value of the alpha channel for this pixel in the 0..255 range */ inline static quint8 opacityU8(const quint8 * U8_pixel) { if (alpha_pos < 0) return OPACITY_OPAQUE_U8; channels_type c = nativeArray(U8_pixel)[alpha_pos]; return KoColorSpaceMaths::scaleToA(c); } inline static qreal opacityF(const quint8 * U8_pixel) { if (alpha_pos < 0) return OPACITY_OPAQUE_F; channels_type c = nativeArray(U8_pixel)[alpha_pos]; return KoColorSpaceMaths::scaleToA(c); } /** * Set the alpha channel for this pixel from a value in the 0..255 range */ inline static void setOpacity(quint8 * pixels, quint8 alpha, qint32 nPixels) { if (alpha_pos < 0) return; qint32 psize = pixelSize; channels_type valpha = KoColorSpaceMaths::scaleToA(alpha); for (; nPixels > 0; --nPixels, pixels += psize) { nativeArray(pixels)[alpha_pos] = valpha; } } inline static void setOpacity(quint8 * pixels, qreal alpha, qint32 nPixels) { if (alpha_pos < 0) return; qint32 psize = pixelSize; channels_type valpha = KoColorSpaceMaths::scaleToA(alpha); for (; nPixels > 0; --nPixels, pixels += psize) { nativeArray(pixels)[alpha_pos] = valpha; } } /** * Convenient function for transforming a quint8* array in a pointer of the native channels type */ inline static const channels_type* nativeArray(const quint8 * a) { return reinterpret_cast(a); } /** * Convenient function for transforming a quint8* array in a pointer of the native channels type */ inline static channels_type* nativeArray(quint8 * a) { return reinterpret_cast< channels_type*>(a); } /** * Allocate nPixels pixels for this colorspace. */ inline static quint8* allocate(quint32 nPixels) { return new quint8[ nPixels * pixelSize ]; } inline static void singleChannelPixel(quint8 *dstPixel, const quint8 *srcPixel, quint32 channelIndex) { const channels_type* src = nativeArray(srcPixel); channels_type* dst = nativeArray(dstPixel); for (uint i = 0; i < channels_nb; i++) { if (i != channelIndex) { dst[i] = 0; } else { dst[i] = src[i]; } } } inline static QString channelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; return QString().setNum(c); } inline static QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) { if (channelIndex > channels_nb) return QString("Error"); channels_type c = nativeArray(pixel)[channelIndex]; return QString().setNum(100. *((qreal)c) / KoColorSpaceMathsTraits< channels_type>::unitValue); } inline static void normalisedChannelsValue(const quint8 *pixel, QVector &channels) { Q_ASSERT((int)channels.count() >= (int)channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { c = nativeArray(pixel)[i]; channels[i] = ((qreal)c) / KoColorSpaceMathsTraits::unitValue; } } inline static void fromNormalisedChannelsValue(quint8 *pixel, const QVector &values) { Q_ASSERT((int)values.count() >= (int)channels_nb); channels_type c; for (uint i = 0; i < channels_nb; i++) { float b = qBound((float)KoColorSpaceMathsTraits::min, (float)KoColorSpaceMathsTraits::unitValue * values[i], (float)KoColorSpaceMathsTraits::max); c = (channels_type)b; nativeArray(pixel)[i] = c; } } inline static void multiplyAlpha(quint8 * pixels, quint8 alpha, qint32 nPixels) { if (alpha_pos < 0) return; channels_type valpha = KoColorSpaceMaths::scaleToA(alpha); for (; nPixels > 0; --nPixels, pixels += pixelSize) { channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = KoColorSpaceMaths::scaleToA(*alpha); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyInverseAlphaU8Mask(quint8 * pixels, const quint8 * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = KoColorSpaceMaths::scaleToA(OPACITY_OPAQUE_U8 - *alpha); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = channels_type(KoColorSpaceMathsTraits::unitValue * (*alpha)); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } inline static void applyInverseAlphaNormedFloatMask(quint8 * pixels, const float * alpha, qint32 nPixels) { if (alpha_pos < 0) return; for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { channels_type valpha = channels_type(KoColorSpaceMathsTraits::unitValue * (1.0f - *alpha)); channels_type* alphapixel = nativeArray(pixels) + alpha_pos; *alphapixel = KoColorSpaceMaths::multiply(*alphapixel, valpha); } } + + inline static void fillInverseAlphaNormedFloatMaskWithColor(quint8 * pixels, const float * alpha, const quint8 *brushColor, qint32 nPixels) { + if (alpha_pos < 0) return; + + for (; nPixels > 0; --nPixels, pixels += pixelSize, ++alpha) { + memcpy(pixels, brushColor, pixelSize); + channels_type valpha = channels_type(KoColorSpaceMathsTraits::unitValue * (1.0f - *alpha)); + *(nativeArray(pixels) + alpha_pos) = valpha; + } + } + + + inline static void fillGrayBrushWithColor(quint8 *pixels, const QRgb *brush, quint8 *brushColor, qint32 nPixels) { + if (alpha_pos >= 0) { + for (; nPixels > 0; --nPixels, pixels += pixelSize, ++brush) { + memcpy(pixels, brushColor, pixelSize); + const quint8 opacity = KoColorSpaceMaths::multiply(OPACITY_OPAQUE_U8 - quint8(qRed(*brush)), quint8(qAlpha(*brush))); + *(nativeArray(pixels) + alpha_pos) = KoColorSpaceMaths::scaleToA(opacity); + } + } else { + for (; nPixels > 0; --nPixels, pixels += pixelSize) { + memcpy(pixels, brushColor, pixelSize); + } + } + } }; #include "KoRgbColorSpaceTraits.h" #include "KoBgrColorSpaceTraits.h" #include "KoGrayColorSpaceTraits.h" #include "KoLabColorSpaceTraits.h" #include "KoXyzColorSpaceTraits.h" #include "KoYcbcrColorSpaceTraits.h" #include "KoCmykColorSpaceTraits.h" #endif diff --git a/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp b/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp index 927ae3c04e..68ab387f20 100644 --- a/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp +++ b/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp @@ -1,98 +1,104 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoRgbU16ColorSpace.h" #include #include #include #include #include #include "KoChannelInfo.h" #include "KoID.h" #include "KoIntegerMaths.h" #include "KoColorConversions.h" +#include KoRgbU16ColorSpace::KoRgbU16ColorSpace() : KoSimpleColorSpace(colorSpaceId(), i18n("RGB (16-bit integer/channel, unmanaged)"), RGBAColorModelID, Integer16BitsColorDepthID) { } KoRgbU16ColorSpace::~KoRgbU16ColorSpace() { } QString KoRgbU16ColorSpace::colorSpaceId() { return QString("RGBA16"); } KoColorSpace* KoRgbU16ColorSpace::clone() const { return new KoRgbU16ColorSpace(); } void KoRgbU16ColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const { QVector channelValues; channelValues << c.blueF() << c.greenF() << c.redF() << c.alphaF(); fromNormalisedChannelsValue(dst, channelValues); } void KoRgbU16ColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const { QVector channelValues(4); normalisedChannelsValue(src, channelValues); c->setRgbF(channelValues[2], channelValues[1], channelValues[0], channelValues[3]); } void KoRgbU16ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma); } QVector KoRgbU16ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void KoRgbU16ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v); } QVector KoRgbU16ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } + +void KoRgbU16ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + fillGrayBrushWithColorPreserveLightnessRGB(dst, brush, brushColor, nPixels); +} diff --git a/libs/pigment/colorspaces/KoRgbU16ColorSpace.h b/libs/pigment/colorspaces/KoRgbU16ColorSpace.h index 0302870e8b..0a0b3ecbc6 100644 --- a/libs/pigment/colorspaces/KoRgbU16ColorSpace.h +++ b/libs/pigment/colorspaces/KoRgbU16ColorSpace.h @@ -1,76 +1,77 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 KORGBU16COLORSPACE_H #define KORGBU16COLORSPACE_H #include #include "KoSimpleColorSpace.h" #include "KoSimpleColorSpaceFactory.h" #include "KoColorModelStandardIds.h" struct KoBgrU16Traits; /** * The alpha mask is a special color strategy that treats all pixels as * alpha value with a color common to the mask. The default color is white. */ class KoRgbU16ColorSpace : public KoSimpleColorSpace { public: KoRgbU16ColorSpace(); ~KoRgbU16ColorSpace() override; static QString colorSpaceId(); virtual KoColorSpace* clone() const; void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const override; void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; + void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override; }; class KoRgbU16ColorSpaceFactory : public KoSimpleColorSpaceFactory { public: KoRgbU16ColorSpaceFactory() : KoSimpleColorSpaceFactory(KoRgbU16ColorSpace::colorSpaceId(), i18n("RGB (16-bit integer/channel, unmanaged)"), true, RGBAColorModelID, Integer16BitsColorDepthID) { } KoColorSpace *createColorSpace(const KoColorProfile *) const override { return new KoRgbU16ColorSpace(); } }; #endif diff --git a/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp b/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp index 9539a18f8d..4b92725c07 100644 --- a/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp +++ b/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp @@ -1,111 +1,117 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "KoRgbU8ColorSpace.h" #include #include #include #include #include #include "KoChannelInfo.h" #include "KoID.h" #include "KoIntegerMaths.h" #include "compositeops/KoCompositeOps.h" #include "KoColorConversions.h" +#include KoRgbU8ColorSpace::KoRgbU8ColorSpace() : KoSimpleColorSpace(colorSpaceId(), i18n("RGB (8-bit integer/channel, unmanaged)"), RGBAColorModelID, Integer8BitsColorDepthID) { addChannel(new KoChannelInfo(i18n("Blue"), 0, 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 0, 255))); addChannel(new KoChannelInfo(i18n("Green"), 1, 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 255, 0))); addChannel(new KoChannelInfo(i18n("Red"), 2, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(255, 0, 0))); addChannel(new KoChannelInfo(i18n("Alpha"), 3, 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT8)); // ADD, ALPHA_DARKEN, BURN, DIVIDE, DODGE, ERASE, MULTIPLY, OVER, OVERLAY, SCREEN, SUBTRACT addStandardCompositeOps(this); } KoRgbU8ColorSpace::~KoRgbU8ColorSpace() { } QString KoRgbU8ColorSpace::colorSpaceId() { return QString("RGBA"); } KoColorSpace* KoRgbU8ColorSpace::clone() const { return new KoRgbU8ColorSpace(); } void KoRgbU8ColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const { QVector channelValues; channelValues << c.blueF() << c.greenF() << c.redF() << c.alphaF(); fromNormalisedChannelsValue(dst, channelValues); } void KoRgbU8ColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const { QVector channelValues(4); normalisedChannelsValue(src, channelValues); c->setRgbF(channelValues[2], channelValues[1], channelValues[0], channelValues[3]); } void KoRgbU8ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma); } QVector KoRgbU8ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } void KoRgbU8ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v); } QVector KoRgbU8ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2]); channelValues[3]=1.0; return channelValues; } + +void KoRgbU8ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + fillGrayBrushWithColorPreserveLightnessRGB(dst, brush, brushColor, nPixels); +} diff --git a/libs/pigment/colorspaces/KoRgbU8ColorSpace.h b/libs/pigment/colorspaces/KoRgbU8ColorSpace.h index 6e402c34b8..70071219b1 100644 --- a/libs/pigment/colorspaces/KoRgbU8ColorSpace.h +++ b/libs/pigment/colorspaces/KoRgbU8ColorSpace.h @@ -1,76 +1,77 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 KORGBU8COLORSPACE_H #define KORGBU8COLORSPACE_H #include #include "KoSimpleColorSpace.h" #include "KoSimpleColorSpaceFactory.h" #include "KoColorModelStandardIds.h" struct KoBgrU8Traits; /** * The alpha mask is a special color strategy that treats all pixels as * alpha value with a color common to the mask. The default color is white. */ class KoRgbU8ColorSpace : public KoSimpleColorSpace { public: KoRgbU8ColorSpace(); ~KoRgbU8ColorSpace() override; static QString colorSpaceId(); virtual KoColorSpace* clone() const; void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const override; void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; + void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override; }; class KoRgbU8ColorSpaceFactory : public KoSimpleColorSpaceFactory { public: KoRgbU8ColorSpaceFactory() : KoSimpleColorSpaceFactory("RGBA", i18n("RGB (8-bit integer/channel, unmanaged)"), true, RGBAColorModelID, Integer8BitsColorDepthID) { } KoColorSpace *createColorSpace(const KoColorProfile *) const override { return new KoRgbU8ColorSpace(); } }; #endif diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp index 2f251ef181..2918507f00 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp @@ -1,111 +1,117 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "RgbF16ColorSpace.h" #include #include #include #include "compositeops/KoCompositeOps.h" #include "compositeops/RgbCompositeOpIn.h" #include "compositeops/RgbCompositeOpOut.h" #include "compositeops/RgbCompositeOpBumpmap.h" #include +#include RgbF16ColorSpace::RgbF16ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_RGBA_HALF_FLT, cmsSigRgbData, p) { addChannel(new KoChannelInfo(i18n("Red"), 0 * sizeof(half), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT16, 2, QColor(255, 0, 0))); addChannel(new KoChannelInfo(i18n("Green"), 1 * sizeof(half), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT16, 2, QColor(0, 255, 0))); addChannel(new KoChannelInfo(i18n("Blue"), 2 * sizeof(half), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT16, 2, QColor(0, 0, 255))); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(half), 3, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT16, 2)); init(); addStandardCompositeOps(this); addCompositeOp(new RgbCompositeOpIn(this)); addCompositeOp(new RgbCompositeOpOut(this)); addCompositeOp(new RgbCompositeOpBumpmap(this)); } bool RgbF16ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *RgbF16ColorSpace::clone() const { return new RgbF16ColorSpace(name(), profile()->clone()); } void RgbF16ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoRgbF16Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("RGB"); labElt.setAttribute("r", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF16Traits::channels_type, qreal>::scaleToA(p->red))); labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF16Traits::channels_type, qreal>::scaleToA(p->green))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF16Traits::channels_type, qreal>::scaleToA(p->blue))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void RgbF16ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoRgbF16Traits::Pixel *p = reinterpret_cast(pixel); p->red = KoColorSpaceMaths< qreal, KoRgbF16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("r"))); p->green = KoColorSpaceMaths< qreal, KoRgbF16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g"))); p->blue = KoColorSpaceMaths< qreal, KoRgbF16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); p->alpha = 1.0; } void RgbF16ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbF16ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } void RgbF16ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbF16ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } + +void RgbF16ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + fillGrayBrushWithColorPreserveLightnessRGB(dst, brush, brushColor, nPixels); +} diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h index e67932713b..249a5f6c12 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.h @@ -1,121 +1,123 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KORGBF16COLORSPACE_H_ #define KORGBF16COLORSPACE_H_ #include "LcmsColorSpace.h" #include "KoColorModelStandardIds.h" struct KoRgbF16Traits; class RgbF16ColorSpace : public LcmsColorSpace { public: RgbF16ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence independence) const override; KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Float16BitsColorDepthID; } virtual KoColorSpace *clone() const; void colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const override; void colorFromXML(quint8* pixel, const QDomElement& elt) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; static QString colorSpaceId() { return QString("RGBAF16"); } bool hasHighDynamicRange() const override { return true; } + + void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override; }; class RgbF16ColorSpaceFactory : public LcmsColorSpaceFactory { public: RgbF16ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_RGBA_HALF_FLT, cmsSigRgbData) { } QString id() const override { return RgbF16ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(RGBAColorModelID.name()).arg(Float16BitsColorDepthID.name()); } bool userVisible() const override { return true; } KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Float16BitsColorDepthID; } int referenceDepth() const override { return 16; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new RgbF16ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "sRGB-elle-V2-g10.icc"; } bool isHdr() const override { return true; } }; #endif diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp index 623a80ffad..d42aee9260 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp @@ -1,116 +1,122 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "RgbF32ColorSpace.h" #include #include #include "compositeops/KoCompositeOps.h" #include "compositeops/RgbCompositeOps.h" #include #include #include +#include RgbF32ColorSpace::RgbF32ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_RGBA_FLT, cmsSigRgbData, p) { const IccColorProfile *icc_p = dynamic_cast(p); Q_ASSERT(icc_p); QVector uiRanges(icc_p->getFloatUIMinMax()); Q_ASSERT(uiRanges.size() == 3); addChannel(new KoChannelInfo(i18n("Red"), 0 * sizeof(float), 0, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, 4, QColor(255, 0, 0), uiRanges[0])); addChannel(new KoChannelInfo(i18n("Green"), 1 * sizeof(float), 1, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, 4, QColor(0, 255, 0), uiRanges[1])); addChannel(new KoChannelInfo(i18n("Blue"), 2 * sizeof(float), 2, KoChannelInfo::COLOR, KoChannelInfo::FLOAT32, 4, QColor(0, 0, 255), uiRanges[2])); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(float), 3, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32, 4)); init(); addStandardCompositeOps(this); addCompositeOp(new RgbCompositeOpIn(this)); addCompositeOp(new RgbCompositeOpOut(this)); addCompositeOp(new RgbCompositeOpBumpmap(this)); } bool RgbF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA16) { return true; } else { return false; } } KoColorSpace *RgbF32ColorSpace::clone() const { return new RgbF32ColorSpace(name(), profile()->clone()); } void RgbF32ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoRgbF32Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("RGB"); labElt.setAttribute("r", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF32Traits::channels_type, qreal>::scaleToA(p->red))); labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF32Traits::channels_type, qreal>::scaleToA(p->green))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoRgbF32Traits::channels_type, qreal>::scaleToA(p->blue))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void RgbF32ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoRgbF32Traits::Pixel *p = reinterpret_cast(pixel); p->red = KoColorSpaceMaths< qreal, KoRgbF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("r"))); p->green = KoColorSpaceMaths< qreal, KoRgbF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g"))); p->blue = KoColorSpaceMaths< qreal, KoRgbF32Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); p->alpha = 1.0; } void RgbF32ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbF32ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } void RgbF32ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbF32ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } + +void RgbF32ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + fillGrayBrushWithColorPreserveLightnessRGB(dst, brush, brushColor, nPixels); +} diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h index bbfff27875..89580dc3be 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.h @@ -1,121 +1,125 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KORGBF32COLORSPACE_H_ #define KORGBF32COLORSPACE_H_ #include "LcmsColorSpace.h" #include "KoColorModelStandardIds.h" struct KoRgbF32Traits; class RgbF32ColorSpace : public LcmsColorSpace { public: RgbF32ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence independence) const override; KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Float32BitsColorDepthID; } virtual KoColorSpace *clone() const; void colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const override; void colorFromXML(quint8* pixel, const QDomElement& elt) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; static QString colorSpaceId() { return QString("RGBAF32"); } bool hasHighDynamicRange() const override { return true; } + + void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override; }; class RgbF32ColorSpaceFactory : public LcmsColorSpaceFactory { public: RgbF32ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_RGBA_FLT, cmsSigRgbData) { } QString id() const override { return RgbF32ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(RGBAColorModelID.name()).arg(Float32BitsColorDepthID.name()); } bool userVisible() const override { return true; } KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Float32BitsColorDepthID; } int referenceDepth() const override { return 32; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new RgbF32ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "sRGB-elle-V2-g10.icc"; } bool isHdr() const override { return true; } + + }; #endif diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp index 981910f385..1656b77d00 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp @@ -1,108 +1,114 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "RgbU16ColorSpace.h" #include #include #include "compositeops/KoCompositeOps.h" #include "compositeops/RgbCompositeOps.h" #include "kis_dom_utils.h" #include +#include RgbU16ColorSpace::RgbU16ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_BGRA_16, cmsSigRgbData, p) { addChannel(new KoChannelInfo(i18n("Blue"), 0 * sizeof(quint16), 2, KoChannelInfo::COLOR, KoChannelInfo::UINT16, 2, QColor(0, 0, 255))); addChannel(new KoChannelInfo(i18n("Green"), 1 * sizeof(quint16), 1, KoChannelInfo::COLOR, KoChannelInfo::UINT16, 2, QColor(0, 255, 0))); addChannel(new KoChannelInfo(i18n("Red"), 2 * sizeof(quint16), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT16, 2, QColor(255, 0, 0))); addChannel(new KoChannelInfo(i18n("Alpha"), 3 * sizeof(quint16), 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT16, 2)); init(); addStandardCompositeOps(this); addCompositeOp(new RgbCompositeOpIn(this)); addCompositeOp(new RgbCompositeOpOut(this)); addCompositeOp(new RgbCompositeOpBumpmap(this)); } bool RgbU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const { if (independence == TO_RGBA8) { return true; } else { return false; } } KoColorSpace *RgbU16ColorSpace::clone() const { return new RgbU16ColorSpace(name(), profile()->clone()); } void RgbU16ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoBgrU16Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("RGB"); labElt.setAttribute("r", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU16Traits::channels_type, qreal>::scaleToA(p->red))); labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU16Traits::channels_type, qreal>::scaleToA(p->green))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU16Traits::channels_type, qreal>::scaleToA(p->blue))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void RgbU16ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoBgrU16Traits::Pixel *p = reinterpret_cast(pixel); p->red = KoColorSpaceMaths< qreal, KoBgrU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("r"))); p->green = KoColorSpaceMaths< qreal, KoBgrU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g"))); p->blue = KoColorSpaceMaths< qreal, KoBgrU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); p->alpha = KoColorSpaceMathsTraits::max; } void RgbU16ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbU16ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } void RgbU16ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbU16ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } + +void RgbU16ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + fillGrayBrushWithColorPreserveLightnessRGB(dst, brush, brushColor, nPixels); +} diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h index a10a003e2b..c94559c4a2 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.h @@ -1,105 +1,106 @@ /* * Copyright (c) 2006 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KORGBU16COLORSPACE_H_ #define KORGBU16COLORSPACE_H_ #include "LcmsColorSpace.h" #include "KoColorModelStandardIds.h" struct KoBgrU16Traits; class RgbU16ColorSpace : public LcmsColorSpace { public: RgbU16ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence independence) const override; KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Integer16BitsColorDepthID; } virtual KoColorSpace *clone() const; void colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const override; void colorFromXML(quint8* pixel, const QDomElement& elt) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; static QString colorSpaceId() { return QString("RGBA16"); } + void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override; }; class RgbU16ColorSpaceFactory : public LcmsColorSpaceFactory { public: RgbU16ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_BGRA_16, cmsSigRgbData) { } QString id() const override { return RgbU16ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(RGBAColorModelID.name()).arg(Integer16BitsColorDepthID.name()); } bool userVisible() const override { return true; } KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Integer16BitsColorDepthID; } int referenceDepth() const override { return 16; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new RgbU16ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "sRGB-elle-V2-g10.icc";//this is a linear space, because 16bit is enough to only enjoy advantages of linear space } }; #endif diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp index f2c4b37e09..ed1834fc11 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp +++ b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp @@ -1,112 +1,118 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 "RgbU8ColorSpace.h" #include #include #include #include #include #include #include #include "compositeops/KoCompositeOps.h" #include "compositeops/RgbCompositeOps.h" #include +#include #define downscale(quantum) (quantum) //((unsigned char) ((quantum)/257UL)) #define upscale(value) (value) // ((quint8) (257UL*(value))) RgbU8ColorSpace::RgbU8ColorSpace(const QString &name, KoColorProfile *p) : LcmsColorSpace(colorSpaceId(), name, TYPE_BGRA_8, cmsSigRgbData, p) { addChannel(new KoChannelInfo(i18n("Blue"), 0, 2, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 0, 255))); addChannel(new KoChannelInfo(i18n("Green"), 1, 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(0, 255, 0))); addChannel(new KoChannelInfo(i18n("Red"), 2, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8, 1, QColor(255, 0, 0))); addChannel(new KoChannelInfo(i18n("Alpha"), 3, 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT8)); init(); addStandardCompositeOps(this); addCompositeOp(new RgbCompositeOpIn(this)); addCompositeOp(new RgbCompositeOpOut(this)); addCompositeOp(new RgbCompositeOpBumpmap(this)); } KoColorSpace *RgbU8ColorSpace::clone() const { return new RgbU8ColorSpace(name(), profile()->clone()); } void RgbU8ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const { const KoBgrU8Traits::Pixel *p = reinterpret_cast(pixel); QDomElement labElt = doc.createElement("RGB"); labElt.setAttribute("r", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU8Traits::channels_type, qreal>::scaleToA(p->red))); labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU8Traits::channels_type, qreal>::scaleToA(p->green))); labElt.setAttribute("b", KisDomUtils::toString(KoColorSpaceMaths< KoBgrU8Traits::channels_type, qreal>::scaleToA(p->blue))); labElt.setAttribute("space", profile()->name()); colorElt.appendChild(labElt); } void RgbU8ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const { KoBgrU8Traits::Pixel *p = reinterpret_cast(pixel); p->red = KoColorSpaceMaths< qreal, KoBgrU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("r"))); p->green = KoColorSpaceMaths< qreal, KoBgrU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g"))); p->blue = KoColorSpaceMaths< qreal, KoBgrU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("b"))); p->alpha = KoColorSpaceMathsTraits::max; } quint8 RgbU8ColorSpace::intensity8(const quint8 *src) const { const KoBgrU8Traits::Pixel *p = reinterpret_cast(src); return (quint8)(p->red * 0.30 + p->green * 0.59 + p->blue * 0.11); } void RgbU8ColorSpace::toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const { RGBToHSY(channelValues[0],channelValues[1],channelValues[2], hue, sat, luma, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbU8ColorSpace::fromHSY(qreal *hue, qreal *sat, qreal *luma) const { QVector channelValues(4); HSYToRGB(*hue, *sat, *luma, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } void RgbU8ColorSpace::toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const { RGBToYUV(channelValues[0],channelValues[1],channelValues[2], y, u, v, lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); } QVector RgbU8ColorSpace::fromYUV(qreal *y, qreal *u, qreal *v) const { QVector channelValues(4); YUVToRGB(*y, *u, *v, &channelValues[0],&channelValues[1],&channelValues[2], lumaCoefficients()[0], lumaCoefficients()[1], lumaCoefficients()[2]); channelValues[3]=1.0; return channelValues; } + +void RgbU8ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const +{ + fillGrayBrushWithColorPreserveLightnessRGB(dst, brush, brushColor, nPixels); +} diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h index b9256a2276..91c1208991 100644 --- a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h +++ b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.h @@ -1,116 +1,118 @@ /* * Copyright (c) 2002 Patrick Julien * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 KO_STRATEGY_COLORSPACE_RGB_H_ #define KO_STRATEGY_COLORSPACE_RGB_H_ #include #include #include "KoColorModelStandardIds.h" struct KoBgrU8Traits; class RgbU8ColorSpace : public LcmsColorSpace { public: RgbU8ColorSpace(const QString &name, KoColorProfile *p); bool willDegrade(ColorSpaceIndependence) const override { return false; } KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Integer8BitsColorDepthID; } virtual KoColorSpace *clone() const; void colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const override; void colorFromXML(quint8 *pixel, const QDomElement &elt) const override; quint8 intensity8(const quint8 * src) const override; void toHSY(const QVector &channelValues, qreal *hue, qreal *sat, qreal *luma) const override; QVector fromHSY(qreal *hue, qreal *sat, qreal *luma) const override; void toYUV(const QVector &channelValues, qreal *y, qreal *u, qreal *v) const override; QVector fromYUV(qreal *y, qreal *u, qreal *v) const override; static QString colorSpaceId() { return QString("RGBA"); } + + void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const override; }; class RgbU8ColorSpaceFactory : public LcmsColorSpaceFactory { public: RgbU8ColorSpaceFactory() : LcmsColorSpaceFactory(TYPE_BGRA_8, cmsSigRgbData) {} bool userVisible() const override { return true; } QString id() const override { return RgbU8ColorSpace::colorSpaceId(); } QString name() const override { return QString("%1 (%2)").arg(RGBAColorModelID.name()).arg(Integer8BitsColorDepthID.name()); } KoID colorModelId() const override { return RGBAColorModelID; } KoID colorDepthId() const override { return Integer8BitsColorDepthID; } int referenceDepth() const override { return 8; } KoColorSpace *createColorSpace(const KoColorProfile *p) const override { return new RgbU8ColorSpace(name(), p->clone()); } QString defaultProfile() const override { return "sRGB-elle-V2-srgbtrc.icc"; } }; #endif // KO_STRATEGY_COLORSPACE_RGB_H_ diff --git a/plugins/paintops/libpaintop/forms/wdgclipboardbrush.ui b/plugins/paintops/libpaintop/forms/wdgclipboardbrush.ui index 69de1c94c9..c5738523c2 100644 --- a/plugins/paintops/libpaintop/forms/wdgclipboardbrush.ui +++ b/plugins/paintops/libpaintop/forms/wdgclipboardbrush.ui @@ -1,209 +1,222 @@ KisWdgClipboardBrush 0 0 454 251 5 15 15 15 15 0 0 110 110 QFrame::Box QFrame::Plain 2 0 Qt::Vertical 20 40 + + + + Create mask from color + + + 60 16777215 Spacing: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + + + false + - Create mask from color + Preserve alpha Qt::Vertical 20 40 Qt::Horizontal 40 20 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Save KisSpacingSelectionWidget QWidget
kis_spacing_selection_widget.h
1
buttonBox accepted() KisWdgClipboardBrush accept() 20 20 20 20 buttonBox rejected() KisWdgClipboardBrush reject() 20 20 20 20
diff --git a/plugins/paintops/libpaintop/forms/wdgcustombrush.ui b/plugins/paintops/libpaintop/forms/wdgcustombrush.ui index 6d04007636..edff13357f 100644 --- a/plugins/paintops/libpaintop/forms/wdgcustombrush.ui +++ b/plugins/paintops/libpaintop/forms/wdgcustombrush.ui @@ -1,318 +1,328 @@ KisWdgCustomBrush 0 0 462 311 6 15 15 15 15 0 0 110 110 QFrame::Box Qt::Vertical 20 40 - - - Spacing: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Create mask from color + + + Name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + + + false + - Create mask from color + Preserve alpha channel 0 110 Brush Style false 20 20 301 71 3 0 0 Style: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Regular Animated 0 0 Selection mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter false 0 0 2 Constant Random Incremental Pressure Angular Qt::Vertical 20 40 Qt::Horizontal 40 20 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Save KisSpacingSelectionWidget QWidget
kis_spacing_selection_widget.h
1
buttonBox accepted() KisWdgCustomBrush accept() 20 20 20 20 buttonBox rejected() KisWdgCustomBrush reject() 20 20 20 20
diff --git a/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui b/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui index ede239ed3f..883d965d63 100644 --- a/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui +++ b/plugins/paintops/libpaintop/forms/wdgpredefinedbrushchooser.ui @@ -1,271 +1,333 @@ WdgPredefinedBrushChooser 0 0 - 646 - 325 + 888 + 454 6 0 0 0 0 0 0 0 0 0 0 0 0 Import 0 0 Stamp 0 0 Clipboard 0 0 0 12 false false Current Brush Tip 2 9 Brush Details 2 5 15 10 - - - - Reset Predefined Tip - - - 0 0 - - - - - 0 - 0 - - - - Rotation: - - + + - Spacing: + Reset Predefined Tip - - - - Size: + + + + + 0 + 0 + - - + + - Use Color as Mask + Size: 0 0 - + + + + Spacing: + + + + + + + Brush mode + + + + + + Mask + + + + + + + Color + + + + + + + Lightness + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 30 + 20 + + + + + + + + + + + + + + + + + + + + + + + Preserve Brush Preset Settings true Qt::Vertical 20 40 + + KisSliderSpinBox + QWidget +
kis_slider_spin_box.h
+ 1 +
KisDoubleSliderSpinBox QWidget
kis_slider_spin_box.h
1
KisSpacingSelectionWidget QWidget
kis_spacing_selection_widget.h
1
diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.cpp b/plugins/paintops/libpaintop/kis_brush_chooser.cpp index e6ec79984a..828d8efc1a 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.cpp +++ b/plugins/paintops/libpaintop/kis_brush_chooser.cpp @@ -1,418 +1,521 @@ /* * Copyright (c) 2004 Adrian Page * Copyright (c) 2009 Sven Langkamp * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Srikanth Tiyyagura * * 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_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisBrushServerProvider.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_multipliers_double_slider_spinbox.h" #include "kis_spacing_selection_widget.h" #include "kis_signals_blocker.h" #include "kis_imagepipe_brush.h" #include "kis_custom_brush_widget.h" #include "kis_clipboard_brush_widget.h" #include #include "kis_global.h" #include "kis_gbr_brush.h" +#include "kis_png_brush.h" #include "kis_debug.h" #include "kis_image.h" #include /// The resource item delegate for rendering the resource preview class KisBrushDelegate : public QAbstractItemDelegate { public: KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {} ~KisBrushDelegate() override {} /// reimplemented void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; /// reimplemented QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override { return option.decorationSize; } }; void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { if (! index.isValid()) return; QImage thumbnail = index.data(Qt::UserRole + KisResourceModel::Thumbnail).value(); QRect itemRect = option.rect; if (thumbnail.height() > itemRect.height() || thumbnail.width() > itemRect.width()) { thumbnail = thumbnail.scaled(itemRect.size() , Qt::KeepAspectRatio, Qt::SmoothTransformation); } painter->save(); int dx = (itemRect.width() - thumbnail.width()) / 2; int dy = (itemRect.height() - thumbnail.height()) / 2; painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail); if (option.state & QStyle::State_Selected) { painter->setPen(QPen(option.palette.highlight(), 2.0)); painter->drawRect(option.rect); painter->setCompositionMode(QPainter::CompositionMode_HardLight); painter->setOpacity(0.65); painter->fillRect(option.rect, option.palette.highlight()); } painter->restore(); } KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char *name) : QWidget(parent), m_stampBrushWidget(0), m_clipboardBrushWidget(0) { setObjectName(name); setupUi(this); brushSizeSpinBox->setRange(0, KSharedConfig::openConfig()->group("").readEntry("maximumBrushSize", 1000), 2); brushSizeSpinBox->setValue(5); brushSizeSpinBox->setExponentRatio(3.0); brushSizeSpinBox->setSuffix(i18n(" px")); brushSizeSpinBox->setExponentRatio(3.0); QObject::connect(brushSizeSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemSize(qreal))); brushRotationSpinBox->setRange(0, 360, 0); brushRotationSpinBox->setValue(0); brushRotationSpinBox->setSuffix(QChar(Qt::Key_degree)); QObject::connect(brushRotationSpinBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetItemRotation(qreal))); brushSpacingSelectionWidget->setSpacing(true, 1.0); connect(brushSpacingSelectionWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); - QObject::connect(useColorAsMaskCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetItemUseColorAsMask(bool))); - m_itemChooser = new KisResourceItemChooser(ResourceType::Brushes, false, this); m_itemChooser->setObjectName("brush_selector"); m_itemChooser->showTaggingBar(true); m_itemChooser->setRowHeight(30); m_itemChooser->setItemDelegate(new KisBrushDelegate(this)); m_itemChooser->setCurrentItem(0); m_itemChooser->setSynced(true); m_itemChooser->setMinimumWidth(100); m_itemChooser->setMinimumHeight(150); m_itemChooser->showButtons(false); // turn the import and delete buttons since we want control over them addPresetButton->setIcon(KisIconUtils::loadIcon("list-add")); deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("trash-empty")); connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource())); connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource())); presetsLayout->addWidget(m_itemChooser); connect(m_itemChooser, SIGNAL(resourceSelected(KoResourceSP )), this, SLOT(updateBrushTip(KoResourceSP ))); stampButton->setIcon(KisIconUtils::loadIcon("list-add")); stampButton->setToolTip(i18n("Creates a brush tip from the current image selection." "\n If no selection is present the whole image will be used.")); clipboardButton->setIcon(KisIconUtils::loadIcon("list-add")); clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard.")); connect(stampButton, SIGNAL(clicked()), this, SLOT(slotOpenStampBrush())); connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush())); QGridLayout *spacingLayout = new QGridLayout(); spacingLayout->setObjectName("spacing grid layout"); resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0")); connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush())); + intAdjustmentMidPoint->setRange(0, 255); + intAdjustmentMidPoint->setPageStep(10); + intAdjustmentMidPoint->setSingleStep(1); + intAdjustmentMidPoint->setPrefix(i18nc("@label:slider", "Neutral point: ")); + + intBrightnessAdjustment->setRange(-100, 100); + intBrightnessAdjustment->setPageStep(10); + intBrightnessAdjustment->setSingleStep(1); + intBrightnessAdjustment->setSuffix("%"); + intBrightnessAdjustment->setPrefix(i18nc("@label:slider", "Brightness: ")); + + intContrastAdjustment->setRange(-100, 100); + intContrastAdjustment->setPageStep(10); + intContrastAdjustment->setSingleStep(1); + intContrastAdjustment->setSuffix("%"); + intContrastAdjustment->setPrefix(i18nc("@label:slider", "Contrast: ")); + + connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushAdjustmentsState())); + connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushAdjustmentsState())); + connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushAdjustmentsState())); + + connect(btnMaskMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); + connect(btnColorMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); + connect(btnLightnessMode, SIGNAL(toggled(bool)), SLOT(slotWriteBrushMode())); + + connect(intAdjustmentMidPoint, SIGNAL(valueChanged(int)), SLOT(slotUpdateBrushAdjustments())); + connect(intBrightnessAdjustment, SIGNAL(valueChanged(int)), SLOT(slotUpdateBrushAdjustments())); + connect(intContrastAdjustment, SIGNAL(valueChanged(int)), SLOT(slotUpdateBrushAdjustments())); + updateBrushTip(m_itemChooser->currentResource()); } KisPredefinedBrushChooser::~KisPredefinedBrushChooser() { } void KisPredefinedBrushChooser::setBrush(KisBrushSP brush) { /** * Warning: since the brushes are always cloned after loading from XML or * fetching from the server, we cannot just ask for that brush explicitly. * Instead, we should search for the brush with the same filename and/or name * and load it. Please take it into account that after selecting the brush * explicitly in the chooser, m_itemChooser->currentResource() might be * **not** the same as the value in m_brush. * * Ideally, if the resource is not found on the server, we should add it, but * it might lead to a set of weird consequences. So for now we just * select nothing. */ KoResourceServer* server = KisBrushServerProvider::instance()->brushServer(); KoResourceSP resource = server->resourceByFilename(brush->filename()); if (!resource) { resource = server->resourceByName(brush->name()); } if (!resource) { resource = brush; } m_itemChooser->setCurrentResource(resource); updateBrushTip(brush, true); } void KisPredefinedBrushChooser::slotResetBrush() { /** * The slot also resets the brush on the server * * TODO: technically, after we refactored all the brushes to be forked, * we can just re-update the brush from the server without reloading. * But it needs testing. */ KisBrushSP brush = m_itemChooser->currentResource().dynamicCast(); if (brush) { brush->load(KisGlobalResourcesInterface::instance()); brush->setScale(1.0); brush->setAngle(0.0); + if (KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data())) { + colorfulBrush->setUseColorAsMask(false); + colorfulBrush->setPreserveLightness(false); + colorfulBrush->setAdjustmentMidPoint(127); + colorfulBrush->setBrightnessAdjustment(0.0); + colorfulBrush->setContrastAdjustment(0.0); + } + updateBrushTip(brush); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemSize(qreal sizeValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { int brushWidth = m_brush->width(); m_brush->setScale(sizeValue / qreal(brushWidth)); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSetItemRotation(qreal rotationValue) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setAngle(rotationValue / 180.0 * M_PI); emit sigBrushChanged(); } } void KisPredefinedBrushChooser::slotSpacingChanged() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); if (m_brush) { m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); emit sigBrushChanged(); } } -void KisPredefinedBrushChooser::slotSetItemUseColorAsMask(bool useColorAsMask) -{ - KIS_SAFE_ASSERT_RECOVER_RETURN(m_brush); - - KisGbrBrush *brush = dynamic_cast(m_brush.data()); - if (brush) { - brush->setUseColorAsMask(useColorAsMask); - emit sigBrushChanged(); - } -} - void KisPredefinedBrushChooser::slotOpenStampBrush() { if(!m_stampBrushWidget) { m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image); m_stampBrushWidget->setModal(false); connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } else { m_stampBrushWidget->setImage(m_image); } QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::slotOpenClipboardBrush() { if(!m_clipboardBrushWidget) { m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image); m_clipboardBrushWidget->setModal(true); connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )), SLOT(slotNewPredefinedBrush(KoResourceSP ))); } QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec(); if(result) { updateBrushTip(m_itemChooser->currentResource()); } } void KisPredefinedBrushChooser::updateBrushTip(KoResourceSP resource, bool isChangingBrushPresets) { QString animatedBrushTipSelectionMode; // incremental, random, etc { KisBrushSP brush = resource.dynamicCast(); m_brush = brush ? brush->clone().dynamicCast() : 0; } if (m_brush) { brushTipNameLabel->setText(i18n(m_brush->name().toUtf8().data())); QString brushTypeString = ""; if (m_brush->brushType() == INVALID) { brushTypeString = i18n("Invalid"); } else if (m_brush->brushType() == MASK) { brushTypeString = i18n("Mask"); } else if (m_brush->brushType() == IMAGE) { brushTypeString = i18n("GBR"); } else if (m_brush->brushType() == PIPE_MASK ) { brushTypeString = i18n("Animated Mask"); // GIH brush // cast to GIH brush and grab parasite name //m_brush KisImagePipeBrushSP pipeBrush = resource.dynamicCast(); animatedBrushTipSelectionMode = pipeBrush->parasiteSelection(); } else if (m_brush->brushType() == PIPE_IMAGE ) { brushTypeString = i18n("Animated Image"); } QString brushDetailsText = QString("%1 (%2 x %3) %4") .arg(brushTypeString) .arg(m_brush->width()) .arg(m_brush->height()) .arg(animatedBrushTipSelectionMode); brushDetailsLabel->setText(brushDetailsText); // keep the current preset's tip settings if we are preserving it // this will set the brush's model data to keep what it currently has for size, spacing, etc. if (preserveBrushPresetSettings->isChecked() && !isChangingBrushPresets) { m_brush->setAutoSpacing(brushSpacingSelectionWidget->autoSpacingActive(), brushSpacingSelectionWidget->autoSpacingCoeff()); m_brush->setAngle(brushRotationSpinBox->value() * M_PI / 180); m_brush->setSpacing(brushSpacingSelectionWidget->spacing()); m_brush->setUserEffectiveSize(brushSizeSpinBox->value()); } brushSpacingSelectionWidget->setSpacing(m_brush->autoSpacingActive(), m_brush->autoSpacingActive() ? m_brush->autoSpacingCoeff() : m_brush->spacing()); brushRotationSpinBox->setValue(m_brush->angle() * 180 / M_PI); brushSizeSpinBox->setValue(m_brush->width() * m_brush->scale()); - // useColorAsMask support is only in gimp brush so far - bool prevColorAsMaskState = useColorAsMaskCheckbox->isChecked(); - KisGbrBrush *gimpBrush = dynamic_cast(m_brush.data()); - if (gimpBrush) { - useColorAsMaskCheckbox->setChecked(gimpBrush->useColorAsMask() || prevColorAsMaskState); - gimpBrush->setUseColorAsMask(prevColorAsMaskState); + emit sigBrushChanged(); + } + + slotUpdateBrushModeButtonsState(); +} + +#include "kis_scaling_size_brush.h" + +void KisPredefinedBrushChooser::slotUpdateBrushModeButtonsState() +{ + KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); + const bool modeSwitchEnabled = colorfulBrush && colorfulBrush->hasColor(); + + if (modeSwitchEnabled) { + if (colorfulBrush->useColorAsMask() && colorfulBrush->preserveLightness()) { + btnLightnessMode->setChecked(true); + } else if (colorfulBrush->useColorAsMask()) { + btnMaskMode->setChecked(true); + } else { + btnColorMode->setChecked(true); } - useColorAsMaskCheckbox->setEnabled(m_brush->hasColor() && gimpBrush); - emit sigBrushChanged(); + intAdjustmentMidPoint->setValue(colorfulBrush->adjustmentMidPoint()); + intBrightnessAdjustment->setValue(qRound(colorfulBrush->brightnessAdjustment() * 100.0)); + intContrastAdjustment->setValue(qRound(colorfulBrush->contrastAdjustment() * 100.0)); + + btnMaskMode->setToolTip("Luminosity of the brush tip image is used as alpha channel for the stroke"); + btnColorMode->setToolTip("The brush tip image is painted as it is"); + btnLightnessMode->setToolTip("Luminosity of the brush tip image is used as lightness correction for the painting color. Alpha channel of the brush tip image is used as alpha for the final stroke"); + intAdjustmentMidPoint->setToolTip("Luminosity value of the brush that will not change the painting color. All brush pixels darker than neutral point will paint with darker color, pixels lighter than neutral point — lighter."); + intBrightnessAdjustment->setToolTip("Brightness correction for the brush"); + intContrastAdjustment->setToolTip("Contrast correction for the brush"); + grpBrushMode->setToolTip(""); + } else { + intAdjustmentMidPoint->setValue(127); + intBrightnessAdjustment->setValue(0); + intContrastAdjustment->setValue(0); + btnMaskMode->setChecked(true); + + btnMaskMode->setToolTip(""); + btnColorMode->setToolTip(""); + btnLightnessMode->setToolTip(""); + intAdjustmentMidPoint->setToolTip(""); + intBrightnessAdjustment->setToolTip(""); + intContrastAdjustment->setToolTip(""); + grpBrushMode->setToolTip("The selected brush tip does not have color channels. The brush will work in \"Mask\" mode."); } + + + grpBrushMode->setEnabled(modeSwitchEnabled); + slotWriteBrushAdjustmentsState(); +} + +void KisPredefinedBrushChooser::slotWriteBrushAdjustmentsState() +{ + intAdjustmentMidPoint->setEnabled(btnLightnessMode->isEnabled() && btnLightnessMode->isChecked()); + intBrightnessAdjustment->setEnabled(btnLightnessMode->isEnabled() && btnLightnessMode->isChecked()); + intContrastAdjustment->setEnabled(btnLightnessMode->isEnabled() && btnLightnessMode->isChecked()); +} + +void KisPredefinedBrushChooser::slotWriteBrushMode() +{ + KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); + if (!colorfulBrush) return; + + if (btnLightnessMode->isChecked()) { + colorfulBrush->setUseColorAsMask(true); + colorfulBrush->setPreserveLightness(true); + } else if (btnMaskMode->isChecked()) { + colorfulBrush->setUseColorAsMask(true); + colorfulBrush->setPreserveLightness(false); + } else { + colorfulBrush->setUseColorAsMask(false); + colorfulBrush->setPreserveLightness(false); + } + + emit sigBrushChanged(); +} + +void KisPredefinedBrushChooser::slotUpdateBrushAdjustments() +{ + KisColorfulBrush *colorfulBrush = dynamic_cast(m_brush.data()); + if (!colorfulBrush) return; + + colorfulBrush->setAdjustmentMidPoint(quint8(intAdjustmentMidPoint->value())); + colorfulBrush->setBrightnessAdjustment(intBrightnessAdjustment->value() / 100.0); + colorfulBrush->setContrastAdjustment(intContrastAdjustment->value() / 100.0); + + emit sigBrushChanged(); } void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResourceSP resource) { m_itemChooser->setCurrentResource(resource); updateBrushTip(resource); } void KisPredefinedBrushChooser::setBrushSize(qreal xPixels, qreal yPixels) { Q_UNUSED(yPixels); qreal oldWidth = m_brush->width() * m_brush->scale(); qreal newWidth = oldWidth + xPixels; newWidth = qMax(newWidth, qreal(0.1)); brushSizeSpinBox->setValue(newWidth); } void KisPredefinedBrushChooser::setImage(KisImageWSP image) { m_image = image; } void KisPredefinedBrushChooser::slotImportNewBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Import); } void KisPredefinedBrushChooser::slotDeleteBrushResource() { m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Remove); } #include "moc_kis_brush_chooser.cpp" diff --git a/plugins/paintops/libpaintop/kis_brush_chooser.h b/plugins/paintops/libpaintop/kis_brush_chooser.h index 76c711a44b..abaae4d07e 100644 --- a/plugins/paintops/libpaintop/kis_brush_chooser.h +++ b/plugins/paintops/libpaintop/kis_brush_chooser.h @@ -1,86 +1,90 @@ /* * Copyright (c) 2004 Adrian Page * * 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_PREDEFINED_BRUSH_CHOOSER_H_ #define KIS_PREDEFINED_BRUSH_CHOOSER_H_ #include #include #include #include "kritapaintop_export.h" #include "ui_wdgpredefinedbrushchooser.h" class KisDoubleSliderSpinBox; class QLabel; class QCheckBox; class KisDoubleSliderSpinBox; class KisSpacingSelectionWidget; class KisCustomBrushWidget; class KisClipboardBrushWidget; class KisResourceItemChooser; class KoResource; class PAINTOP_EXPORT KisPredefinedBrushChooser : public QWidget, public Ui::WdgPredefinedBrushChooser { Q_OBJECT public: KisPredefinedBrushChooser(QWidget *parent = 0, const char *name = 0); ~KisPredefinedBrushChooser() override; KisBrushSP brush() { return m_brush; }; void setBrush(KisBrushSP brush); void setBrushSize(qreal xPixels, qreal yPixels); void setImage(KisImageWSP image); private Q_SLOTS: void slotResetBrush(); void slotSetItemSize(qreal); void slotSetItemRotation(qreal); void slotSpacingChanged(); - void slotSetItemUseColorAsMask(bool); void slotOpenStampBrush(); void slotOpenClipboardBrush(); void slotImportNewBrushResource(); void slotDeleteBrushResource(); - void slotNewPredefinedBrush(KoResourceSP ); - void updateBrushTip(KoResourceSP , bool isChangingBrushPresets = false); + void slotNewPredefinedBrush(KoResourceSP); + void updateBrushTip(KoResourceSP, bool isChangingBrushPresets = false); + void slotUpdateBrushModeButtonsState(); + void slotWriteBrushAdjustmentsState(); + + void slotWriteBrushMode(); + void slotUpdateBrushAdjustments(); Q_SIGNALS: void sigBrushChanged(); private: KisBrushSP m_brush; KisResourceItemChooser* m_itemChooser; KisImageWSP m_image; KisCustomBrushWidget* m_stampBrushWidget; KisClipboardBrushWidget* m_clipboardBrushWidget; }; #endif // KIS_PREDEFINED_BRUSH_CHOOSER_H_ diff --git a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp index 4c95e14c65..d51d8deff6 100644 --- a/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_clipboard_brush_widget.cpp @@ -1,164 +1,167 @@ /* * Copyright (c) 2005 Bart Coppens * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2013 Somsubhra Bairi * * 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_clipboard_brush_widget.h" #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_clipboard.h" #include "kis_paint_device.h" #include "kis_gbr_brush.h" #include "KisBrushServerProvider.h" #include "kis_icon.h" KisClipboardBrushWidget::KisClipboardBrushWidget(QWidget *parent, const QString &caption, KisImageWSP /*image*/) : KisWdgClipboardBrush(parent) { setWindowTitle(caption); preview->setScaledContents(false); preview->setFixedSize(preview->size()); preview->setStyleSheet("border: 2px solid #222; border-radius: 4px; padding: 5px; font: normal 10px;"); m_rServer = KisBrushServerProvider::instance()->brushServer(); m_brush = 0; m_clipboard = KisClipboard::instance(); connect(m_clipboard, SIGNAL(clipChanged()), this, SLOT(slotCreateBrush())); connect(colorAsmask, SIGNAL(toggled(bool)), this, SLOT(slotUpdateUseColorAsMask(bool))); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotAddPredefined())); connect(nameEdit, SIGNAL(textEdited(const QString&)), this, SLOT(slotUpdateSaveButton())); spacingWidget->setSpacing(true, 1.0); connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); } KisClipboardBrushWidget::~KisClipboardBrushWidget() { } void KisClipboardBrushWidget::slotCreateBrush() { // do nothing if it's hidden otherwise it can break the active brush is something is copied if (m_clipboard->hasClip() && !isHidden()) { pd = m_clipboard->clip(QRect(0, 0, 0, 0), false); //Weird! Don't know how this works! if (pd) { QRect rc = pd->exactBounds(); m_brush = KisBrushSP(new KisGbrBrush(pd, rc.x(), rc.y(), rc.width(), rc.height())); m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush->setFilename(TEMPORARY_CLIPBOARD_BRUSH_FILENAME); m_brush->setName(TEMPORARY_CLIPBOARD_BRUSH_NAME); m_brush->setValid(true); int w = preview->size().width()-10; preview->setPixmap(QPixmap::fromImage(m_brush->image().scaled(w, w, Qt::KeepAspectRatio))); } } else { preview->setText(i18n("Nothing copied\n to Clipboard")); } if (!m_brush) { buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); } else { buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); colorAsmask->setChecked(true); // initializing this has to happen here since we need a valid brush for it to work + preserveAlpha->setEnabled(true); + preserveAlpha->setChecked(false); } } void KisClipboardBrushWidget::slotSpacingChanged() { if (m_brush) { m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); } } void KisClipboardBrushWidget::showEvent(QShowEvent *) { slotCreateBrush(); } void KisClipboardBrushWidget::slotUpdateUseColorAsMask(bool useColorAsMask) { + preserveAlpha->setEnabled(useColorAsMask); if (m_brush) { static_cast(m_brush.data())->setUseColorAsMask(useColorAsMask); int w = preview->size().width()-10; preview->setPixmap(QPixmap::fromImage(m_brush->image().scaled(w, w, Qt::KeepAspectRatio))); } } void KisClipboardBrushWidget::slotAddPredefined() { if(!m_brush) return; QString dir = KoResourcePaths::saveLocation("data", ResourceType::Brushes); QString extension = ".gbr"; QString name = nameEdit->text(); if (m_rServer) { KisGbrBrushSP resource = m_brush->clone().dynamicCast(); if (nameEdit->text().isEmpty()) { resource->setName(QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm")); } else { resource->setName(name); } resource->setFilename(resource->name().split(" ").join("_") + extension); if (colorAsmask->isChecked()) { - resource->makeMaskImage(); + resource->makeMaskImage(preserveAlpha->isChecked()); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } close(); } void KisClipboardBrushWidget::slotUpdateSaveButton() { if (QFileInfo(m_rServer->saveLocation() + "/" + nameEdit->text().split(" ").join("_") + ".gbr").exists()) { buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Overwrite")); } else { buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Save")); } } #include "moc_kis_clipboard_brush_widget.cpp" diff --git a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp b/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp index 5a7103e4b9..75b9f04260 100644 --- a/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp +++ b/plugins/paintops/libpaintop/kis_custom_brush_widget.cpp @@ -1,268 +1,268 @@ /* * Copyright (c) 2005 Bart Coppens * 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_custom_brush_widget.h" #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_layer.h" #include "kis_paint_device.h" #include "kis_gbr_brush.h" #include "kis_imagepipe_brush.h" #include #include "KisBrushServerProvider.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include #include #include "kis_iterator_ng.h" #include "kis_image_barrier_locker.h" KisCustomBrushWidget::KisCustomBrushWidget(QWidget *parent, const QString& caption, KisImageWSP image) : KisWdgCustomBrush(parent) , m_image(image) { setWindowTitle(caption); preview->setScaledContents(false); preview->setFixedSize(preview->size()); preview->setStyleSheet("border: 2px solid #222; border-radius: 4px; padding: 5px; font: normal 10px;"); m_rServer = KisBrushServerProvider::instance()->brushServer(); m_brush = 0; connect(this, SIGNAL(accepted()), SLOT(slotAddPredefined())); connect(brushStyle, SIGNAL(activated(int)), this, SLOT(slotUpdateCurrentBrush(int))); connect(colorAsMask, SIGNAL(toggled(bool)), this, SLOT(slotUpdateUseColorAsMask(bool))); connect(comboBox2, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCurrentBrush(int))); colorAsMask->setChecked(true); // use color as mask by default. This is by far the most common way to make tip. spacingWidget->setSpacing(true, 1.0); connect(spacingWidget, SIGNAL(sigSpacingChanged()), SLOT(slotSpacingChanged())); } KisCustomBrushWidget::~KisCustomBrushWidget() { } KisBrushSP KisCustomBrushWidget::brush() { return m_brush; } void KisCustomBrushWidget::setImage(KisImageWSP image){ m_image = image; createBrush(); updatePreviewImage(); } void KisCustomBrushWidget::showEvent(QShowEvent *) { slotUpdateCurrentBrush(0); } void KisCustomBrushWidget::updatePreviewImage() { QImage brushImage = m_brush ? m_brush->brushTipImage() : QImage(); if (!brushImage.isNull()) { int w = preview->size().width() - 10; // 10 for the padding... brushImage = brushImage.scaled(w, w, Qt::KeepAspectRatio); } preview->setPixmap(QPixmap::fromImage(brushImage)); } void KisCustomBrushWidget::slotUpdateCurrentBrush(int) { if (brushStyle->currentIndex() == 0) { comboBox2->setEnabled(false); } else { comboBox2->setEnabled(true); } if (m_image) { createBrush(); updatePreviewImage(); } } void KisCustomBrushWidget::slotSpacingChanged() { if (m_brush) { m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); } } void KisCustomBrushWidget::slotUpdateUseColorAsMask(bool useColorAsMask) { + preserveAlpha->setEnabled(useColorAsMask); if (m_brush) { static_cast(m_brush.data())->setUseColorAsMask(useColorAsMask); updatePreviewImage(); } } void KisCustomBrushWidget::slotUpdateSaveButton() { QString suffix = ".gbr"; if (brushStyle->currentIndex() != 0) { suffix = ".gih"; } if (QFileInfo(m_rServer->saveLocation() + "/" + nameLineEdit->text().split(" ").join("_") + suffix).exists()) { buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Overwrite")); } else { buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Save")); } } void KisCustomBrushWidget::slotAddPredefined() { QString dir = KoResourcePaths::saveLocation("data", ResourceType::Brushes); QString name = nameLineEdit->text(); if (nameLineEdit->text().isEmpty()) { name = (QDateTime::currentDateTime().toString("yyyy-MM-ddThh:mm")); } // Add it to the brush server, so that it automatically gets to the mediators, and // so to the other brush choosers can pick it up, if they want to if (m_rServer && m_brush) { if (m_brush->clone().dynamicCast()) { KisGbrBrushSP resource = m_brush->clone().dynamicCast(); resource->setName(name); resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); if (colorAsMask->isChecked()) { - resource->makeMaskImage(); + resource->makeMaskImage(preserveAlpha->isChecked()); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } else { KisImagePipeBrushSP resource = m_brush->clone().dynamicCast(); resource->setName(name); resource->setFilename(resource->name().split(" ").join("_") + resource->defaultFileExtension()); if (colorAsMask->isChecked()) { - resource->makeMaskImage(); + resource->makeMaskImage(preserveAlpha->isChecked()); } m_rServer->addResource(resource.dynamicCast()); emit sigNewPredefinedBrush(resource); } - } close(); } void KisCustomBrushWidget::createBrush() { if (!m_image) return; if (brushStyle->currentIndex() == 0) { KisSelectionSP selection = m_image->globalSelection(); // create copy of the data m_image->barrierLock(); KisPaintDeviceSP dev = new KisPaintDevice(*m_image->projection()); m_image->unlock(); if (!selection) { m_brush = KisBrushSP(new KisGbrBrush(dev, 0, 0, m_image->width(), m_image->height())); } else { // apply selection mask QRect r = selection->selectedExactRect(); KisHLineIteratorSP pixelIt = dev->createHLineIteratorNG(r.x(), r.top(), r.width()); KisHLineConstIteratorSP maskIt = selection->projection()->createHLineIteratorNG(r.x(), r.top(), r.width()); for (qint32 y = r.top(); y <= r.bottom(); ++y) { do { dev->colorSpace()->applyAlphaU8Mask(pixelIt->rawData(), maskIt->oldRawData(), 1); } while (pixelIt->nextPixel() && maskIt->nextPixel()); pixelIt->nextRow(); maskIt->nextRow(); } m_brush = KisBrushSP(new KisGbrBrush(dev, r.x(), r.y(), r.width(), r.height())); } } else { // For each layer in the current image, create a new image, and add it to the list QVector< QVector > devices; devices.push_back(QVector()); int w = m_image->width(); int h = m_image->height(); KisImageBarrierLocker locker(m_image); // We only loop over the rootLayer. Since we actually should have a layer selection // list, no need to elaborate on that here and now KoProperties properties; properties.setProperty("visible", true); QList layers = m_image->root()->childNodes(QStringList("KisLayer"), properties); KisNodeSP node; Q_FOREACH (KisNodeSP node, layers) { devices[0].push_back(node->projection().data()); } QVector modes; switch (comboBox2->currentIndex()) { case 0: modes.push_back(KisParasite::Constant); break; case 1: modes.push_back(KisParasite::Random); break; case 2: modes.push_back(KisParasite::Incremental); break; case 3: modes.push_back(KisParasite::Pressure); break; case 4: modes.push_back(KisParasite::Angular); break; default: modes.push_back(KisParasite::Incremental); } m_brush = KisBrushSP(new KisImagePipeBrush(m_image->objectName(), w, h, devices, modes)); } static_cast(m_brush.data())->setUseColorAsMask(colorAsMask->isChecked()); m_brush->setSpacing(spacingWidget->spacing()); m_brush->setAutoSpacing(spacingWidget->autoSpacingActive(), spacingWidget->autoSpacingCoeff()); m_brush->setFilename(TEMPORARY_FILENAME); m_brush->setName(TEMPORARY_BRUSH_NAME); m_brush->setValid(true); }