QPixmap/QPicture缓存绘图异常:PyQtGraph曲线渲染问题求助
PyQtGraph PlotCurveItem 缓存绘制优化方案
问题概述
- 重写
PlotCurveItem的paint方法时,用QPixmap缓存绘制路径后调用painter.drawPixmap渲染,曲线显示异常;但直接绘制到QPainter显示正常。 - 改用
QPicture实现增量绘制新数据的逻辑,无法保留已绘制内容。 - 核心需求:通过缓存机制避免重复计算曲线路径,降低多曲线场景下的CPU占用。
可复现代码
import sys import numpy as np import pyqtgraph as pg from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout from PyQt5.QtGui import QPainter, QPixmap, QPicture, QPen from PyQt5.QtCore import Qt class CachedPlotCurveItem(pg.PlotCurveItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pixmap_cache = None self.picture_cache = QPicture() self.last_data = None def paint(self, painter, option, widget=None): # 尝试用QPixmap缓存的方式(显示异常) data = self.getData() if data is None: return x, y = data if not np.array_equal(x, self.last_data): self.last_data = x.copy() rect = self.boundingRect() self.pixmap_cache = QPixmap(rect.size().toSize()) self.pixmap_cache.fill(Qt.transparent) p = QPainter(self.pixmap_cache) p.setPen(self.opts['pen']) path = self.generatePath(x, y) p.drawPath(path) p.end() if self.pixmap_cache is not None: painter.drawPixmap(self.boundingRect().topLeft(), self.pixmap_cache) # 直接绘制(显示正常) # path = self.generatePath(*data) # painter.setPen(self.opts['pen']) # painter.drawPath(path) # 尝试用QPicture增量绘制(无法保留内容) # if not np.array_equal(x, self.last_data): # self.last_data = x.copy() # p = QPainter(self.picture_cache) # p.setPen(self.opts['pen']) # path = self.generatePath(x, y) # p.drawPath(path) # p.end() # painter.drawPicture(0, 0, self.picture_cache) class MainWindow(QMainWindow): def __init__(self): super().__init__() central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) self.plot_widget = pg.PlotWidget() layout.addWidget(self.plot_widget) # 添加多条曲线模拟多曲线场景 for i in range(10): x = np.linspace(0, 10, 1000) y = np.sin(x + i * 0.5) curve = CachedPlotCurveItem(x, y, pen=QPen(Qt.QtGlobalColor(i % 10))) self.plot_widget.addItem(curve) # 定时更新数据模拟动态场景 self.timer = pg.QtCore.QTimer() self.timer.timeout.connect(self.update_data) self.timer.start(100) self.counter = 0 def update_data(self): self.counter += 0.1 for i, curve in enumerate(self.plot_widget.listDataItems()): x = np.linspace(0, 10, 1000) y = np.sin(x + i * 0.5 + self.counter) curve.setData(x, y) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
解决方案
一、修复QPixmap缓存显示异常问题
问题根源是QPixmap的坐标系与QGraphicsItem局部坐标系不匹配,且未处理视图变换。修改后的实现:
class FixedPixmapCurveItem(pg.PlotCurveItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.pixmap_cache = None self.cache_valid = False self.last_bounds = None self.last_data = None def paint(self, painter, option, widget=None): data = self.getData() if data is None: return x, y = data bounds = self.boundingRect() # 数据更新/边界变化时重新生成缓存 if not self.cache_valid or bounds != self.last_bounds or not np.array_equal(x, self.last_data): self.last_data = x.copy() self.last_bounds = bounds self.pixmap_cache = QPixmap(bounds.size().toSize()) self.pixmap_cache.fill(Qt.transparent) # 对齐坐标系后绘制到Pixmap p = QPainter(self.pixmap_cache) p.translate(-bounds.left(), -bounds.top()) p.setPen(self.opts['pen']) path = self.generatePath(x, y) p.drawPath(path) p.end() self.cache_valid = True # 准确绘制缓存到Item区域 painter.drawPixmap(bounds, self.pixmap_cache, self.pixmap_cache.rect()) def setData(self, *args, **kwargs): super().setData(*args, **kwargs) self.cache_valid = False # 数据更新标记缓存无效
关键修复点:
- 绘制到QPixmap时,通过
translate将坐标系原点对齐到Item局部坐标系,避免路径偏移。 - 绘制缓存时使用区域匹配的
drawPixmap调用,确保缓存填充Item边界。 - 添加
cache_valid标记,仅必要时更新缓存,减少计算开销。
二、修复QPicture增量绘制问题
QPicture是绘制指令容器,增量绘制需在历史内容基础上追加新指令。修改后的实现:
class IncrementalPictureCurveItem(pg.PlotCurveItem): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.base_picture = QPicture() # 保存历史绘制内容 self.last_data = None def paint(self, painter, option, widget=None): data = self.getData() if data is None: return x, y = data if not np.array_equal(x, self.last_data): self.last_data = x.copy() # 新建QPicture,先绘制历史内容再追加新数据 new_picture = QPicture() p = QPainter(new_picture) p.drawPicture(0, 0, self.base_picture) p.setPen(self.opts['pen']) path = self.generatePath(x, y) p.drawPath(path) p.end() self.base_picture = new_picture painter.drawPicture(0, 0, self.base_picture) def clear_cache(self): self.base_picture = QPicture() self.last_data = None
关键修复点:
- 维护
base_picture保存历史内容,每次更新时新建QPicture合并新旧绘制指令。 - 避免直接在原QPicture上重复绘制,防止坐标系混乱和指令叠加错误。
三、多曲线场景优化建议
- 静态曲线:仅初始化或数据更新时生成一次缓存,后续复用。
- 动态曲线:设置数据变化阈值,仅当变化量达标时更新缓存,减少重绘次数。
- 绑定
ViewBox的sigRangeChanged信号,视图缩放/平移时标记缓存无效,确保缓存随视图同步更新。
内容的提问来源于stack exchange,提问作者pourjour
相关产品推荐
相关产品推荐

