Use QDeclarativePixmap in the Particle System
authorAlan Alpert <alan.alpert@nokia.com>
Wed, 25 Jan 2012 09:18:25 +0000 (19:18 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 9 Feb 2012 06:32:07 +0000 (07:32 +0100)
This allows for source URLs to come from network sources.

Change-Id: I416edca010e77e507598eaf4eead4291f044f379
Reviewed-by: Martin Jones <martin.jones@nokia.com>
src/quick/items/qquicksprite.cpp
src/quick/items/qquicksprite_p.h
src/quick/items/qquickspriteengine.cpp
src/quick/items/qquickspriteengine_p.h
src/quick/items/qquickspriteimage.cpp
src/quick/particles/qquickimageparticle.cpp
src/quick/particles/qquickimageparticle_p.h
src/quick/particles/qquickmaskextruder.cpp
src/quick/particles/qquickmaskextruder_p.h

index 46d1944..4de7880 100644 (file)
@@ -40,6 +40,7 @@
 ****************************************************************************/
 
 #include "qquicksprite_p.h"
+#include <qdeclarative.h>
 #include <QDebug>
 
 QT_BEGIN_NAMESPACE
@@ -255,4 +256,11 @@ int QQuickSprite::variedDuration() const //Deals with precedence when multiple d
     return QQuickStochasticState::variedDuration() * m_frames;
 }
 
+void QQuickSprite::startImageLoading()
+{
+    m_pix.clear(this);
+    if (!m_source.isEmpty())
+        m_pix.load(qmlEngine(this), m_source);
+}
+
 QT_END_NAMESPACE
index bf0a465..4c5e5ff 100644 (file)
@@ -46,6 +46,7 @@
 #include <QUrl>
 #include <QVariantMap>
 #include <QDeclarativeListProperty>
+#include <QtQuick/private/qdeclarativepixmapcache_p.h>
 #include "qquickspriteengine_p.h"
 
 QT_BEGIN_HEADER
@@ -90,7 +91,6 @@ public:
         return m_frameWidth;
     }
 
-
     bool reverse() const
     {
         return m_reverse;
@@ -181,6 +181,7 @@ public slots:
         if (m_source != arg) {
             m_source = arg;
             emit sourceChanged(arg);
+            startImageLoading();
         }
     }
 
@@ -200,7 +201,6 @@ public slots:
         }
     }
 
-
     void setReverse(bool arg)
     {
         if (m_reverse != arg) {
@@ -273,6 +273,9 @@ public slots:
         }
     }
 
+private slots:
+    void startImageLoading();
+
 private:
     friend class QQuickImageParticle;
     friend class QQuickSpriteImage;
@@ -295,6 +298,7 @@ private:
     int m_frameDuration;
     int m_frameDurationVariation;
     bool m_frameSync;
+    QDeclarativePixmap m_pix;
 };
 
 QT_END_NAMESPACE
index a076a0c..c19a5e6 100644 (file)
@@ -41,6 +41,8 @@
 
 #include "qquickspriteengine_p.h"
 #include "qquicksprite_p.h"
+#include <qdeclarativeinfo.h>
+#include <qdeclarative.h>
 #include <QDebug>
 #include <QPainter>
 #include <QSet>
@@ -100,12 +102,12 @@ QQuickStochasticEngine::~QQuickStochasticEngine()
 }
 
 QQuickSpriteEngine::QQuickSpriteEngine(QObject *parent)
-    : QQuickStochasticEngine(parent)
+    : QQuickStochasticEngine(parent), m_startedImageAssembly(false)
 {
 }
 
 QQuickSpriteEngine::QQuickSpriteEngine(QList<QQuickSprite*> sprites, QObject *parent)
-    : QQuickStochasticEngine(parent)
+    : QQuickStochasticEngine(parent), m_startedImageAssembly(false)
 {
     foreach (QQuickSprite* sprite, sprites)
         m_states << (QQuickStochasticState*)sprite;
@@ -305,16 +307,35 @@ void QQuickStochasticEngine::setGoal(int state, int sprite, bool jump)
     return;
 }
 
-QImage QQuickSpriteEngine::assembledImage()
+QDeclarativePixmap::Status QQuickSpriteEngine::status()//Composed status of all Sprites
 {
-    int h = 0;
-    int w = 0;
-    m_maxFrames = 0;
-    m_imageStateCount = 0;
-    int maxSize = 0;
+    if (!m_startedImageAssembly)
+        return QDeclarativePixmap::Null;
+    int null, loading, ready;
+    null = loading = ready = 0;
+    foreach (QQuickSprite* s, m_sprites) {
+        switch (s->m_pix.status()) {
+            case QDeclarativePixmap::Null : null++; break;
+            case QDeclarativePixmap::Loading : loading++; break;
+            case QDeclarativePixmap::Error : return QDeclarativePixmap::Error;
+            case QDeclarativePixmap::Ready : ready++; break;
+        }
+    }
+    if (null)
+        return QDeclarativePixmap::Null;
+    if (loading)
+        return QDeclarativePixmap::Loading;
+    if (ready)
+        return QDeclarativePixmap::Ready;
+    return QDeclarativePixmap::Null;
+}
 
-    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
-    //qDebug() << "MAX TEXTURE SIZE" << maxSize;
+void QQuickSpriteEngine::startAssemblingImage()
+{
+    if (m_startedImageAssembly)
+        return;
+
+    //This could also trigger the start of the image loading in Sprites, however that currently happens in Sprite::setSource
     foreach (QQuickStochasticState* s, m_states){
         QQuickSprite* sprite = qobject_cast<QQuickSprite*>(s);
         if (sprite)
@@ -322,18 +343,35 @@ QImage QQuickSpriteEngine::assembledImage()
         else
             qDebug() << "Error: Non-sprite in QQuickSpriteEngine";
     }
+    m_startedImageAssembly = true;
+}
 
+QImage QQuickSpriteEngine::assembledImage()
+{
+    QDeclarativePixmap::Status stat = status();
+    if (stat == QDeclarativePixmap::Error)
+        foreach (QQuickSprite* s, m_sprites)
+            if (s->m_pix.isError())
+                qmlInfo(s) << s->m_pix.error();
+
+    if (stat != QDeclarativePixmap::Ready)
+        return QImage();
+
+    int h = 0;
+    int w = 0;
+    m_maxFrames = 0;
+    m_imageStateCount = 0;
+    int maxSize = 0;
+
+    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
+    //qDebug() << "MAX TEXTURE SIZE" << maxSize;
     foreach (QQuickSprite* state, m_sprites){
         if (state->frames() > m_maxFrames)
             m_maxFrames = state->frames();
 
-        QImage img(state->source().toLocalFile());
-        if (img.isNull()) {
-            qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
-            return QImage();
-        }
+        QImage img = state->m_pix.image();
 
-        //Check that the frame sizes are the same within one engine
+        //Check that the frame sizes are the same within one sprite
         if (!state->m_frameWidth)
             state->m_frameWidth = img.width() / state->frames();
 
@@ -347,10 +385,10 @@ QImage QQuickSpriteEngine::assembledImage()
             int rowsNeeded = helper::divRoundUp(state->frames(), (maxSize / state->frameWidth()));
             if (h + rowsNeeded * state->frameHeight() > maxSize){
                 if (rowsNeeded * state->frameHeight() > maxSize)
-                    qWarning() << "SpriteEngine: Animation too large to fit in one texture:" << state->source().toLocalFile();
+                    qmlInfo(state) << "SpriteEngine: Animation too large to fit in one texture:" << state->source().toLocalFile();
                 else
-                    qWarning() << "SpriteEngine: Animations too large to fit in one texture, pushed over the edge by:" << state->source().toLocalFile();
-                qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
+                    qmlInfo(state) << "SpriteEngine: Animations too large to fit in one texture, pushed over the edge by:" << state->source().toLocalFile();
+                qmlInfo(state) << "SpriteEngine: Your texture max size today is " << maxSize;
             }
             state->m_generatedCount = rowsNeeded;
             h += state->frameHeight() * rowsNeeded;
index 34de728..500d526 100644 (file)
@@ -50,6 +50,7 @@
 #include <QDeclarativeListProperty>
 #include <QImage>
 #include <QPair>
+#include <QtQuick/private/qdeclarativepixmapcache_p.h>
 
 QT_BEGIN_HEADER
 
@@ -61,7 +62,7 @@ class Q_AUTOTEST_EXPORT QQuickStochasticState : public QObject //Currently for i
     Q_OBJECT
     Q_PROPERTY(int duration READ duration WRITE setDuration NOTIFY durationChanged)
     Q_PROPERTY(int durationVariation READ durationVariation WRITE setDurationVariation NOTIFY durationVariationChanged)
-    //Note than manually advanced sprites need to query this variable and implement own behaviour for it
+    //Note that manually advanced sprites need to query this variable and implement own behaviour for it
     Q_PROPERTY(bool randomStart READ randomStart WRITE setRandomStart NOTIFY randomStartChanged)
     Q_PROPERTY(QVariantMap to READ to WRITE setTo NOTIFY toChanged)
     Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
@@ -268,21 +269,30 @@ public:
     int spriteState(int sprite=0);
     int spriteStart(int sprite=0);
     int spriteFrames(int sprite=0);
-    int spriteDuration(int sprite=0);//Full duration, not per frame
+    int spriteDuration(int sprite=0);
     int spriteX(int sprite=0);
     int spriteY(int sprite=0);
     int spriteWidth(int sprite=0);
     int spriteHeight(int sprite=0);
     int spriteCount();//Like state count
     int maxFrames();
-    QString realName(int sprite=0);//Gives the parent sprite name for pseudosprites
-    QImage assembledImage();
 
     virtual void restart(int index=0);
     virtual void advance(int index=0);
+
+    //Similar API to QDeclarativePixmap for async loading convenience
+    bool isNull() { return status() == QDeclarativePixmap::Null; }
+    bool isReady() { return status() == QDeclarativePixmap::Ready; }
+    bool isLoading() { return status() == QDeclarativePixmap::Loading; }
+    bool isError() { return status() == QDeclarativePixmap::Error; }
+    QDeclarativePixmap::Status status();//Composed status of all Sprites
+    void startAssemblingImage();
+    QImage assembledImage();
+
 private:
     int pseudospriteProgress(int,int,int*rd=0);
     QList<QQuickSprite*> m_sprites;
+    bool m_startedImageAssembly;
 };
 
 //Common use is to have your own list property which is transparently an engine
index 755c41d..1b3b577 100644 (file)
@@ -49,6 +49,7 @@
 #include <QtQuick/qsgtexturematerial.h>
 #include <QtQuick/qsgtexture.h>
 #include <QtQuick/qquickcanvas.h>
+#include <QtDeclarative/qdeclarativeinfo.h>
 #include <QFile>
 #include <cmath>
 #include <qmath.h>
@@ -324,7 +325,14 @@ static QSGGeometry::AttributeSet SpriteImage_AttributeSet =
 QSGGeometryNode* QQuickSpriteImage::buildNode()
 {
     if (!m_spriteEngine) {
-        qWarning() << "SpriteImage: No sprite engine...";
+        qmlInfo(this) << "No sprite engine...";
+        return 0;
+    } else if (m_spriteEngine->status() == QDeclarativePixmap::Null) {
+        m_spriteEngine->startAssemblingImage();
+        update();//Schedule another update, where we will check again
+        return 0;
+    } else if (m_spriteEngine->status() == QDeclarativePixmap::Loading) {
+        update();//Schedule another update, where we will check again
         return 0;
     }
 
index 08a1621..ea67c7f 100644 (file)
@@ -41,6 +41,7 @@
 
 #include <QtQuick/private/qsgcontext_p.h>
 #include <private/qsgadaptationlayer_p.h>
+#include <private/qquickitem_p.h>
 #include <QtQuick/qsgnode.h>
 #include <QtQuick/qsgtexturematerial.h>
 #include <QtQuick/qsgtexture.h>
@@ -53,6 +54,7 @@
 #include <QtQuick/qsgengine.h>
 #include <QtQuick/private/qsgtexture_p.h>
 #include <private/qdeclarativeglobal_p.h>
+#include <QtDeclarative/qdeclarativeinfo.h>
 #include <cmath>
 
 QT_BEGIN_NAMESPACE
@@ -784,9 +786,19 @@ void fillUniformArrayFromImage(float* array, const QImage& img, int size)
     Default is true.
 */
 
+/*!
+    \qmlproperty Status QtQuick.Particles2::ImageParticle::status
+
+    The status of loading the image.
+*/
+
 
 QQuickImageParticle::QQuickImageParticle(QQuickItem* parent)
     : QQuickParticlePainter(parent)
+    , m_image(0)
+    , m_colorTable(0)
+    , m_sizeTable(0)
+    , m_opacityTable(0)
     , m_color_variation(0.0)
     , m_rootNode(0)
     , m_material(0)
@@ -813,6 +825,7 @@ QQuickImageParticle::QQuickImageParticle(QQuickItem* parent)
     , m_lastLevel(Unknown)
     , m_debugMode(false)
     , m_entryEffect(Fade)
+    , m_buildingNodes(false)
 {
     setFlag(ItemHasContents);
 }
@@ -828,9 +841,19 @@ QDeclarativeListProperty<QQuickSprite> QQuickImageParticle::sprites()
 
 void QQuickImageParticle::setImage(const QUrl &image)
 {
-    if (image == m_image_name)
+    if (image.isEmpty()){
+        if (m_image) {
+            delete m_image;
+            emit imageChanged();
+        }
         return;
-    m_image_name = image;
+    }
+
+    if (!m_image)
+        m_image = new ImageData;
+    if (image == m_image->source)
+        return;
+    m_image->source = image;
     emit imageChanged();
     reset();
 }
@@ -838,27 +861,57 @@ void QQuickImageParticle::setImage(const QUrl &image)
 
 void QQuickImageParticle::setColortable(const QUrl &table)
 {
-    if (table == m_colortable_name)
+    if (table.isEmpty()){
+        if (m_colorTable) {
+            delete m_colorTable;
+            emit colortableChanged();
+        }
         return;
-    m_colortable_name = table;
+    }
+
+    if (!m_colorTable)
+        m_colorTable = new ImageData;
+    if (table == m_colorTable->source)
+        return;
+    m_colorTable->source = table;
     emit colortableChanged();
     reset();
 }
 
 void QQuickImageParticle::setSizetable(const QUrl &table)
 {
-    if (table == m_sizetable_name)
+    if (table.isEmpty()){
+        if (m_sizeTable) {
+            delete m_sizeTable;
+            emit sizetableChanged();
+        }
         return;
-    m_sizetable_name = table;
+    }
+
+    if (!m_sizeTable)
+        m_sizeTable = new ImageData;
+    if (table == m_sizeTable->source)
+        return;
+    m_sizeTable->source = table;
     emit sizetableChanged();
     reset();
 }
 
 void QQuickImageParticle::setOpacitytable(const QUrl &table)
 {
-    if (table == m_opacitytable_name)
+    if (table.isEmpty()){
+        if (m_opacityTable) {
+            delete m_opacityTable;
+            emit opacitytableChanged();
+        }
+        return;
+    }
+
+    if (!m_opacityTable)
+        m_opacityTable = new ImageData;
+    if (table == m_opacityTable->source)
         return;
-    m_opacitytable_name = table;
+    m_opacityTable->source = table;
     emit opacitytableChanged();
     reset();
 }
@@ -1199,24 +1252,63 @@ QQuickParticleData* QQuickImageParticle::getShadowDatum(QQuickParticleData* datu
     return m_shadowData[datum->group][datum->index];
 }
 
-QSGGeometryNode* QQuickImageParticle::buildParticleNodes()
+bool QQuickImageParticle::loadingSomething()
+{
+    return (m_image && m_image->pix.isLoading())
+        || (m_colorTable && m_colorTable->pix.isLoading())
+        || (m_sizeTable && m_sizeTable->pix.isLoading())
+        || (m_opacityTable && m_opacityTable->pix.isLoading())
+        || (m_spriteEngine && m_spriteEngine->isLoading());
+}
+
+void QQuickImageParticle::buildParticleNodes()//Starts async parts, like loading images.
+{
+    if (m_rootNode || loadingSomething())
+        return;
+
+    if (!m_buildingNodes) {
+        if (m_image) {//ImageData created on setSource
+            m_image->pix.clear(this);
+            m_image->pix.load(qmlEngine(this), m_image->source);
+        }
+
+        if (m_spriteEngine)
+            m_spriteEngine->startAssemblingImage();
+
+        if (m_colorTable)
+            m_colorTable->pix.load(qmlEngine(this), m_colorTable->source);
+
+        if (m_sizeTable)
+            m_sizeTable->pix.load(qmlEngine(this), m_sizeTable->source);
+
+        if (m_opacityTable)
+            m_opacityTable->pix.load(qmlEngine(this), m_opacityTable->source);
+
+        m_buildingNodes = true;
+        if (loadingSomething())
+            return;
+    }
+    finishBuildParticleNodes();
+}
+
+void QQuickImageParticle::finishBuildParticleNodes()
 {
+    m_buildingNodes = false;
 #ifdef QT_OPENGL_ES_2
     if (m_count * 4 > 0xffff) {
         printf("ImageParticle: Too many particles - maximum 16,000 per ImageParticle.\n");//ES 2 vertex count limit is ushort
-        return 0;
+        return;
     }
 #endif
 
     if (count() <= 0)
-        return 0;
+        return;
 
     m_debugMode = m_system->m_debugMode;
 
     if (m_sprites.count() || m_bypassOptimizations) {
         perfLevel = Sprites;
-    } else if (!m_colortable_name.isEmpty() || !m_sizetable_name.isEmpty()
-               || !m_opacitytable_name.isEmpty()) {
+    } else if (m_colorTable || m_sizeTable || m_opacityTable) {
         perfLevel = Tabled;
     } else if (m_autoRotation || m_rotation || m_rotationVariation
                || m_rotationSpeed || m_rotationSpeedVariation
@@ -1251,27 +1343,6 @@ QSGGeometryNode* QQuickImageParticle::buildParticleNodes()
     if (perfLevel >= Colored  && !m_color.isValid())
         m_color = QColor(Qt::white);//Hidden default, but different from unset
 
-    QImage image;
-    if (perfLevel >= Sprites){
-        if (!m_spriteEngine) {
-            qWarning() << "ImageParticle: No sprite engine...";
-            //Sprite performance mode with static image is supported, but not advised
-            //Note that in this case it always uses shadow data
-        } else {
-            image = m_spriteEngine->assembledImage();
-            if (image.isNull())//Warning is printed in engine
-                return 0;
-        }
-    }
-
-    if ( image.isNull() ) {
-        image = QImage(m_image_name.toLocalFile());
-        if (image.isNull()) {
-            printf("ImageParticle: loading image failed '%s'\n", qPrintable(m_image_name.toLocalFile()));
-            return 0;
-        }
-    }
-
     clearShadows();
     if (m_material)
         m_material = 0;
@@ -1280,23 +1351,55 @@ QSGGeometryNode* QQuickImageParticle::buildParticleNodes()
     QImage colortable;
     QImage sizetable;
     QImage opacitytable;
+    QImage image;
+    bool imageLoaded = false;
     switch (perfLevel) {//Fallthrough intended
     case Sprites:
+        if (!m_spriteEngine) {
+            qWarning() << "ImageParticle: No sprite engine...";
+            //Sprite performance mode with static image is supported, but not advised
+            //Note that in this case it always uses shadow data
+        } else {
+            image = m_spriteEngine->assembledImage();
+            if (image.isNull())//Warning is printed in engine
+                return;
+            imageLoaded = true;
+        }
         m_material = SpriteMaterial::createMaterial();
+        if (imageLoaded)
+            getState<ImageMaterialData>(m_material)->texture = QSGPlainTexture::fromImage(image);
         getState<ImageMaterialData>(m_material)->animSheetSize = QSizeF(image.size());
         if (m_spriteEngine)
             m_spriteEngine->setCount(m_count);
     case Tabled:
         if (!m_material)
             m_material = TabledMaterial::createMaterial();
-        colortable = QImage(m_colortable_name.toLocalFile());
-        sizetable = QImage(m_sizetable_name.toLocalFile());
-        opacitytable = QImage(m_opacitytable_name.toLocalFile());
-        if (colortable.isNull()){
+
+        if (m_colorTable) {
+            if (m_colorTable->pix.isReady())
+                colortable = m_colorTable->pix.image();
+            else
+                qmlInfo(this) << "Error loading color table: " << m_colorTable->pix.error();
+        }
+
+        if (m_sizeTable) {
+            if (m_sizeTable->pix.isReady())
+                sizetable = m_sizeTable->pix.image();
+            else
+                qmlInfo(this) << "Error loading size table: " << m_sizeTable->pix.error();
+        }
+
+        if (m_opacityTable) {
+            if (m_opacityTable->pix.isReady())
+                opacitytable = m_opacityTable->pix.image();
+            else
+                qmlInfo(this) << "Error loading opacity table: " << m_opacityTable->pix.error();
+        }
+
+        if (colortable.isNull()){//###Goes through image just for this
             colortable = QImage(1,1,QImage::Format_ARGB32);
             colortable.fill(Qt::white);
         }
-        Q_ASSERT(!colortable.isNull());
         getState<ImageMaterialData>(m_material)->colorTable = QSGPlainTexture::fromImage(colortable);
         fillUniformArrayFromImage(getState<ImageMaterialData>(m_material)->sizeTable, sizetable, UNIFORM_ARRAY_SIZE);
         fillUniformArrayFromImage(getState<ImageMaterialData>(m_material)->opacityTable, opacitytable, UNIFORM_ARRAY_SIZE);
@@ -1309,7 +1412,16 @@ QSGGeometryNode* QQuickImageParticle::buildParticleNodes()
     default://Also Simple
         if (!m_material)
             m_material = SimpleMaterial::createMaterial();
-        getState<ImageMaterialData>(m_material)->texture = QSGPlainTexture::fromImage(image);
+        if (!imageLoaded) {
+            if (!m_image->pix.isReady()) {
+                qmlInfo(this) << m_image->pix.error();
+                delete m_material;
+                return;
+            }
+            //getState<ImageMaterialData>(m_material)->texture //TODO: Shouldn't this be better? But not crash?
+            //    = QQuickItemPrivate::get(this)->sceneGraphContext()->textureForFactory(m_imagePix.textureFactory());
+            getState<ImageMaterialData>(m_material)->texture = QSGPlainTexture::fromImage(m_image->pix.image());
+        }
         getState<ImageMaterialData>(m_material)->texture->setFiltering(QSGTexture::Linear);
         getState<ImageMaterialData>(m_material)->entry = (qreal) m_entryEffect;
         m_material->setFlag(QSGMaterial::Blending);
@@ -1390,7 +1502,8 @@ QSGGeometryNode* QQuickImageParticle::buildParticleNodes()
             (*(m_nodes.begin()))->appendChildNode(node);
     }
 
-    return *(m_nodes.begin());
+    m_rootNode = *(m_nodes.begin());
+    update();
 }
 
 QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *)
@@ -1409,6 +1522,7 @@ QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *)
         m_material = 0;
 
         m_pleaseReset = false;
+        m_buildingNodes = false;//Cancel a part-way build
     }
 
     if (m_system && m_system->isRunning() && !m_system->isPaused()){
@@ -1417,6 +1531,8 @@ QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *)
             update();
             foreach (QSGGeometryNode* node, m_nodes)
                 node->markDirty(QSGNode::DirtyGeometry);
+        } else if (m_buildingNodes) {
+            update();//To call prepareNextFrame() again from the renderThread
         }
     }
 
@@ -1426,7 +1542,7 @@ QSGNode *QQuickImageParticle::updatePaintNode(QSGNode *, UpdatePaintNodeData *)
 void QQuickImageParticle::prepareNextFrame()
 {
     if (m_rootNode == 0){//TODO: Staggered loading (as emitted)
-        m_rootNode = buildParticleNodes();
+        buildParticleNodes();
         if (m_debugMode) {
             qDebug() << "QQuickImageParticle Feature level: " << perfLevel;
             qDebug() << "QQuickImageParticle Nodes: ";
index e42332b..a0f1595 100644 (file)
@@ -43,6 +43,7 @@
 #define ULTRAPARTICLE_H
 #include "qquickparticlepainter_p.h"
 #include "qquickdirection_p.h"
+#include <private/qdeclarativepixmapcache_p.h>
 #include <QDeclarativeListProperty>
 #include <QtQuick/qsgsimplematerial.h>
 #include <QtGui/qcolor.h>
@@ -149,6 +150,11 @@ class QQuickImageParticle : public QQuickParticlePainter
 {
     Q_OBJECT
     Q_PROPERTY(QUrl source READ image WRITE setImage NOTIFY imageChanged)
+    Q_PROPERTY(QDeclarativeListProperty<QQuickSprite> sprites READ sprites)
+    Q_PROPERTY(Status status READ status NOTIFY statusChanged)
+    //### Is it worth having progress like Image has?
+    //Q_PROPERTY(qreal progress READ progress NOTIFY progressChanged)
+
     Q_PROPERTY(QUrl colorTable READ colortable WRITE setColortable NOTIFY colortableChanged)
     Q_PROPERTY(QUrl sizeTable READ sizetable WRITE setSizetable NOTIFY sizetableChanged)
     Q_PROPERTY(QUrl opacityTable READ opacitytable WRITE setOpacitytable NOTIFY opacitytableChanged)
@@ -172,20 +178,20 @@ class QQuickImageParticle : public QQuickParticlePainter
     //to 180 will lead to facing away from the direction of motion
     Q_PROPERTY(bool autoRotation READ autoRotation WRITE setAutoRotation NOTIFY autoRotationChanged RESET resetRotation)
 
-    //###Call i/j? Makes more sense to those with vector calculus experience, and I could even add the cirumflex in QML?
     //xVector is the vector from the top-left point to the top-right point, and is multiplied by current size
     Q_PROPERTY(QQuickDirection* xVector READ xVector WRITE setXVector NOTIFY xVectorChanged RESET resetDeformation)
     //yVector is the same, but top-left to bottom-left. The particle is always a parallelogram.
     Q_PROPERTY(QQuickDirection* yVector READ yVector WRITE setYVector NOTIFY yVectorChanged RESET resetDeformation)
-    Q_PROPERTY(QDeclarativeListProperty<QQuickSprite> sprites READ sprites)
     Q_PROPERTY(bool spritesInterpolate READ spritesInterpolate WRITE setSpritesInterpolate NOTIFY spritesInterpolateChanged)
 
     Q_PROPERTY(EntryEffect entryEffect READ entryEffect WRITE setEntryEffect NOTIFY entryEffectChanged)
     Q_ENUMS(EntryEffect)
+    Q_ENUMS(Status)
 public:
     explicit QQuickImageParticle(QQuickItem *parent = 0);
     virtual ~QQuickImageParticle();
 
+    enum Status { Null, Ready, Loading, Error };
 
     QDeclarativeListProperty<QQuickSprite> sprites();
     QQuickStochasticEngine* spriteEngine() {return m_spriteEngine;}
@@ -205,16 +211,16 @@ public:
         Sprites
     };
 
-    QUrl image() const { return m_image_name; }
+    QUrl image() const { return m_image ? m_image->source : QUrl(); }
     void setImage(const QUrl &image);
 
-    QUrl colortable() const { return m_colortable_name; }
+    QUrl colortable() const { return m_colorTable ? m_colorTable->source : QUrl(); }
     void setColortable(const QUrl &table);
 
-    QUrl sizetable() const { return m_sizetable_name; }
+    QUrl sizetable() const { return m_sizeTable ? m_sizeTable->source : QUrl(); }
     void setSizetable (const QUrl &table);
 
-    QUrl opacitytable() const { return m_opacitytable_name; }
+    QUrl opacitytable() const { return m_opacityTable ? m_opacityTable->source : QUrl(); }
     void setOpacitytable(const QUrl &table);
 
     QColor color() const { return m_color; }
@@ -253,6 +259,8 @@ public:
 
     EntryEffect entryEffect() const { return m_entryEffect; }
 
+    Status status() const { return m_status; }
+
     void resetColor();
     void resetRotation();
     void resetDeformation();
@@ -297,6 +305,8 @@ signals:
 
     void entryEffectChanged(EntryEffect arg);
 
+    void statusChanged(Status arg);
+
 public slots:
     void reloadColor(const Color4ub &c, QQuickParticleData* d);
     void setAlphaVariation(qreal arg);
@@ -336,18 +346,24 @@ protected:
 
     QSGNode *updatePaintNode(QSGNode *, UpdatePaintNodeData *);
     void prepareNextFrame();
-    QSGGeometryNode* buildParticleNodes();
+    void buildParticleNodes();
 
 private slots:
     void createEngine(); //### method invoked by sprite list changing (in engine.h) - pretty nasty
 
     void spriteAdvance(int spriteIndex);
     void spritesUpdate(qreal time = 0 );
+    void finishBuildParticleNodes();
 private:
-    QUrl m_image_name;
-    QUrl m_colortable_name;
-    QUrl m_sizetable_name;
-    QUrl m_opacitytable_name;
+    struct ImageData {
+        QUrl source;
+        QDeclarativePixmap pix;
+    };
+    ImageData *m_image;
+    ImageData *m_colorTable;
+    ImageData *m_sizeTable;
+    ImageData *m_opacityTable;
+    bool loadingSomething();
 
 
     QColor m_color;
@@ -419,6 +435,8 @@ private:
         return static_cast<QSGSimpleMaterial<MaterialData> *>(m)->state();
     }
     EntryEffect m_entryEffect;
+    Status m_status;
+    bool m_buildingNodes;
 };
 
 QT_END_NAMESPACE
index 90a664a..3ed2eb9 100644 (file)
@@ -40,6 +40,8 @@
 ****************************************************************************/
 
 #include "qquickmaskextruder_p.h"
+#include <QtDeclarative/qdeclarative.h>
+#include <QtDeclarative/qdeclarativeinfo.h>
 #include <QImage>
 #include <QDebug>
 QT_BEGIN_NAMESPACE
@@ -65,6 +67,36 @@ QQuickMaskExtruder::QQuickMaskExtruder(QObject *parent) :
 {
 }
 
+void QQuickMaskExtruder::setSource(QUrl arg)
+{
+    if (m_source != arg) {
+        m_source = arg;
+
+        m_lastHeight = -1;//Trigger reset
+        m_lastWidth = -1;
+        emit sourceChanged(arg);
+        startMaskLoading();
+    }
+}
+
+void QQuickMaskExtruder::startMaskLoading()
+{
+    m_pix.clear(this);
+    if (m_source.isEmpty())
+        return;
+    m_pix.load(qmlEngine(this), m_source);
+    if (m_pix.isLoading())
+        m_pix.connectFinished(this, SLOT(finishMaskLoading()));
+    else
+        finishMaskLoading();
+}
+
+void QQuickMaskExtruder::finishMaskLoading()
+{
+    if (m_pix.isError())
+        qmlInfo(this) << m_pix.error();
+}
+
 QPointF QQuickMaskExtruder::extrude(const QRectF &r)
 {
     ensureInitialized(r);
@@ -88,19 +120,15 @@ void QQuickMaskExtruder::ensureInitialized(const QRectF &r)
 {
     if (m_lastWidth == r.width() && m_lastHeight == r.height())
         return;//Same as before
+    if (!m_pix.isReady())
+        return;
     m_lastWidth = r.width();
     m_lastHeight = r.height();
 
-    m_img = QImage();
     m_mask.clear();
-    if (m_source.isEmpty())
-        return;
-    m_img = QImage(m_source.toLocalFile());
-    if (m_img.isNull()){
-        qWarning() << "MaskShape: Cannot load" << qPrintable(m_source.toLocalFile());
-        return;
-    }
-    m_img = m_img.createAlphaMask();
+
+    m_img = m_pix.image().createAlphaMask();
+    m_pix.clear();
     m_img = m_img.convertToFormat(QImage::Format_Mono);//Else LSB, but I think that's easier
     m_img = m_img.scaled(r.size().toSize());//TODO: Do they need aspect ratio stuff? Or tiling?
     for (int i=0; i<r.width(); i++){
index 0882140..0c9f10b 100644 (file)
@@ -42,6 +42,7 @@
 #ifndef MASKEXTRUDER_H
 #define MASKEXTRUDER_H
 #include "qquickparticleextruder_p.h"
+#include <private/qdeclarativepixmapcache_p.h>
 #include <QUrl>
 #include <QImage>
 
@@ -68,24 +69,22 @@ signals:
     void sourceChanged(QUrl arg);
 
 public slots:
+    void setSource(QUrl arg);
+
+private slots:
+    void startMaskLoading();
+    void finishMaskLoading();
 
-    void setSource(QUrl arg)
-    {
-        if (m_source != arg) {
-            m_source = arg;
-            m_lastHeight = -1;//Trigger reset
-            m_lastWidth = -1;
-            emit sourceChanged(arg);
-        }
-    }
 private:
     QUrl m_source;
 
     void ensureInitialized(const QRectF &r);
     int m_lastWidth;
     int m_lastHeight;
+    QDeclarativePixmap m_pix;
     QImage m_img;
     QList<QPointF> m_mask;//TODO: More memory efficient datastructures
+    //Perhaps just the mask for the largest bounds is stored, and interpolate up
 };
 
 QT_END_NAMESPACE