]> git.sesse.net Git - vlc/blob - modules/gui/qt4/util/pictureflow.cpp
Qt/ML: Pictureflow should work with VLCModel
[vlc] / modules / gui / qt4 / util / pictureflow.cpp
1 /*
2   PictureFlow - animated image show widget
3   http://pictureflow.googlecode.com
4
5   Copyright (C) 2009 Ariya Hidayat (ariya@kde.org)
6   Copyright (C) 2008 Ariya Hidayat (ariya@kde.org)
7   Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
8
9   Permission is hereby granted, free of charge, to any person obtaining a copy
10   of this software and associated documentation files (the "Software"), to deal
11   in the Software without restriction, including without limitation the rights
12   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13   copies of the Software, and to permit persons to whom the Software is
14   furnished to do so, subject to the following conditions:
15
16   The above copyright notice and this permission notice shall be included in
17   all copies or substantial portions of the Software.
18
19   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25   THE SOFTWARE.
26 */
27
28 #include "pictureflow.hpp"
29 #include "components/playlist/ml_model.hpp"
30
31 // detect Qt version
32 #if QT_VERSION < 0x040300
33 #error PictureFlow widgets need Qt 4.3 or later
34 #endif
35
36 #include <QApplication>
37 #include <QImage>
38 #include <QKeyEvent>
39 #include <QPainter>
40 #include <QPixmap>
41 #include <QTimer>
42 #include <QVector>
43 #include <QWidget>
44 #include <QHash>
45 #include "../components/playlist/playlist_model.hpp" /* getArtPixmap etc */
46 #include "../components/playlist/sorting.h"          /* Columns List */
47 #include "input_manager.hpp"
48
49 // for fixed-point arithmetic, we need minimum 32-bit long
50 // long long (64-bit) might be useful for multiplication and division
51 typedef long PFreal;
52 #define PFREAL_SHIFT 10
53 #define PFREAL_ONE (1 << PFREAL_SHIFT)
54
55 #define IANGLE_MAX 1024
56 #define IANGLE_MASK 1023
57
58 inline PFreal fmul(PFreal a, PFreal b)
59 {
60     return ((long long)(a))*((long long)(b)) >> PFREAL_SHIFT;
61 }
62
63 inline PFreal fdiv(PFreal num, PFreal den)
64 {
65     long long p = (long long)(num) << (PFREAL_SHIFT * 2);
66     long long q = p / (long long)den;
67     long long r = q >> PFREAL_SHIFT;
68
69     return r;
70 }
71
72 inline PFreal fsin(int iangle)
73 {
74     // warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed!
75     static const PFreal tab[] = {
76         3,    103,    202,    300,    394,    485,    571,    652,
77         726,    793,    853,    904,    947,    980,   1004,   1019,
78         1023,   1018,   1003,    978,    944,    901,    849,    789,
79         721,    647,    566,    479,    388,    294,    196,     97,
80         -4,   -104,   -203,   -301,   -395,   -486,   -572,   -653,
81         -727,   -794,   -854,   -905,   -948,   -981,  -1005,  -1020,
82         -1024,  -1019,  -1004,   -979,   -945,   -902,   -850,   -790,
83         -722,   -648,   -567,   -480,   -389,   -295,   -197,    -98,
84         3
85     };
86
87     while (iangle < 0)
88         iangle += IANGLE_MAX;
89     iangle &= IANGLE_MASK;
90
91     int i = (iangle >> 4);
92     PFreal p = tab[i];
93     PFreal q = tab[(i+1)];
94     PFreal g = (q - p);
95     return p + g *(iangle - i*16) / 16;
96 }
97
98 inline PFreal fcos(int iangle)
99 {
100     return fsin(iangle + (IANGLE_MAX >> 2));
101 }
102
103 /* ----------------------------------------------------------
104
105 PictureFlowState stores the state of all slides, i.e. all the necessary
106 information to be able to render them.
107
108 PictureFlowAnimator is responsible to move the slides during the
109 transition between slides, to achieve the effect similar to Cover Flow,
110 by changing the state.
111
112 PictureFlowSoftwareRenderer (or PictureFlowOpenGLRenderer) is
113 the actual 3-d renderer. It should render all slides given the state
114 (an instance of PictureFlowState).
115
116 Instances of all the above three classes are stored in
117 PictureFlowPrivate.
118
119 ------------------------------------------------------- */
120
121 struct SlideInfo {
122     int slideIndex;
123     int angle;
124     PFreal cx;
125     PFreal cy;
126     int blend;
127 };
128
129 class PictureFlowState
130 {
131 public:
132     PictureFlowState();
133     ~PictureFlowState();
134
135     void reposition();
136     void reset();
137
138     QRgb backgroundColor;
139     int slideWidth;
140     int slideHeight;
141     PictureFlow::ReflectionEffect reflectionEffect;
142
143     int angle;
144     int spacing;
145     PFreal offsetX;
146     PFreal offsetY;
147
148     VLCModel *model;
149     SlideInfo centerSlide;
150     QVector<SlideInfo> leftSlides;
151     QVector<SlideInfo> rightSlides;
152     int centerIndex;
153 };
154
155 class PictureFlowAnimator
156 {
157 public:
158     PictureFlowAnimator();
159     PictureFlowState* state;
160
161     void start(int slide);
162     void stop(int slide);
163     void update();
164
165     int target;
166     int step;
167     int frame;
168     QTimer animateTimer;
169 };
170
171 class PictureFlowAbstractRenderer
172 {
173 public:
174     PictureFlowAbstractRenderer(): state(0), dirty(false), widget(0) {}
175     virtual ~PictureFlowAbstractRenderer() {}
176
177     PictureFlowState* state;
178     bool dirty;
179     QWidget* widget;
180
181     virtual void init() = 0;
182     virtual void paint() = 0;
183 };
184
185 class PictureFlowSoftwareRenderer: public PictureFlowAbstractRenderer
186 {
187 public:
188     PictureFlowSoftwareRenderer();
189     ~PictureFlowSoftwareRenderer();
190
191     virtual void init();
192     virtual void paint();
193
194 private:
195     QSize size;
196     QRgb bgcolor;
197     int effect;
198     QImage buffer;
199     QVector<PFreal> rays;
200     QImage* blankSurface;
201
202     void render();
203     void renderSlides();
204     QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1);
205     QImage* surface(QModelIndex);
206     QHash<QString, QImage*> cache;
207 };
208
209 // ------------- PictureFlowState ---------------------------------------
210
211 PictureFlowState::PictureFlowState():
212         backgroundColor(0), slideWidth(150), slideHeight(200),
213         reflectionEffect(PictureFlow::BlurredReflection), centerIndex(0)
214 {
215 }
216
217 PictureFlowState::~PictureFlowState()
218 {
219 }
220
221 // readjust the settings, call this when slide dimension is changed
222 void PictureFlowState::reposition()
223 {
224     angle = 70 * IANGLE_MAX / 360;  // approx. 70 degrees tilted
225
226     offsetX = slideWidth / 2 * (PFREAL_ONE - fcos(angle));
227     offsetY = slideWidth / 2 * fsin(angle);
228     offsetX += slideWidth * PFREAL_ONE;
229     offsetY += slideWidth * PFREAL_ONE / 4;
230     spacing = 40;
231 }
232
233 // adjust slides so that they are in "steady state" position
234 void PictureFlowState::reset()
235 {
236     centerSlide.angle = 0;
237     centerSlide.cx = 0;
238     centerSlide.cy = 0;
239     centerSlide.slideIndex = centerIndex;
240     centerSlide.blend = 256;
241
242     leftSlides.resize(6);
243     for (int i = 0; i < (int)leftSlides.count(); i++) {
244         SlideInfo& si = leftSlides[i];
245         si.angle = angle;
246         si.cx = -(offsetX + spacing * i * PFREAL_ONE);
247         si.cy = offsetY;
248         si.slideIndex = centerIndex - 1 - i;
249         si.blend = 256;
250         if (i == (int)leftSlides.count() - 2)
251             si.blend = 128;
252         if (i == (int)leftSlides.count() - 1)
253             si.blend = 0;
254     }
255
256     rightSlides.resize(6);
257     for (int i = 0; i < (int)rightSlides.count(); i++) {
258         SlideInfo& si = rightSlides[i];
259         si.angle = -angle;
260         si.cx = offsetX + spacing * i * PFREAL_ONE;
261         si.cy = offsetY;
262         si.slideIndex = centerIndex + 1 + i;
263         si.blend = 256;
264         if (i == (int)rightSlides.count() - 2)
265             si.blend = 128;
266         if (i == (int)rightSlides.count() - 1)
267             si.blend = 0;
268     }
269 }
270
271 // ------------- PictureFlowAnimator  ---------------------------------------
272
273 PictureFlowAnimator::PictureFlowAnimator():
274         state(0), target(0), step(0), frame(0)
275 {
276 }
277
278 void PictureFlowAnimator::start(int slide)
279 {
280     target = slide;
281     if (!animateTimer.isActive() && state) {
282         step = (target < state->centerSlide.slideIndex) ? -1 : 1;
283         animateTimer.start(30);
284     }
285 }
286
287 void PictureFlowAnimator::stop(int slide)
288 {
289     step = 0;
290     target = slide;
291     frame = slide << 16;
292     animateTimer.stop();
293 }
294
295 void PictureFlowAnimator::update()
296 {
297     if (!animateTimer.isActive())
298         return;
299     if (step == 0)
300         return;
301     if (!state)
302         return;
303
304     int speed = 16384;
305
306 #if 0
307     // deaccelerate when approaching the target
308     const int max = 2 * 65536;
309
310     int fi = frame;
311     fi -= (target << 16);
312     if (fi < 0)
313         fi = -fi;
314     fi = qMin(fi, max);
315
316     int ia = IANGLE_MAX * (fi - max / 2) / (max * 2);
317     speed = 512 + 16384 * (PFREAL_ONE + fsin(ia)) / PFREAL_ONE;
318 #endif
319
320     frame += speed * step;
321
322     int index = frame >> 16;
323     int pos = frame & 0xffff;
324     int neg = 65536 - pos;
325     int tick = (step < 0) ? neg : pos;
326     PFreal ftick = (tick * PFREAL_ONE) >> 16;
327
328     if (step < 0)
329         index++;
330
331     if (state->centerIndex != index) {
332         state->centerIndex = index;
333         frame = index << 16;
334         state->centerSlide.slideIndex = state->centerIndex;
335         for (int i = 0; i < (int)state->leftSlides.count(); i++)
336             state->leftSlides[i].slideIndex = state->centerIndex - 1 - i;
337         for (int i = 0; i < (int)state->rightSlides.count(); i++)
338             state->rightSlides[i].slideIndex = state->centerIndex + 1 + i;
339     }
340
341     state->centerSlide.angle = (step * tick * state->angle) >> 16;
342     state->centerSlide.cx = -step * fmul(state->offsetX, ftick);
343     state->centerSlide.cy = fmul(state->offsetY, ftick);
344
345     if (state->centerIndex == target) {
346         stop(target);
347         state->reset();
348         return;
349     }
350
351     for (int i = 0; i < (int)state->leftSlides.count(); i++) {
352         SlideInfo& si = state->leftSlides[i];
353         si.angle = state->angle;
354         si.cx = -(state->offsetX + state->spacing * i * PFREAL_ONE + step * state->spacing * ftick);
355         si.cy = state->offsetY;
356     }
357
358     for (int i = 0; i < (int)state->rightSlides.count(); i++) {
359         SlideInfo& si = state->rightSlides[i];
360         si.angle = -state->angle;
361         si.cx = state->offsetX + state->spacing * i * PFREAL_ONE - step * state->spacing * ftick;
362         si.cy = state->offsetY;
363     }
364
365     if (step > 0) {
366         PFreal ftick = (neg * PFREAL_ONE) >> 16;
367         state->rightSlides[0].angle = -(neg * state->angle) >> 16;
368         state->rightSlides[0].cx = fmul(state->offsetX, ftick);
369         state->rightSlides[0].cy = fmul(state->offsetY, ftick);
370     } else {
371         PFreal ftick = (pos * PFREAL_ONE) >> 16;
372         state->leftSlides[0].angle = (pos * state->angle) >> 16;
373         state->leftSlides[0].cx = -fmul(state->offsetX, ftick);
374         state->leftSlides[0].cy = fmul(state->offsetY, ftick);
375     }
376
377     // must change direction ?
378     if (target < index) if (step > 0)
379             step = -1;
380     if (target > index) if (step < 0)
381             step = 1;
382
383     // the first and last slide must fade in/fade out
384     int nleft = state->leftSlides.count();
385     int nright = state->rightSlides.count();
386     int fade = pos / 256;
387
388     for (int index = 0; index < nleft; index++) {
389         int blend = 256;
390         if (index == nleft - 1)
391             blend = (step > 0) ? 0 : 128 - fade / 2;
392         if (index == nleft - 2)
393             blend = (step > 0) ? 128 - fade / 2 : 256 - fade / 2;
394         if (index == nleft - 3)
395             blend = (step > 0) ? 256 - fade / 2 : 256;
396         state->leftSlides[index].blend = blend;
397     }
398     for (int index = 0; index < nright; index++) {
399         int blend = (index < nright - 2) ? 256 : 128;
400         if (index == nright - 1)
401             blend = (step > 0) ? fade / 2 : 0;
402         if (index == nright - 2)
403             blend = (step > 0) ? 128 + fade / 2 : fade / 2;
404         if (index == nright - 3)
405             blend = (step > 0) ? 256 : 128 + fade / 2;
406         state->rightSlides[index].blend = blend;
407     }
408
409 }
410
411 // ------------- PictureFlowSoftwareRenderer ---------------------------------------
412
413 PictureFlowSoftwareRenderer::PictureFlowSoftwareRenderer():
414         PictureFlowAbstractRenderer(), size(0, 0), bgcolor(0), effect(-1), blankSurface(0)
415 {
416 }
417
418 PictureFlowSoftwareRenderer::~PictureFlowSoftwareRenderer()
419 {
420     buffer = QImage();
421     cache.clear();
422     delete blankSurface;
423 }
424
425 void PictureFlowSoftwareRenderer::paint()
426 {
427     if (!widget)
428         return;
429
430     if (widget->size() != size)
431         init();
432
433     if (state->backgroundColor != bgcolor) {
434         bgcolor = state->backgroundColor;
435     }
436
437     if ((int)(state->reflectionEffect) != effect) {
438         effect = (int)state->reflectionEffect;
439     }
440
441     if (dirty)
442         render();
443
444     QPainter painter(widget);
445     painter.drawImage(QPoint(0, 0), buffer);
446
447     QModelIndex index = state->model->index( state->centerIndex, 0, state->model->currentIndex().parent() );
448
449 }
450
451 void PictureFlowSoftwareRenderer::init()
452 {
453     if (!widget)
454         return;
455
456     blankSurface = 0;
457
458     size = widget->size();
459     int ww = size.width();
460     int wh = size.height();
461     int w = (ww + 1) / 2;
462     int h = (wh + 1) / 2;
463
464     buffer = QImage(ww, wh, QImage::Format_RGB32);
465     buffer.fill(bgcolor);
466
467     rays.resize(w*2);
468     for (int i = 0; i < w; i++) {
469         PFreal gg = ((PFREAL_ONE >> 1) + i * PFREAL_ONE) / (2 * h);
470         rays[w-i-1] = -gg;
471         rays[w+i] = gg;
472     }
473
474     dirty = true;
475 }
476
477 // TODO: optimize this with lookup tables
478 static QRgb blendColor(QRgb c1, QRgb c2, int blend)
479 {
480     int r = qRed(c1) * blend / 256 + qRed(c2) * (256 - blend) / 256;
481     int g = qGreen(c1) * blend / 256 + qGreen(c2) * (256 - blend) / 256;
482     int b = qBlue(c1) * blend / 256 + qBlue(c2) * (256 - blend) / 256;
483     return qRgb(r, g, b);
484 }
485
486
487 static QImage* prepareSurface(const QImage* slideImage, int w, int h, QRgb bgcolor,
488                               PictureFlow::ReflectionEffect reflectionEffect, QModelIndex index)
489 {
490     Qt::TransformationMode mode = Qt::SmoothTransformation;
491     QImage img = slideImage->scaled(w, h, Qt::IgnoreAspectRatio, mode);
492
493     // slightly larger, to accomodate for the reflection
494     int hs = h * 2;
495     int hofs = h / 3;
496
497     // offscreen buffer: black is sweet
498     QImage* result = new QImage(hs, w, QImage::Format_RGB32);
499     QFont font( index.data( Qt::FontRole ).value<QFont>() );
500     QPainter imagePainter( result );
501     QTransform rotation;
502     imagePainter.setFont( font );
503     rotation.rotate(90);
504     rotation.scale(1,-1);
505     rotation.translate( 0, hofs );
506     result->fill(bgcolor);
507
508     // transpose the image, this is to speed-up the rendering
509     // because we process one column at a time
510     // (and much better and faster to work row-wise, i.e in one scanline)
511     /*
512     for (int x = 0; x < w; x++)
513         for (int y = 0; y < h; y++)
514             result->setPixel(hofs + y, x, img.pixel(x, y));
515     */
516     imagePainter.drawImage( hofs+h, 0, img );
517     if (reflectionEffect != PictureFlow::NoReflection) {
518         // create the reflection
519         int ht = hs - h - hofs;
520         int hte = ht;
521         for (int x = 0; x < w; x++)
522         {
523             QRgb *line = (QRgb*)(result->scanLine( x ));
524             for (int y = 0; y < ht; y++) {
525                 QRgb color = img.pixel(x, img.height() - y - 1);
526                 line[h+hofs+y] = blendColor( color, bgcolor, 128*(hte-y)/hte );
527                 //result->setPixel(h + hofs + y, x, blendColor(color, bgcolor, 128*(hte - y) / hte));
528             }
529         }
530
531         if (reflectionEffect == PictureFlow::BlurredReflection) {
532             // blur the reflection everything first
533             // Based on exponential blur algorithm by Jani Huhtanen
534             QRect rect(hs / 2, 0, hs / 2, w);
535             rect &= result->rect();
536
537             int r1 = rect.top();
538             int r2 = rect.bottom();
539             int c1 = rect.left();
540             int c2 = rect.right();
541
542             int bpl = result->bytesPerLine();
543             int rgba[4];
544             unsigned char* p;
545
546             // how many times blur is applied?
547             // for low-end system, limit this to only 1 loop
548             for (int loop = 0; loop < 2; loop++) {
549                 for (int col = c1; col <= c2; col++) {
550                     p = result->scanLine(r1) + col * 4;
551                     for (int i = 0; i < 3; i++)
552                         rgba[i] = p[i] << 4;
553
554                     p += bpl;
555                     for (int j = r1; j < r2; j++, p += bpl)
556                         for (int i = 0; i < 3; i++)
557                             p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
558                 }
559
560                 for (int row = r1; row <= r2; row++) {
561                     p = result->scanLine(row) + c1 * 4;
562                     for (int i = 0; i < 3; i++)
563                         rgba[i] = p[i] << 4;
564
565                     p += 4;
566                     for (int j = c1; j < c2; j++, p += 4)
567                         for (int i = 0; i < 3; i++)
568                             p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
569                 }
570
571                 for (int col = c1; col <= c2; col++) {
572                     p = result->scanLine(r2) + col * 4;
573                     for (int i = 0; i < 3; i++)
574                         rgba[i] = p[i] << 4;
575
576                     p -= bpl;
577                     for (int j = r1; j < r2; j++, p -= bpl)
578                         for (int i = 0; i < 3; i++)
579                             p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
580                 }
581
582                 for (int row = r1; row <= r2; row++) {
583                     p = result->scanLine(row) + c2 * 4;
584                     for (int i = 0; i < 3; i++)
585                         rgba[i] = p[i] << 4;
586
587                     p -= 4;
588                     for (int j = c1; j < c2; j++, p -= 4)
589                         for (int i = 0; i < 3; i++)
590                             p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
591                 }
592             }
593
594             // overdraw to leave only the reflection blurred (but not the actual image)
595             imagePainter.setTransform( rotation );
596             imagePainter.drawImage( 0, 0, img );
597             imagePainter.setBrush( QBrush( Qt::lightGray ) );
598             imagePainter.setPen( QColor( Qt::lightGray ) );
599             QFontMetrics fm = imagePainter.fontMetrics();
600             imagePainter.drawText( 0, img.height()+ 13, VLCModel::getMeta( index, COLUMN_TITLE ) );
601             imagePainter.drawText( 0, img.height()+ 13 + fm.xHeight()*2, VLCModel::getMeta( index, COLUMN_ARTIST ) );
602             /*
603             for (int x = 0; x < w; x++)
604                 for (int y = 0; y < h; y++)
605                     result->setPixel(hofs + y, x, img.pixel(x, y));
606             */
607         }
608     }
609
610     return result;
611 }
612
613 QImage* PictureFlowSoftwareRenderer::surface(QModelIndex index)
614 {
615     if (!state || !index.isValid())
616         return 0;
617
618     QImage* img = new QImage(VLCModel::getArtPixmap( index,
619                                          QSize( state->slideWidth, state->slideHeight ) ).toImage());
620
621     QImage* sr = prepareSurface(img, state->slideWidth, state->slideHeight, bgcolor, state->reflectionEffect, index );
622
623     delete img;
624     return sr;
625 }
626
627 // Renders a slide to offscreen buffer. Returns a rect of the rendered area.
628 // col1 and col2 limit the column for rendering.
629 QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, int col2)
630 {
631     int blend = slide.blend;
632     if (!blend)
633         return QRect();
634
635     QModelIndex index;
636
637     QString artURL;
638
639     PLModel* plm = dynamic_cast<PLModel*>( state->model );
640 #ifdef MEDIA_LIBRARY
641     MLModel* mlm = dynamic_cast<MLModel*>( state->model );
642 #endif
643     if( plm != 0 )
644     {
645         index = ((PLModel*)state->model)->index( slide.slideIndex, 0, state->model->currentIndex().parent() );
646         if( !index.isValid() )
647             return QRect();
648
649         PLItem *item = static_cast<PLItem*>( index.internalPointer() );
650         artURL = InputManager::decodeArtURL( item->inputItem() );
651     }
652 #ifdef MEDIA_LIBRARY
653     else if( mlm != 0 )
654     {
655         index = ((MLModel*)state->model)->index( slide.slideIndex, 0, QModelIndex() );
656         if( !index.isValid() )
657             return QRect();
658
659         MLItem *item = static_cast<MLItem*>( index.internalPointer() );
660         artURL = qfu( item->getMedia()->psz_cover );
661     }
662 #endif
663     QString key = QString("%1%2%3%4").arg(VLCModel::getMeta( index, COLUMN_TITLE )).arg( VLCModel::getMeta( index, COLUMN_ARTIST ) ).arg(index.data( VLCModel::IsCurrentRole ).toBool() ).arg( artURL );
664
665     QImage* src;
666     if( cache.contains( key ) )
667        src = cache.value( key );
668     else
669     {
670        src = surface( index );
671        cache.insert( key, src );
672     }
673     if (!src)
674         return QRect();
675
676     QRect rect(0, 0, 0, 0);
677
678     int sw = src->height();
679     int sh = src->width();
680     int h = buffer.height();
681     int w = buffer.width();
682
683     if (col1 > col2) {
684         int c = col2;
685         col2 = col1;
686         col1 = c;
687     }
688
689     col1 = (col1 >= 0) ? col1 : 0;
690     col2 = (col2 >= 0) ? col2 : w - 1;
691     col1 = qMin(col1, w - 1);
692     col2 = qMin(col2, w - 1);
693
694     int zoom = 100;
695     int distance = h * 100 / zoom;
696     PFreal sdx = fcos(slide.angle);
697     PFreal sdy = fsin(slide.angle);
698     PFreal xs = slide.cx - state->slideWidth * sdx / 2;
699     PFreal ys = slide.cy - state->slideWidth * sdy / 2;
700     PFreal dist = distance * PFREAL_ONE;
701
702     int xi = qMax((PFreal)0, (w * PFREAL_ONE / 2) + fdiv(xs * h, dist + ys) >> PFREAL_SHIFT);
703     if (xi >= w)
704     {
705         return rect;
706     }
707
708     bool flag = false;
709     rect.setLeft(xi);
710     for (int x = qMax(xi, col1); x <= col2; x++) {
711         PFreal hity = 0;
712         PFreal fk = rays[x];
713         if (sdy) {
714             fk = fk - fdiv(sdx, sdy);
715             hity = -fdiv((rays[x] * distance - slide.cx + slide.cy * sdx / sdy), fk);
716         }
717
718         dist = distance * PFREAL_ONE + hity;
719         if (dist < 0)
720             continue;
721
722         PFreal hitx = fmul(dist, rays[x]);
723         PFreal hitdist = fdiv(hitx - slide.cx, sdx);
724
725         int column = sw / 2 + (hitdist >> PFREAL_SHIFT);
726         if (column >= sw)
727             break;
728         if (column < 0)
729             continue;
730
731         rect.setRight(x);
732         if (!flag)
733             rect.setLeft(x);
734         flag = true;
735
736         int y1 = h / 2;
737         int y2 = y1 + 1;
738         QRgb* pixel1 = (QRgb*)(buffer.scanLine(y1)) + x;
739         QRgb* pixel2 = (QRgb*)(buffer.scanLine(y2)) + x;
740         QRgb pixelstep = pixel2 - pixel1;
741
742         int center = (sh / 2);
743         int dy = dist / h;
744         int p1 = center * PFREAL_ONE - dy / 2;
745         int p2 = center * PFREAL_ONE + dy / 2;
746
747         const QRgb *ptr = (const QRgb*)(src->scanLine(column));
748         if (blend == 256)
749             while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
750                 *pixel1 = ptr[p1 >> PFREAL_SHIFT];
751                 *pixel2 = ptr[p2 >> PFREAL_SHIFT];
752                 p1 -= dy;
753                 p2 += dy;
754                 y1--;
755                 y2++;
756                 pixel1 -= pixelstep;
757                 pixel2 += pixelstep;
758             }
759         else
760             while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
761                 QRgb c1 = ptr[p1 >> PFREAL_SHIFT];
762                 QRgb c2 = ptr[p2 >> PFREAL_SHIFT];
763                 *pixel1 = blendColor(c1, bgcolor, blend);
764                 *pixel2 = blendColor(c2, bgcolor, blend);
765                 p1 -= dy;
766                 p2 += dy;
767                 y1--;
768                 y2++;
769                 pixel1 -= pixelstep;
770                 pixel2 += pixelstep;
771             }
772     }
773
774     rect.setTop(0);
775     rect.setBottom(h - 1);
776     return rect;
777 }
778
779 void PictureFlowSoftwareRenderer::renderSlides()
780 {
781     int nleft = state->leftSlides.count();
782     int nright = state->rightSlides.count();
783
784     QRect r = renderSlide(state->centerSlide);
785     int c1 = r.left();
786     int c2 = r.right();
787
788     for (int index = 0; index < nleft; index++) {
789         QRect rs = renderSlide(state->leftSlides[index], 0, c1 - 1);
790         if (!rs.isEmpty())
791             c1 = rs.left();
792     }
793     for (int index = 0; index < nright; index++) {
794         QRect rs = renderSlide(state->rightSlides[index], c2 + 1, buffer.width());
795         if (!rs.isEmpty())
796             c2 = rs.right();
797     }
798 }
799
800 // Render the slides. Updates only the offscreen buffer.
801 void PictureFlowSoftwareRenderer::render()
802 {
803     buffer.fill(state->backgroundColor);
804     renderSlides();
805     dirty = false;
806 }
807
808 // -----------------------------------------
809
810 class PictureFlowPrivate
811 {
812 public:
813     PictureFlowState* state;
814     PictureFlowAnimator* animator;
815     PictureFlowAbstractRenderer* renderer;
816     QTimer triggerTimer;
817 };
818
819
820 PictureFlow::PictureFlow(QWidget* parent, VLCModel* _p_model): QWidget(parent)
821 {
822     d = new PictureFlowPrivate;
823     d->state = new PictureFlowState;
824     d->state->model = _p_model;
825     d->state->reset();
826     d->state->reposition();
827
828     d->renderer = new PictureFlowSoftwareRenderer;
829     d->renderer->state = d->state;
830     d->renderer->widget = this;
831     d->renderer->init();
832
833     d->animator = new PictureFlowAnimator;
834     d->animator->state = d->state;
835     QObject::connect(&d->animator->animateTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
836
837     QObject::connect(&d->triggerTimer, SIGNAL(timeout()), this, SLOT(render()));
838
839     setAttribute(Qt::WA_StaticContents, true);
840     setAttribute(Qt::WA_OpaquePaintEvent, true);
841     setAttribute(Qt::WA_NoSystemBackground, true);
842 }
843
844 PictureFlow::~PictureFlow()
845 {
846     delete d->renderer;
847     delete d->animator;
848     delete d->state;
849     delete d;
850 }
851
852 int PictureFlow::slideCount() const
853 {
854     return d->state->model->rowCount( d->state->model->currentIndex().parent() );
855 }
856
857 QColor PictureFlow::backgroundColor() const
858 {
859     return QColor(d->state->backgroundColor);
860 }
861
862 void PictureFlow::setBackgroundColor(const QColor& c)
863 {
864     d->state->backgroundColor = c.rgb();
865     triggerRender();
866 }
867
868 QSize PictureFlow::slideSize() const
869 {
870     return QSize(d->state->slideWidth, d->state->slideHeight);
871 }
872
873 void PictureFlow::setSlideSize(QSize size)
874 {
875     d->state->slideWidth = size.width();
876     d->state->slideHeight = size.height();
877     d->state->reposition();
878     triggerRender();
879 }
880
881 PictureFlow::ReflectionEffect PictureFlow::reflectionEffect() const
882 {
883     return d->state->reflectionEffect;
884 }
885
886 void PictureFlow::setReflectionEffect(ReflectionEffect effect)
887 {
888     d->state->reflectionEffect = effect;
889     triggerRender();
890 }
891
892 int PictureFlow::centerIndex() const
893 {
894     return d->state->centerIndex;
895 }
896
897 void PictureFlow::setCenterIndex(int index)
898 {
899     index = qMin(index, slideCount() - 1);
900     index = qMax(index, 0);
901     d->state->centerIndex = index;
902     d->state->reset();
903     d->animator->stop(index);
904     triggerRender();
905 }
906
907 void PictureFlow::clear()
908 {
909     d->state->reset();
910     triggerRender();
911 }
912
913 void PictureFlow::render()
914 {
915     d->renderer->dirty = true;
916     update();
917 }
918
919 void PictureFlow::triggerRender()
920 {
921     d->triggerTimer.setSingleShot(true);
922     d->triggerTimer.start(0);
923 }
924
925 void PictureFlow::showPrevious()
926 {
927     int step = d->animator->step;
928     int center = d->state->centerIndex;
929
930     if (step > 0)
931         d->animator->start(center);
932
933     if (step == 0)
934         if (center > 0)
935             d->animator->start(center - 1);
936
937     if (step < 0)
938         d->animator->target = qMax(0, center - 2);
939 }
940
941 void PictureFlow::showNext()
942 {
943     int step = d->animator->step;
944     int center = d->state->centerIndex;
945
946     if (step < 0)
947         d->animator->start(center);
948
949     if (step == 0)
950         if (center < slideCount() - 1)
951             d->animator->start(center + 1);
952
953     if (step > 0)
954         d->animator->target = qMin(center + 2, slideCount() - 1);
955 }
956
957 void PictureFlow::showSlide(int index)
958 {
959     index = qMax(index, 0);
960     index = qMin(slideCount() - 1, index);
961     if (index == d->state->centerSlide.slideIndex)
962         return;
963
964     d->animator->start(index);
965 }
966
967 void PictureFlow::keyPressEvent(QKeyEvent* event)
968 {
969     if (event->key() == Qt::Key_Left) {
970         if (event->modifiers() == Qt::ControlModifier)
971             showSlide(centerIndex() - 10);
972         else
973             showPrevious();
974         event->accept();
975         return;
976     }
977
978     if (event->key() == Qt::Key_Right) {
979         if (event->modifiers() == Qt::ControlModifier)
980             showSlide(centerIndex() + 10);
981         else
982             showNext();
983         event->accept();
984         return;
985     }
986
987     event->ignore();
988 }
989
990 void PictureFlow::mousePressEvent(QMouseEvent* event)
991 {
992     if (event->x() > width() / 2 + d->state->slideWidth/2 )
993         showNext();
994     else if (event->x() < width() / 2 - d->state->slideWidth/2 )
995         showPrevious();
996     else if ( d->state->model->currentIndex().row() != d->state->centerIndex )
997         d->state->model->activateItem( d->state->model->index( d->state->centerIndex, 0,
998                                                                d->state->model->currentIndex().parent() ) );
999 }
1000
1001 void PictureFlow::paintEvent(QPaintEvent* event)
1002 {
1003     Q_UNUSED(event);
1004     d->renderer->paint();
1005 }
1006
1007 void PictureFlow::resizeEvent(QResizeEvent* event)
1008 {
1009     triggerRender();
1010     QWidget::resizeEvent(event);
1011 }
1012
1013 void PictureFlow::updateAnimation()
1014 {
1015     int old_center = d->state->centerIndex;
1016     d->animator->update();
1017     triggerRender();
1018     if (d->state->centerIndex != old_center)
1019         emit centerIndexChanged(d->state->centerIndex);
1020 }
1021