[graphics/krita] libs/ui/tool: Cache crosshair drawing, fix offset circle preview rotation when mirrored

Emmet O'Neill <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 51bdf6de3ed9a267b35b5a01fd26f774c30712a3 by Emmet O'Neill, on behalf of Dat Le.
Committed on 13/08/2026 at 23:16.
Pushed by emmetoneill into branch 'master'.

Cache crosshair drawing, fix offset circle preview rotation when mirrored

M  +71   -24   libs/ui/tool/KisAsyncColorSamplerHelper.cpp

https://invent.kde.org/graphics/krita/-/commit/51bdf6de3ed9a267b35b5a01fd26f774c30712a3

diff --git a/libs/ui/tool/KisAsyncColorSamplerHelper.cpp b/libs/ui/tool/KisAsyncColorSamplerHelper.cpp
index e9e3c1d5f71..c61d4ff98f8 100644
--- a/libs/ui/tool/KisAsyncColorSamplerHelper.cpp
+++ b/libs/ui/tool/KisAsyncColorSamplerHelper.cpp
@@ -73,13 +73,14 @@ struct KisAsyncColorSamplerHelper::Private
 
     bool circleZoomPreviewEnabled {false};
     qreal circleZoomPreviewScale {5};
-    int circleZoomPreviewCrosshairSize {15};
     KisConfig::ColorSamplerPreviewCirclePosition circlePreviewPosition = KisConfig::ColorSamplerPreviewCirclePosition::Center;
     QPointF sampleDocPoint;
 
     QPainterPath cacheCircleInnerClip;
     QRect cacheCanvasPreviewRect;
     QImage cacheCanvasPreviewImage;
+    QImage cacheCrosshairImage;
+    int cacheCirclePreviewDiameter;
 
     QColor currentColor;
     QColor baseColor;
@@ -184,7 +185,16 @@ struct KisAsyncColorSamplerHelper::Private
                 break;
             }
 
-            return circleRect.translated(QPointF(x, y));
+            QTransform tf;
+
+            qreal canvasRotationAngle = canvas->rotationAngle();
+            if (!qFuzzyIsNull(canvasRotationAngle)) {
+                tf.rotate(mirrored ? canvasRotationAngle : -canvasRotationAngle);
+            }
+
+            QPointF offset = tf.map(QPointF(x,y));
+
+            circleRect.translate(offset.x(), offset.y());
         }
 
         return circleRect;
@@ -235,6 +245,40 @@ struct KisAsyncColorSamplerHelper::Private
 
         return pixelRect;
     }
+
+    QImage crosshairForOffsettedCircle() {
+        if (!cacheCrosshairImage.isNull() && cacheCirclePreviewDiameter == circlePreviewDiameter) return cacheCrosshairImage;
+
+        cacheCirclePreviewDiameter = circlePreviewDiameter;
+
+        int gap = 4;
+        int crosshairRadius = circlePreviewDiameter / 2 * 0.3;
+        cacheCrosshairImage = QImage(crosshairRadius * 2 + 1, crosshairRadius * 2 + 1, QImage::Format_ARGB32);
+        cacheCrosshairImage.fill(Qt::transparent);
+
+        QPainter painter(&cacheCrosshairImage);\
+        painter.translate(crosshairRadius + 1, crosshairRadius + 1);
+
+        painter.setBrush(Qt::NoBrush);
+
+        painter.setPen(Qt::black);
+        painter.drawLine(QLine(-crosshairRadius, 0, crosshairRadius, 0));
+        painter.drawLine(QLine(0, -crosshairRadius, 0, crosshairRadius));
+
+        painter.setPen(Qt::white);
+        painter.drawLine(QLine(-crosshairRadius, 1, crosshairRadius, 1));
+        painter.drawLine(QLine(1, -crosshairRadius, 1, crosshairRadius));
+
+        painter.drawLine(QLine(-crosshairRadius, -1, crosshairRadius, -1));
+        painter.drawLine(QLine(-1, -crosshairRadius, -1, crosshairRadius));
+
+        painter.setCompositionMode(QPainter::CompositionMode_Clear);
+        painter.setPen(Qt::NoPen);
+        painter.setBrush(Qt::transparent);
+        painter.drawRect(QRect(QPoint(-gap, -gap), QPoint(gap, gap)));
+
+        return cacheCrosshairImage;
+    }
 };
 
 KisAsyncColorSamplerHelper::KisAsyncColorSamplerHelper(KisCanvas2 *canvas)
@@ -531,15 +575,6 @@ void KisAsyncColorSamplerHelper::paintCircle(QPainter &gc,
     QPainter cachePainter(&m_d->cache);
     cachePainter.setRenderHint(QPainter::Antialiasing);
 
-    QColor backgroundColor = colorWithAlpha(m_d->backgroundColor, OPACITY_OPAQUE_U8 / 2 + 1);
-    qreal penWidth = m_d->circlePreviewDiameter > 100 ? (2.0 * dpr) : (1.0 * dpr);
-    QPen pen = QPen(backgroundColor, penWidth);
-    if (m_d->circlePreviewOutlineEnabled) {
-        cachePainter.setPen(pen);
-    } else {
-        cachePainter.setPen(Qt::NoPen);
-    }
-
     QRectF cacheRect = m_d->cache.rect();
 
     // The color sampler preview is an outline and those rotate along
@@ -555,6 +590,15 @@ void KisAsyncColorSamplerHelper::paintCircle(QPainter &gc,
     if (needsNewCache || (needsDualColor && !qFuzzyCompare(m_d->cacheRotation, canvasRotationAngle))) {
         m_d->cacheRotation = canvasRotationAngle;
 
+        QColor backgroundColor = colorWithAlpha(m_d->backgroundColor, OPACITY_OPAQUE_U8 / 2 + 1);
+        qreal penWidth = m_d->circlePreviewDiameter > 100 ? (2.0 * dpr) : (1.0 * dpr);
+        QPen pen = QPen(backgroundColor, penWidth);
+        if (m_d->circlePreviewOutlineEnabled) {
+            cachePainter.setPen(pen);
+        } else {
+            cachePainter.setPen(Qt::NoPen);
+        }
+
         QRectF outerRect = cacheRect.marginsRemoved(QMarginsF(penWidth, penWidth, penWidth, penWidth));
 
         if (needsDualColor) {
@@ -644,17 +688,21 @@ void KisAsyncColorSamplerHelper::paintCircle(QPainter &gc,
 
         // Draw crosshair if preview is offseted
         if (m_d->circlePreviewPosition != KisConfig::ColorSamplerPreviewCirclePosition::Center) {
-            QColor crosshairColor = Qt::black;
-            // Apparently this fomular is outdated and inaccurate. But the accurate version require calculating power, probably overkill anyway
-            qreal luminance = (0.299 * currentColor.redF() + 0.587 * currentColor.greenF() + 0.114 * currentColor.blueF());
-            if (luminance < 0.5) crosshairColor = Qt::white;
-
-            cachePainter.setPen(crosshairColor);
-            cachePainter.setBrush(crosshairColor);
-            cachePainter.drawLine(cacheCenter + QPointF(-m_d->circleZoomPreviewCrosshairSize, 0), cacheCenter + QPointF(-5, 0));
-            cachePainter.drawLine(cacheCenter + QPointF(5, 0), cacheCenter + QPointF(m_d->circleZoomPreviewCrosshairSize, 0));
-            cachePainter.drawLine(cacheCenter + QPointF(0, -m_d->circleZoomPreviewCrosshairSize), cacheCenter + QPointF(0, -5));
-            cachePainter.drawLine(cacheCenter + QPointF(0, 5), cacheCenter + QPointF(0, m_d->circleZoomPreviewCrosshairSize));
+            QImage crosshairImage = m_d->crosshairForOffsettedCircle();
+
+            QRectF targetRect = crosshairImage.rect();
+            targetRect.moveCenter(cacheCenter);
+
+            cachePainter.save();
+            cachePainter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
+
+            cachePainter.translate(cacheCenter);
+            cachePainter.rotate(-m_d->cacheRotation);
+            cachePainter.translate(-cacheCenter);
+
+            cachePainter.drawImage(targetRect, crosshairImage);
+
+            cachePainter.restore();
         }
     }
 
@@ -702,8 +750,6 @@ QImage KisAsyncColorSamplerHelper::cacheCanvasImage(QRect &canvasPixelRect) {
 }
 
 void KisAsyncColorSamplerHelper::paintCircleCanvasPreview(QPainter &gc, const QRectF &viewRectF, const QRectF &zoomDocRectF, const QPainterPath &clip) {
-    gc.save();
-
     KisImageWSP image = m_d->canvas->image();
 
     QRect canvasPixelRect = image->documentToPixel(zoomDocRectF).toRect();
@@ -715,6 +761,7 @@ void KisAsyncColorSamplerHelper::paintCircleCanvasPreview(QPainter &gc, const QR
     QImage cachedImage = cacheCanvasImage(canvasPixelRect);
     if (cachedImage.isNull()) return;
 
+    gc.save();
     gc.setCompositionMode(QPainter::CompositionMode_SourceOver);
 
     gc.setClipPath(clip);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.