Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Multiple same-source Images are exhausting RAM



  • Hello.
    I have a simple QML file containing an Image. This file is resued several times (20-30) in my application (both directly and in a Repeater). The idea of the file is to crop the image using the different parts of it. Editing the image is not possible.
    The problem is that the applications starts to use more and more RAM for the exact same Image (from 100mb -> 1GB). Is there a way to solve this?

    Samples:
    MyImage.qml:

    import QtQuick 2.12
    
    Item {
        id: root
    
        property int nowX: 0
        property int nowY: 0
        property int imageSource: 0
    
        implicitWidth: 50
        implicitHeight: 50
    
        Item {
            clip: true
    
            Image {
                x: root.nowX
                y: root.nowY
    
                source: root.imageSource
                fillMode: Image.PreserveAspectFit
            }
        }
    }
    

    Main file:

    import QtQuick 2.12
    
    Flow {
        Repeater {
               model: [[0,0], [100,100], [210, 240]] // Sample values
    
            MyImage {
                imageSource: "somePathToA1024x1024Image"
                nowX: modelData[0]
                nowY: modelData[1]
            }
        }
    }
    

  • Lifetime Qt Champion

    Hi,

    Do you really need such a big image ?
    What about using an image cache through for example a custom image provider.



  • @SGaist

    The image actually is from the user (this is a tool and that MyImage.qml is the editor).
    It's an image from gaming textures (216x216, 512x512, 1024, 2048), so I can't change it.
    I tried QQuickImageProvider but I misunderstood it's idea.

    PyQt5:

    class CachedImageProvider(QQuickImageProvider):
        def __init__(self):
            QQuickImageProvider.__init__(self, QQuickImageProvider.Image)
            self._cache = {}
    
        def requestImage(self, filePath, requestedSize): # filePath = id
            fileUrl = QUrl(filePath).toLocalFile() # remove file://
    
            if not fileUrl:
                return QImage(1, 1, QImage.Format_ARGB32), QSize(1, 1)
    
            if fileUrl in self._cache:
                return self._cache[fileUrl], self._cache[fileUrl].size()
    
            image = None
            with open(fileUrl, 'rb') as handle:
                image = QImage.fromData(handle.read())
    
            if not image:
                return QImage(1, 1, QImage.Format_ARGB32), QSize(1, 1)
    
            self._cache[fileUrl] = image
            return image, image.size()
    
    if __name__ == "__main__":
        QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
        app = QGuiApplication(sys.argv)
    
    
        qmlRegisterType(CommonFontFile, 'Backend', 1, 0, 'BackendFile')
    
        engine = QQmlApplicationEngine()
        engine.addImageProvider('CachedImageProvider', CachedImageProvider())
    
        engine.load('qrc:/qml/main.qml')
        if not engine.rootObjects():
            sys.exit(-1)
    
        res = app.exec_()
        # Deleting the view before it goes out of scope is required to make sure all child QML instances
        # are destroyed in the correct order.
        del engine
        sys.exit(res)
    
    

    It works correctly (with the proper scheme and id from QML side), and shows correct. But RAM usage still increase with each instance. Also, the provider is called once, as the documentation says. Disabling cache does nothing, as the provider's cache isn't saved.
    I am missing something for sure, and sorry about that.
    Thanks for your time! :)


  • Lifetime Qt Champion

    I was thinking about QPixmapCache.



  • @SGaist

    So something like this? (Still, same)

    class CachedImageProvider(QQuickImageProvider):
        def __init__(self):
            QQuickImageProvider.__init__(self, QQuickImageProvider.Pixmap)
    
        def requestPixmap(self, filePath, requestedSize): # filePath = id
            fileUrl = QUrl(filePath).toLocalFile()
    
            if not fileUrl:
                return QPixmap(1, 1), QSize(1, 1)
    
            pm = QPixmap(1, 1)
            if not QPixmapCache.find(fileUrl):
                pm.load(fileUrl)
                QPixmapCache.insert(fileUrl, pm)
    
            return pm, pm.size()
    

  • Lifetime Qt Champion

    It's rather:

        pm = QPixmapCache.find(fileUrl)
        if pm is None:
            pm = QPixmap(fileUrl)
            QPixmapCache.insert(fileUrl, pm)
    
        return pm, pm.size()
    

    As it is, your code sample fills the QPixmapCache or returns a 1, 1 QPixmap, however, it does re-use the content of the cache.


Log in to reply