diff --git a/libs/pigment/resources/KoPattern.cpp b/libs/pigment/resources/KoPattern.cpp index adeb95d2b1..5d818551e8 100644 --- a/libs/pigment/resources/KoPattern.cpp +++ b/libs/pigment/resources/KoPattern.cpp @@ -1,375 +1,391 @@ /* This file is part of the KDE project Copyright (c) 2000 Matthias Elter 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 library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { struct GimpPatternHeader { quint32 header_size; /* header_size = sizeof (PatternHeader) + brush name */ quint32 version; /* pattern file version # */ quint32 width; /* width of pattern */ quint32 height; /* height of pattern */ quint32 bytes; /* depth of pattern in bytes : 1, 2, 3 or 4*/ quint32 magic_number; /* GIMP brush magic number */ }; // Yes! This is _NOT_ what my pat.txt file says. It's really not 'GIMP', but 'GPAT' quint32 const GimpPatternMagic = (('G' << 24) + ('P' << 16) + ('A' << 8) + ('T' << 0)); } KoPattern::KoPattern(const QString& file) : KoResource(file) { } KoPattern::KoPattern(const QImage &image, const QString &name, const QString &folderName) : KoResource(QString()) { setPatternImage(image); setName(name); QFileInfo fileInfo(folderName + '/' + name + defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(folderName + '/' + name + QString::number(i) + defaultFileExtension()); i++; } setFilename(fileInfo.filePath()); } KoPattern::~KoPattern() { } KoPattern::KoPattern(const KoPattern &rhs) : KoResource(rhs), m_pattern(rhs.m_pattern), m_md5(rhs.m_md5) { } KoResourceSP KoPattern::clone() const { return KoResourceSP(new KoPattern(*this)); } bool KoPattern::loadPatFromDevice(QIODevice *dev) { QByteArray data = dev->readAll(); return init(data); } bool KoPattern::savePatToDevice(QIODevice* dev) const { // Header: header_size (24+name length),version,width,height,colordepth of brush,magic,name // depth: 1 = greyscale, 2 = greyscale + A, 3 = RGB, 4 = RGBA // magic = "GPAT", as a single uint32, the docs are wrong here! // name is UTF-8 (\0-terminated! The docs say nothing about this!) // _All_ data in network order, it seems! (not mentioned in gimp-2.2.8/devel-docs/pat.txt!!) // We only save RGBA at the moment // Version is 1 for now... GimpPatternHeader ph; QByteArray utf8Name = name().toUtf8(); char const* name = utf8Name.data(); int nameLength = qstrlen(name); ph.header_size = qToBigEndian((quint32)sizeof(GimpPatternHeader) + nameLength + 1); // trailing 0 ph.version = qToBigEndian((quint32)1); ph.width = qToBigEndian((quint32)width()); ph.height = qToBigEndian((quint32)height()); ph.bytes = qToBigEndian((quint32)4); ph.magic_number = qToBigEndian((quint32)GimpPatternMagic); QByteArray bytes = QByteArray::fromRawData(reinterpret_cast(&ph), sizeof(GimpPatternHeader)); int wrote = dev->write(bytes); bytes.clear(); if (wrote == -1) return false; wrote = dev->write(name, nameLength + 1); // Trailing 0 apparently! if (wrote == -1) return false; int k = 0; bytes.resize(width() * height() * 4); for (qint32 y = 0; y < height(); ++y) { for (qint32 x = 0; x < width(); ++x) { // RGBA only QRgb pixel = m_pattern.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; } bool KoPattern::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) { Q_UNUSED(resourcesInterface); QString fileExtension; int index = filename().lastIndexOf('.'); if (index != -1) fileExtension = filename().mid(index + 1).toLower(); bool result; if (fileExtension == "pat") { result = loadPatFromDevice(dev); } else { QImage image; // Workaround for some OS (Debian, Ubuntu), where loading directly from the QIODevice // fails with "libpng error: IDAT: CRC error" QByteArray data = dev->readAll(); QBuffer buffer(&data); result = image.load(&buffer, fileExtension.toUpper().toLatin1()); setPatternImage(image); } return result; } bool KoPattern::saveToDevice(QIODevice *dev) const { QString fileExtension; int index = filename().lastIndexOf('.'); if (index != -1) fileExtension = filename().mid(index + 1).toLower(); bool result = false; if (fileExtension == "pat") { result = savePatToDevice(dev); } else { result = m_pattern.save(dev, fileExtension.toUpper().toLatin1()); } return result && KoResource::saveToDevice(dev); } bool KoPattern::init(QByteArray& bytes) { int dataSize = bytes.size(); const char* data = bytes.constData(); // load Gimp patterns GimpPatternHeader bh; qint32 k; char* name; if ((int)sizeof(GimpPatternHeader) > dataSize) { return false; } memcpy(&bh, data, sizeof(GimpPatternHeader)); bh.header_size = qFromBigEndian(bh.header_size); bh.version = qFromBigEndian(bh.version); bh.width = qFromBigEndian(bh.width); bh.height = qFromBigEndian(bh.height); bh.bytes = qFromBigEndian(bh.bytes); bh.magic_number = qFromBigEndian(bh.magic_number); if ((int)bh.header_size > dataSize || bh.header_size == 0) { return false; } int size = bh.header_size - sizeof(GimpPatternHeader); name = new char[size]; memcpy(name, data + sizeof(GimpPatternHeader), size); if (name[size - 1]) { delete[] name; return false; } // size -1 so we don't add the end 0 to the QString... setName(QString::fromLatin1(name, size -1)); delete[] name; if (bh.width == 0 || bh.height == 0) { return false; } QImage::Format imageFormat; if (bh.bytes == 1 || bh.bytes == 3) { imageFormat = QImage::Format_RGB32; } else { imageFormat = QImage::Format_ARGB32; } QImage pattern = QImage(bh.width, bh.height, imageFormat); if (pattern.isNull()) { return false; } k = bh.header_size; if (bh.bytes == 1) { // Grayscale qint32 val; for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x, ++k) { if (k > dataSize) { qWarning() << "failed to load grayscale pattern" << filename(); return false; } val = data[k]; pixels[x] = qRgb(val, val, val); } } // It was grayscale, so make the pattern as small as possible // by converting it to Indexed8 pattern = pattern.convertToFormat(QImage::Format_Indexed8); } else if (bh.bytes == 2) { // Grayscale + A qint32 val; qint32 alpha; for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x, ++k) { if (k + 2 > dataSize) { qWarning() << "failed to load grayscale +_ alpha pattern" << filename(); return false; } val = data[k]; alpha = data[k++]; pixels[x] = qRgba(val, val, val, alpha); } } } else if (bh.bytes == 3) { // RGB without alpha for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x) { if (k + 3 > dataSize) { qWarning() << "failed to load RGB pattern" << filename(); return false; } pixels[x] = qRgb(data[k], data[k + 1], data[k + 2]); k += 3; } } } else if (bh.bytes == 4) { // Has alpha for (quint32 y = 0; y < bh.height; ++y) { QRgb* pixels = reinterpret_cast( pattern.scanLine(y) ); for (quint32 x = 0; x < bh.width; ++x) { if (k + 4 > dataSize) { qWarning() << "failed to load RGB + Alpha pattern" << filename(); return false; } pixels[x] = qRgba(data[k], data[k + 1], data[k + 2], data[k + 3]); k += 4; } } } else { return false; } if (pattern.isNull()) { return false; } setPatternImage(pattern); setValid(true); return true; } qint32 KoPattern::width() const { return m_pattern.width(); } qint32 KoPattern::height() const { return m_pattern.height(); } void KoPattern::setPatternImage(const QImage& image) { m_pattern = image; + checkForAlpha(image); setImage(image); setValid(true); } QString KoPattern::defaultFileExtension() const { return QString(".pat"); } QImage KoPattern::pattern() const { return m_pattern; } +void KoPattern::checkForAlpha(const QImage& image) { + m_hasAlpha = false; + for (int y = 0; y < image.height(); y++) { + for (int x = 0; x < image.width(); x++) { + if (qAlpha(image.pixel(x, y)) != 255) { + m_hasAlpha = true; + break; + } + } + } +} + +bool KoPattern::hasAlpha() { + return m_hasAlpha; +} \ No newline at end of file diff --git a/libs/pigment/resources/KoPattern.h b/libs/pigment/resources/KoPattern.h index 16ae4d5976..5456867bd1 100644 --- a/libs/pigment/resources/KoPattern.h +++ b/libs/pigment/resources/KoPattern.h @@ -1,89 +1,93 @@ /* Copyright (c) 2000 Matthias Elter 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; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef KOPATTERN_H #define KOPATTERN_H #include #include #include #include class KoPattern; typedef QSharedPointer KoPatternSP; /// Write API docs here class KRITAPIGMENT_EXPORT KoPattern : public KoResource { public: /** * Creates a new KoPattern object using @p filename. No file is opened * in the constructor, you have to call load. * * @param filename the file name to save and load from. */ explicit KoPattern(const QString &filename); KoPattern(const QImage &image, const QString &name, const QString &folderName); ~KoPattern() override; KoPattern(const KoPattern &rhs); KoPattern& operator=(const KoPattern& rhs) = delete; KoResourceSP clone() const override; public: bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override; bool saveToDevice(QIODevice* dev) const override; bool loadPatFromDevice(QIODevice *dev); bool savePatToDevice(QIODevice* dev) const; qint32 width() const; qint32 height() const; QString defaultFileExtension() const override; QPair resourceType() const override { return QPair(ResourceType::Patterns, ""); } /** * @brief pattern the actual pattern image * @return a valid QImage. There are no guarantees to the image format. */ QImage pattern() const; + bool hasAlpha(); + private: bool init(QByteArray& data); void setPatternImage(const QImage& image); + void checkForAlpha(const QImage& image); private: QImage m_pattern; + bool m_hasAlpha = false; mutable QByteArray m_md5; }; Q_DECLARE_METATYPE(KoPattern*) Q_DECLARE_METATYPE(QSharedPointer) #endif // KOPATTERN_H diff --git a/plugins/paintops/libpaintop/KisTextureMaskInfo.cpp b/plugins/paintops/libpaintop/KisTextureMaskInfo.cpp index bbfb2a83f8..224bd0d01a 100644 --- a/plugins/paintops/libpaintop/KisTextureMaskInfo.cpp +++ b/plugins/paintops/libpaintop/KisTextureMaskInfo.cpp @@ -1,247 +1,257 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisTextureMaskInfo.h" #include #include #include "kis_embedded_pattern_manager.h" #include #include #include #include #include #include /**********************************************************************/ /* KisTextureMaskInfo */ /**********************************************************************/ KisTextureMaskInfo::KisTextureMaskInfo(int levelOfDetail) : m_levelOfDetail(levelOfDetail) { } KisTextureMaskInfo::KisTextureMaskInfo(const KisTextureMaskInfo &rhs) : m_levelOfDetail(rhs.m_levelOfDetail), m_pattern(rhs.m_pattern), m_scale(rhs.m_scale), m_brightness(rhs.m_brightness), m_contrast(rhs.m_contrast), m_neutralPoint(rhs.m_neutralPoint), m_invert(rhs.m_invert), m_cutoffLeft(rhs.m_cutoffLeft), m_cutoffRight(rhs.m_cutoffRight), m_cutoffPolicy(rhs.m_cutoffPolicy) { } KisTextureMaskInfo::~KisTextureMaskInfo() { } bool operator==(const KisTextureMaskInfo &lhs, const KisTextureMaskInfo &rhs) { return lhs.m_levelOfDetail == rhs.m_levelOfDetail && (lhs.m_pattern == rhs.m_pattern || (lhs.m_pattern && rhs.m_pattern && lhs.m_pattern->md5() == rhs.m_pattern->md5())) && qFuzzyCompare(lhs.m_scale, rhs.m_scale) && qFuzzyCompare(lhs.m_brightness, rhs.m_brightness) && qFuzzyCompare(lhs.m_contrast, rhs.m_contrast) && qFuzzyCompare(lhs.m_neutralPoint, rhs.m_neutralPoint) && lhs.m_invert == rhs.m_invert && lhs.m_cutoffLeft == rhs.m_cutoffLeft && lhs.m_cutoffRight == rhs.m_cutoffRight && lhs.m_cutoffPolicy == rhs.m_cutoffPolicy; } KisTextureMaskInfo &KisTextureMaskInfo::operator=(const KisTextureMaskInfo &rhs) { m_levelOfDetail = rhs.m_levelOfDetail; m_pattern = rhs.m_pattern; m_scale = rhs.m_scale; m_brightness = rhs.m_brightness; m_contrast = rhs.m_contrast; m_neutralPoint = rhs.m_neutralPoint; m_invert = rhs.m_invert; m_cutoffLeft = rhs.m_cutoffLeft; m_cutoffRight = rhs.m_cutoffRight; m_cutoffPolicy = rhs.m_cutoffPolicy; return *this; } int KisTextureMaskInfo::levelOfDetail() const { return m_levelOfDetail; } bool KisTextureMaskInfo::hasMask() const { return m_mask; } KisPaintDeviceSP KisTextureMaskInfo::mask() { return m_mask; } QRect KisTextureMaskInfo::maskBounds() const { return m_maskBounds; } bool KisTextureMaskInfo::fillProperties(const KisPropertiesConfigurationSP setting, KisResourcesInterfaceSP resourcesInterface) { if (!setting->hasProperty("Texture/Pattern/PatternMD5")) { return false; } m_pattern = KisEmbeddedPatternManager::tryFetchPattern(setting, resourcesInterface); if (!m_pattern) { warnKrita << "WARNING: Couldn't load the pattern for a stroke"; return false; } m_scale = setting->getDouble("Texture/Pattern/Scale", 1.0); m_brightness = setting->getDouble("Texture/Pattern/Brightness"); m_contrast = setting->getDouble("Texture/Pattern/Contrast", 1.0); m_neutralPoint = setting->getDouble("Texture/Pattern/NeutralPoint", 0.5); m_invert = setting->getBool("Texture/Pattern/Invert"); m_cutoffLeft = setting->getInt("Texture/Pattern/CutoffLeft", 0); m_cutoffRight = setting->getInt("Texture/Pattern/CutoffRight", 255); m_cutoffPolicy = setting->getInt("Texture/Pattern/CutoffPolicy", 0); return true; } void KisTextureMaskInfo::recalculateMask() { if (!m_pattern) return; - const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8(); + const KoColorSpace* cs; + bool hasAlpha = m_pattern->hasAlpha(); + if (hasAlpha) { + cs = KoColorSpaceRegistry::instance()->rgb8(); + } else { + cs = KoColorSpaceRegistry::instance()->alpha8(); + } if (!m_mask) { m_mask = new KisPaintDevice(cs); } QImage mask = m_pattern->pattern(); if ((mask.format() != QImage::Format_RGB32) | (mask.format() != QImage::Format_ARGB32)) { mask = mask.convertToFormat(QImage::Format_ARGB32); } qreal scale = m_scale * KisLodTransform::lodToScale(m_levelOfDetail); if (!qFuzzyCompare(scale, 0.0)) { QTransform tf; tf.scale(scale, scale); QRect rc = KisAlgebra2D::ensureRectNotSmaller(tf.mapRect(mask.rect()), QSize(2,2)); mask = mask.scaled(rc.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); } QRgb* pixel = reinterpret_cast(mask.bits()); const int width = mask.width(); const int height = mask.height(); //KisHLineIteratorSP iter = m_mask->createHLineIteratorNG(0, 0, width); for (int row = 0; row < height; ++row) { for (int col = 0; col < width; ++col) { const QRgb currentPixel = pixel[row * width + col]; const int red = qRed(currentPixel); const int green = qGreen(currentPixel); const int blue = qBlue(currentPixel); float alpha = qAlpha(currentPixel) / 255.0; const int grayValue = (red * 11 + green * 16 + blue * 5) / 32; float maskValue = (grayValue / 255.0) * alpha + (1 - alpha); maskValue = maskValue - m_brightness; maskValue = ((maskValue - 0.5)*m_contrast)+0.5; if (maskValue > 1.0) {maskValue = 1;} else if (maskValue < 0) {maskValue = 0;} if (m_invert) { maskValue = 1 - maskValue; } if (m_cutoffPolicy == 1 && (maskValue < (m_cutoffLeft / 255.0) || maskValue > (m_cutoffRight / 255.0))) { // mask out the dab if it's outside the pattern's cuttoff points alpha = OPACITY_TRANSPARENT_F; } else if (m_cutoffPolicy == 2 && (maskValue < (m_cutoffLeft / 255.0) || maskValue > (m_cutoffRight / 255.0))) { alpha = OPACITY_OPAQUE_F; } maskValue = qBound(0.0f, maskValue, 1.0f); float neutralAdjustedValue; //Adjust neutral point in linear fashion. Uses separate linear equations from 0 to neutralPoint, and neutralPoint to 1, //to prevent loss of detail (clipping). if (m_neutralPoint == 1 || (m_neutralPoint !=0 && maskValue <= m_neutralPoint)) { neutralAdjustedValue = maskValue / (2 * m_neutralPoint); } else { neutralAdjustedValue = 0.5 + (maskValue-m_neutralPoint) / (2 - 2 * m_neutralPoint); } int finalValue = neutralAdjustedValue * 255; pixel[row * width + col] = QColor(finalValue, finalValue, finalValue, alpha * 255).rgba(); } } m_mask->convertFromQImage(mask, 0); m_maskBounds = QRect(0, 0, width, height); } +bool KisTextureMaskInfo::hasAlpha() { + return m_pattern->hasAlpha(); +} + /**********************************************************************/ /* KisTextureMaskInfoCache */ /**********************************************************************/ Q_GLOBAL_STATIC(KisTextureMaskInfoCache, s_instance) KisTextureMaskInfoCache *KisTextureMaskInfoCache::instance() { return s_instance; } KisTextureMaskInfoSP KisTextureMaskInfoCache::fetchCachedTextureInfo(KisTextureMaskInfoSP info) { QMutexLocker locker(&m_mutex); KisTextureMaskInfoSP &cachedInfo = info->levelOfDetail() > 0 ? m_lodInfo : m_mainInfo; if (!cachedInfo || *cachedInfo != *info) { cachedInfo = info; cachedInfo->recalculateMask(); } return cachedInfo; } diff --git a/plugins/paintops/libpaintop/KisTextureMaskInfo.h b/plugins/paintops/libpaintop/KisTextureMaskInfo.h index 4176db4f43..167a4101a0 100644 --- a/plugins/paintops/libpaintop/KisTextureMaskInfo.h +++ b/plugins/paintops/libpaintop/KisTextureMaskInfo.h @@ -1,92 +1,94 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISTEXTUREMASKINFO_H #define KISTEXTUREMASKINFO_H #include #include #include #include #include class KisTextureMaskInfo; class KisResourcesInterface; class KisTextureMaskInfo : public boost::equality_comparable { public: KisTextureMaskInfo(int levelOfDetail); KisTextureMaskInfo(const KisTextureMaskInfo &rhs); ~KisTextureMaskInfo(); friend bool operator==(const KisTextureMaskInfo &lhs, const KisTextureMaskInfo &rhs); KisTextureMaskInfo& operator=(const KisTextureMaskInfo &rhs); int levelOfDetail() const; bool hasMask() const; KisPaintDeviceSP mask(); QRect maskBounds() const; bool fillProperties(const KisPropertiesConfigurationSP setting, KisResourcesInterfaceSP resourcesInterface); void recalculateMask(); + bool hasAlpha(); + private: int m_levelOfDetail = 0; KoPatternSP m_pattern = 0; qreal m_scale = 1.0; qreal m_brightness = 0.0; qreal m_contrast = 1.0; qreal m_neutralPoint = 0.5; bool m_invert = false; int m_cutoffLeft = 0; int m_cutoffRight = 255; int m_cutoffPolicy = 0; KisPaintDeviceSP m_mask; QRect m_maskBounds; }; typedef QSharedPointer KisTextureMaskInfoSP; struct KisTextureMaskInfoCache { static KisTextureMaskInfoCache *instance(); KisTextureMaskInfoSP fetchCachedTextureInfo(KisTextureMaskInfoSP info); private: QMutex m_mutex; QSharedPointer m_lodInfo; QSharedPointer m_mainInfo; }; #endif // KISTEXTUREMASKINFO_H diff --git a/plugins/paintops/libpaintop/kis_texture_option.cpp b/plugins/paintops/libpaintop/kis_texture_option.cpp index a43868e6e0..f04e8cfc59 100644 --- a/plugins/paintops/libpaintop/kis_texture_option.cpp +++ b/plugins/paintops/libpaintop/kis_texture_option.cpp @@ -1,432 +1,437 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2012 * Copyright (C) Mohit Goyal , (C) 2014 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_texture_option.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_embedded_pattern_manager.h" #include #include "kis_texture_chooser.h" #include "KoMixColorsOp.h" #include #include "kis_signals_blocker.h" #include KisTextureOption::KisTextureOption() : KisPaintOpOption(KisPaintOpOption::TEXTURE, true) , m_textureOptions(new KisTextureChooser()) { setObjectName("KisTextureOption"); setConfigurationPage(m_textureOptions); connect(m_textureOptions->textureSelectorWidget, SIGNAL(resourceSelected(KoResourceSP )), SLOT(resetGUI(KoResourceSP ))); connect(m_textureOptions->textureSelectorWidget, SIGNAL(resourceSelected(KoResourceSP )), SLOT(emitSettingChanged())); connect(m_textureOptions->scaleSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); connect(m_textureOptions->brightnessSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); connect(m_textureOptions->contrastSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); connect(m_textureOptions->neutralPointSlider, SIGNAL(valueChanged(qreal)), SLOT(emitSettingChanged())); connect(m_textureOptions->offsetSliderX, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged())); connect(m_textureOptions->randomOffsetX, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_textureOptions->randomOffsetY, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); connect(m_textureOptions->offsetSliderY, SIGNAL(valueChanged(int)), SLOT(emitSettingChanged())); connect(m_textureOptions->cmbTexturingMode, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_textureOptions->cmbCutoffPolicy, SIGNAL(currentIndexChanged(int)), SLOT(emitSettingChanged())); connect(m_textureOptions->cutoffSlider, SIGNAL(sigModifiedBlack(int)), SLOT(emitSettingChanged())); connect(m_textureOptions->cutoffSlider, SIGNAL(sigModifiedWhite(int)), SLOT(emitSettingChanged())); connect(m_textureOptions->chkInvert, SIGNAL(toggled(bool)), SLOT(emitSettingChanged())); resetGUI(m_textureOptions->textureSelectorWidget->currentResource()); } KisTextureOption::~KisTextureOption() { delete m_textureOptions; } void KisTextureOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const { KoPatternSP pattern; { KisSignalsBlocker b(m_textureOptions->textureSelectorWidget); KoResourceSP resource = m_textureOptions->textureSelectorWidget->currentResource(); if (!resource) return; pattern = resource.staticCast(); if (!pattern) return; } setting->setProperty("Texture/Pattern/Enabled", isChecked()); if (!isChecked()) { return; } qreal scale = m_textureOptions->scaleSlider->value(); qreal brightness = m_textureOptions->brightnessSlider->value(); qreal contrast = m_textureOptions->contrastSlider->value(); qreal neutralPoint = m_textureOptions->neutralPointSlider->value(); int offsetX = m_textureOptions->offsetSliderX->value(); if (m_textureOptions ->randomOffsetX->isChecked()) { m_textureOptions->offsetSliderX ->setEnabled(false); m_textureOptions->offsetSliderX ->blockSignals(true); m_textureOptions->offsetSliderX ->setValue(offsetX); m_textureOptions->offsetSliderX ->blockSignals(false); } else { m_textureOptions->offsetSliderX ->setEnabled(true); } int offsetY = m_textureOptions->offsetSliderY->value(); if (m_textureOptions ->randomOffsetY->isChecked()) { m_textureOptions->offsetSliderY ->setEnabled(false); m_textureOptions->offsetSliderY ->blockSignals(true); m_textureOptions->offsetSliderY ->setValue(offsetY); m_textureOptions->offsetSliderY ->blockSignals(false); } else { m_textureOptions->offsetSliderY ->setEnabled(true); } int texturingMode = m_textureOptions->cmbTexturingMode->currentIndex(); bool invert = (m_textureOptions->chkInvert->checkState() == Qt::Checked); setting->setProperty("Texture/Pattern/Scale", scale); setting->setProperty("Texture/Pattern/Brightness", brightness); setting->setProperty("Texture/Pattern/Contrast", contrast); setting->setProperty("Texture/Pattern/NeutralPoint", neutralPoint); setting->setProperty("Texture/Pattern/OffsetX", offsetX); setting->setProperty("Texture/Pattern/OffsetY", offsetY); setting->setProperty("Texture/Pattern/TexturingMode", texturingMode); setting->setProperty("Texture/Pattern/CutoffLeft", m_textureOptions->cutoffSlider->black()); setting->setProperty("Texture/Pattern/CutoffRight", m_textureOptions->cutoffSlider->white()); setting->setProperty("Texture/Pattern/CutoffPolicy", m_textureOptions->cmbCutoffPolicy->currentIndex()); setting->setProperty("Texture/Pattern/Invert", invert); setting->setProperty("Texture/Pattern/MaximumOffsetX",m_textureOptions->offsetSliderX ->maximum()); setting->setProperty("Texture/Pattern/MaximumOffsetY",m_textureOptions->offsetSliderY ->maximum()); setting->setProperty("Texture/Pattern/isRandomOffsetX",m_textureOptions ->randomOffsetX ->isChecked()); setting->setProperty("Texture/Pattern/isRandomOffsetY",m_textureOptions ->randomOffsetY ->isChecked()); KisEmbeddedPatternManager::saveEmbeddedPattern(setting, pattern); } void KisTextureOption::readOptionSetting(const KisPropertiesConfigurationSP setting) { setChecked(setting->getBool("Texture/Pattern/Enabled")); if (!isChecked()) { return; } KoPatternSP pattern = KisEmbeddedPatternManager::loadEmbeddedPattern(setting, KisGlobalResourcesInterface::instance()); if (!pattern) { pattern =m_textureOptions->textureSelectorWidget->currentResource().staticCast(); } m_textureOptions->textureSelectorWidget->setCurrentPattern(pattern); m_textureOptions->scaleSlider->setValue(setting->getDouble("Texture/Pattern/Scale", 1.0)); m_textureOptions->brightnessSlider->setValue(setting->getDouble("Texture/Pattern/Brightness")); m_textureOptions->contrastSlider->setValue(setting->getDouble("Texture/Pattern/Contrast", 1.0)); m_textureOptions->neutralPointSlider->setValue(setting->getDouble("Texture/Pattern/NeutralPoint", 0.5)); m_textureOptions->offsetSliderX->setValue(setting->getInt("Texture/Pattern/OffsetX")); m_textureOptions->offsetSliderY->setValue(setting->getInt("Texture/Pattern/OffsetY")); m_textureOptions->randomOffsetX->setChecked(setting->getBool("Texture/Pattern/isRandomOffsetX")); m_textureOptions->randomOffsetY->setChecked(setting->getBool("Texture/Pattern/isRandomOffsetY")); m_textureOptions->cmbTexturingMode->setCurrentIndex(setting->getInt("Texture/Pattern/TexturingMode", KisTextureProperties::MULTIPLY)); m_textureOptions->cmbCutoffPolicy->setCurrentIndex(setting->getInt("Texture/Pattern/CutoffPolicy")); m_textureOptions->cutoffSlider->slotModifyBlack(setting->getInt("Texture/Pattern/CutoffLeft", 0)); m_textureOptions->cutoffSlider->slotModifyWhite(setting->getInt("Texture/Pattern/CutoffRight", 255)); m_textureOptions->chkInvert->setChecked(setting->getBool("Texture/Pattern/Invert")); } void KisTextureOption::lodLimitations(KisPaintopLodLimitations *l) const { l->limitations << KoID("texture-pattern", i18nc("PaintOp instant preview limitation", "Texture->Pattern (low quality preview)")); } void KisTextureOption::resetGUI(KoResourceSP res) { KoPatternSP pattern = res.staticCast(); if (!pattern) return; m_textureOptions->offsetSliderX->setRange(0, pattern->pattern().width() / 2); m_textureOptions->offsetSliderY->setRange(0, pattern->pattern().height() / 2); } /**********************************************************************/ /* KisTextureProperties */ /**********************************************************************/ KisTextureProperties::KisTextureProperties(int levelOfDetail) : m_levelOfDetail(levelOfDetail) { KoResourceServer* rserver = KoResourceServerProvider::instance()->gradientServer(); m_gradient = dynamic_cast(rserver->resources().first()); } void KisTextureProperties::fillProperties(const KisPropertiesConfigurationSP setting, KisResourcesInterfaceSP resourcesInterface) { if (!setting->hasProperty("Texture/Pattern/PatternMD5")) { m_enabled = false; return; } m_maskInfo = toQShared(new KisTextureMaskInfo(m_levelOfDetail)); if (!m_maskInfo->fillProperties(setting, resourcesInterface)) { warnKrita << "WARNING: Couldn't load the pattern for a stroke"; m_enabled = false; return; } m_maskInfo = KisTextureMaskInfoCache::instance()->fetchCachedTextureInfo(m_maskInfo); m_enabled = setting->getBool("Texture/Pattern/Enabled", false); m_offsetX = setting->getInt("Texture/Pattern/OffsetX"); m_offsetY = setting->getInt("Texture/Pattern/OffsetY"); m_texturingMode = (TexturingMode) setting->getInt("Texture/Pattern/TexturingMode", MULTIPLY); m_strengthOption.readOptionSetting(setting); m_strengthOption.resetAllSensors(); } QList KisTextureProperties::prepareEmbeddedResources(const KisPropertiesConfigurationSP setting, KisResourcesInterfaceSP resourcesInterface) { QList resources; KoPatternSP pattern = KisEmbeddedPatternManager::loadEmbeddedPattern(setting, resourcesInterface); if (pattern) { resources << pattern; } return resources; } void KisTextureProperties::setTextureGradient(KoAbstractGradientSP gradient) { if (gradient) { m_gradient = gradient; m_cachedGradient.setGradient(gradient, 256); } } //Convert a pixel to a QColor. We need this instead of calling ColorSpace::toQColor() because toQColor() is extremely slow, //even though it works pretty much exactly the same way... void KisTextureProperties::createQColorFromPixel(QColor& dest, const quint8* pixel, const KoColorSpace *cs) { QVector channelValuesF(4); //Probably doesn't need to check for RGBA, because the only time we call it is for //the mask, which we explicitly set as rbg8 colorspace before calling this. It's here from previous testing //where it was necessary, and left in just in case it's needed in the future. QString csid = cs->id(); if (csid.contains("RGBA")) { cs->normalisedChannelsValue(pixel, channelValuesF); } else { const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel); quint8* pixelRGBA = new quint8[rgbPixelSize]; cs->toRgbA16(pixel, pixelRGBA, 1); KoColorSpaceTrait::normalisedChannelsValue(pixelRGBA, channelValuesF); delete pixelRGBA; } dest.setRgbF(channelValuesF[2], channelValuesF[1], channelValuesF[0], channelValuesF[3]); } void KisTextureProperties::applyLightness(KisFixedPaintDeviceSP dab, const QPoint& offset, const KisPaintInformation& info) { if (!m_enabled) return; - KisPaintDeviceSP fillMaskDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); - QRect rect = dab->bounds(); - KisPaintDeviceSP mask = m_maskInfo->mask(); + bool maskHasAlpha = m_maskInfo->hasAlpha(); const QRect maskBounds = m_maskInfo->maskBounds(); + KisPaintDeviceSP fillMaskDevice = new KisPaintDevice(mask->colorSpace()); + QRect rect = dab->bounds(); + KIS_SAFE_ASSERT_RECOVER_RETURN(mask); int x = offset.x() % maskBounds.width() - m_offsetX; int y = offset.y() % maskBounds.height() - m_offsetY; KisFillPainter fillMaskPainter(fillMaskDevice); fillMaskPainter.fillRect(x - 1, y - 1, rect.width() + 2, rect.height() + 2, mask, maskBounds); fillMaskPainter.end(); qreal pressure = m_strengthOption.apply(info); quint8* dabData = dab->data(); - KisHLineIteratorSP lightIter = fillMaskDevice->createHLineIteratorNG(x, y, rect.width()); + KisHLineIteratorSP iter = fillMaskDevice->createHLineIteratorNG(x, y, rect.width()); for (int row = 0; row < rect.height(); ++row) { for (int col = 0; col < rect.width(); ++col) { - const quint8* maskData = lightIter->oldRawData(); + const quint8* maskData = iter->oldRawData(); QColor maskColor; - createQColorFromPixel(maskColor, maskData, mask->colorSpace()); - + if (maskHasAlpha) { + createQColorFromPixel(maskColor, maskData, mask->colorSpace()); + } else { + quint8 gray = *maskData; + maskColor = QColor::fromRgb(gray, gray, gray); + } QRgb maskQRgb = maskColor.rgba(); dab->colorSpace()->fillGrayBrushWithColorAndLightnessWithStrength(dabData, &maskQRgb, dabData, pressure, 1); - lightIter->nextPixel(); + iter->nextPixel(); dabData += dab->pixelSize(); } - lightIter->nextRow(); + iter->nextRow(); } } void KisTextureProperties::applyGradient(KisFixedPaintDeviceSP dab, const QPoint& offset, const KisPaintInformation& info) { if (!m_enabled) return; KisPaintDeviceSP fillDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); QRect rect = dab->bounds(); KisPaintDeviceSP mask = m_maskInfo->mask(); const QRect maskBounds = m_maskInfo->maskBounds(); KIS_SAFE_ASSERT_RECOVER_RETURN(mask); int x = offset.x() % maskBounds.width() - m_offsetX; int y = offset.y() % maskBounds.height() - m_offsetY; KisFillPainter fillPainter(fillDevice); fillPainter.fillRect(x - 1, y - 1, rect.width() + 2, rect.height() + 2, mask, maskBounds); fillPainter.end(); qreal pressure = m_strengthOption.apply(info); quint8* dabData = dab->data(); //for gradient textures... KoMixColorsOp* colorMix = dab->colorSpace()->mixColorsOp(); qint16 colorWeights[2]; colorWeights[0] = pressure * 255; colorWeights[1] = (1.0 - pressure) * 255; quint8* colors[2]; KisHLineIteratorSP iter = fillDevice->createHLineIteratorNG(x, y, rect.width()); for (int row = 0; row < rect.height(); ++row) { for (int col = 0; col < rect.width(); ++col) { if (m_gradient && m_gradient->valid()) { qreal gradientvalue = qreal(*iter->oldRawData()) / 255.0; KoColor paintcolor; if (m_useCachedGradient) { paintcolor.setColor(m_cachedGradient.cachedAt(gradientvalue), m_gradient->colorSpace()); } else { paintcolor = KoColor(m_gradient->colorSpace()); m_gradient->colorAt(paintcolor, gradientvalue); } paintcolor.setOpacity(qMin(paintcolor.opacityF(), dab->colorSpace()->opacityF(dabData))); paintcolor.convertTo(dab->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); colors[0] = paintcolor.data(); KoColor dabColor(dabData, dab->colorSpace()); colors[1] = dabColor.data(); colorMix->mixColors(colors, colorWeights, 2, dabData); } iter->nextPixel(); dabData += dab->pixelSize(); } iter->nextRow(); } } void KisTextureProperties::apply(KisFixedPaintDeviceSP dab, const QPoint &offset, const KisPaintInformation & info) { if (!m_enabled) return; if (m_texturingMode == LIGHTNESS) { applyLightness(dab, offset, info); return; } else if (m_texturingMode == GRADIENT) { applyGradient(dab, offset, info); return; } KisPaintDeviceSP fillDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); QRect rect = dab->bounds(); KisPaintDeviceSP mask = m_maskInfo->mask(); const QRect maskBounds = m_maskInfo->maskBounds(); KIS_SAFE_ASSERT_RECOVER_RETURN(mask); int x = offset.x() % maskBounds.width() - m_offsetX; int y = offset.y() % maskBounds.height() - m_offsetY; KisFillPainter fillPainter(fillDevice); fillPainter.fillRect(x - 1, y - 1, rect.width() + 2, rect.height() + 2, mask, maskBounds); fillPainter.end(); qreal pressure = m_strengthOption.apply(info); quint8* dabData = dab->data(); KisHLineIteratorSP iter = fillDevice->createHLineIteratorNG(x, y, rect.width()); for (int row = 0; row < rect.height(); ++row) { for (int col = 0; col < rect.width(); ++col) { if (m_texturingMode == MULTIPLY) { dab->colorSpace()->multiplyAlpha(dabData, quint8(*iter->oldRawData() * pressure), 1); } else { int pressureOffset = (1.0 - pressure) * 255; qint16 maskA = *iter->oldRawData() + pressureOffset; quint8 dabA = dab->colorSpace()->opacityU8(dabData); dabA = qMax(0, (qint16)dabA - maskA); dab->colorSpace()->setOpacity(dabData, dabA, 1); } iter->nextPixel(); dabData += dab->pixelSize(); } iter->nextRow(); } }