[Solved] QAnimations: what's the best way to print flippable cards?



  • Hi Qt community.

    So I'm going to use QAnimations to draw flippable cards into a QGraphicsView/QGraphicsScene, but I'm not yet familiar with QAnimations in general. How would you draw flippable cards (constructed QImages converted into QPixmaps) in a QGraphicsView? What's the easiest/cleanest way to handle two-sided 2D objects?


  • Lifetime Qt Champion

    Hi,

    Are you looking for something like described in this thread ?



  • @SGaist Yes, although the OP didn't seem to be using animations.

    My understanding of Qt animations is: you specify the location of the object you want to move at t=0, the location of the destination of that object, the duration of the movement, and Qt will draw everything for you. Keeping that in mind, I'm asking what I should do if I want to progressively scale (horizontally for example) an object from 1 to -1, and change its image halfway when it is scaled to 0 (so that it does look like that object has been flipped).

    Not sure if the above was clear, I can try again if I'm asked.



  • Nobody has ever come across my problem before? Nobody has ever tried to flip 2D animations?



  • Well, since I get no reply, I suppose that there is no obvious answer.

    I think I'll try to use QSequentialAnimationGroups whenever I need to flip a card: a first animation that scales the (face-up) card from 1 to 0, and a second animation that scales the (face-down) card from 0 to 1. The QPixmap of the first animation would be different than the QPixmap of the second one. Hopefully that will work and look natural.


  • Lifetime Qt Champion

    Hi,

    Please, practice some patience, allow 24 to 48 hours before bumping your own thread. This forum is community driven and not all people active here live in the same timezone as you.

    From the thread I linked, you have the base to do the flip itself. From there you can create a new QGraphicsObject derived class and add a custom property that you will use with the animation framework to animate the flip.



  • @SGaist Thank you for your reply and sorry for the impatience.

    1. Would you mind dropping a very small code sample that would do the stuff you described? I'm not yet familiar with animations and properties, you'll be sparing me a lot - a lot - of time.

    2. About Qt itself. If I want to draw, say, 150 cards that each have a unique image while face-up, but all have the same image while face-down, and if all of them are face-down at a given time, Qt would be storing/drawing 150 identical QGraphicsPixmapItems whereas it would have been possible (with SFML for example) to store only 1 image and draw it 150 times. That looks very sub-optimal, but since Qt handles everything itself, I don't have much of a choice here, right?


  • Lifetime Qt Champion

    1. I wouldn't mind but I don't have it at hand, however the Animated Tile Example shows how to animate the position. You can adapt that for your needs.

    2. QPixmap is one of the implicitly shared classes of Qt so you would indeed get 150 items but they will use all the same pixmap. Not that you can create your own item that contains both pixmap and set the right one when you flip it.



  • @SGaist Thanks for your reply.

    1. I've already read that example, but it doesn't tackle my problem. I have no idea how to adapt it to flip tiles. I apologize if that's obvious :S

  • Qt Champions 2016

    @Pippin
    Hi this one is having flip code
    https://blog.qt.io/blog/2009/06/09/flippin-widgets-medium-rare-please/
    with animation. maybe you can reuse some of it.


  • Lifetime Qt Champion

    @mrjj good one ! Indeed, the code easily be re-used since it's already using the graphics view framework.



  • @mrjj Thanks a lot, that is very helpful. A bit old though (2009), I see a method rootState() that doesn't exist anymore.


  • Qt Champions 2016

    @Pippin
    indeed at bit old.
    They talk about it here. (rootState())
    http://stackoverflow.com/questions/7051146/qt-animation-member-doesnt-exist

    QState *state1 = new QState(machine);
    seems to be same as rootState



  • @mrjj Thank you again. I've been willing to use your link but I would have questions.

    How can I link a QState to a QGraphics(Pixmap)Item ? QState::assignProperty takes QObjects as first argument, but QGraphics(Pixmap)Item are not QObjects.


  • Qt Champions 2016

    hi
    i think you need to make you own QGraphicsObject
    that controls the image drawing.
    Then add a new property for switching the image to next one and
    use that for QState::assignProperty



  • @mrjj said:

    hi
    i think you need to make you own QGraphicsObject
    that controls the image drawing.
    Then add a new property for switching the image to next one and
    use that for QState::assignProperty

    I'm really not familiar enough with Qt for that sadly... It's a real shame there is no simple way to do that. I'm a bit surprised that the beginner that I am finds right away something easy that is not easily done on Qt.


  • Qt Champions 2016

    @Pippin
    well the c++ of Qt does take some practice and knowledge of Class and subclasses and other
    c++ related topic. Once mastering common c++ methods, its not that hard as it else would seem.

    Did you have a look at QML ? its another qt way that is more easy in regards to more dynamic
    user interfaces.
    For example it can just flip
    http://doc.qt.io/qt-4.8/qml-flipable.html#details
    and tons of other easy to use features.



  • @mrjj I don't know how to use QML, the tutorial shows code but I failed to understand where to put it / how to link it with the rest of the project. I'm doing everything through qmake, make and ./run in the terminal. I'm also not sure if QML can do anything I could do with C++ methods :S


  • Qt Champions 2016

    @Pippin
    its not like c++
    there is a viewer program and you can also embed in c++
    http://www.ics.com/blog/whole-shebang-running-qml-files-directly
    Google is your friend
    You can mix with c++ so you can do most.
    But depends on what you really need for project.



  • @mrjj said:

    @Pippin
    its not like c++
    there is a viewer program and you can also embed in c++
    http://www.ics.com/blog/whole-shebang-running-qml-files-directly
    Google is your friend
    You can mix with c++ so you can do most.
    But depends on what you really need for project.

    The thing is, the QGraphicsView/Scene is only part of a window, which is part of my global project. How exactly do I insert QML inside a Qt project?


  • Qt Champions 2016

    hi
    you can use createWindowContainer
    http://www.ics.com/blog/combining-qt-widgets-and-qml-qwidgetcreatewindowcontainer
    or like this
    http://doc.qt.io/qt-4.8/qml-integration.html
    (check if works for 5.5)

    But if most of is c++ , it might be overkill to use QML just to draw some cards.
    Even if easy animation wise. You can try it out and see how much a hassle it is.



  • Would it work if I created a class that inherits both from QObject and QGraphicsPixmapItem? Would that make the assignProperty thing work or is it more complicated than that?


  • Qt Champions 2016

    @Pippin
    That should work. Remember Q_OBJECT in class.
    Not much more needed
    adding a property for advancing image
    http://doc.qt.io/qt-5.5/properties.html
    then use the CurrentFrame (or what you call it) with animation system to
    load next image .
    If I understand correctly that you have sequence of images that is the card flipping.



  • Thank you for your help so far @mrjj

    So I've created the class QSpecialGraphicsPixmapItem, which basically inherits from QObject and QGraphicsPixmapItem.

    	QPixmap *pix = new QPixmap("Images/A.png");
    	QSpecialGraphicsPixmapItem* item = new QSpecialGraphicsPixmapItem(*pix);
    	item->setPos(100, 100);
    	WindowScene.addItem(item);
    	
    	QStateMachine *machine = new QStateMachine();
    	
    	QState *state1 = new QState();
    	state1->assignProperty(item, "rotation", 90);
    	
    	QState *state2 = new QState();
    	state2->assignProperty(item, "rotation", 0);
    	
    	state1->addTransition(&Button1, SIGNAL(clicked()), state2);
    	state2->addTransition(&Button1, SIGNAL(clicked()), state1);
    	
    	machine->addState(state1);
    	machine->addState(state2);
    	machine->setInitialState(state1);
    	machine->start();
    

    But when I compile & execute it, the pixmap is not rotated by 90 as it should be (since state1 is the initial state). What am I missing? Edit: nvm, got it. I just had to add

    Q_PROPERTY(qreal rotation READ rotation WRITE setRotation)
    

    in the definition of the class QSpecialGraphicsPixmapItem. I'm starting to get it.


  • Qt Champions 2016

    Good work!
    If it works, please update to solved.
    Im pretty sure many more in the future will want to flip cards. :)



  • @mrjj Well not yet, I'm not quite flipping QGraphicsPixmapItems yet ^^

    Let's suppose I want to flip an horizontal, face-down card so that it becomes a vertical, face-up card. Let's suppose I also want to move it in the process, from point A to point B. There are 3 transformations to operate at once:

    • a rotation round the Z axis (horizontal to vertical: 90 degrees)
    • a rotation round the Y axis (face-down to face-up: 180 degrees)
    • a translation from (x_a, y_a) to (x_b, y_b)

    My plan is to first create a transformation that will do half the above (1):

    • a rotation round the Z axis (horizontal to oblique: 45 degrees)
    • a rotation round the Y axis (face-down to <interim state> : 90 degrees)
    • a translation from (x_a, y_a) to ([x_a + x_b]/2, [y_a + y_b]/2)

    When this animation is over, I have to find a way to automatically run another function that will change the QPixmap, then initiate the rest of the transformation (2). I have no idea which signal conveys the information "Animation is Finished" in Qt 5.5 though. Once this is coded, I only have to create the second, and last, animation that will operate at once (3):

    • a rotation round the Z axis (oblique to vertical: 45 degrees)
    • a rotation round the Y axis (<interim state> to face-up : 90 degrees)
    • a translation from([x_a + x_b]/2, [y_a + y_b]/2) to (x_b, y_b)

    So basically, to do that, I have to find out the QTransform objects that will do (1) and (3) for me, and I have to find out how to "know" that my QObject has finished its first animation so that I can change its QPixmap then initiate the second animation (2).

    If someone knows how to handle either part of the solution, feel free to share... Thank you.

    Or maybe I should forget about the rotation round the Y axis and simply realize a scale from 0 to 1 x-wise. Not sure which is better.


  • Qt Champions 2016

    oh, biit too early to celebrate ;)
    Animations can tell when finished.
    connect(animation, signal( finished() ), this, slot( XXx );
    Also look at QSequentialAnimationGroup
    http://doc.qt.io/qt-5/qsequentialanimationgroup.html#details



  • @mrjj Okay thanks, I'll look into it. Do you have any idea how to use an animation based on a QTransform object? Is it even possible? Like, putting the starting value at Id, and the ending value at the matrix we want?

    Or should I rather create 3 animations on the same object (1 translation + 1 scale x-wise OR 1 rotation round Y + 1 rotation round Z) ? Does Qt allow more than 1 animation at a time for a given object?


  • Qt Champions 2016

    @Pippin

    Well I only used QPropertyAnimation which needs a property.
    Not seen any that took a QTransform. So I think it can't out of the box but
    it might be possible in some way.

    --Does Qt allow more than 1 animation at a time for a given object
    That I never tried. But if different props, I see no reason why not.


  • Lifetime Qt Champion

    QParallelAnimationGroup to apply several animations at once

    There's no QTransform property put that can be added to a custom object



  • Okay I'm almost there! Switching the 2 Pixmaps is done successfully, the card is moved, rotated and scaled at the same time.

    But the overall process doesn't look natural because, while the translation's speed is constant, the QGraphicsItem is not centered. (If I move the item to the position (x, y), the top left corner of the item will match (x, y) instead of its center.) Because of this, the item first seems to move slowly (typically when I'm scaling it from 1 to 0), and then faster (typically when I'm scaling it back from 0 to 1).

    How can I specify that I want my items centered on the position I draw them onto?


  • Qt Champions 2016

    @Pippin
    Hi
    Good work.
    Maybe its stupid question but if you draw it yourself, cant you just draw it centered ?
    All you need is size of area and sixe of item, then you can offset x,y
    sort of like this:
    Draws images centered.

    QImage source;
    QPainter painter(...);
    QRect rect(source.rect());
    QRect devRect(painter.device()->width(), painter.device()->height());
    rect.moveCenter(devRect.center());
    painter.drawImage(rect.topLeft(), source);
    


  • @mrjj

    I don't understand, how can I draw it myself? I'm doing everything through animations. I'm not using any QImage or QPainter at this point. Qt is basically drawing everything itself from Point A to Point B, and I'm looking for a way to make it do that in a centered manner.


  • Qt Champions 2016

    @Pippin
    Ok. Just read "on the position I draw them onto?" that you did draw it yourself.
    So I guess you cannot adjust x,y yourself. sorry.



  • So does this mean there's no way to solve this problem with animations? I've tried to set the "TransformOriginPoint" to the center of the item but that didn't change anything apparently. I've also looked for an option that would allow "drawing on center" but I couldn't find anything.


  • Qt Champions 2016

    @Pippin said:
    Hi its just means that I dont know :)
    You can do many things with Transform
    like
    QTransform trans;
    trans.translate(500, 250);
    item->setTransform(trans);

    so there can easy be a way to do it.

    I suggest you make a nice new question for it in forum asking how to
    animated using center and see if anyone knows.



  • Okay I've managed it. Let me show you the code of the class I created for flippable QGraphicsPixmapItems. It's very tricky because the "transformOriginPoint" is just the origin used for rotations. So if you decide to scale the item horizontally, you would have to center it afterwards on the "transformOriginPoint" because Qt won't do that for you.

    Thank you @mrjj for your help and patience. That was immensely appreciated :-)

    QSpecialGraphicsPixmapItem.hpp

    #pragma once
    
    #include <QGraphicsPixmapItem>
    #include <QObject>
    
    class QSpecialGraphicsPixmapItem : public QObject, public QGraphicsPixmapItem
    {
    	Q_OBJECT
    	Q_PROPERTY(qreal stage READ stage WRITE setStage)
    	
    	private:
    	
    		const QPixmap& faceUp;
    		const QPixmap& faceDown;
    		
    		qreal xInitialSpot;
    		qreal yInitialSpot;
    		qreal xFinalSpot;
    		qreal yFinalSpot;
    		qreal initialRotation;
    		qreal finalRotation;
    		qreal step;
    		
    		bool isFaceUp;
    		bool isFlipped;
    
    	public:
    	
    		QSpecialGraphicsPixmapItem(const QPixmap&, const QPixmap&, const QPointF&, qreal);
    		~QSpecialGraphicsPixmapItem(void);
    		
    		qreal stage(void) const;
    		void setStage(qreal);
    		void prepareAnimation(const QPointF&, qreal, bool);
    };
    

    QSpecialGraphicsPixmapItem.cpp

    #include <QtMath>
    #include "QSpecialGraphicsPixmapItem.hpp"
    
    QSpecialGraphicsPixmapItem::QSpecialGraphicsPixmapItem(const QPixmap& pixmap, const QPixmap& pixmap2, const QPointF& location, qreal rotation) :
    	QObject(),
    	QGraphicsPixmapItem(pixmap2),
    	faceUp(pixmap),
    	faceDown(pixmap2),
    	xInitialSpot(0.),
    	yInitialSpot(0.),
    	xFinalSpot(location.x()),
    	yFinalSpot(location.y()),
    	initialRotation(0.),
    	finalRotation(rotation),
    	step(0.),
    	isFaceUp(false),
    	isFlipped(false)
    {
    	QGraphicsItem::setTransformOriginPoint(CARD_WIDTH/2., CARD_HEIGHT/2.);
    	QGraphicsItem::setRotation(rotation);
    	QGraphicsItem::setPos(location.x() - CARD_WIDTH/2., location.y() - CARD_HEIGHT/2.);
    }
    
    QSpecialGraphicsPixmapItem::~QSpecialGraphicsPixmapItem(void)
    {
    }
    
    qreal QSpecialGraphicsPixmapItem::stage(void) const
    {
    	return step;
    }
    
    void QSpecialGraphicsPixmapItem::setStage(qreal foo)
    {
    	if (isFlipped && foo >= .5 && step < .5)
    	{
    		isFaceUp ? QGraphicsPixmapItem::setPixmap(faceDown) : QGraphicsPixmapItem::setPixmap(faceUp);
    		isFaceUp = not isFaceUp;
    	}
    
    	if (foo < 1.)
    	{
    		qreal r = initialRotation + foo*(finalRotation - initialRotation);
    	
    		if (isFlipped)
    		{
    			qreal dis1 = (1. - qFabs(1. - 2.*foo))*CARD_WIDTH/2.;
    			qreal dis2 = (1. - qFabs(1. - 2.*foo))*CARD_HEIGHT/2.;
    			qreal cosX = qCos(qDegreesToRadians(r));
    			qreal sinX = qSin(qDegreesToRadians(r));
    		
    			QGraphicsItem::setTransform(QTransform().rotate(-r)*QTransform().scale(qFabs(1 - 2*foo), 1.)*QTransform().rotate(r));
    			QGraphicsItem::setPos(xInitialSpot + foo*(xFinalSpot - xInitialSpot) - CARD_WIDTH/2. + (dis1 + dis2*sinX/2.)*cosX,
    				yInitialSpot + foo*(yFinalSpot - yInitialSpot) - CARD_HEIGHT/2. + (dis2 + dis1*cosX/2.)*sinX);
    		}
    		else
    			QGraphicsItem::setPos(xInitialSpot + foo*(xFinalSpot - xInitialSpot) - CARD_WIDTH/2., 
    				yInitialSpot + foo*(yFinalSpot - yInitialSpot) - CARD_HEIGHT/2.);
    		
    		QGraphicsItem::setRotation(r);
    	}
    	else
    	{
    		QGraphicsItem::resetTransform();
    		QGraphicsItem::setRotation(finalRotation);
    		QGraphicsItem::setPos(xFinalSpot - CARD_WIDTH/2., yFinalSpot - CARD_HEIGHT/2.);
    	}
    	
    	step = foo;
    }
    
    void QSpecialGraphicsPixmapItem::prepareAnimation(const QPointF& foo, qreal goo, bool bar)
    {
    	if (xFinalSpot == foo.x() && yFinalSpot == foo.y() && finalRotation == goo && isFlipped == bar)
    		return;
    
    	xInitialSpot = xFinalSpot;
    	yInitialSpot = yFinalSpot;
    	xFinalSpot = foo.x();
    	yFinalSpot = foo.y();
    	initialRotation = finalRotation;
    	finalRotation = goo;	
    	isFlipped = bar;
    	step = 0.;
    }
    

    So with this class I can flip, translate and rotate a QGraphicsPixmapItem at the same time in 1 move. I named the property stage although I had to give the variable another name for compilation purposes (step). This variable has no real meaning, except that it always starts at 0 and always ends at 1, so it could represent the progress of the animation. In my project all my QGraphicsPixmapItems have the same height and width, so I took the liberty of using two constant variables CARD_WIDTH and CARD_HEIGHT for all of them. When you declare the object, you have to specify its face-up pixmap, its face-down pixmap, its initial location and its initial rotation value:

    item = new QSpecialGraphicsPixmapItem(pix, pix2, QPointF(150., 150.), 0.);
    WindowScene.addItem(item);
    

    After that we want to move that object. Before creating an animation, you have to provide the object with its final location, its final rotation value, and if you flip it in the process or not. This is what QSpecialGraphicsPixmapItem::prepareAnimation is for. For example, we want to move the object to [500, 500], rotate it by 90 degrees and flip it.

    item->prepareTransform(QPointF(500., 500.), 90., true);
    	
    QPropertyAnimation *anim = new QPropertyAnimation(item, "stage");
    anim->setDuration(1000);
    anim->setEndValue(1.);
    anim->start();
    

    And that will do. A few things:

    • If you provide the location [x, y] (either in the constructor or later when you call prepareAnimation), the class will actually set it to [x - CARD_WIDTH/2, y - CARD_HEIGHT/2] so that it's centered on the location.
    • If you take a look at setStage, you'll see weird numbers like (dis1 + dis2*sinX/2.)*cosX and (dis2 + dis1*cosX/2.)*sinX. If you decide to flip, rotate and translate an object at the same time, its trajectory can be a bit weird so I had to do a little math to make it look more natural. Unfortunately, these are not the correct numbers. Tests showed that [dis1*cosX, 0] is the appropriate (x-wise) gap if you flip a stationary, vertical card. Likewise, tests showed that [0, dis2*sinX] is the appropriate (y-wise) gap if you flip a stationary, horizontal card. But I failed to find out the appropriate gaps when you flip, rotate and translate the object at the same time. However, [(dis1 + dis2*sinX/2.)*cosX, (dis2 + dis1*cosX/2.)*sinX] seems decent enough. If someone has a deeper understanding of how QTransform works than me (...shouldn't be that hard ^^), and figures out the appropriate gaps, please share it!
    • I took me several days to think, try, despair, rethink, retry... before getting there. I'm only doing it because I need Qt in my project for other things. If you don't really need Qt for your project, you really should use the SFML instead. As a newbie I could do all this with the SFML within a few hours (and then months later, I painfully realized that I needed to do that with Qt). Really, the SFML is great. It's very user-friendly, simple, and very optimized. I haven't compared anything yet, but I have no doubt that all this dreadful code I had to write for Qt will turn out awfully slower than any code using the SFML...

    If someone knows how to improve my code, in any way (simplicity, optimization), please share it! :)


  • Qt Champions 2016

    @Pippin
    Most welcome.
    And thank you for the nice followup.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.