File indexing completed on 2024-05-12 15:58:53
0001 /* 0002 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "krita_utils.h" 0008 0009 #include <QtCore/qmath.h> 0010 0011 #include <QRect> 0012 #include <QRegion> 0013 #include <QPainterPath> 0014 #include <QPolygonF> 0015 #include <QPen> 0016 #include <QPainter> 0017 0018 #include "kis_algebra_2d.h" 0019 0020 #include <KoColorSpaceRegistry.h> 0021 0022 #include "kis_image.h" 0023 #include "kis_image_config.h" 0024 #include "kis_debug.h" 0025 #include "kis_node.h" 0026 #include "kis_sequential_iterator.h" 0027 #include "kis_random_accessor_ng.h" 0028 0029 #include <KisRenderedDab.h> 0030 0031 0032 namespace KritaUtils 0033 { 0034 0035 QSize optimalPatchSize() 0036 { 0037 KisImageConfig cfg(true); 0038 return QSize(cfg.updatePatchWidth(), 0039 cfg.updatePatchHeight()); 0040 } 0041 0042 QVector<QRect> splitRectIntoPatches(const QRect &rc, const QSize &patchSize) 0043 { 0044 using namespace KisAlgebra2D; 0045 0046 0047 QVector<QRect> patches; 0048 0049 const qint32 firstCol = divideFloor(rc.x(), patchSize.width()); 0050 const qint32 firstRow = divideFloor(rc.y(), patchSize.height()); 0051 0052 // TODO: check if -1 is needed here 0053 const qint32 lastCol = divideFloor(rc.x() + rc.width(), patchSize.width()); 0054 const qint32 lastRow = divideFloor(rc.y() + rc.height(), patchSize.height()); 0055 0056 for(qint32 i = firstRow; i <= lastRow; i++) { 0057 for(qint32 j = firstCol; j <= lastCol; j++) { 0058 QRect maxPatchRect(j * patchSize.width(), i * patchSize.height(), 0059 patchSize.width(), patchSize.height()); 0060 QRect patchRect = rc & maxPatchRect; 0061 0062 if (!patchRect.isEmpty()) { 0063 patches.append(patchRect); 0064 } 0065 } 0066 } 0067 0068 return patches; 0069 } 0070 0071 QVector<QRect> splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize) 0072 { 0073 QVector<QRect> patches; 0074 0075 for (qint32 y = rc.y(); y < rc.y() + rc.height(); y += patchSize.height()) { 0076 for (qint32 x = rc.x(); x < rc.x() + rc.width(); x += patchSize.width()) { 0077 patches << QRect(x, y, 0078 qMin(rc.x() + rc.width() - x, patchSize.width()), 0079 qMin(rc.y() + rc.height() - y, patchSize.height())); 0080 } 0081 } 0082 0083 return patches; 0084 } 0085 0086 QVector<QRect> splitRegionIntoPatches(const KisRegion ®ion, const QSize &patchSize) 0087 { 0088 QVector<QRect> patches; 0089 0090 Q_FOREACH (const QRect rect, region.rects()) { 0091 patches << KritaUtils::splitRectIntoPatches(rect, patchSize); 0092 } 0093 0094 return patches; 0095 } 0096 0097 bool checkInTriangle(const QRectF &rect, 0098 const QPolygonF &triangle) 0099 { 0100 return triangle.intersected(rect).boundingRect().isValid(); 0101 } 0102 0103 0104 KisRegion splitTriangles(const QPointF ¢er, 0105 const QVector<QPointF> &points) 0106 { 0107 0108 Q_ASSERT(points.size()); 0109 Q_ASSERT(!(points.size() & 1)); 0110 0111 QVector<QPolygonF> triangles; 0112 QRect totalRect; 0113 0114 for (int i = 0; i < points.size(); i += 2) { 0115 QPolygonF triangle; 0116 triangle << center; 0117 triangle << points[i]; 0118 triangle << points[i+1]; 0119 0120 totalRect |= triangle.boundingRect().toAlignedRect(); 0121 triangles << triangle; 0122 } 0123 0124 0125 const int step = 64; 0126 const int right = totalRect.x() + totalRect.width(); 0127 const int bottom = totalRect.y() + totalRect.height(); 0128 0129 QVector<QRect> dirtyRects; 0130 0131 for (int y = totalRect.y(); y < bottom;) { 0132 int nextY = qMin((y + step) & ~(step-1), bottom); 0133 0134 for (int x = totalRect.x(); x < right;) { 0135 int nextX = qMin((x + step) & ~(step-1), right); 0136 0137 QRect rect(x, y, nextX - x, nextY - y); 0138 0139 Q_FOREACH (const QPolygonF &triangle, triangles) { 0140 if(checkInTriangle(rect, triangle)) { 0141 dirtyRects << rect; 0142 break; 0143 } 0144 } 0145 0146 x = nextX; 0147 } 0148 y = nextY; 0149 } 0150 return KisRegion(std::move(dirtyRects)); 0151 } 0152 0153 KisRegion splitPath(const QPainterPath &path) 0154 { 0155 QVector<QRect> dirtyRects; 0156 QRect totalRect = path.boundingRect().toAlignedRect(); 0157 0158 // adjust the rect for antialiasing to work 0159 totalRect = totalRect.adjusted(-1,-1,1,1); 0160 0161 const int step = 64; 0162 const int right = totalRect.x() + totalRect.width(); 0163 const int bottom = totalRect.y() + totalRect.height(); 0164 0165 for (int y = totalRect.y(); y < bottom;) { 0166 int nextY = qMin((y + step) & ~(step-1), bottom); 0167 0168 for (int x = totalRect.x(); x < right;) { 0169 int nextX = qMin((x + step) & ~(step-1), right); 0170 0171 QRect rect(x, y, nextX - x, nextY - y); 0172 0173 if(path.intersects(rect)) { 0174 dirtyRects << rect; 0175 } 0176 0177 x = nextX; 0178 } 0179 y = nextY; 0180 } 0181 0182 return KisRegion(std::move(dirtyRects)); 0183 } 0184 0185 QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value) 0186 { 0187 return QLocale().toString(value, 'f', 1); 0188 } 0189 0190 qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue) 0191 { 0192 qreal maxDimension = qMax(bounds.width(), bounds.height()); 0193 return qMax(portion * maxDimension, minValue); 0194 } 0195 0196 bool tryMergePoints(QPainterPath &path, 0197 const QPointF &startPoint, 0198 const QPointF &endPoint, 0199 qreal &distance, 0200 qreal distanceThreshold, 0201 bool lastSegment) 0202 { 0203 qreal length = (endPoint - startPoint).manhattanLength(); 0204 0205 if (lastSegment || length > distanceThreshold) { 0206 if (lastSegment) { 0207 qreal wrappedLength = 0208 (endPoint - QPointF(path.elementAt(0))).manhattanLength(); 0209 0210 if (length < distanceThreshold || 0211 wrappedLength < distanceThreshold) { 0212 0213 return true; 0214 } 0215 } 0216 0217 distance = 0; 0218 return false; 0219 } 0220 0221 distance += length; 0222 0223 if (distance > distanceThreshold) { 0224 path.lineTo(endPoint); 0225 distance = 0; 0226 } 0227 0228 return true; 0229 } 0230 0231 QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold) 0232 { 0233 QPainterPath newPath; 0234 QPointF startPoint; 0235 qreal distance = 0; 0236 0237 int count = path.elementCount(); 0238 for (int i = 0; i < count; i++) { 0239 QPainterPath::Element e = path.elementAt(i); 0240 QPointF endPoint = QPointF(e.x, e.y); 0241 0242 switch (e.type) { 0243 case QPainterPath::MoveToElement: 0244 newPath.moveTo(endPoint); 0245 break; 0246 case QPainterPath::LineToElement: 0247 if (!tryMergePoints(newPath, startPoint, endPoint, 0248 distance, lengthThreshold, i == count - 1)) { 0249 0250 newPath.lineTo(endPoint); 0251 } 0252 break; 0253 case QPainterPath::CurveToElement: { 0254 Q_ASSERT(i + 2 < count); 0255 0256 if (!tryMergePoints(newPath, startPoint, endPoint, 0257 distance, lengthThreshold, i == count - 1)) { 0258 0259 e = path.elementAt(i + 1); 0260 Q_ASSERT(e.type == QPainterPath::CurveToDataElement); 0261 QPointF ctrl1 = QPointF(e.x, e.y); 0262 e = path.elementAt(i + 2); 0263 Q_ASSERT(e.type == QPainterPath::CurveToDataElement); 0264 QPointF ctrl2 = QPointF(e.x, e.y); 0265 newPath.cubicTo(ctrl1, ctrl2, endPoint); 0266 } 0267 0268 i += 2; 0269 } 0270 default: 0271 ; 0272 } 0273 startPoint = endPoint; 0274 } 0275 0276 return newPath; 0277 } 0278 0279 QList<QPainterPath> splitDisjointPaths(const QPainterPath &path) 0280 { 0281 QList<QPainterPath> resultList; 0282 QList<QPolygonF> inputPolygons = path.toSubpathPolygons(); 0283 0284 Q_FOREACH (const QPolygonF &poly, inputPolygons) { 0285 QPainterPath testPath; 0286 testPath.addPolygon(poly); 0287 0288 if (resultList.isEmpty()) { 0289 resultList.append(testPath); 0290 continue; 0291 } 0292 0293 QPainterPath mergedPath = testPath; 0294 0295 for (auto it = resultList.begin(); it != resultList.end(); /*noop*/) { 0296 if (it->intersects(testPath)) { 0297 mergedPath.addPath(*it); 0298 it = resultList.erase(it); 0299 } else { 0300 ++it; 0301 } 0302 } 0303 0304 resultList.append(mergedPath); 0305 } 0306 0307 return resultList; 0308 } 0309 0310 quint8 mergeOpacity(quint8 opacity, quint8 parentOpacity) 0311 { 0312 if (parentOpacity != OPACITY_OPAQUE_U8) { 0313 opacity = (int(opacity) * parentOpacity) / OPACITY_OPAQUE_U8; 0314 } 0315 return opacity; 0316 } 0317 0318 QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags) 0319 { 0320 QBitArray flags = childFlags; 0321 0322 if (!flags.isEmpty() && 0323 !parentFlags.isEmpty() && 0324 flags.size() == parentFlags.size()) { 0325 0326 flags &= parentFlags; 0327 0328 } else if (!parentFlags.isEmpty()) { 0329 flags = parentFlags; 0330 } 0331 0332 return flags; 0333 } 0334 0335 bool compareChannelFlags(QBitArray f1, QBitArray f2) 0336 { 0337 if (f1.isNull() && f2.isNull()) return true; 0338 0339 if (f1.isNull()) { 0340 f1.fill(true, f2.size()); 0341 } 0342 0343 if (f2.isNull()) { 0344 f2.fill(true, f1.size()); 0345 } 0346 0347 return f1 == f2; 0348 } 0349 0350 QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value) { 0351 return value ? i18n("on") : i18n("off"); 0352 } 0353 0354 KisNodeSP nearestNodeAfterRemoval(KisNodeSP node) 0355 { 0356 KisNodeSP newNode = node->nextSibling(); 0357 0358 if (!newNode) { 0359 newNode = node->prevSibling(); 0360 } 0361 0362 if (!newNode) { 0363 newNode = node->parent(); 0364 } 0365 0366 return newNode; 0367 } 0368 0369 void renderExactRect(QPainter *p, const QRect &rc) 0370 { 0371 p->drawRect(rc.adjusted(0,0,-1,-1)); 0372 } 0373 0374 void renderExactRect(QPainter *p, const QRect &rc, const QPen &pen) 0375 { 0376 QPen oldPen = p->pen(); 0377 p->setPen(pen); 0378 renderExactRect(p, rc); 0379 p->setPen(oldPen); 0380 } 0381 0382 QImage convertQImageToGrayA(const QImage &image) 0383 { 0384 QImage dstImage(image.size(), QImage::Format_ARGB32); 0385 0386 // TODO: if someone feel bored, a more optimized version of this would be welcome 0387 const QSize size = image.size(); 0388 for(int y = 0; y < size.height(); ++y) { 0389 for(int x = 0; x < size.width(); ++x) { 0390 const QRgb pixel = image.pixel(x,y); 0391 const int gray = qGray(pixel); 0392 dstImage.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(pixel))); 0393 } 0394 } 0395 0396 return dstImage; 0397 } 0398 0399 void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func) { 0400 KisSequentialConstIterator dstIt(dev, rc); 0401 while (dstIt.nextPixel()) { 0402 const quint8 *dstPtr = dstIt.rawDataConst(); 0403 func(*dstPtr); 0404 } 0405 } 0406 0407 void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func) { 0408 KisSequentialIterator dstIt(dev, rc); 0409 while (dstIt.nextPixel()) { 0410 quint8 *dstPtr = dstIt.rawData(); 0411 *dstPtr = func(*dstPtr); 0412 } 0413 } 0414 0415 qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion) { 0416 const KoColorSpace *cs = dev->colorSpace(); 0417 0418 const qreal linearPortion = std::sqrt(samplePortion); 0419 const qreal ratio = qreal(rect.width()) / rect.height(); 0420 const int xStep = qMax(1, qRound(1.0 / linearPortion * ratio)); 0421 const int yStep = qMax(1, qRound(1.0 / linearPortion / ratio)); 0422 0423 int numTransparentPixels = 0; 0424 int numPixels = 0; 0425 0426 KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(); 0427 for (int y = rect.y(); y <= rect.bottom(); y += yStep) { 0428 for (int x = rect.x(); x <= rect.right(); x += xStep) { 0429 it->moveTo(x, y); 0430 const quint8 alpha = cs->opacityU8(it->rawDataConst()); 0431 0432 if (alpha != OPACITY_OPAQUE_U8) { 0433 numTransparentPixels++; 0434 } 0435 0436 numPixels++; 0437 } 0438 } 0439 0440 if (numPixels == 0) { 0441 return 0; // avoid dividing by 0 0442 } 0443 return qreal(numTransparentPixels) / numPixels; 0444 } 0445 0446 void mirrorDab(Qt::Orientation dir, const QPoint ¢er, KisRenderedDab *dab, bool skipMirrorPixels) 0447 { 0448 const QRect rc = dab->realBounds(); 0449 0450 if (dir == Qt::Horizontal) { 0451 const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x(); 0452 0453 if (!skipMirrorPixels) { 0454 dab->device->mirror(true, false); 0455 } 0456 dab->offset.rx() = mirrorX; 0457 } else /* if (dir == Qt::Vertical) */ { 0458 const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y(); 0459 0460 if (!skipMirrorPixels) { 0461 dab->device->mirror(false, true); 0462 } 0463 dab->offset.ry() = mirrorY; 0464 } 0465 } 0466 0467 void mirrorDab(Qt::Orientation dir, const QPointF ¢er, KisRenderedDab *dab, bool skipMirrorPixels) 0468 { 0469 const QRect rc = dab->realBounds(); 0470 0471 if (dir == Qt::Horizontal) { 0472 const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x(); 0473 0474 if (!skipMirrorPixels) { 0475 dab->device->mirror(true, false); 0476 } 0477 dab->offset.rx() = mirrorX; 0478 } else /* if (dir == Qt::Vertical) */ { 0479 const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y(); 0480 0481 if (!skipMirrorPixels) { 0482 dab->device->mirror(false, true); 0483 } 0484 dab->offset.ry() = mirrorY; 0485 } 0486 } 0487 0488 void mirrorRect(Qt::Orientation dir, const QPoint ¢er, QRect *rc) 0489 { 0490 if (dir == Qt::Horizontal) { 0491 const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x(); 0492 rc->moveLeft(mirrorX); 0493 } else /* if (dir == Qt::Vertical) */ { 0494 const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y(); 0495 rc->moveTop(mirrorY); 0496 } 0497 } 0498 0499 void mirrorRect(Qt::Orientation dir, const QPointF ¢er, QRect *rc) 0500 { 0501 if (dir == Qt::Horizontal) { 0502 const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x(); 0503 rc->moveLeft(mirrorX); 0504 } else /* if (dir == Qt::Vertical) */ { 0505 const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y(); 0506 rc->moveTop(mirrorY); 0507 } 0508 } 0509 0510 void mirrorPoint(Qt::Orientation dir, const QPoint ¢er, QPointF *pt) 0511 { 0512 if (dir == Qt::Horizontal) { 0513 pt->rx() = -(pt->x() - qreal(center.x())) + center.x(); 0514 } else /* if (dir == Qt::Vertical) */ { 0515 pt->ry() = -(pt->y() - qreal(center.y())) + center.y(); 0516 } 0517 } 0518 0519 void mirrorPoint(Qt::Orientation dir, const QPointF ¢er, QPointF *pt) 0520 { 0521 if (dir == Qt::Horizontal) { 0522 pt->rx() = -(pt->x() - qreal(center.x())) + center.x(); 0523 } else /* if (dir == Qt::Vertical) */ { 0524 pt->ry() = -(pt->y() - qreal(center.y())) + center.y(); 0525 } 0526 } 0527 0528 QTransform pathShapeBooleanSpaceWorkaround(KisImageSP image) 0529 { 0530 return QTransform::fromScale(image->xRes(), image->yRes()); 0531 } 0532 0533 void thresholdOpacity(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode) 0534 { 0535 const KoColorSpace *cs = device->colorSpace(); 0536 0537 if (mode == ThresholdCeil) { 0538 KisSequentialIterator it(device, rect); 0539 while (it.nextPixel()) { 0540 if (cs->opacityU8(it.rawDataConst()) > 0) { 0541 cs->setOpacity(it.rawData(), quint8(255), 1); 0542 } 0543 } 0544 } else if (mode == ThresholdFloor) { 0545 KisSequentialIterator it(device, rect); 0546 while (it.nextPixel()) { 0547 if (cs->opacityU8(it.rawDataConst()) < 255) { 0548 cs->setOpacity(it.rawData(), quint8(0), 1); 0549 } 0550 } 0551 } else if (mode == ThresholdMaxOut) { 0552 KisSequentialIterator it(device, rect); 0553 int numConseqPixels = it.nConseqPixels(); 0554 while (it.nextPixels(numConseqPixels)) { 0555 numConseqPixels = it.nConseqPixels(); 0556 cs->setOpacity(it.rawData(), quint8(255), numConseqPixels); 0557 } 0558 } 0559 } 0560 0561 void thresholdOpacityAlpha8(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode) 0562 { 0563 if (mode == ThresholdCeil) { 0564 filterAlpha8Device(device, rect, 0565 [] (quint8 value) { 0566 return value > 0 ? 255 : value; 0567 }); 0568 } else if (mode == ThresholdFloor) { 0569 filterAlpha8Device(device, rect, 0570 [] (quint8 value) { 0571 return value < 255 ? 0 : value; 0572 }); 0573 } else if (mode == ThresholdMaxOut) { 0574 device->fill(rect, KoColor(Qt::white, device->colorSpace())); 0575 } 0576 } 0577 0578 QVector<QPoint> rasterizeHLine(const QPoint &startPoint, const QPoint &endPoint) 0579 { 0580 QVector<QPoint> points; 0581 rasterizeHLine(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); }); 0582 return points; 0583 } 0584 0585 QVector<QPoint> rasterizeVLine(const QPoint &startPoint, const QPoint &endPoint) 0586 { 0587 QVector<QPoint> points; 0588 rasterizeVLine(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); }); 0589 return points; 0590 } 0591 0592 QVector<QPoint> rasterizeLineDDA(const QPoint &startPoint, const QPoint &endPoint) 0593 { 0594 QVector<QPoint> points; 0595 rasterizeLineDDA(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); }); 0596 return points; 0597 } 0598 0599 QVector<QPoint> rasterizePolylineDDA(const QVector<QPoint> &polylinePoints) 0600 { 0601 QVector<QPoint> points; 0602 rasterizePolylineDDA(polylinePoints, [&points](const QPoint &point) { points.append(point); }); 0603 return points; 0604 } 0605 0606 QVector<QPoint> rasterizePolygonDDA(const QVector<QPoint> &polygonPoints) 0607 { 0608 QVector<QPoint> points; 0609 rasterizePolygonDDA(polygonPoints, [&points](const QPoint &point) { points.append(point); }); 0610 return points; 0611 } 0612 0613 }