]> git.sesse.net Git - vlc/blob - modules/gui/qt4/util/pictureflow.cpp
Qt: initial pass for CoverFlow view of the playlist
[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 <QCache>
37 #include <QHash>
38 #include <QImage>
39 #include <QKeyEvent>
40 #include <QPainter>
41 #include <QPixmap>
42 #include <QTimer>
43 #include <QVector>
44 #include <QWidget>
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     QVector<QImage*> slideImages;
140
141     int angle;
142     int spacing;
143     PFreal offsetX;
144     PFreal offsetY;
145
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     QCache<int, QImage> surfaceCache;
199     QHash<int, QImage*> imageHash;
200
201     void render();
202     void renderSlides();
203     QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1);
204     QImage* surface(int slideIndex);
205 };
206
207 // ------------- PictureFlowState ---------------------------------------
208
209 PictureFlowState::PictureFlowState():
210         backgroundColor(0), slideWidth(150), slideHeight(200),
211         reflectionEffect(PictureFlow::BlurredReflection), centerIndex(0)
212 {
213 }
214
215 PictureFlowState::~PictureFlowState()
216 {
217     for (int i = 0; i < (int)slideImages.count(); i++)
218         delete slideImages[i];
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 / 4;
305
306 #if 1
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     surfaceCache.clear();
421     buffer = QImage();
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         surfaceCache.clear();
436     }
437
438     if ((int)(state->reflectionEffect) != effect) {
439         effect = (int)state->reflectionEffect;
440         surfaceCache.clear();
441     }
442
443     if (dirty)
444         render();
445
446     QPainter painter(widget);
447     painter.drawImage(QPoint(0, 0), buffer);
448 }
449
450 void PictureFlowSoftwareRenderer::init()
451 {
452     if (!widget)
453         return;
454
455     surfaceCache.clear();
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)
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     result->fill(bgcolor);
500
501     // transpose the image, this is to speed-up the rendering
502     // because we process one column at a time
503     // (and much better and faster to work row-wise, i.e in one scanline)
504     for (int x = 0; x < w; x++)
505         for (int y = 0; y < h; y++)
506             result->setPixel(hofs + y, x, img.pixel(x, y));
507
508     if (reflectionEffect != PictureFlow::NoReflection) {
509         // create the reflection
510         int ht = hs - h - hofs;
511         int hte = ht;
512         for (int x = 0; x < w; x++)
513             for (int y = 0; y < ht; y++) {
514                 QRgb color = img.pixel(x, img.height() - y - 1);
515                 result->setPixel(h + hofs + y, x, blendColor(color, bgcolor, 128*(hte - y) / hte));
516             }
517
518         if (reflectionEffect == PictureFlow::BlurredReflection) {
519             // blur the reflection everything first
520             // Based on exponential blur algorithm by Jani Huhtanen
521             QRect rect(hs / 2, 0, hs / 2, w);
522             rect &= result->rect();
523
524             int r1 = rect.top();
525             int r2 = rect.bottom();
526             int c1 = rect.left();
527             int c2 = rect.right();
528
529             int bpl = result->bytesPerLine();
530             int rgba[4];
531             unsigned char* p;
532
533             // how many times blur is applied?
534             // for low-end system, limit this to only 1 loop
535             for (int loop = 0; loop < 2; loop++) {
536                 for (int col = c1; col <= c2; col++) {
537                     p = result->scanLine(r1) + col * 4;
538                     for (int i = 0; i < 3; i++)
539                         rgba[i] = p[i] << 4;
540
541                     p += bpl;
542                     for (int j = r1; j < r2; j++, p += bpl)
543                         for (int i = 0; i < 3; i++)
544                             p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
545                 }
546
547                 for (int row = r1; row <= r2; row++) {
548                     p = result->scanLine(row) + c1 * 4;
549                     for (int i = 0; i < 3; i++)
550                         rgba[i] = p[i] << 4;
551
552                     p += 4;
553                     for (int j = c1; j < c2; j++, p += 4)
554                         for (int i = 0; i < 3; i++)
555                             p[i] = (rgba[i] += (((p[i] << 4) - rgba[i])) >> 1) >> 4;
556                 }
557
558                 for (int col = c1; col <= c2; col++) {
559                     p = result->scanLine(r2) + 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) + c2 * 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
581             // overdraw to leave only the reflection blurred (but not the actual image)
582             for (int x = 0; x < w; x++)
583                 for (int y = 0; y < h; y++)
584                     result->setPixel(hofs + y, x, img.pixel(x, y));
585         }
586     }
587
588     return result;
589 }
590
591 QImage* PictureFlowSoftwareRenderer::surface(int slideIndex)
592 {
593     if (!state)
594         return 0;
595     if (slideIndex < 0)
596         return 0;
597     if (slideIndex >= (int)state->slideImages.count())
598         return 0;
599
600     int key = slideIndex;
601
602     QImage* img = state->slideImages.at(slideIndex);
603     bool empty = img ? img->isNull() : true;
604     if (empty) {
605         surfaceCache.remove(key);
606         imageHash.remove(slideIndex);
607         if (!blankSurface) {
608             int sw = state->slideWidth;
609             int sh = state->slideHeight;
610
611             QImage img = QImage(sw, sh, QImage::Format_RGB32);
612
613             QPainter painter(&img);
614             QPoint p1(sw*4 / 10, 0);
615             QPoint p2(sw*6 / 10, sh);
616             QLinearGradient linearGrad(p1, p2);
617             linearGrad.setColorAt(0, Qt::black);
618             linearGrad.setColorAt(1, Qt::white);
619             painter.setBrush(linearGrad);
620             painter.fillRect(0, 0, sw, sh, QBrush(linearGrad));
621
622             painter.setPen(QPen(QColor(64, 64, 64), 4));
623             painter.setBrush(QBrush());
624             painter.drawRect(2, 2, sw - 3, sh - 3);
625             painter.end();
626
627             blankSurface = prepareSurface(&img, sw, sh, bgcolor, state->reflectionEffect);
628         }
629         return blankSurface;
630     }
631
632     bool exist = imageHash.contains(slideIndex);
633     if (exist)
634         if (img == imageHash.find(slideIndex).value())
635             if (surfaceCache.contains(key))
636                 return surfaceCache[key];
637
638     QImage* sr = prepareSurface(img, state->slideWidth, state->slideHeight, bgcolor, state->reflectionEffect);
639     surfaceCache.insert(key, sr);
640     imageHash.insert(slideIndex, img);
641
642     return sr;
643 }
644
645 // Renders a slide to offscreen buffer. Returns a rect of the rendered area.
646 // col1 and col2 limit the column for rendering.
647 QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, int col2)
648 {
649     int blend = slide.blend;
650     if (!blend)
651         return QRect();
652
653     QImage* src = surface(slide.slideIndex);
654     if (!src)
655         return QRect();
656
657     QRect rect(0, 0, 0, 0);
658
659     int sw = src->height();
660     int sh = src->width();
661     int h = buffer.height();
662     int w = buffer.width();
663
664     if (col1 > col2) {
665         int c = col2;
666         col2 = col1;
667         col1 = c;
668     }
669
670     col1 = (col1 >= 0) ? col1 : 0;
671     col2 = (col2 >= 0) ? col2 : w - 1;
672     col1 = qMin(col1, w - 1);
673     col2 = qMin(col2, w - 1);
674
675     int zoom = 100;
676     int distance = h * 100 / zoom;
677     PFreal sdx = fcos(slide.angle);
678     PFreal sdy = fsin(slide.angle);
679     PFreal xs = slide.cx - state->slideWidth * sdx / 2;
680     PFreal ys = slide.cy - state->slideWidth * sdy / 2;
681     PFreal dist = distance * PFREAL_ONE;
682
683     int xi = qMax((PFreal)0, (w * PFREAL_ONE / 2) + fdiv(xs * h, dist + ys) >> PFREAL_SHIFT);
684     if (xi >= w)
685         return rect;
686
687     bool flag = false;
688     rect.setLeft(xi);
689     for (int x = qMax(xi, col1); x <= col2; x++) {
690         PFreal hity = 0;
691         PFreal fk = rays[x];
692         if (sdy) {
693             fk = fk - fdiv(sdx, sdy);
694             hity = -fdiv((rays[x] * distance - slide.cx + slide.cy * sdx / sdy), fk);
695         }
696
697         dist = distance * PFREAL_ONE + hity;
698         if (dist < 0)
699             continue;
700
701         PFreal hitx = fmul(dist, rays[x]);
702         PFreal hitdist = fdiv(hitx - slide.cx, sdx);
703
704         int column = sw / 2 + (hitdist >> PFREAL_SHIFT);
705         if (column >= sw)
706             break;
707         if (column < 0)
708             continue;
709
710         rect.setRight(x);
711         if (!flag)
712             rect.setLeft(x);
713         flag = true;
714
715         int y1 = h / 2;
716         int y2 = y1 + 1;
717         QRgb* pixel1 = (QRgb*)(buffer.scanLine(y1)) + x;
718         QRgb* pixel2 = (QRgb*)(buffer.scanLine(y2)) + x;
719         QRgb pixelstep = pixel2 - pixel1;
720
721         int center = (sh / 2);
722         int dy = dist / h;
723         int p1 = center * PFREAL_ONE - dy / 2;
724         int p2 = center * PFREAL_ONE + dy / 2;
725
726         const QRgb *ptr = (const QRgb*)(src->scanLine(column));
727         if (blend == 256)
728             while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
729                 *pixel1 = ptr[p1 >> PFREAL_SHIFT];
730                 *pixel2 = ptr[p2 >> PFREAL_SHIFT];
731                 p1 -= dy;
732                 p2 += dy;
733                 y1--;
734                 y2++;
735                 pixel1 -= pixelstep;
736                 pixel2 += pixelstep;
737             }
738         else
739             while ((y1 >= 0) && (y2 < h) && (p1 >= 0)) {
740                 QRgb c1 = ptr[p1 >> PFREAL_SHIFT];
741                 QRgb c2 = ptr[p2 >> PFREAL_SHIFT];
742                 *pixel1 = blendColor(c1, bgcolor, blend);
743                 *pixel2 = blendColor(c2, bgcolor, blend);
744                 p1 -= dy;
745                 p2 += dy;
746                 y1--;
747                 y2++;
748                 pixel1 -= pixelstep;
749                 pixel2 += pixelstep;
750             }
751     }
752
753     rect.setTop(0);
754     rect.setBottom(h - 1);
755     return rect;
756 }
757
758 void PictureFlowSoftwareRenderer::renderSlides()
759 {
760     int nleft = state->leftSlides.count();
761     int nright = state->rightSlides.count();
762
763     QRect r = renderSlide(state->centerSlide);
764     int c1 = r.left();
765     int c2 = r.right();
766
767     for (int index = 0; index < nleft; index++) {
768         QRect rs = renderSlide(state->leftSlides[index], 0, c1 - 1);
769         if (!rs.isEmpty())
770             c1 = rs.left();
771     }
772     for (int index = 0; index < nright; index++) {
773         QRect rs = renderSlide(state->rightSlides[index], c2 + 1, buffer.width());
774         if (!rs.isEmpty())
775             c2 = rs.right();
776     }
777 }
778
779 // Render the slides. Updates only the offscreen buffer.
780 void PictureFlowSoftwareRenderer::render()
781 {
782     buffer.fill(state->backgroundColor);
783     renderSlides();
784     dirty = false;
785 }
786
787 // -----------------------------------------
788
789 class PictureFlowPrivate
790 {
791 public:
792     PictureFlowState* state;
793     PictureFlowAnimator* animator;
794     PictureFlowAbstractRenderer* renderer;
795     QTimer triggerTimer;
796 };
797
798
799 PictureFlow::PictureFlow(QWidget* parent): QWidget(parent)
800 {
801     d = new PictureFlowPrivate;
802
803     d->state = new PictureFlowState;
804     d->state->reset();
805     d->state->reposition();
806
807     d->renderer = new PictureFlowSoftwareRenderer;
808     d->renderer->state = d->state;
809     d->renderer->widget = this;
810     d->renderer->init();
811
812     d->animator = new PictureFlowAnimator;
813     d->animator->state = d->state;
814     QObject::connect(&d->animator->animateTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
815
816     QObject::connect(&d->triggerTimer, SIGNAL(timeout()), this, SLOT(render()));
817
818     setAttribute(Qt::WA_StaticContents, true);
819     setAttribute(Qt::WA_OpaquePaintEvent, true);
820     setAttribute(Qt::WA_NoSystemBackground, true);
821 }
822
823 PictureFlow::~PictureFlow()
824 {
825     delete d->renderer;
826     delete d->animator;
827     delete d->state;
828     delete d;
829 }
830
831 int PictureFlow::slideCount() const
832 {
833     return d->state->slideImages.count();
834 }
835
836 QColor PictureFlow::backgroundColor() const
837 {
838     return QColor(d->state->backgroundColor);
839 }
840
841 void PictureFlow::setBackgroundColor(const QColor& c)
842 {
843     d->state->backgroundColor = c.rgb();
844     triggerRender();
845 }
846
847 QSize PictureFlow::slideSize() const
848 {
849     return QSize(d->state->slideWidth, d->state->slideHeight);
850 }
851
852 void PictureFlow::setSlideSize(QSize size)
853 {
854     d->state->slideWidth = size.width();
855     d->state->slideHeight = size.height();
856     d->state->reposition();
857     triggerRender();
858 }
859
860 PictureFlow::ReflectionEffect PictureFlow::reflectionEffect() const
861 {
862     return d->state->reflectionEffect;
863 }
864
865 void PictureFlow::setReflectionEffect(ReflectionEffect effect)
866 {
867     d->state->reflectionEffect = effect;
868     triggerRender();
869 }
870
871 QImage PictureFlow::slide(int index) const
872 {
873     QImage* i = 0;
874     if ((index >= 0) && (index < slideCount()))
875         i = d->state->slideImages[index];
876     return i ? QImage(*i) : QImage();
877 }
878
879 void PictureFlow::addSlide(const QImage& image)
880 {
881     int c = d->state->slideImages.count();
882     d->state->slideImages.resize(c + 1);
883     d->state->slideImages[c] = new QImage(image);
884     triggerRender();
885 }
886
887 void PictureFlow::addSlide(const QPixmap& pixmap)
888 {
889     addSlide(pixmap.toImage());
890 }
891
892 void PictureFlow::removeSlide(int index)
893 {
894     int c = d->state->slideImages.count();
895     if (index >= 0 && index < c) {
896         d->state->slideImages.remove(index);
897         triggerRender();
898     }
899 }
900
901 void PictureFlow::setSlide(int index, const QImage& image)
902 {
903     if ((index >= 0) && (index < slideCount())) {
904         QImage* i = image.isNull() ? 0 : new QImage(image);
905         delete d->state->slideImages[index];
906         d->state->slideImages[index] = i;
907         triggerRender();
908     }
909 }
910
911 void PictureFlow::setSlide(int index, const QPixmap& pixmap)
912 {
913     setSlide(index, pixmap.toImage());
914 }
915
916 int PictureFlow::centerIndex() const
917 {
918     return d->state->centerIndex;
919 }
920
921 void PictureFlow::setCenterIndex(int index)
922 {
923     index = qMin(index, slideCount() - 1);
924     index = qMax(index, 0);
925     d->state->centerIndex = index;
926     d->state->reset();
927     d->animator->stop(index);
928     triggerRender();
929 }
930
931 void PictureFlow::clear()
932 {
933     int c = d->state->slideImages.count();
934     for (int i = 0; i < c; i++)
935         delete d->state->slideImages[i];
936     d->state->slideImages.resize(0);
937
938     d->state->reset();
939     triggerRender();
940 }
941
942 void PictureFlow::render()
943 {
944     d->renderer->dirty = true;
945     update();
946 }
947
948 void PictureFlow::triggerRender()
949 {
950     d->triggerTimer.setSingleShot(true);
951     d->triggerTimer.start(0);
952 }
953
954 void PictureFlow::showPrevious()
955 {
956     int step = d->animator->step;
957     int center = d->state->centerIndex;
958
959     if (step > 0)
960         d->animator->start(center);
961
962     if (step == 0)
963         if (center > 0)
964             d->animator->start(center - 1);
965
966     if (step < 0)
967         d->animator->target = qMax(0, center - 2);
968 }
969
970 void PictureFlow::showNext()
971 {
972     int step = d->animator->step;
973     int center = d->state->centerIndex;
974
975     if (step < 0)
976         d->animator->start(center);
977
978     if (step == 0)
979         if (center < slideCount() - 1)
980             d->animator->start(center + 1);
981
982     if (step > 0)
983         d->animator->target = qMin(center + 2, slideCount() - 1);
984 }
985
986 void PictureFlow::showSlide(int index)
987 {
988     index = qMax(index, 0);
989     index = qMin(slideCount() - 1, index);
990     if (index == d->state->centerSlide.slideIndex)
991         return;
992
993     d->animator->start(index);
994 }
995
996 void PictureFlow::keyPressEvent(QKeyEvent* event)
997 {
998     if (event->key() == Qt::Key_Left) {
999         if (event->modifiers() == Qt::ControlModifier)
1000             showSlide(centerIndex() - 10);
1001         else
1002             showPrevious();
1003         event->accept();
1004         return;
1005     }
1006
1007     if (event->key() == Qt::Key_Right) {
1008         if (event->modifiers() == Qt::ControlModifier)
1009             showSlide(centerIndex() + 10);
1010         else
1011             showNext();
1012         event->accept();
1013         return;
1014     }
1015
1016     event->ignore();
1017 }
1018
1019 void PictureFlow::mousePressEvent(QMouseEvent* event)
1020 {
1021     if (event->x() > width() / 2)
1022         showNext();
1023     else
1024         showPrevious();
1025 }
1026
1027 void PictureFlow::paintEvent(QPaintEvent* event)
1028 {
1029     Q_UNUSED(event);
1030     d->renderer->paint();
1031 }
1032
1033 void PictureFlow::resizeEvent(QResizeEvent* event)
1034 {
1035     triggerRender();
1036     QWidget::resizeEvent(event);
1037 }
1038
1039 void PictureFlow::updateAnimation()
1040 {
1041     int old_center = d->state->centerIndex;
1042     d->animator->update();
1043     triggerRender();
1044     if (d->state->centerIndex != old_center)
1045         emit centerIndexChanged(d->state->centerIndex);
1046 }
1047