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