[graphics/krita] libs/ui/tool: Cache surrounding area of preview
Emmet O'Neill <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit e7179104b700b780c30b81d4250f3eeeb71b8bd1 by Emmet O'Neill, on behalf of Le Tien Dat.
Committed on 13/08/2026 at 23:16.
Pushed by emmetoneill into branch 'master'.
Cache surrounding area of preview
M +69 -17 libs/ui/tool/KisAsyncColorSamplerHelper.cpp
M +4 -0 libs/ui/tool/KisAsyncColorSamplerHelper.h
https://invent.kde.org/graphics/krita/-/commit/e7179104b700b780c30b81d4250f3eeeb71b8bd1
diff --git a/libs/ui/tool/KisAsyncColorSamplerHelper.cpp b/libs/ui/tool/KisAsyncColorSamplerHelper.cpp
index a342c67e339..c024cdf9f6d 100644
--- a/libs/ui/tool/KisAsyncColorSamplerHelper.cpp
+++ b/libs/ui/tool/KisAsyncColorSamplerHelper.cpp
@@ -71,7 +71,11 @@ struct KisAsyncColorSamplerHelper::Private
bool circlePreviewExtraCircles {true};
qreal circleZoomPreviewScale {2};
QRectF previewDocRect;
+
QPainterPath cacheCircleInnerClip;
+ QVector<KisReferenceImage*> cacheReferenceImageList; // Sorted reference images by zIndex
+ QRect cacheCanvasRect;
+ QImage cacheCanvasImage;
QColor currentColor;
QColor baseColor;
@@ -171,6 +175,21 @@ struct KisAsyncColorSamplerHelper::Private
const QRectF colorPreviewDocumentRect = converter().viewToDocument(colorPreviewViewRect);
return colorPreviewDocumentRect.translated(outlineDocPoint);
}
+
+ void updateCachedReferenceImages() {
+ KisDocument *doc = canvas->viewManager()->document();
+ if (!doc || !doc->referenceImagesLayer()) {
+ cacheReferenceImageList.clear();
+ return;
+ }
+
+ // TODO: Perhaps use KoShapeManager->shapeAt(QRect)?
+ cacheReferenceImageList = doc->referenceImagesLayer()->referenceImages();
+ // Sort by zIndex to show correct order
+ std::sort(cacheReferenceImageList.begin(), cacheReferenceImageList.end(), [](KisReferenceImage *a, KisReferenceImage *b){
+ return a->zIndex() < b->zIndex();
+ });
+ }
};
KisAsyncColorSamplerHelper::KisAsyncColorSamplerHelper(KisCanvas2 *canvas)
@@ -223,6 +242,11 @@ void KisAsyncColorSamplerHelper::activate(bool sampleCurrentLayer, bool pickFgCo
m_d->circlePreviewExtraCircles = cfg.colorSamplerPreviewCircleExtraCirclesEnabled();
m_d->circleZoomPreviewScale = cfg.colorSamplerZoomPreviewScale()/100.0; // saved in percentages
+ // Reset the cache
+ m_d->cacheCanvasImage = QImage();
+ m_d->cacheCanvasRect = QRect();
+ m_d->updateCachedReferenceImages();
+
m_d->activationDelayTimer.start();
}
@@ -584,15 +608,54 @@ void KisAsyncColorSamplerHelper::paintCircle(QPainter &gc,
gc.restore();
}
+QImage KisAsyncColorSamplerHelper::cacheCanvasImage(QRect &canvasPixelRect) {
+ KisImageWSP canvasImage = m_d->canvas->image();
+
+ if (!canvasImage->bounds().intersects(canvasPixelRect)) {
+ return QImage();
+ }
+
+ // If already cached the whole canvas, just use it from now on
+ if (m_d->cacheCanvasRect == canvasImage->bounds()) {
+ dbgUI << "Using cached canvas image!";
+ return m_d->cacheCanvasImage;
+ }
+
+ if (m_d->cacheCanvasRect.isEmpty() || !m_d->cacheCanvasRect.contains(canvasPixelRect)) {
+ // Cache an area larger than the needed preview area
+ // to avoid rapid small dynamic allocations
+ qreal cacheScale = 4;
+ QRect cacheCanvasRect = canvasPixelRect;
+ cacheCanvasRect.setSize(canvasPixelRect.size() * cacheScale);
+ cacheCanvasRect.moveCenter(canvasPixelRect.center());
+
+ // if cache larger than canvas, then just copy whole canvas
+ if (cacheCanvasRect.width() >= canvasImage->width() || cacheCanvasRect.height() >= canvasImage->height()) {
+ cacheCanvasRect = canvasImage->bounds();
+ }
+
+ dbgUI << "Invalid cache. Copying canvas image";
+ dbgUI << "Canvas preview: " << canvasPixelRect;
+ dbgUI << "Canvas cache: " << cacheCanvasRect;
+
+ m_d->cacheCanvasImage = canvasImage->convertToQImage(cacheCanvasRect, canvasImage->profile());
+
+ m_d->cacheCanvasRect = cacheCanvasRect;
+ }
+
+ canvasPixelRect.translate(-m_d->cacheCanvasRect.topLeft());
+
+ return m_d->cacheCanvasImage;
+}
+
void KisAsyncColorSamplerHelper::paintCircleCanvasPreview(QPainter &gc, const QRectF &viewRectF, const QRectF &sampleDocRectF, const QPainterPath &clip) {
gc.save();
- QRect sampleCanvasRect = m_d->canvas->image()->documentToPixel(sampleDocRectF).toRect();
+ QRect canvasPixelRectF = m_d->canvas->image()->documentToPixel(sampleDocRectF).toRect();
// In case zoom and scale is both so large that sample size becomes 0
- if (sampleCanvasRect.isEmpty()) sampleCanvasRect.setSize(QSize(1, 1));
+ if (canvasPixelRectF.isEmpty()) canvasPixelRectF.setSize(QSize(1, 1));
-
- QImage canvasPreview = m_d->canvas->image()->convertToQImage(sampleCanvasRect, nullptr);
+ QImage cachedImage = cacheCanvasImage(canvasPixelRectF);
gc.setCompositionMode(QPainter::CompositionMode_SourceOver);
gc.setClipPath(clip);
@@ -600,30 +663,19 @@ void KisAsyncColorSamplerHelper::paintCircleCanvasPreview(QPainter &gc, const QR
// QtDoc: The image is scaled to fit the rectangle, if both the image and rectangle size disagree.
// Since the piece of canvas is (zoomPreviewScale) times smaller than cacheRect
// drawImage will scale it up that many times, thus achieving the zoom effect
- gc.drawImage(viewRectF, canvasPreview);
+ gc.drawImage(viewRectF, cachedImage, canvasPixelRectF);
gc.restore();
}
// Won't show opacity because the color sampled doesn't respect opacity either
void KisAsyncColorSamplerHelper::paintCircleReferenceImagePreview(QPainter &gc, const QRectF &viewRectF, const QRectF &sampleDocRectF, const QPainterPath &clip) {
- KisDocument *doc = m_d->canvas->viewManager()->document();
- if (!doc) return;
- KisReferenceImagesLayerSP refLayer = doc->referenceImagesLayer();
- if (!refLayer) return;
-
gc.save();
gc.setCompositionMode(QPainter::CompositionMode_SourceOver);
gc.setClipPath(clip);
- // TODO: Perhaps use KoShapeManager->shapeAt(QRect)?
- QVector<KisReferenceImage*> refList = refLayer->referenceImages();
- // Sort by zIndex to show correct order
- std::sort(refList.begin(), refList.end(), [](KisReferenceImage *a, KisReferenceImage *b){
- return a->zIndex() < b->zIndex();
- });
- Q_FOREACH(KisReferenceImage *refImage, refList) {
+ Q_FOREACH(KisReferenceImage *refImage, m_d->cacheReferenceImageList) {
// Check if sampleRect intersect with reference image
QPolygonF outline = refImage->absoluteTransformation().map(refImage->outlineRect());
if (!outline.intersects(QPolygonF(sampleDocRectF))) continue;
diff --git a/libs/ui/tool/KisAsyncColorSamplerHelper.h b/libs/ui/tool/KisAsyncColorSamplerHelper.h
index c1cb0e4a534..25bb6266239 100644
--- a/libs/ui/tool/KisAsyncColorSamplerHelper.h
+++ b/libs/ui/tool/KisAsyncColorSamplerHelper.h
@@ -85,6 +85,10 @@ private:
// Return true if at least one reference image preview was drawn
void paintCircleReferenceImagePreview(QPainter &gc, const QRectF &viewRectF, const QRectF &sampleDocRectF, const QPainterPath &clip);
+ // Returns a cached canvas image that should either contain the canvasPixelRectF, or the whole canvas if canvasPixelRectF > canvas size.
+ // Also modify the input rect to point to the same position relative to the returned image
+ QImage cacheCanvasImage(QRect &canvasPixelRect);
+
struct Private;
QScopedPointer<Private> m_d;
};