diff --git a/libs/image/krita_utils.cpp b/libs/image/krita_utils.cpp index 736cf27d2e..b1cf8e713a 100644 --- a/libs/image/krita_utils.cpp +++ b/libs/image/krita_utils.cpp @@ -1,478 +1,509 @@ /* * Copyright (c) 2011 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 "krita_utils.h" #include #include #include #include #include #include #include #include "kis_algebra_2d.h" #include #include "kis_image_config.h" #include "kis_debug.h" #include "kis_node.h" #include "kis_sequential_iterator.h" #include "kis_random_accessor_ng.h" #include namespace KritaUtils { QSize optimalPatchSize() { KisImageConfig cfg(true); return QSize(cfg.updatePatchWidth(), cfg.updatePatchHeight()); } QVector splitRectIntoPatches(const QRect &rc, const QSize &patchSize) { using namespace KisAlgebra2D; QVector patches; const qint32 firstCol = divideFloor(rc.x(), patchSize.width()); const qint32 firstRow = divideFloor(rc.y(), patchSize.height()); // TODO: check if -1 is needed here const qint32 lastCol = divideFloor(rc.x() + rc.width(), patchSize.width()); const qint32 lastRow = divideFloor(rc.y() + rc.height(), patchSize.height()); for(qint32 i = firstRow; i <= lastRow; i++) { for(qint32 j = firstCol; j <= lastCol; j++) { QRect maxPatchRect(j * patchSize.width(), i * patchSize.height(), patchSize.width(), patchSize.height()); QRect patchRect = rc & maxPatchRect; if (!patchRect.isEmpty()) { patches.append(patchRect); } } } return patches; } QVector splitRegionIntoPatches(const QRegion ®ion, const QSize &patchSize) { QVector patches; Q_FOREACH (const QRect rect, region.rects()) { patches << KritaUtils::splitRectIntoPatches(rect, patchSize); } return patches; } bool checkInTriangle(const QRectF &rect, const QPolygonF &triangle) { return triangle.intersected(rect).boundingRect().isValid(); } QRegion KRITAIMAGE_EXPORT splitTriangles(const QPointF ¢er, const QVector &points) { Q_ASSERT(points.size()); Q_ASSERT(!(points.size() & 1)); QVector triangles; QRect totalRect; for (int i = 0; i < points.size(); i += 2) { QPolygonF triangle; triangle << center; triangle << points[i]; triangle << points[i+1]; totalRect |= triangle.boundingRect().toAlignedRect(); triangles << triangle; } const int step = 64; const int right = totalRect.x() + totalRect.width(); const int bottom = totalRect.y() + totalRect.height(); QRegion dirtyRegion; for (int y = totalRect.y(); y < bottom;) { int nextY = qMin((y + step) & ~(step-1), bottom); for (int x = totalRect.x(); x < right;) { int nextX = qMin((x + step) & ~(step-1), right); QRect rect(x, y, nextX - x, nextY - y); Q_FOREACH (const QPolygonF &triangle, triangles) { if(checkInTriangle(rect, triangle)) { dirtyRegion |= rect; break; } } x = nextX; } y = nextY; } return dirtyRegion; } QRegion KRITAIMAGE_EXPORT splitPath(const QPainterPath &path) { QRect totalRect = path.boundingRect().toAlignedRect(); // adjust the rect for antialiasing to work totalRect = totalRect.adjusted(-1,-1,1,1); const int step = 64; const int right = totalRect.x() + totalRect.width(); const int bottom = totalRect.y() + totalRect.height(); QRegion dirtyRegion; for (int y = totalRect.y(); y < bottom;) { int nextY = qMin((y + step) & ~(step-1), bottom); for (int x = totalRect.x(); x < right;) { int nextX = qMin((x + step) & ~(step-1), right); QRect rect(x, y, nextX - x, nextY - y); if(path.intersects(rect)) { dirtyRegion |= rect; } x = nextX; } y = nextY; } return dirtyRegion; } QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value) { return QString("%1").arg(value, 6, 'f', 1); } qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue) { qreal maxDimension = qMax(bounds.width(), bounds.height()); return qMax(portion * maxDimension, minValue); } bool tryMergePoints(QPainterPath &path, const QPointF &startPoint, const QPointF &endPoint, qreal &distance, qreal distanceThreshold, bool lastSegment) { qreal length = (endPoint - startPoint).manhattanLength(); if (lastSegment || length > distanceThreshold) { if (lastSegment) { qreal wrappedLength = (endPoint - QPointF(path.elementAt(0))).manhattanLength(); if (length < distanceThreshold || wrappedLength < distanceThreshold) { return true; } } distance = 0; return false; } distance += length; if (distance > distanceThreshold) { path.lineTo(endPoint); distance = 0; } return true; } QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold) { QPainterPath newPath; QPointF startPoint; qreal distance = 0; int count = path.elementCount(); for (int i = 0; i < count; i++) { QPainterPath::Element e = path.elementAt(i); QPointF endPoint = QPointF(e.x, e.y); switch (e.type) { case QPainterPath::MoveToElement: newPath.moveTo(endPoint); break; case QPainterPath::LineToElement: if (!tryMergePoints(newPath, startPoint, endPoint, distance, lengthThreshold, i == count - 1)) { newPath.lineTo(endPoint); } break; case QPainterPath::CurveToElement: { Q_ASSERT(i + 2 < count); if (!tryMergePoints(newPath, startPoint, endPoint, distance, lengthThreshold, i == count - 1)) { e = path.elementAt(i + 1); Q_ASSERT(e.type == QPainterPath::CurveToDataElement); QPointF ctrl1 = QPointF(e.x, e.y); e = path.elementAt(i + 2); Q_ASSERT(e.type == QPainterPath::CurveToDataElement); QPointF ctrl2 = QPointF(e.x, e.y); newPath.cubicTo(ctrl1, ctrl2, endPoint); } i += 2; } default: ; } startPoint = endPoint; } return newPath; } QList splitDisjointPaths(const QPainterPath &path) { QList resultList; QList inputPolygons = path.toSubpathPolygons(); Q_FOREACH (const QPolygonF &poly, inputPolygons) { QPainterPath testPath; testPath.addPolygon(poly); if (resultList.isEmpty()) { resultList.append(testPath); continue; } QPainterPath mergedPath = testPath; for (auto it = resultList.begin(); it != resultList.end(); /*noop*/) { if (it->intersects(testPath)) { mergedPath.addPath(*it); it = resultList.erase(it); } else { ++it; } } resultList.append(mergedPath); } return resultList; } quint8 mergeOpacity(quint8 opacity, quint8 parentOpacity) { if (parentOpacity != OPACITY_OPAQUE_U8) { opacity = (int(opacity) * parentOpacity) / OPACITY_OPAQUE_U8; } return opacity; } QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags) { QBitArray flags = childFlags; if (!flags.isEmpty() && !parentFlags.isEmpty() && flags.size() == parentFlags.size()) { flags &= parentFlags; } else if (!parentFlags.isEmpty()) { flags = parentFlags; } return flags; } bool compareChannelFlags(QBitArray f1, QBitArray f2) { if (f1.isNull() && f2.isNull()) return true; if (f1.isNull()) { f1.fill(true, f2.size()); } if (f2.isNull()) { f2.fill(true, f1.size()); } return f1 == f2; } QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value) { return value ? i18n("on") : i18n("off"); } KisNodeSP nearestNodeAfterRemoval(KisNodeSP node) { KisNodeSP newNode = node->nextSibling(); if (!newNode) { newNode = node->prevSibling(); } if (!newNode) { newNode = node->parent(); } return newNode; } void renderExactRect(QPainter *p, const QRect &rc) { p->drawRect(rc.adjusted(0,0,-1,-1)); } void renderExactRect(QPainter *p, const QRect &rc, const QPen &pen) { QPen oldPen = p->pen(); p->setPen(pen); renderExactRect(p, rc); p->setPen(oldPen); } QImage convertQImageToGrayA(const QImage &image) { QImage dstImage(image.size(), QImage::Format_ARGB32); // TODO: if someone feel bored, a more optimized version of this would be welcome const QSize size = image.size(); for(int y = 0; y < size.height(); ++y) { for(int x = 0; x < size.width(); ++x) { const QRgb pixel = image.pixel(x,y); const int gray = qGray(pixel); dstImage.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(pixel))); } } return dstImage; } QColor blendColors(const QColor &c1, const QColor &c2, qreal r1) { const qreal r2 = 1.0 - r1; return QColor::fromRgbF( c1.redF() * r1 + c2.redF() * r2, c1.greenF() * r1 + c2.greenF() * r2, c1.blueF() * r1 + c2.blueF() * r2); } void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func) { KisSequentialConstIterator dstIt(dev, rc); while (dstIt.nextPixel()) { const quint8 *dstPtr = dstIt.rawDataConst(); func(*dstPtr); } } void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func) { KisSequentialIterator dstIt(dev, rc); while (dstIt.nextPixel()) { quint8 *dstPtr = dstIt.rawData(); *dstPtr = func(*dstPtr); } } qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion) { const KoColorSpace *cs = dev->colorSpace(); const qreal linearPortion = std::sqrt(samplePortion); const qreal ratio = qreal(rect.width()) / rect.height(); const int xStep = qMax(1, qRound(1.0 / linearPortion * ratio)); const int yStep = qMax(1, qRound(1.0 / linearPortion / ratio)); int numTransparentPixels = 0; int numPixels = 0; KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(rect.x(), rect.y()); for (int y = rect.y(); y <= rect.bottom(); y += yStep) { for (int x = rect.x(); x <= rect.right(); x += xStep) { it->moveTo(x, y); const quint8 alpha = cs->opacityU8(it->rawDataConst()); if (alpha != OPACITY_OPAQUE_U8) { numTransparentPixels++; } numPixels++; } } return qreal(numTransparentPixels) / numPixels; } void mirrorDab(Qt::Orientation dir, const QPoint ¢er, KisRenderedDab *dab) { const QRect rc = dab->realBounds(); if (dir == Qt::Horizontal) { const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x(); dab->device->mirror(true, false); dab->offset.rx() = mirrorX; } else /* if (dir == Qt::Vertical) */ { const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y(); dab->device->mirror(false, true); dab->offset.ry() = mirrorY; } } void mirrorRect(Qt::Orientation dir, const QPoint ¢er, QRect *rc) { if (dir == Qt::Horizontal) { const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x(); rc->moveLeft(mirrorX); } else /* if (dir == Qt::Vertical) */ { const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y(); rc->moveTop(mirrorY); } } + + qreal colorDifference(const QColor &c1, const QColor &c2) + { + const qreal dr = c1.redF() - c2.redF(); + const qreal dg = c1.greenF() - c2.greenF(); + const qreal db = c1.blueF() - c2.blueF(); + + return std::sqrt(2 * pow2(dr) + 4 * pow2(dg) + 3 * pow2(db)); + } + + void dragColor(QColor *color, const QColor &baseColor, qreal threshold) + { + while (colorDifference(*color, baseColor) < threshold) { + + QColor newColor = *color; + + if (newColor.lightnessF() > baseColor.lightnessF()) { + newColor = newColor.lighter(120); + } else { + newColor = newColor.darker(120); + } + + if (newColor == *color) { + break; + } + + *color = newColor; + } + } + + } diff --git a/libs/image/krita_utils.h b/libs/image/krita_utils.h index cc3e04d740..34108af377 100644 --- a/libs/image/krita_utils.h +++ b/libs/image/krita_utils.h @@ -1,105 +1,119 @@ /* * Copyright (c) 2011 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 __KRITA_UTILS_H #define __KRITA_UTILS_H class QRect; class QRectF; class QSize; class QPen; class QPointF; class QPainterPath; class QBitArray; class QPainter; class KisRenderedDab; #include #include "kritaimage_export.h" #include "kis_types.h" #include "krita_container_utils.h" #include namespace KritaUtils { QSize KRITAIMAGE_EXPORT optimalPatchSize(); QVector KRITAIMAGE_EXPORT splitRectIntoPatches(const QRect &rc, const QSize &patchSize); QVector KRITAIMAGE_EXPORT splitRegionIntoPatches(const QRegion ®ion, const QSize &patchSize); QRegion KRITAIMAGE_EXPORT splitTriangles(const QPointF ¢er, const QVector &points); QRegion KRITAIMAGE_EXPORT splitPath(const QPainterPath &path); QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value); qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue); QPainterPath KRITAIMAGE_EXPORT trySimplifyPath(const QPainterPath &path, qreal lengthThreshold); /** * Split a path \p path into a set of disjoint (non-intersectable) * paths if possible. * * It tries to follow odd-even fill rule, but has a small problem: * If you have three selections included into each other twice, * then the smallest selection will be included into the final subpath, * although it shouldn't according to odd-even-fill rule. It is still * to be fixed. */ QList KRITAIMAGE_EXPORT splitDisjointPaths(const QPainterPath &path); quint8 KRITAIMAGE_EXPORT mergeOpacity(quint8 opacity, quint8 parentOpacity); QBitArray KRITAIMAGE_EXPORT mergeChannelFlags(const QBitArray &flags, const QBitArray &parentFlags); bool KRITAIMAGE_EXPORT compareChannelFlags(QBitArray f1, QBitArray f2); QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value); KisNodeSP KRITAIMAGE_EXPORT nearestNodeAfterRemoval(KisNodeSP node); /** * When drawing a rect Qt uses quite a weird algorithm. It * draws 4 lines: * o at X-es: rect.x() and rect.right() + 1 * o at Y-s: rect.y() and rect.bottom() + 1 * * Which means that bottom and right lines of the rect are painted * outside the virtual rectangle the rect defines. This methods overcome this issue by * painting the adjusted rect. */ void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc); /** * \see renderExactRect(QPainter *p, const QRect &rc) */ void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc, const QPen &pen); QImage KRITAIMAGE_EXPORT convertQImageToGrayA(const QImage &image); QColor KRITAIMAGE_EXPORT blendColors(const QColor &c1, const QColor &c2, qreal r1); + /** + * \return an approximate difference between \p c1 and \p c2 + * in a (nonlinear) range [0, 3] + * + * The colors are compared using the formula: + * difference = sqrt(2 * diff_R^2 + 4 * diff_G^2 + 3 * diff_B^2) + */ + qreal KRITAIMAGE_EXPORT colorDifference(const QColor &c1, const QColor &c2); + + /** + * Make the color \p color differ from \p baseColor for at least \p threshold value + */ + void KRITAIMAGE_EXPORT dragColor(QColor *color, const QColor &baseColor, qreal threshold); + void KRITAIMAGE_EXPORT applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func); void KRITAIMAGE_EXPORT filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func); qreal KRITAIMAGE_EXPORT estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion); void KRITAIMAGE_EXPORT mirrorDab(Qt::Orientation dir, const QPoint ¢er, KisRenderedDab *dab); void KRITAIMAGE_EXPORT mirrorRect(Qt::Orientation dir, const QPoint ¢er, QRect *rc); } #endif /* __KRITA_UTILS_H */ diff --git a/libs/ui/kis_node_view_color_scheme.cpp b/libs/ui/kis_node_view_color_scheme.cpp index 341b907777..b351593485 100644 --- a/libs/ui/kis_node_view_color_scheme.cpp +++ b/libs/ui/kis_node_view_color_scheme.cpp @@ -1,185 +1,192 @@ /* * Copyright (c) 2015 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_node_view_color_scheme.h" #include #include +#include "krita_utils.h" +#include #include Q_GLOBAL_STATIC(KisNodeViewColorScheme, s_instance) struct KisNodeViewColorScheme::Private { Private() { if (colorLabels.isEmpty()) { colorLabels << Qt::transparent; colorLabels << QColor(91,173,220); colorLabels << QColor(151,202,63); colorLabels << QColor(247,229,61); colorLabels << QColor(255,170,63); colorLabels << QColor(177,102,63); colorLabels << QColor(238,50,51); colorLabels << QColor(191,106,209); colorLabels << QColor(118,119,114); + + const QColor noLabelSetColor = qApp->palette().color(QPalette::Highlight); + for (auto it = colorLabels.begin(); it != colorLabels.end(); ++it) { + KritaUtils::dragColor(&(*it), noLabelSetColor, 0.3); + } } } static QVector colorLabels; }; QVector KisNodeViewColorScheme::Private::colorLabels; KisNodeViewColorScheme::KisNodeViewColorScheme() : m_d(new Private) { } KisNodeViewColorScheme::~KisNodeViewColorScheme() { } KisNodeViewColorScheme* KisNodeViewColorScheme::instance() { return s_instance; } QColor KisNodeViewColorScheme::gridColor(const QStyleOptionViewItem &option, QTreeView *view) { const int gridHint = view->style()->styleHint(QStyle::SH_Table_GridLineColor, &option, view); const QColor gridColor = static_cast(gridHint); return gridColor; } int KisNodeViewColorScheme::visibilitySize() const { return 16; } int KisNodeViewColorScheme::visibilityMargin() const { return 2; } int KisNodeViewColorScheme::thumbnailSize() const { return 20; } int KisNodeViewColorScheme::thumbnailMargin() const { return 3; } int KisNodeViewColorScheme::decorationSize() const { return 12; } int KisNodeViewColorScheme::decorationMargin() const { return 1; } int KisNodeViewColorScheme::textMargin() const { return 2; } int KisNodeViewColorScheme::iconSize() const { return 16; } int KisNodeViewColorScheme::iconMargin() const { return 1; } int KisNodeViewColorScheme::border() const { return 1; } int KisNodeViewColorScheme::rowHeight() const { return border() + 2 * thumbnailMargin() + thumbnailSize(); } int KisNodeViewColorScheme::visibilityColumnWidth() const { return border() + 2 * visibilityMargin() + visibilitySize() + border(); } int KisNodeViewColorScheme::indentation() const { return 2 * thumbnailMargin() + thumbnailSize() + border(); } QRect KisNodeViewColorScheme::relThumbnailRect() const { return QRect(-indentation(), border(), thumbnailSize() + 2 * thumbnailMargin(), thumbnailSize() + 2 * thumbnailMargin()); } QRect KisNodeViewColorScheme::relDecorationRect() const { return QRect(border() + decorationMargin(), border() + decorationMargin(), decorationSize(), decorationSize()); } QRect KisNodeViewColorScheme::relExpandButtonRect() const { const int newY = rowHeight() - decorationMargin() - decorationSize(); QRect rc = relDecorationRect(); rc.moveTop(newY); return rc; } QColor KisNodeViewColorScheme::colorLabel(int index) const { /** * We should ensure that the index of the overflowing range * will never be zero again. */ if (index >= m_d->colorLabels.size()) { index = 1 + index % (m_d->colorLabels.size() - 1); } else { index = index % m_d->colorLabels.size(); } return m_d->colorLabels[index]; } QVector KisNodeViewColorScheme::allColorLabels() const { return m_d->colorLabels; }