How can I mirror a movie and play it in reverse? - qt

I have been playing with QMovie a bit trying to mirror a movie as well as play it in reverse.
For the mirroring bit I tried assigning a negative width to no avail. Since QImage does offer facilities for this I had hoped QMovie would do the same.
There don't seem to be any facilities in the QMovie object for those things, so I'm wondering if I could manipulate the QIODevice to the QMovie object instead to achieve this, but this is completely new territory for me and I don't see anything obvious in the documentation that would achieve mirroring or playing in reverse.
The code example to start with would be the same as on the PySide page:
label = QLabel()
movie = QMovie("animations/fire.gif")
label.setMovie(movie)
movie.start()
Any ideas would be greatly appreciated.
Thanks,
Frank

QMovie doesn't provide the methods to set the current image, so you have to use directly QImageReader to play it in reverse (using QImageReader::jumpToImage()). This is not very easy bacause the delay between a frame and the next can change, but however you can get it calling QImageReader::nextImageDelay().
To display the movie, you can implement your own widget to draw the movie as you want.
In the paintEvent() you can set a transformation to the painter to get the mirror effect and than draw the current image of the movie.
Example:
void MyWidget::paintEvent(QPaintEvent * event)
{
QPainter painter(this);
painter.scale(-1, 1); // x-axis mirror
//here maybe you must adjust the transformation to center and scale the movie.
painter.drawImage(0, 0, currentImage);
}
To play the movie you have to set a timer that change the current image.
Example:
//you must create a timer in the constructor and connect it to this slot
void MyWidget::timeoutSlot()
{
int currentImageIndex;
//here you have to compute the image index
imageReader.jumpToImage(currentImageIndex);
currentImage = imageReader.read(); //maybe you want to read it only if the index is changed
update();
}
Here you can find an example of widget subclass, with timer and painter transformation
See also QImageReader, QTimer, QWidget::paintEvent

Related

Display image using QImage without using pixmap in Qt?

I have a requirement to read pixel values from the picture displayed on the GraphicScene layout. How can I display image using QImage without using pixmap in Qt so that I am able to read the pixel values?
On most platforms, a QPixmap is a thin wrapper around a QImage. The conversions between the two are cheap - especially the pixmap-to-image conversion. Thus, you can use the QGraphicsPixmapItem and use item->pixmap().toImage() without much worry. To confirm that QPixmap is indeed a wrapper, the following check will do:
bool isPixmapThin(const QPixmap &pix) {
auto const a = pix.toImage();
auto const b = pix.toImage();
return a.bits() == b.bits();
}
In all cases, ensure that the image you take from the pixmap won't detach, i.e. always make it const (as in the code example above).

Refreshing MainWindow in a loop to simulate a move of QWidget in QT

I am new in QT and my problem is to refresh the page in a loop to make a move on QWidget.
In detail, I have too many points (It is the path which will be followed by an ellipse and they will be drawn as line) and I have an ellipse which will move on the screen according to given two points. During its move, the path is changed. So lines will be drawn again according to new path and the ellipse should follow the new path. What I did as follows:
void MainWindow::paint(...){
painter.drawEllipse(circle) //circle is QRectF
//Also I need to draw lines according to pathPlanned
}
bool MainWindow::replan(){
//it calculates the planned path and if the ellipse does not reached the destination it can change the planned path here
}
void MainWindow::execute(){
while(replan()){
for (it = plannedPath->begin(); it != plannedPath->end(); it++){
//Lines should be redraw according to new pathPlanned
}
circle(...) // new position of ellipse is changed here
// I tried to put QThread::msleep(10) but I learned that it blocks GUI and then deleted it.
}
}
My problem is that loop is working so fast (as usual) and it can not refresh the page until it finishes everything. Then Immediately ellipse is drawn on the destination. I can not see the moves of ellipse.
How can I fix that?
Instead of using QThread::msleep(10), use following
QEventLoop loop;
QTimer::singleShot(100, &loop, SLOT(quit()));
loop.exec();
This will process events after each redraw of ellipse so UI will get updated
You need to use Qt animation framework for that. There are a lot of samples in official documentation. In this case you will not block main event loop and your animations will be smooth.
If you use custom drawing, don't forget to call QWidget::repaint() or QWidget::update() to refresh widget content.
Don't use long time loops in main thread. Use timers + slots.

Is there any easy way to copy QPainter state?

I want to implment push_group/pop_group of cairo with QPainter, but QPainter resets all its state while begin() with a new painterDevice, so I have to save/revert all state manually.
Yes, just check out QPainter::save() and QPainter::restore().
If you want to save/restore between the lifespan of multiple QPainters, you have to do it manually. You could just create a class PainterState that encapsulates the painter state (pen, brush, transform, etc.), and then store a QStack<PainterState>.
There is a QPainterState class, but it is for internal use only, and I think it's only for use with a single QPainter. See the source ("qpainter_p.h") if you're interested in the QPainterState members (too many to copy here).
When constructing the QPainter object, you can draw it to a QPicture. Then it can be reloaded when needed and painted out to the real QPaintDevice.
QPicture picture;
QPainter painterQueued;
painterQueued.begin(&picture); // paint in picture
painterQueued.drawEllipse(10,20, 80,70); // draw an ellipse
painterQueued.end(); // painting done
QImage myImage;
QPainter painterTarget;
painterTarget.begin(&myImage); // paint in myImage
painterTarget.drawPicture(0, 0, picture); // draw the picture at (0,0)
painterTarget.end(); // painting done
You could queue up many QPicture objects in a list, stack, etc, and replay them when needed.

Best way to create a long line (or cross line) cursor in Qt GraphicsView

The easy way to create the long cross line cursor (as long as viewport) is create a cross line graphicsItem, when mouse moved, set the item's pos property.
But this way will be very slow when the scene is complex, because it should update the whole viewport to update the cursor's pos.
The another easy way is setCursor(QCursor(..)),use a QPixmap to define the long cross line, this way will very fast , but the cursor will exceed the viewport rect.
Is there another way to show a long cross line cursor fastly?
Thanks very much!
If I understand correctly, you want to draw an horizontal line and a vertical line, crossing at the cursor position, and being as large as the viewport is.
A poosible solution would be to reimplement QGraphicsScene::drawForeground() to draw the two lines with the painter.
The problem is that the scene doesn't know about the mouse position. This means the view will have to track it and inform the scene when the mouse position has changed.
To do that, you'll have to create your own GraphicsScene (inheriting QGraphicsScene) and your own GraphicsView (inheriting QGraphicsView).
On your GraphicsView constructor, you'll have to start mouse tracking. This will make your you receive a mouseMoveEvent each time the mouse moves inside the view :
GraphicsViewTrack::GraphicsViewTrack(QWidget* parent) : QGraphicsView(parent) {
setMouseTracking(true);
}
void GraphicsViewTrack::mouseMoveEvent(QMouseEvent* pEvent) {
QPointF MousePos = this->mapToScene(pEvent->pos());
emit mousePosChanged(MousePos.toPoint());
}
As you can see in the code snippet above, the view is emitting a signal (mousePosChanged) to which the scene will be connected. This signal contains the mouse position, converted to the scene's coordinates.
Now, on the scene side, you have to add a slot which will be called when the mouse position changed, store the new mouse position in a member variable and reimplement QGraphicsScene::drawForeground() :
void GraphicsSceneCross::drawForeground(QPainter* painter, const QRectF& rect) {
QRectF SceneRect = this->sceneRect();
painter->setPen(QPen(Qt::black, 1));
painter->drawLine(SceneRect.left(), m_MousePos.y(), SceneRect.right(), m_MousePos.y());
painter->drawLine(m_MousePos.x(), SceneRect.top(), m_MousePos.x(), SceneRect.bottom());
}
void GraphicsSceneCross::onMouseChanged(QPoint NewMousePos) {
m_MousePos = NewMousePos; // Store the mouse position in a member variable
invalidate(); // Tells the scene it should be redrawn
}
The last thing to do is connect the GraphicsView's signal to the GraphicsScene slot.
I'll let you check if this solution is acceptable performance wise.
Based on Jerome answer and using python I created this code in my QGraphicsScene subclass:
def drawForeground(self, painter, rect):
if self.guidesEnabled:
painter.setClipRect(rect)
painter.setPen(self.guidePen)
painter.drawLine(self.coords.x(), rect.top(), self.coords.x(), rect.bottom())
painter.drawLine(rect.left(), self.coords.y(), rect.right(), self.coords.y())
def mouseMoveEvent(self, event):
self.coords = event.scenePos()
self.invalidate()
It should be straighforward for you to write its appropiate C++ code. Note that I take advantage of the rect argument passed by Qt Api framework and I clip the painter to that
area, since it's the visible area to be drawn.
I also cache the pen object, since I realized in other experiments that creating objects while painting will penalty performace and doing it so you also give the user the chance to set a custom pen in your program options.
I have found a way to do this!
I develops under Windows system, so can use lower GDI api jumping out of Qt's painting system.
The detail is get the HDC of the QGraphicsView's viewPort. Then in the QMouseEvent of QGraphicsView use "MoveToEx" and "LineTo" drawing two lines on the viewport,then I should do is erase the "old" cursor, It's easy to do this using "setROP2(HDC dc,R2_NOT)",then draw the old Cursor stored again.
This method doesn't enter the QPainter system, so the GraphicsItems under the cursor will not be repaint.
To solve the filker problem when the mouse moves fast, I don't use "double buffer". I used QTimer to update the cursor at CPU's idle. The detail is in QMouseEvent ,don't update the cursor at time ,but store the position to a list, When CPU idle, draw the cursor at the list of positions
I wish this will help others who meets the same problem with me.
Thanks Jérôme, who gave me useful tip of QGraphicsScene.

Conceptual question: Loose Coupling

I am building a graphic board like project where i am facing a design issue.
Main Class is Board which is a canvas responsible for handling mouse events when drawing shapes. It also has context variables such as currentShape or snapFlag to activate grid magnetism.
To handle the moving / resizing / rotating of the shapes, they inherit from a third party open source tool called ObjectHandles (flex).
I have a baseShape extending ObjectHandles main class to override some of its internal functions, like the onMove function.
When creating a shape (mouse down, move, mouse up) this is handle by the Board and it knows about his own snap flag.
var mouseUpPoint:Point = boardCanvas.globalToLocal(new Point(event.stageX, event.stageY));
var snapMouseUpPoint = snapPoint(mouseUpPoint.x, mouseUpPoint.y);
In my overidden onMove method i would like the shape to be aware of the Board snap flag and when its changing. How do i do this ?
Do i pass the Board as a parameter in my basicShape constructor so that i can check snap ?
Do i pass the flag as a parameter and somehow make all shapes listen for change ?
What is the cleanest solution ?
Thanks a lot.
I would approach this from a slightly different angle. I assume that the Board object traps mouse events first, so that it can decide which shape has been clicked on. I would have the board trap mouse movements as well, passing the correct (snapped or unsnapped) coordinates "down" to the selected Shape object, rather than letting the shape object figure it out.
This leaves the grid snap handling to the Board, and keeps your Shape object onMove method free of clutter.
Not knowing your app:
Is it ever possible for a Shape to have it's own 'snap' behavior? That is, could a Shape be excluded from snapping while others aren't? If so, make snapFlag a member of Shape. When snapFlag is set on the Board, iterate through your Shapes and set or don't set according to your rules.
If snapping behavior applies to all Shapes on the Board, consider an event-driven model (if it's available - I'm a Flex noob). When a Shape moves, have it raise an OnMove event. The Board can then respond and decide to 'snap' the Shape into place if it's appropriate.
If snap behavior applies to all Shapes and events aren't available, I'd just say the hell with loose coupling in this case - make the Shapes Board-aware. It sounds like you're saving a bunch of code by using the ObjectHandle. That benefit may out-weigh the cost of coupling your UI elements.
Just trying to think together with you..
I see no big deal in Shapes having IBoard interface.
Though, I don't like the idea that they have to check the flag on the board...
How would you pass the flag as parameter? In OnMove() method? didn't understood this quite well...could you expand?
Though..
If you try to think a bit about SRP - single responsibility principle...what is the responsibility of Shape classes?
Yea, this is what eJames wrote already.
It feels to me that their main responsibility is probably NOT handling mouse events...here need to know more about your application, but my general feeling is why not someone else get this mouse down and then figure out what the shape should do with it and for instance call Draw() on the Shape with new coordinates?
Let's say you want to apply something like Composite pattern (Shapes inside shapes...) and you want them to be able to handle those mouse events themselves...but then
Then it would be logical if they perceived this mouse event in their local coordinates, but then I think you should provide all the information through this event (local coordinates, mouse status...) so that they don't have to ask for "global" variables on the board...
Passing the flag as a parameter for the shape constructor. But it wont be good since flag is going to change and i have to make each shape to update their flag copy on change.
Its true that shape responsibility is not to know how to handle mouse events. But thats what ObjectHandles do: react to events, update height width rotation parameter of the shape.
Maybe i should transfer some of the library code in my board class to handle shape selection and movement / resizing / rotation.
OnMouseMove ObjectHandles
protected function onMouseMove(event:MouseEvent) : void
{
if( ! visible ) { return; }
if( ! event.buttonDown )
{
setMouseCursor( event.stageX, event.stageY );
return;
}
if(parent == null )
{
return;
}
var dest:Point = parent.globalToLocal( new Point(event.stageX, event.stageY) );
var desiredPos:Point = new Point();
var desiredSize:Point = new Point();
var desiredRotation:Number = 0;
... plenty more
then
if( wasMoved ) { dispatchMoving() ; }
if( wasResized ) { dispatchResizing() ; }
if( wasRotated ) { dispatchRotating(); }
So i can not listen for move event and tell the board to snap it since the shape is already moving freely. I should add snap here:
var dest:Point = parent.globalToLocal( new Point(event.stageX, event.stageY) );
All shapes follow the snap rule there can not be one snapping and the other free.
Solved it this way:
Since i overridde onMouseMove in my baseShape class and i am using PureMVC framework, i just made baseShape aware of my boardMediator.
override protected function onMouseMove(event:MouseEvent) : void
{
[...]
// added on override
var board:BoardMediator = ApplicationFacade.getInstance().retrieveMediator(BoardMediator.NAME) as BoardMediator;
Then
desiredPos = board.snapPoint(desiredPos.x, desiredPos.y);
Maybe not super pretty but it works, o
Overridding the globalToLocal method in my board view did work too but some more calculations were done inside onMouseMove resulting in an out of alignment snap move.
Use ObjectHandles Version 2, and then create a constraint to do what you want.

Resources