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