1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtQuick module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qquickanimatedsprite_p.h"
43 #include "qquicksprite_p.h"
44 #include "qquickspriteengine_p.h"
45 #include <QtQuick/private/qsgcontext_p.h>
46 #include <private/qsgadaptationlayer_p.h>
47 #include <private/qqmlglobal_p.h>
48 #include <QtQuick/qsgnode.h>
49 #include <QtQuick/qsgtexturematerial.h>
50 #include <QtQuick/qsgtexture.h>
51 #include <QtQuick/qquickcanvas.h>
52 #include <QtQml/qqmlinfo.h>
60 static const char vertexShaderCode[] =
61 "attribute highp vec2 vPos;\n"
62 "attribute highp vec2 vTex;\n"
63 "uniform highp vec3 animData;// w,h(premultiplied of anim), interpolation progress\n"
64 "uniform highp vec4 animPos;//x,y, x,y (two frames for interpolation)\n"
66 "uniform highp mat4 qt_Matrix;\n"
68 "varying highp vec4 fTexS;\n"
69 "varying lowp float progress;\n"
73 " progress = animData.z;\n"
74 " //Calculate frame location in texture\n"
75 " fTexS.xy = animPos.xy + vTex.xy * animData.xy;\n"
76 " //Next frame is also passed, for interpolation\n"
77 " fTexS.zw = animPos.zw + vTex.xy * animData.xy;\n"
79 " gl_Position = qt_Matrix * vec4(vPos.x, vPos.y, 0, 1);\n"
82 static const char fragmentShaderCode[] =
83 "uniform sampler2D texture;\n"
84 "uniform lowp float qt_Opacity;\n"
86 "varying highp vec4 fTexS;\n"
87 "varying lowp float progress;\n"
90 " gl_FragColor = mix(texture2D(texture, fTexS.xy), texture2D(texture, fTexS.zw), progress) * qt_Opacity;\n"
93 class QQuickAnimatedSpriteMaterial : public QSGMaterial
96 QQuickAnimatedSpriteMaterial();
97 ~QQuickAnimatedSpriteMaterial();
98 virtual QSGMaterialType *type() const { static QSGMaterialType type; return &type; }
99 virtual QSGMaterialShader *createShader() const;
100 virtual int compare(const QSGMaterial *other) const
102 return this - static_cast<const QQuickAnimatedSpriteMaterial *>(other);
116 QQuickAnimatedSpriteMaterial::QQuickAnimatedSpriteMaterial()
126 setFlag(Blending, true);
129 QQuickAnimatedSpriteMaterial::~QQuickAnimatedSpriteMaterial()
134 class AnimatedSpriteMaterialData : public QSGMaterialShader
137 AnimatedSpriteMaterialData(const char * /* vertexFile */ = 0, const char * /* fragmentFile */ = 0)
142 QSGMaterialShader::deactivate();
144 for (int i=0; i<8; ++i) {
145 program()->setAttributeArray(i, GL_FLOAT, chunkOfBytes, 1, 0);
149 virtual void updateState(const RenderState &state, QSGMaterial *newEffect, QSGMaterial *)
151 QQuickAnimatedSpriteMaterial *m = static_cast<QQuickAnimatedSpriteMaterial *>(newEffect);
154 program()->setUniformValue(m_opacity_id, state.opacity());
155 program()->setUniformValue(m_animData_id, m->animW, m->animH, m->animT);
156 program()->setUniformValue(m_animPos_id, m->animX1, m->animY1, m->animX2, m->animY2);
158 if (state.isMatrixDirty())
159 program()->setUniformValue(m_matrix_id, state.combinedMatrix());
162 virtual void initialize() {
163 m_matrix_id = program()->uniformLocation("qt_Matrix");
164 m_opacity_id = program()->uniformLocation("qt_Opacity");
165 m_animData_id = program()->uniformLocation("animData");
166 m_animPos_id = program()->uniformLocation("animPos");
169 virtual const char *vertexShader() const { return vertexShaderCode; }
170 virtual const char *fragmentShader() const { return fragmentShaderCode; }
172 virtual char const *const *attributeNames() const {
173 static const char *attr[] = {
186 static float chunkOfBytes[1024];
189 float AnimatedSpriteMaterialData::chunkOfBytes[1024];
191 QSGMaterialShader *QQuickAnimatedSpriteMaterial::createShader() const
193 return new AnimatedSpriteMaterialData;
196 struct AnimatedSpriteVertex {
203 struct AnimatedSpriteVertices {
204 AnimatedSpriteVertex v1;
205 AnimatedSpriteVertex v2;
206 AnimatedSpriteVertex v3;
207 AnimatedSpriteVertex v4;
211 \qmlclass AnimatedSprite QQuickAnimatedSprite
212 \inqmlmodule QtQuick 2
214 \ingroup qtquick-visual
215 \brief Draws a sprite animation
217 AnimatedSprite provides rendering and control over animations which are provided
218 as multiple frames in the same image file. You can play it at a fixed speed, at the
219 frame rate of your display, or manually advance and control the progress.
221 For details of how a sprite animation is defined see the \l{Sprite Animation} overview.
222 Note that the AnimatedSprite type does not use Sprite types to define multiple animations,
223 but instead encapsulates a single animation itself.
227 \qmlproperty bool QtQuick2::AnimatedSprite::running
229 Whether the sprite is animating or not.
235 \qmlproperty bool QtQuick2::AnimatedSprite::interpolate
237 If true, interpolation will occur between sprite frames to make the
238 animation appear smoother.
244 \qmlproperty qreal QtQuick2::AnimatedSprite::frameRate
246 Frames per second to show in the animation. Values equal to or below 0 are invalid.
248 If frameRate is valid then it will be used to calculate the duration of the frames.
249 If not, and frameDuration is valid , then frameDuration will be used.
251 Changing this parameter will restart the animation.
255 \qmlproperty int QtQuick2::AnimatedSprite::frameDuration
257 Duration of each frame of the animation. Values equal to or below 0 are invalid.
259 If frameRate is valid then it will be used to calculate the duration of the frames.
260 If not, and frameDuration is valid, then frameDuration will be used.
262 Changing this parameter will restart the animation.
266 \qmlproperty int QtQuick2::AnimatedSprite::frameCount
268 Number of frames in this AnimatedSprite.
271 \qmlproperty int QtQuick2::AnimatedSprite::frameHeight
273 Height of a single frame in this AnimatedSprite.
275 May be omitted if it is the only sprite in the file.
278 \qmlproperty int QtQuick2::AnimatedSprite::frameWidth
280 Width of a single frame in this AnimatedSprite.
282 May be omitted if it is the only sprite in the file.
285 \qmlproperty int QtQuick2::AnimatedSprite::frameX
287 The X coordinate in the image file of the first frame of the AnimatedSprite.
289 May be omitted if the first frame starts in the upper left corner of the file.
292 \qmlproperty int QtQuick2::AnimatedSprite::frameY
294 The Y coordinate in the image file of the first frame of the AnimatedSprite.
296 May be omitted if the first frame starts in the upper left corner of the file.
299 \qmlproperty url QtQuick2::AnimatedSprite::source
301 The image source for the animation.
303 If frameHeight and frameWidth are not specified, it is assumed to be a single long row of square frames.
304 Otherwise, it can be multiple contiguous rows or rectangluar frames, when one row runs out the next will be used.
306 If frameX and frameY are specified, the row of frames will be taken with that x/y coordinate as the upper left corner.
310 \qmlproperty bool QtQuick2::AnimatedSprite::reverse
312 If true, then the animation will be played in reverse.
318 \qmlproperty bool QtQuick2::AnimatedSprite::frameSync
320 If true, then the animation will have no duration. Instead, the animation will advance
321 one frame each time a frame is rendered to the screen. This syncronizes it with the painting
322 rate as opposed to elapsed time.
324 If frameSync is set to true, it overrides both frameRate and frameDuration.
328 Changing this parameter will restart the animation.
332 \qmlproperty int QtQuick2::AnimatedSprite::loops
334 After playing the animation this many times, the animation will automatically stop. Negative values are invalid.
336 If this is set to AnimatedSprite.Infinite the animation will not stop playing on its own.
338 Default is AnimatedSprite.Infinite
342 \qmlproperty bool QtQuick2::AnimatedSprite::paused
344 When paused, the current frame can be advanced manually.
350 \qmlproperty int QtQuick2::AnimatedSprite::currentFrame
352 When paused, the current frame can be advanced manually by setting this property or calling advance().
356 //TODO: Implicitly size element to size of sprite
357 QQuickAnimatedSprite::QQuickAnimatedSprite(QQuickItem *parent) :
361 , m_sprite(new QQuickSprite)
364 , m_pleaseReset(false)
367 , m_interpolate(true)
372 setFlag(ItemHasContents);
373 connect(this, SIGNAL(runningChanged(bool)),
374 this, SLOT(update()));
375 connect(this, SIGNAL(widthChanged()),
376 this, SLOT(sizeVertices()));
377 connect(this, SIGNAL(heightChanged()),
378 this, SLOT(sizeVertices()));
381 bool QQuickAnimatedSprite::isCurrentFrameChangedConnected()
383 IS_SIGNAL_CONNECTED(this, QQuickAnimatedSprite, currentFrameChanged, (int));
386 void QQuickAnimatedSprite::reloadImage()
388 if (!isComponentComplete())
390 createEngine();//### It's not as inefficient as it sounds, but it still sucks having to recreate the engine
393 void QQuickAnimatedSprite::componentComplete()
396 QQuickItem::componentComplete();
401 void QQuickAnimatedSprite::start()
404 if (!isComponentComplete())
408 if (m_spriteEngine) {
409 m_spriteEngine->stop(0);
410 m_spriteEngine->updateSprites(0);
411 m_spriteEngine->start(0);
413 emit currentFrameChanged(0);
414 emit runningChanged(true);
418 void QQuickAnimatedSprite::stop()
421 if (!isComponentComplete())
424 emit runningChanged(false);
427 void QQuickAnimatedSprite::advance(int frames)
431 //TODO-C: May not work when running - only when paused
432 m_curFrame += frames;
433 while (m_curFrame < 0)
434 m_curFrame += m_sprite->frames();
435 m_curFrame = m_curFrame % m_sprite->frames();
436 emit currentFrameChanged(m_curFrame);
439 void QQuickAnimatedSprite::pause()
443 m_pauseOffset = m_timestamp.elapsed();
445 emit pausedChanged(true);
448 void QQuickAnimatedSprite::resume()
452 m_pauseOffset = m_pauseOffset - m_timestamp.elapsed();
454 emit pausedChanged(false);
457 void QQuickAnimatedSprite::createEngine()
460 delete m_spriteEngine;
461 QList<QQuickSprite*> spriteList;
462 spriteList << m_sprite;
463 m_spriteEngine = new QQuickSpriteEngine(QList<QQuickSprite*>(spriteList), this);
464 m_spriteEngine->startAssemblingImage();
468 static QSGGeometry::Attribute AnimatedSprite_Attributes[] = {
469 QSGGeometry::Attribute::create(0, 2, GL_FLOAT, true), // pos
470 QSGGeometry::Attribute::create(1, 2, GL_FLOAT), // tex
473 static QSGGeometry::AttributeSet AnimatedSprite_AttributeSet =
475 2, // Attribute Count
476 (2+2) * sizeof(float),
477 AnimatedSprite_Attributes
480 void QQuickAnimatedSprite::sizeVertices()
485 AnimatedSpriteVertices *p = (AnimatedSpriteVertices *) m_node->geometry()->vertexData();
499 QSGGeometryNode* QQuickAnimatedSprite::buildNode()
501 if (!m_spriteEngine) {
502 qmlInfo(this) << "No sprite engine...";
504 } else if (m_spriteEngine->status() == QQuickPixmap::Null) {
505 m_spriteEngine->startAssemblingImage();
506 update();//Schedule another update, where we will check again
508 } else if (m_spriteEngine->status() == QQuickPixmap::Loading) {
509 update();//Schedule another update, where we will check again
513 m_material = new QQuickAnimatedSpriteMaterial();
515 QImage image = m_spriteEngine->assembledImage();
518 m_sheetSize = QSizeF(image.size());
519 m_material->texture = canvas()->createTextureFromImage(image);
520 m_material->texture->setFiltering(QSGTexture::Linear);
521 m_spriteEngine->start(0);
522 m_material->animT = 0;
523 m_material->animX1 = m_spriteEngine->spriteX() / m_sheetSize.width();
524 m_material->animY1 = m_spriteEngine->spriteY() / m_sheetSize.height();
525 m_material->animX2 = m_material->animX1;
526 m_material->animY2 = m_material->animY1;
527 m_material->animW = m_spriteEngine->spriteWidth() / m_sheetSize.width();
528 m_material->animH = m_spriteEngine->spriteHeight() / m_sheetSize.height();
532 QSGGeometry *g = new QSGGeometry(AnimatedSprite_AttributeSet, vCount, iCount);
533 g->setDrawingMode(GL_TRIANGLES);
535 AnimatedSpriteVertices *p = (AnimatedSpriteVertices *) g->vertexData();
537 QRectF texRect = m_material->texture->normalizedTextureSubRect();
539 p->v1.tx = texRect.topLeft().x();
540 p->v1.ty = texRect.topLeft().y();
542 p->v2.tx = texRect.topRight().x();
543 p->v2.ty = texRect.topRight().y();
545 p->v3.tx = texRect.bottomLeft().x();
546 p->v3.ty = texRect.bottomLeft().y();
548 p->v4.tx = texRect.bottomRight().x();
549 p->v4.ty = texRect.bottomRight().y();
551 quint16 *indices = g->indexDataAsUShort();
561 m_node = new QSGGeometryNode();
562 m_node->setGeometry(g);
563 m_node->setMaterial(m_material);
564 m_node->setFlag(QSGGeometryNode::OwnsMaterial);
569 void QQuickAnimatedSprite::reset()
571 m_pleaseReset = true;
574 QSGNode *QQuickAnimatedSprite::updatePaintNode(QSGNode *, UpdatePaintNodeData *)
581 m_pleaseReset = false;
589 m_node->markDirty(QSGNode::DirtyMaterial);
595 void QQuickAnimatedSprite::prepareNextFrame()
598 m_node = buildNode();
599 if (m_node == 0) //error creating node
602 int timeInt = m_timestamp.elapsed() + m_pauseOffset;
603 qreal time = timeInt / 1000.;
605 double frameAt; //double just for modf
606 qreal progress = 0.0;
607 int lastFrame = m_curFrame;
609 //Advance State (keeps time for psuedostates)
610 m_spriteEngine->updateSprites(timeInt);
612 //Advance AnimatedSprite
613 qreal animT = m_spriteEngine->spriteStart()/1000.0;
614 qreal frameCount = m_spriteEngine->spriteFrames();
615 qreal frameDuration = m_spriteEngine->spriteDuration()/frameCount;
616 if (frameDuration > 0) {
617 qreal frame = (time - animT)/(frameDuration / 1000.0);
618 frame = qBound(qreal(0.0), frame, frameCount - qreal(1.0));//Stop at count-1 frames until we have between anim interpolation
619 progress = modf(frame,&frameAt);
620 if (m_curFrame > frameAt) //went around
622 m_curFrame = frameAt;
625 if (m_curFrame >= frameCount){
628 m_spriteEngine->advance();
630 frameAt = m_curFrame;
633 if (m_loops > 0 && m_curLoop >= m_loops) {
638 frameAt = m_curFrame;
640 if (m_curFrame != lastFrame && isCurrentFrameChangedConnected())
641 emit currentFrameChanged(m_curFrame);
642 if (m_spriteEngine->sprite()->reverse())
643 frameAt = (m_spriteEngine->spriteFrames() - 1) - frameAt;
644 qreal y = m_spriteEngine->spriteY() / m_sheetSize.height();
645 qreal w = m_spriteEngine->spriteWidth() / m_sheetSize.width();
646 qreal h = m_spriteEngine->spriteHeight() / m_sheetSize.height();
647 qreal x1 = m_spriteEngine->spriteX() / m_sheetSize.width();
650 if (frameAt < (m_spriteEngine->spriteFrames()-1))
653 m_material->animX1 = x1;
654 m_material->animY1 = y;
655 m_material->animX2 = x2;
656 m_material->animY2 = y;
657 m_material->animW = w;
658 m_material->animH = h;
659 m_material->animT = m_interpolate ? progress : 0.0;