MythTV master
galleryslide.cpp
Go to the documentation of this file.
1// C++
2#include <algorithm>
3#include <cmath> // for roundf
4
5// MythTV
10
11// MythFrontend
12#include "galleryslide.h"
13
14#define LOC QString("Slide: ")
15#define SBLOC QString("SlideBuffer: ")
16
17
18// Number of slides to use for buffering image requests.
19// When browsing quickly the buffer will load consecutive slides until it fills.
20// If too large, rapid browsing will be stodgy (sequential access) for images that
21// aren't cached (Cached images are always fast).
22// If too small, rapid browsing will result in skipping slides rather than flicking
23// quickly through them.
24// Minimum is 4: 3 for displaying a transition, 1 to handle load requests
25static constexpr size_t SLIDE_BUFFER_SIZE { 9 };
26
27
33void AbstractAnimation::Start(bool forwards, float speed)
34{
35 m_forwards = forwards;
36 m_speed = speed;
37 m_running = true;
38}
39
40
49void Animation::Set(const QVariant& from, const QVariant& to,
50 std::chrono::milliseconds duration,
51 const QEasingCurve& curve, UIEffects::Centre centre)
52{
53 setStartValue(from);
54 setEndValue(to);
55 m_centre = centre;
56 setDuration(duration.count());
57 setEasingCurve(curve);
58}
59
60
66void Animation::Start(bool forwards, float speed)
67{
68 auto duration_ms = std::chrono::milliseconds(duration());
69 if (duration_ms == 0ms)
70 return;
71
72 m_elapsed = forwards ? 0ms : duration_ms;
73 setCurrentTime(m_elapsed.count());
74
75 AbstractAnimation::Start(forwards, speed);
76}
77
78
82{
83 if (!m_running)
84 return;
85
86 std::chrono::milliseconds current = MythDate::currentMSecsSinceEpochAsDuration();
87 std::chrono::milliseconds interval = std::min(current - m_lastUpdate, 50ms);
89 m_elapsed += (m_forwards ? interval : -interval) * static_cast<int>(m_speed);
90 setCurrentTime(m_elapsed.count());
91
92 // Detect completion
93 if ((m_forwards && m_elapsed.count() >= duration()) || (!m_forwards && m_elapsed <= 0ms))
94 Finished();
95}
96
97
102void Animation::updateCurrentValue(const QVariant &value)
103{
104 if (m_parent && m_running)
105 {
107
108 switch (m_type)
109 {
110 case None: break;
111 case Position: m_parent->SetPosition(value.toPoint()); break;
112 case Alpha: m_parent->SetAlpha(value.toInt()); break;
113 case Zoom: m_parent->SetZoom(value.toFloat()); break;
114 case HorizontalZoom: m_parent->SetHorizontalZoom(value.toFloat()); break;
115 case VerticalZoom: m_parent->SetVerticalZoom(value.toFloat()); break;
116 case Angle: m_parent->SetAngle(value.toFloat()); break;
117 }
118 }
119}
120
121
127{
128 // Signal group when child completes
129 m_group.append(child);
131}
132
133
138{
139 qDeleteAll(m_group);
140 m_group.clear();
141}
142
143
147{
148 if (!m_running || m_current < 0 || m_current >= m_group.size())
149 return;
150
151 // Pulse current running child
152 m_group.at(m_current)->Pulse();
153}
154
155
161void SequentialAnimation::Start(bool forwards, float speed)
162{
163 if (m_group.empty())
164 return;
165
166 m_current = forwards ? 0 : m_group.size() - 1;
167
168 // Start group, then first child
169 GroupAnimation::Start(forwards, speed);
170 m_group.at(m_current)->Start(m_forwards, m_speed);
171}
172
173
179{
180 // Set group speed for subsequent children
182
183 // Set active child
184 if (!m_running || m_current < 0 || m_current >= m_group.size())
185 return;
186
187 m_group.at(m_current)->SetSpeed(speed);
188}
189
190
195{
196 bool finished { false };
197
198 // Finish group when last child finishes
199 if (m_forwards)
200 {
201 m_current++;
202 finished = (m_current == m_group.size());
203 }
204 else
205 {
206 m_current--;
207 finished = (m_current < 0);
208 }
209
210 if (finished)
212 else
213 // Start next child
214 m_group.at(m_current)->Start(m_forwards, m_speed);
215}
216
217
221{
222 if (m_running)
223 {
224 // Pulse all children
225 for (AbstractAnimation *animation : std::as_const(m_group))
226 animation->Pulse();
227 }
228}
229
230
236void ParallelAnimation::Start(bool forwards, float speed)
237{
238 if (m_group.empty())
239 return;
240
241 m_finished = m_group.size();
242
243 // Start group, then all children
244 GroupAnimation::Start(forwards, speed);
245 for (AbstractAnimation *animation : std::as_const(m_group))
246 animation->Start(m_forwards, m_speed);
247}
248
249
255{
256 // Set group speed, then all children
258 for (AbstractAnimation *animation : std::as_const(m_group))
259 animation->SetSpeed(m_speed);
260}
261
262
267{
268 // Finish group when last child finishes
269 if (--m_finished == 0)
271}
272
273
278void PanAnimation::updateCurrentValue(const QVariant &value)
279{
280 if (m_parent && m_running)
281 {
282 Slide *image = m_parent;
283 image->SetPan(value.toPoint());
284 }
285}
286
287
294Slide::Slide(MythUIType *parent, const QString& name, MythUIImage *image)
295 : MythUIImage(parent, name)
296{
297 // Clone from image
298 CopyFrom(image);
299
300 // Null parent indicates we should become a child of the image (after
301 // copy to avoid recursion)
302 if (!parent)
303 {
304 // Slides sit on top of parent image area
305 SetArea(MythRect(image->GetArea().toQRect()));
306 m_area.moveTo(0, 0);
307 setParent(image);
308 m_parent = image;
309 image->AddChild(this);
310 }
311
312 // Provide animations for pan & zoom
313 if (GetPainter()->SupportsAnimation())
314 {
316 m_panAnimation = new PanAnimation(this);
317 }
318
319 connect(this, &MythUIImage::LoadComplete, this, &Slide::SlideLoaded);
320}
321
322
327{
328 delete m_zoomAnimation;
329 delete m_panAnimation;
330 LOG(VB_GUI, LOG_DEBUG, "Deleted Slide " + objectName());
331}
332
333
338{
339 m_state = kEmpty;
340 m_data.clear();
341 m_waitingFor.clear();
342 SetCropRect(0, 0, 0, 0);
343 SetVisible(false);
344}
345
346
352{
353 switch (m_state)
354 {
355 case kEmpty: return 'e';
356 case kFailed: return 'f';
357 case kLoaded: return m_waitingFor ? 'r' : 'a';
358 case kLoading: return m_waitingFor ? 'l' : 'p';
359 }
360 return '?';
361}
362
363
376bool Slide::LoadSlide(const ImagePtrK& im, int direction, bool notifyCompletion)
377{
378 m_direction = direction;
379 m_waitingFor = notifyCompletion ? im : ImagePtrK();
380
381 if (im == m_data)
382 {
383 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Already loading/loaded %1 in %2")
384 .arg(im->m_filePath, objectName()));
385
386 if (m_state >= kLoaded && notifyCompletion)
387 // Image has been pre-loaded
388 emit ImageLoaded(this);
389
390 return (m_state >= kLoaded);
391 }
392
393 // Is a different image loading ?
394 if (m_state == kLoading)
395 {
396 // Can't abort image loads, so must wait for it to finish
397 // before starting new load
398 m_waitingFor = im;
399
400 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Postponing load of %1 in %2")
401 .arg(im->m_filePath, objectName()));
402
403 return false;
404 }
405
406 // Start load
407 m_data = im;
409
410 if (im->m_type == kVideoFile)
411 {
412 // Use thumbnail, which has already been orientated
413 SetFilename(im->m_thumbNails.at(0).second);
415 }
416 else
417 {
418 // Load image
419 SetFilename(im->m_url);
420 SetOrientation(Orientation(m_data->m_orientation).GetCurrent());
421 }
422
423 // Load in background
424 Load(true);
425 return false;
426}
427
428
436{
438 if (m_state == kFailed)
439 LOG(VB_GENERAL, LOG_ERR, LOC +
440 QString("Failed to load %1").arg(m_data->m_filePath));
441
442 // Ignore superseded requests and preloads
443 if (m_data == m_waitingFor)
444 {
445 // Loaded image is the latest requested
446 emit ImageLoaded(this);
447 }
448 else if (m_waitingFor)
449 {
450 LOG(VB_FILE, LOG_DEBUG, LOC + QString("Starting delayed load %1")
451 .arg(m_waitingFor->m_filePath));
452
453 // Start latest postponed load
455 }
456}
457
458
464void Slide::Zoom(int percentage)
465{
466 // Sentinel indicates reset to default zoom
467 float newZoom = (percentage == 0)
468 ? 1.0F
469 : std::clamp(m_zoom * (1.0F + percentage / 100.0F), MIN_ZOOM, MAX_ZOOM);
470 if (newZoom != m_zoom)
471 {
472 if (m_zoomAnimation)
473 {
474 m_zoomAnimation->Set(m_zoom, newZoom, 250ms, QEasingCurve::OutQuad);
476 }
477 else
478 {
479 SetZoom(newZoom);
480 }
481 }
482}
483
484
491void Slide::SetZoom(float zoom)
492{
493 m_zoom = zoom;
495
496 // TODO
497 // MythUIImage displaces widget or doesn't centre for some combinations of
498 // zoom centre/cropping so frig centre for now.
500
501 SetPan(m_pan);
502}
503
504
509void Slide::Pan(QPoint offset)
510{
511 // Panning only possible when zoomed in
512 if (m_zoom > 1.0F)
513 {
514 QPoint start = m_pan;
515
516 // Sentinel indicates reset to centre
517 // Panning is applied to original (unzoomed) image co-ords.
518 // Adjust offset for zoom so that pan moves a constant screen distance rather
519 // than constant image distance
520 QPoint dest = offset.isNull() ? QPoint(0, 0) : start + offset / m_zoom;
521
522 if (m_panAnimation)
523 {
524 m_panAnimation->Set(start, dest, 250ms, QEasingCurve::Linear);
526 }
527 else
528 {
529 SetPan(dest);
530 }
531 }
532}
533
534
541void Slide::SetPan(QPoint pos)
542{
543 if (m_state == kFailed)
544 {
545 m_pan = pos;
546 return;
547 }
548
549 // Determine zoom of largest dimension
550 QRect imageArea = m_images[m_curPos]->rect();
551 float hRatio = float(imageArea.height()) / m_area.height();
552 float wRatio = float(imageArea.width()) / m_area.width();
553 float ratio = std::max(hRatio, wRatio); // TODO create a Rational number class
554
555 if (m_zoom != 0.0F)
556 ratio /= m_zoom;
557
558 // Determine crop area
559 int h = std::min(int(roundf(m_area.height() * ratio)), imageArea.height());
560 int w = std::min(int(roundf(m_area.width() * ratio)), imageArea.width());
561 int x = imageArea.center().x() - (w / 2);
562 int y = imageArea.center().y() - (h / 2);
563
564 // Constrain pan to boundaries
565 int limitX = (imageArea.width() - w) / 2;
566 int limitY = (imageArea.height() - h) / 2;
567 m_pan.setX(std::clamp(pos.x(), -limitX, limitX));
568 m_pan.setY(std::clamp(pos.y(), -limitY, limitY));
569
570 SetCropRect(x + m_pan.x(), y + m_pan.y(), w, h);
571 SetRedraw();
572}
573
574
579{
580 // Update zoom/pan animations
581 if (m_zoomAnimation)
583
584 if (m_panAnimation)
586}
587
588
590{
591 LOG(VB_GUI, LOG_DEBUG, "Deleted Slidebuffer");
592}
593
594
596{
597 QMutexLocker lock(&m_mutexQ);
598 for (Slide *s : std::as_const(m_queue))
599 s->Clear();
600 LOG(VB_GUI, LOG_DEBUG, "Aborted Slidebuffer");
601}
602
603
610{
611 // Require at least 4 slides: 2 for transitions, 1 to handle further requests
612 // and 1 to prevent solitary slide from being used whilst it is loading
613 size_t size = std::max(SLIDE_BUFFER_SIZE, 4_UZ);
614
615 // Fill buffer with slides cloned from the XML image widget
616
617 // Create first as a child of the XML image.
618 auto *slide = new Slide(nullptr, "slide0", &image);
619
620 // Buffer is notified when it has loaded image
621 connect(slide, &Slide::ImageLoaded,
622 this, qOverload<Slide*>(&SlideBuffer::Flush));
623
624 m_queue.enqueue(slide);
625
626 // Rest are simple clones of first
627 for (size_t i = 1; i < size; ++i)
628 {
629 slide = new Slide(&image, QString("slide%1").arg(i), slide);
630
631 // All slides (except first) start off hidden
632 slide->SetVisible(false);
633
634 // Buffer is notified when it has loaded image
635 connect(slide, &Slide::ImageLoaded,
636 this, qOverload<Slide*>(&SlideBuffer::Flush));
637
638 m_queue.enqueue(slide);
639 }
640
641 m_nextLoad = 1;
642}
643
644
650{
651 QMutexLocker lock(&m_mutexQ);
652
653 QString state;
654 for (int i = 0; i < m_queue.size(); ++i)
655 {
656 QChar code(m_queue.at(i)->GetDebugState());
657 state += (i == m_nextLoad ? code.toUpper() : code);
658 }
659 return QString("[%1] (%2)").arg(state, m_queue.head()->objectName());
660}
661
662
670bool SlideBuffer::Load(const ImagePtrK& im, int direction)
671{
672 if (!im)
673 return false;
674
675 QMutexLocker lock(&m_mutexQ);
676
677 // Start loading image in next available slide
678 Slide *slide = m_queue.at(m_nextLoad);
679
680 // Further load requests will go to same slide if no free ones are available
681 if (m_nextLoad < m_queue.size() - 1)
682 ++m_nextLoad;
683
684 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("Loading %1 in %2, %3")
685 .arg(im->m_filePath, slide->objectName(), BufferState()));
686
687 return slide->LoadSlide(im, direction, true);
688}
689
690
696{
697 if (!im)
698 return;
699
700 QMutexLocker lock(&m_mutexQ);
701
702 // Start loading image in next available slide
703 Slide *slide = m_queue.at(m_nextLoad);
704
705 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("Preloading %1 in %2, %3")
706 .arg(im->m_filePath, slide->objectName(), BufferState()));
707
708 // Load silently
709 slide->LoadSlide(im);
710}
711
712
718{
719 QMutexLocker lock(&m_mutexQ);
720
721 // Reset slide & return to buffer for re-use
722 Slide *slide = m_queue.dequeue();
723 slide->Clear();
724 m_queue.enqueue(slide);
725
726 QString name = slide->objectName();
727
728 // Free constrained load ptr now a spare slide is available
729 if (!m_queue.at(--m_nextLoad)->IsEmpty())
730 ++m_nextLoad;
731
732 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("Released %1").arg(name));
733
734 // Flush any pending slides that originate from multiple requests (skipping)
735 Flush(m_queue.head(), "Pending");
736}
737
738
745void SlideBuffer::Flush(Slide *slide, const QString& reason)
746{
747 QMutexLocker lock(&m_mutexQ);
748
749 // Determine number of consecutive slides that are now available after head
750 // Include last slide to ensure transition speed is consistent: it will never
751 // be displayed because queue size is always > 2
752 int available = 1;
753 while (available < m_queue.size() && m_queue.at(available)->IsLoaded())
754 ++available;
755
756 if (available == 1)
757 return;
758
759 // Notify that more slides are available
760 ImagePtrK im = slide->GetImageData();
761 QString path = im ? im->m_filePath : "Unknown";
762
763 LOG(VB_FILE, LOG_DEBUG, SBLOC + QString("%1 %2 in %3, %4")
764 .arg(reason, path, slide->objectName(), BufferState()));
765
766 emit SlideReady(--available);
767}
768
770{
771 Flush(slide, "Loaded");
772};
Base animation class that is driven by a Myth pulse and implements variable speed.
Definition: galleryslide.h:27
virtual void Finished()
To be called when animation completes.
Definition: galleryslide.h:39
void finished()
Signals animation has finished.
virtual void Start(bool forwards, float speed=1.0)
Initialise & start base animation.
float m_speed
Real-time = 1.0, Double-speed = 2.0.
Definition: galleryslide.h:48
bool m_forwards
Play direction.
Definition: galleryslide.h:46
bool m_running
True whilst animation is active.
Definition: galleryslide.h:47
A single animation controlling alpha, zoom, rotation and position.
Definition: galleryslide.h:55
@ HorizontalZoom
Definition: galleryslide.h:58
std::chrono::milliseconds m_lastUpdate
Definition: galleryslide.h:85
UIEffects::Centre m_centre
Definition: galleryslide.h:81
void updateCurrentValue(const QVariant &value) override
Update animated value.
void Start(bool forwards=true, float speed=1.0) override
Start a single animation.
Slide * m_parent
Image to be animated.
Definition: galleryslide.h:79
void Pulse() override
Progress single animation.
void Set(const QVariant &from, const QVariant &to, std::chrono::milliseconds duration=500ms, const QEasingCurve &curve=QEasingCurve::InOutCubic, UIEffects::Centre centre=UIEffects::Middle)
Initialises an animation.
Type m_type
Definition: galleryslide.h:80
std::chrono::milliseconds m_elapsed
Current millisec position within animation, 0..duration.
Definition: galleryslide.h:84
void SetSpeed(float speed) override
Definition: galleryslide.h:98
QList< AbstractAnimation * > m_group
Definition: galleryslide.h:104
void Clear() override
Delete all child animations.
void Start(bool forwards, float speed=1.0) override
Initialise & start base animation.
Definition: galleryslide.h:96
virtual void Add(AbstractAnimation *child)
Add child animation to group.
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:18
QRect toQRect(void) const
Definition: mythrect.cpp:405
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:98
bool Load(bool allowLoadInBackground=true, bool forceStat=false)
Load the image(s), wraps ImageLoader::LoadImage()
QHash< int, MythImage * > m_images
Definition: mythuiimage.h:170
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
void SetFilename(const QString &filename)
Must be followed by a call to Load() to load the image.
unsigned int m_curPos
Definition: mythuiimage.h:178
void SetCropRect(int x, int y, int width, int height)
Crop the image using the given rectangle, useful for removing unsightly edges from imported images or...
void LoadComplete()
void SetOrientation(int orientation)
Saves the exif orientation value of the first image in the widget.
The base class on which all widgets and screens are based.
Definition: mythuitype.h:86
void AddChild(MythUIType *child)
Add a child UIType.
Definition: mythuitype.cpp:89
UIEffects m_effects
Definition: mythuitype.h:284
virtual void SetVisible(bool visible)
virtual MythPainter * GetPainter(void)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:610
void SetAngle(float angle)
Definition: mythuitype.cpp:979
void SetRedraw(void)
Definition: mythuitype.cpp:313
void SetVerticalZoom(float zoom)
Definition: mythuitype.cpp:973
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:885
MythUIType * m_parent
Definition: mythuitype.h:297
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:533
void SetAlpha(int newalpha)
Definition: mythuitype.cpp:942
void SetCentre(UIEffects::Centre centre)
Definition: mythuitype.cpp:956
void SetHorizontalZoom(float zoom)
Definition: mythuitype.cpp:967
MythRect m_area
Definition: mythuitype.h:277
Encapsulates Exif orientation processing.
Definition: imagemetadata.h:63
int GetCurrent() const
Determines orientation required for an image.
Specialised animation for panning slideshow images (MythUI doesn't support panning)
Definition: galleryslide.h:148
void updateCurrentValue(const QVariant &value) override
Update pan value.
void SetSpeed(float speed) override
Change speed of group and all child animations.
void Finished() override
A child animation has completed.
void Start(bool forwards, float speed=1.0) override
Start parallel group. All children play simultaneously.
int m_finished
Count of child animations that have finished.
Definition: galleryslide.h:141
void Pulse() override
Progress parallel animations.
void Start(bool forwards, float speed=1.0) override
Start sequential animation.
void Finished() override
A child animation has completed.
void Pulse() override
Progress sequential animation.
int m_current
Index of child currently playing.
Definition: galleryslide.h:123
void SetSpeed(float speed) override
Change speed of current child animation and all subsequent ones.
QString BufferState()
Determines buffer state for debug logging.
void Flush(Slide *slide, const QString &reason)
Signal if any slides are waiting to be displayed.
void ReleaseCurrent()
Move head slide to back of queue and flush waiting slides.
void Initialise(MythUIImage &image)
Construct buffer.
int m_nextLoad
Index of first spare slide, (or last slide if none spare)
Definition: galleryslide.h:253
bool Load(const ImagePtrK &im, int direction)
Assign an image to next available slide, start loading and signal when done.
void Preload(const ImagePtrK &im)
Load an image in next available slide.
QRecursiveMutex m_mutexQ
Queue protection.
Definition: galleryslide.h:251
QQueue< Slide * > m_queue
Queue of slides.
Definition: galleryslide.h:252
void SlideReady(int count)
Signals that buffer has (count) loaded slides awaiting display.
~SlideBuffer() override
A specialised image for slideshows.
Definition: galleryslide.h:157
int m_direction
Navigation that created this image, -1 = Prev, 0 = Update, 1 = Next.
Definition: galleryslide.h:196
QChar GetDebugState() const
Return debug status.
float m_zoom
Current zoom, 1.0 = fullsize.
Definition: galleryslide.h:194
void Pan(QPoint offset)
Initiate pan.
QPoint m_pan
Pan position (0,0) = no pan.
Definition: galleryslide.h:199
void ImageLoaded(Slide *)
Generated when the last requested image has loaded.
PanAnimation * m_panAnimation
Dedicated animation for panning, if supported.
Definition: galleryslide.h:198
void Zoom(int percentage)
Initiate slide zoom.
Animation * m_zoomAnimation
Dedicated animation for zoom, if supported.
Definition: galleryslide.h:197
ImagePtrK GetImageData() const
Definition: galleryslide.h:165
ImagePtrK m_data
The image currently loading/loaded.
Definition: galleryslide.h:191
SlideState m_state
Slide validity.
Definition: galleryslide.h:190
void SetZoom(float zoom)
Sets slide zoom.
bool LoadSlide(const ImagePtrK &im, int direction=0, bool notifyCompletion=false)
Load slide with an image.
void SetPan(QPoint pos)
Sets slide pan.
void Pulse() override
Update pan & zoom animations.
@ kLoading
Definition: galleryslide.h:188
void SlideLoaded()
An image has completed loading.
void Clear()
Reset slide to unused state.
~Slide() override
Destructor.
ImagePtrK m_waitingFor
The most recently requested image. Null for preloads. Differs from m_data when skipping.
Definition: galleryslide.h:193
Slide(MythUIType *parent, const QString &name, MythUIImage *image)
Clone slide from a theme MythUIImage.
Centre m_centre
#define LOC
#define SBLOC
static constexpr size_t SLIDE_BUFFER_SIZE
Defines specialised images used by the Gallery slideshow and the animation framework used by transfor...
#define MAX_ZOOM
Definition: galleryslide.h:20
#define MIN_ZOOM
Definition: galleryslide.h:19
Handles Exif/FFMpeg metadata tags for images.
QSharedPointer< ImageItemK > ImagePtrK
Definition: imagetypes.h:165
@ kVideoFile
A video.
Definition: imagetypes.h:40
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
std::chrono::milliseconds currentMSecsSinceEpochAsDuration(void)
Definition: mythdate.cpp:207
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206