使用 QGraphicsScene 和 QGraphicsView 进行平铺

发布于 2024-11-05 22:22:13 字数 376 浏览 5 评论 0原文

我正在创建一个图像可视化工具,可以在 Qt 中打开大图像(2gb+)。 我通过将大图像分成几个 512X512 的图块来做到这一点。然后,我加载原始图像大小的 QGraphicsScene 并使用 addPixmap 将每个图块添加到 QGraphic 场景中。因此,最终,对于最终用户来说,它看起来像是一个巨大的图像,而实际上它是场景中粘在一起的连续的较小图像阵列。首先,这是一个好方法吗?

尝试将所有图块加载到场景中会占用大量内存。所以我考虑只加载视图中可见的图块。我已经成功地子类化 QGraphicsScene 并覆盖其拖动事件,从而使我能够知道接下来需要根据移动加载哪些图块。我的问题是跟踪滚动条上的移动。有什么方法可以创建一个每次滚动条移动时都会被调用的事件。子类化 QGraphicsView 不是一个选项。

I'm creating a image visualizer that open large images(2gb+) in Qt.
I'm doing this by breaking the large image into several tiles of 512X512. I then load a QGraphicsScene of the original image size and use addPixmap to add each tile onto the QGraphic Scene. So ultimately it looks like a huge image to the end user when in fact it is a continuous array of smaller images stuck together on the scene.First of is this a good approach?

Trying to load all the tiles onto the scene takes up a lot of memory. So I'm thinking of only loading the tiles that are visible in the view. I've already managed to subclass QGraphicsScene and override its drag event thus enabling me to know which tiles need to be loaded next based on movement. My problem is tracking movement on the scrollbars. Is there any way I can create an event that get called every time the scrollbar moves. Subclassing QGraphicsView in not an option.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

菊凝晚露 2024-11-12 22:22:13

QGraphicsScene 足够智能,不会渲染不可见的内容,因此您需要执行以下操作:

添加包裹像素图的类,而不是加载和添加像素图,然后仅在首次渲染时加载它。 (计算机科学家喜欢称其为“代理模式”)。然后您可以根据计时器卸载像素图。 (如果卸载得太早,它们将被透明地重新加载。)您甚至可以通知此代理路径当前的缩放级别,以便在将较低分辨率的图像渲染得较小时加载它们。


编辑:这里有一些代码可以帮助您开始。请注意,QGraphicsScene 绘制的所有内容都是 QGraphicsItem,(如果您调用 ::addPixmap,它会转换为 ...GraphicsItem 在幕后),所以这就是你想要子类化的内容:(

我什至还没有编译这个,所以“警告 lector”,但它正在做正确的事情;)

class MyPixmap: public QGraphicsItem{
    public:
        // make sure to set `item` to nullptr in the constructor
        MyPixmap()
            : QGraphicsItem(...), item(nullptr){
        }

        // you will need to add a destructor
        // (and probably a copy constructor and assignment operator)

        QRectF boundingRect() const{
            // return the size
            return QRectF( ... );
        }

        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                   QWidget *widget){
            if(nullptr == item){
                // load item:
                item = new QGraphicsPixmapItem( ... );
            }
            item->paint(painter, option, widget);
        }

     private:
         // you'll probably want to store information about where you're
         // going to load the pixmap from, too

         QGraphicsPixmapItem *item;
};

然后你可以将你的像素图添加到 QGraphicsScene 使用 QGraphicsScene::addItem(...)

QGraphicsScene is smart enough not to render what isn't visible, so here's what you need to do:

Instead of loading and adding pixmaps, add classes that wrap the pixmap, and only load it when they are first rendered. (Computer scientists like to call this a "proxy pattern"). You could then unload the pixmap based on a timer. (They would be transparently re-loaded if unloaded too soon.) You could even notify this proxy path of the current zoom level, so that it loads lower resolution images when they will be rendered smaller.


Edit: here's some code to get you started. Note that everything that QGraphicsScene draws is a QGraphicsItem, (if you call ::addPixmap, it's converted to a ...GraphicsItem behind the scenes), so that's what you want to subclass:

(I haven't even compiled this, so "caveat lector", but it's doing the right thing ;)

class MyPixmap: public QGraphicsItem{
    public:
        // make sure to set `item` to nullptr in the constructor
        MyPixmap()
            : QGraphicsItem(...), item(nullptr){
        }

        // you will need to add a destructor
        // (and probably a copy constructor and assignment operator)

        QRectF boundingRect() const{
            // return the size
            return QRectF( ... );
        }

        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                   QWidget *widget){
            if(nullptr == item){
                // load item:
                item = new QGraphicsPixmapItem( ... );
            }
            item->paint(painter, option, widget);
        }

     private:
         // you'll probably want to store information about where you're
         // going to load the pixmap from, too

         QGraphicsPixmapItem *item;
};

then you can add your pixmaps to the QGraphicsScene using QGraphicsScene::addItem(...)

自此以后,行同陌路 2024-11-12 22:22:13

虽然答案已经选定,但我还是想表达一下我的意见。

我不喜欢所选的答案,特别是因为计时器的使用。卸载像素图的计时器?假设用户实际上想仔细看看图像,几秒钟后 - 砰,图像被卸载,他将必须做一些事情才能重新出现图像。或者您可能会放置另一个计时器,在几秒钟后加载像素图?或者您会检查您的数千个项目是否可见?这不仅非常令人恼火和错误,而且意味着您的程序将一直使用资源。假设用户最小化你的程序并播放电影,他会想知道为什么我的电影每隔几秒就会冻结......

好吧,如果我误解了使用计时器的建议想法,请原谅我。

其实mmutz提出的想法更好。它让我想起了 Mandelbrot 示例。看看它。您可以重写此部分以加载需要显示的图像部分,而不是计算要绘制的内容。

总之,我将提出另一种使用 QGraphicsView 的解决方案,以更简单的方式:

1)检查图像的大小而不加载图像(使用 QImageReader)

2)使场景的大小等于图像的大小

3)而不是使用像素图项重新实现 DrawBackground() 函数。其中一个参数将为您提供新的暴露矩形 - 这意味着如果用户滚动一点点,您将仅加载并绘制这个新部分(要仅加载图像的一部分,请使用 setClipRect() ,然后使用 read() 方法QImageReader 类的)。如果有一些变换,您可以从其他参数(即 QPainter)获取它们,并在绘制图像之前将它们应用到图像。

在我看来,最好的解决方案是将我的解决方案与 Mandelbrot 中显示的线程相结合示例

我现在能想到的唯一问题是用户是否以较大的比例因子缩小。那么你将在一段时间内需要大量资源来加载和缩放巨大的图像。好吧,我现在看到 QImageReader 有一些我还没有尝试过的功能 - setScaledSize(),它可能正是我们所需要的 - 如果您设置比例大小然后加载图像,可能它不会首先加载整个图像 - 尝试一下。另一种方法是限制比例因子,如果您坚持使用像素图项目的方法,无论如何您都应该这样做。

希望这有帮助。

Although an answer has already been chosen, I'd like to express my opinion.

I don't like the selected answer, especially because of that usage of timers. A timer to unload the pixmaps? Say that the user actually wants to take a good look at the image, and after a couple of seconds - bam, the image is unloaded, he will have to do something in order the image to reappear. Or may be you will put another timer, that loads the pixmaps after another couple of seconds? Or you will check among your thousand of items if they are visible? Not only is this very very irritating and wrong, but that means that your program will be using resources all the time. Say the user minimizes you program and plays a movie, he will wonder why on earth my movie is freezing every couple of seconds...

Well, if I misunderstood the proposed idea of using timers, execuse me.

Actually the idea that mmutz suggested is better. It reminded me of the Mandelbrot example. Take a look at it. Instead of calculating what to draw you can rewrite this part to loading that part of the image that you need to show.

In conclusion I will propose another solution using QGraphicsView in a much simpler way:

1) check the size of the image without loading the image (use QImageReader)

2) make your scene's size equal to that of the image

3) instead of using pixmap items reimplement the DrawBackground() function. One of the parameters will give you the new exposed rectangle - meaning that if the user scrolls just a little bit, you will load and draw only this new part(to load only part of an image use setClipRect() and then read() methods of the QImageReader class). If there are some transformations you can get them from the other parameter(which is QPainter) and apply them to the image before you draw it.

In my opinion the best solution will be to combine my solution with the threading shown in the Mandelbrot example.

The only problem that I can think of now is if the user zooms out with a big scale factor. Then you will need a lot of resources for some time to load and scale a huge image. Well I see now that there is some function of the QImageReader that I haven't tried yet - setScaledSize(), which maybe do just what we need - if you set a scale size and then load the image maybe it won't load first the entire image – try it. Another way is just to limit the scale factor, a thing that you should do anyway if you stick to the method with the pixmap items.

Hope this helps.

白鸥掠海 2024-11-12 22:22:13

除非你绝对需要视图是一个QGraphicsView(例如,因为你将其他对象放置在大背景像素图的顶部),否则我真的建议只子类化QAbstractScrollArea并重新实现scrollContentsBy()paintEvent()

添加像素图的 LRU 缓存(请参阅 QPixmapCache 以获得灵感,尽管它是全局的),并使 paintEvent() 将使用的像素图拉到前面,并进行设置。

如果这听起来比 QGraphicsItem 更多工作,请相信我,事实并非如此:)

Unless you absolutely need the view to be a QGraphicsView (e.g. because you place other objects on top of the large background pixmap), I'd really recommend just subclassing QAbstractScrollArea and reimplementing scrollContentsBy() and paintEvent().

Add in a LRU cache of pixmaps (see QPixmapCache for inspiration, though that one is global), and make the paintEvent() pull used pixmaps to the front, and be set.

If this sounds like more work than the QGraphicsItem, believe me, it's not :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文