/*
PictureFlow - animated image show widget
http://pictureflow.googlecode.com
Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
Copyright (C) 2008 Ariya Hidayat (ariya@kde.org)
Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "PictureFlow.h"
// detect Qt version
#if QT_VERSION >= 0x040000
#define PICTUREFLOW_QT4
#elif QT_VERSION >= 0x030000
#define PICTUREFLOW_QT3
#elif QT_VERSION >= 235
#define PICTUREFLOW_QT2
#else
#error PictureFlow widgets need Qt 2, Qt 3 or Qt 4
#endif
#include <QGraphicsSceneResizeEvent>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneMoveEvent>
#ifdef PICTUREFLOW_QT4
#include <QApplication>
#include <QCache>
#include <QHash>
#include <QImage>
#include <QKeyEvent>
#include <QPainter>
#include <QPixmap>
#include <QTimer>
#include <QVector>
#include <QWidget>
#endif
#include <QDebug>
#ifdef PICTUREFLOW_QT3
#include <qapplication.h>
#include <qcache.h>
#include <qimage.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qdatetime.h>
#include <qtimer.h>
#include <qvaluevector.h>
#include <qwidget.h>
#define qMax(x,y) ((x) > (y)) ? (x) : (y)
#define qMin(x,y) ((x) < (y)) ? (x) : (y)
#define QVector QValueVector
#define toImage convertToImage
#define contains find
#define modifiers state
#define ControlModifier ControlButton
#endif
#ifdef PICTUREFLOW_QT2
#include <qapplication.h>
#include <qarray.h>
#include <qcache.h>
#include <qimage.h>
#include <qintdict.h>
#include <qpainter.h>
#include <qpixmap.h>
#include <qdatetime.h>
#include <qtimer.h>
#include <qwidget.h>
#define qMax(x,y) ((x) > (y)) ? (x) : (y)
#define qMin(x,y) ((x) < (y)) ? (x) : (y)
#define QVector QArray
#define toImage convertToImage
#define contains find
#define modifiers state
#define ControlModifier ControlButton
#define flush flushX
#endif
// for fixed-point arithmetic, we need minimum 32-bit long
// long long (64-bit) might be useful for multiplication and division
typedef long PFreal;
#define PFREAL_SHIFT 10
#define PFREAL_ONE (1 << PFREAL_SHIFT)
#define IANGLE_MAX 1024
#define IANGLE_MASK 1023
namespace WRT {
const int slideRatio1 = 2;
const int slideRatio2 = 5;
const int KScrollTimeout = 250;
inline PFreal fmul(PFreal a, PFreal b)
{
return ((long long)(a))*((long long)(b)) >> PFREAL_SHIFT;
}
inline PFreal fdiv(PFreal num, PFreal den)
{
long long p = (long long)(num) << (PFREAL_SHIFT*2);
long long q = p / (long long)den;
long long r = q >> PFREAL_SHIFT;
return r;
}
inline PFreal fsin(int iangle)
{
// warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed!
static const PFreal tab[] = {
3, 103, 202, 300, 394, 485, 571, 652,
726, 793, 853, 904, 947, 980, 1004, 1019,
1023, 1018, 1003, 978, 944, 901, 849, 789,
721, 647, 566, 479, 388, 294, 196, 97,
-4, -104, -203, -301, -395, -486, -572, -653,
-727, -794, -854, -905, -948, -981, -1005, -1020,
-1024, -1019, -1004, -979, -945, -902, -850, -790,
-722, -648, -567, -480, -389, -295, -197, -98,
3
};
while(iangle < 0)
iangle += IANGLE_MAX;
iangle &= IANGLE_MASK;
int i = (iangle >> 4);
PFreal p = tab[i];
PFreal q = tab[(i+1)];
PFreal g = (q - p);
return p + g * (iangle-i*16)/16;
}
inline PFreal fcos(int iangle)
{
return fsin(iangle + (IANGLE_MAX >> 2));
}
/* ----------------------------------------------------------
PictureFlowState stores the state of all slides, i.e. all the necessary
information to be able to render them.
PictureFlowAnimator is responsible to move the slides during the
transition between slides, to achieve the effect similar to Cover Flow,
by changing the state.
PictureFlowSoftwareRenderer (or PictureFlowOpenGLRenderer) is
the actual 3-d renderer. It should render all slides given the state
(an instance of PictureFlowState).
Instances of all the above three classes are stored in
PictureFlowPrivate.
------------------------------------------------------- */
struct SlideInfo
{
int slideIndex;
int angle;
PFreal cx;
PFreal cy;
int blend;
};
class PictureFlowState
{
public:
PictureFlowState();
~PictureFlowState();
void reposition();
void reset();
QRgb backgroundColor;
int slideWidth;
int slideHeight;
ReflectionEffect reflectionEffect;
QVector<QImage*> slideImages;
int angle;
int spacing;
PFreal offsetX;
PFreal offsetY;
SlideInfo centerSlide;
QVector<SlideInfo> leftSlides;
QVector<SlideInfo> rightSlides;
int centerIndex;
};
class PictureFlowAnimator
{
public:
PictureFlowAnimator();
PictureFlowState* state;
void start(int slide);
void stop(int slide);
void update();
int target;
int step;
int frame;
QTimer animateTimer;
};
class PictureFlowAbstractRenderer
{
public:
PictureFlowAbstractRenderer(): state(0), dirty(false), widget(0), gWidget(0), gPainter(0) {}
virtual ~PictureFlowAbstractRenderer() {}
PictureFlowState* state;
bool dirty;
QWidget* widget;
QGraphicsWidget* gWidget;
QPainter* gPainter;
QRect cRect; // central rect
virtual void init() = 0;
virtual void paint() = 0;
};
class PictureFlowSoftwareRenderer: public PictureFlowAbstractRenderer
{
public:
PictureFlowSoftwareRenderer();
~PictureFlowSoftwareRenderer();
virtual void init();
virtual void paint();
QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1);
private:
QSize size;
QRgb bgcolor;
int effect;
QImage buffer;
QVector<PFreal> rays;
QImage* blankSurface;
#ifdef PICTUREFLOW_QT4
QCache<int,QImage> surfaceCache;
QHash<int,QImage*> imageHash;
#endif
#ifdef PICTUREFLOW_QT3
QCache<QImage> surfaceCache;
QMap<int,QImage*> imageHash;
#endif
#ifdef PICTUREFLOW_QT2
QCache<QImage> surfaceCache;
QIntDict<QImage> imageHash;
#endif
void render();
void renderSlides();
QImage* surface(int slideIndex);
};
// ------------- PictureFlowState ---------------------------------------
PictureFlowState::PictureFlowState():
backgroundColor(0), slideWidth(150), slideHeight(200),
reflectionEffect(BlurredReflection), centerIndex(0)
{
}
PictureFlowState::~PictureFlowState()
{
for(int i = 0; i < (int)slideImages.count(); i++)
delete slideImages[i];
}
// readjust the settings, call this when slide dimension is changed
void PictureFlowState::reposition()
{
angle = 60*IANGLE_MAX / 360;
offsetX = slideWidth/2 * (PFREAL_ONE-fcos(angle));
offsetY = slideWidth/2 * fsin(angle);
offsetX += slideWidth * PFREAL_ONE;
offsetY += slideWidth * PFREAL_ONE / 4;
spacing = 20;
}
// adjust slides so that they are in "steady state" position
void PictureFlowState::reset()
{
centerSlide.angle = 0;
centerSlide.cx = 0;
centerSlide.cy = 0;
centerSlide.slideIndex = centerIndex;
centerSlide.blend = 256;
leftSlides.resize(6);
for(int i = 0; i < (int)leftSlides.count(); i++)
{
SlideInfo& si = leftSlides[i];
si.angle = angle;
si.cx = -(offsetX + spacing*i*PFREAL_ONE);
si.cy = offsetY;
si.slideIndex = centerIndex-1-i;
si.blend = 256;
if(i == (int)leftSlides.count()-2)
si.blend = 128;
if(i == (int)leftSlides.count()-1)
si.blend = 0;
}
rightSlides.resize(6);
for(int i = 0; i < (int)rightSlides.count(); i++)
{
SlideInfo& si = rightSlides[i];
si.angle = -angle;
si.cx = offsetX + spacing*i*PFREAL_ONE;
si.cy = offsetY;
si.slideIndex = centerIndex+1+i;
si.blend = 256;
if(i == (int)rightSlides.count()-2)
si.blend = 128;
if(i == (int)rightSlides.count()-1)
si.blend = 0;
}
}
// ------------- PictureFlowAnimator ---------------------------------------
PictureFlowAnimator::PictureFlowAnimator():
state(0), target(0), step(0), frame(0)
{
}
void PictureFlowAnimator::start(int slide)
{
target = slide;
if(!animateTimer.isActive() && state)
{
step = (target < state->centerSlide.slideIndex) ? -1 : 1;
animateTimer.start(30);
}
}
void PictureFlowAnimator::stop(int slide)
{
step = 0;
target = slide;
frame = slide << 16;
animateTimer.stop();
}
void PictureFlowAnimator::update()
{
if(!animateTimer.isActive())
return;
if(step == 0)
return;
if(!state)
return;
int speed = 16384/4;
#if 1
// deaccelerate when approaching the target
// we disabled clicking until the animation is done so this has to be fast enough to not annoy users
const int max = 65536 + 16384; // was 65536*2 but that was too slow when we disabled clicks
int fi = frame;
fi -= (target << 16);
if(fi < 0)
fi = -fi;
fi = qMin(fi, max);
int ia = IANGLE_MAX * (fi-max/2) / (max*2);
speed = 512 + 16384 * (PFREAL_ONE+fsin(ia))/PFREAL_ONE;
#endif
frame += speed*step;
int index = frame >> 16;
int pos = frame & 0xffff;
int neg = 65536 - pos;
int tick = (step < 0) ? neg : pos;
PFreal ftick = (tick * PFREAL_ONE) >> 16;
if(step < 0)
index++;
if(state->centerIndex != index)
{
state->centerIndex = index;
frame = index << 16;
state->centerSlide.slideIndex = state->centerIndex;
for(int i = 0; i < (int)state->leftSlides.count(); i++)
state->leftSlides[i].slideIndex = state->centerIndex-1-i;
for(int i = 0; i < (int)state->rightSlides.count(); i++)
state->rightSlides[i].slideIndex = state->centerIndex+1+i;
}
state->centerSlide.angle = (step * tick * state->angle) >> 16;
state->centerSlide.cx = -step * fmul(state->offsetX, ftick);
state->centerSlide.cy = fmul(state->offsetY, ftick);
if(state->centerIndex == target)
{
stop(target);
state->reset();
return;
}
for(int i = 0; i < (int)state->leftSlides.count(); i++)
{
SlideInfo& si = state->leftSlides[i];
si.angle = state->angle;
si.cx = -(state->offsetX + state->spacing*i*PFREAL_ONE + step*state->spacing*ftick);
si.cy = state->offsetY;
}
for(int i = 0; i < (int)state->rightSlides.count(); i++)
{
SlideInfo& si = state->rightSlides[i];
si.angle = -state->angle;
si.cx = state->offsetX + state->spacing*i*PFREAL_ONE - step*state->spacing*ftick;
si.cy = state->offsetY;
}
if(step > 0)
{
PFreal ftick = (neg * PFREAL_ONE) >> 16;
state->rightSlides[0].angle = -(neg * state->angle) >> 16;
state->rightSlides[0].cx = fmul(state->offsetX, ftick);
state->rightSlides[0].cy = fmul(state->offsetY, ftick);
}
else
{
PFreal ftick = (pos * PFREAL_ONE) >> 16;
state->leftSlides[0].angle = (pos * state->angle) >> 16;
state->leftSlides[0].cx = -fmul(state->offsetX, ftick);
state->leftSlides[0].cy = fmul(state->offsetY, ftick);
}
// must change direction ?
if(target < index) if(step > 0)
step = -1;
if(target > index) if(step < 0)
step = 1;
// the first and last slide must fade in/fade out
int nleft = state->leftSlides.count();
int nright = state->rightSlides.count();
int fade = pos / 256;
for(int index = 0; index < nleft; index++)
{
int blend = 256;
if(index == nleft-1)
blend = (step > 0) ? 0 : 128-fade/2;
if(index == nleft-2)
blend = (step > 0) ? 128-fade/2 : 256-fade/2;
if(index == nleft-3)
blend = (step > 0) ? 256-fade/2 : 256;
state->leftSlides[index].blend = blend;
}
for(int index = 0; index < nright; index++)
{
int blend = (index < nright-2) ? 256 : 128;
if(index == nright-1)
blend = (step > 0) ? fade/2 : 0;
if(index == nright-2)
blend = (step > 0) ? 128+fade/2 : fade/2;
if(index == nright-3)
blend = (step > 0) ? 256 : 128+fade/2;
state->rightSlides[index].blend = blend;
}
}
// ------------- PictureFlowSoftwareRenderer ---------------------------------------
PictureFlowSoftwareRenderer::PictureFlowSoftwareRenderer():
PictureFlowAbstractRenderer(), size(0,0), bgcolor(0), effect(-1), blankSurface(0)
{
#ifdef PICTUREFLOW_QT3
surfaceCache.setAutoDelete(true);
#endif
}
PictureFlowSoftwareRenderer::~PictureFlowSoftwareRenderer()
{
surfaceCache.clear();
buffer = QImage();
delete blankSurface;
}
void PictureFlowSoftwareRenderer::paint()
{
if(!widget && !gWidget)
return;
if(widget && widget->size() != size)
init();
else if (gWidget && gWidget->size().toSize() != size)
init();
if(state->backgroundColor != bgcolor)
{
bgcolor = state->backgroundColor;
surfaceCache.clear();
}
if((int)(state->reflectionEffect) != effect)
{
effect = (int)state->reflectionEffect;
surfaceCache.clear();
}
if(dirty)
render();
if (widget) {
QPainter painter(widget);
painter.drawImage(QPoint(0,0), buffer);
} else if (gWidget && gPainter) {
gPainter->drawImage(QPoint(0,0), buffer);
}
}
void PictureFlowSoftwareRenderer::init()
{
if(!widget && !gWidget)
return;
surfaceCache.clear();
blankSurface = 0;
if (widget)
size = widget->size();
else
size = gWidget->size().toSize();
int ww = size.width();
int wh = size.height();
int w = (ww+1)/2;
int h = (wh+1)/2;
#ifdef PICTUREFLOW_QT4
buffer = QImage(ww, wh, QImage::Format_RGB32);
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
buffer.create(ww, wh, 32);
#endif
buffer.fill(bgcolor);
rays.resize(w*2);
for(int i = 0; i < w; i++)
{
PFreal gg = ((PFREAL_ONE >> 1) + i * PFREAL_ONE) / (2*h);
rays[w-i-1] = -gg;
rays[w+i] = gg;
}
dirty = true;
}
// TODO: optimize this with lookup tables
static QRgb blendColor(QRgb c1, QRgb c2, int blend)
{
int r = qRed(c1) * blend/256 + qRed(c2)*(256-blend)/256;
int g = qGreen(c1) * blend/256 + qGreen(c2)*(256-blend)/256;
int b = qBlue(c1) * blend/256 + qBlue(c2)*(256-blend)/256;
return qRgb(r, g, b);
}
static QImage* prepareSurface(const QImage* slideImage, int w, int h, QRgb bgcolor,
ReflectionEffect reflectionEffect)
{
#ifdef PICTUREFLOW_QT4
Qt::TransformationMode mode = Qt::SmoothTransformation;
QImage img = slideImage->scaled(w, h, Qt::IgnoreAspectRatio, mode);
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
QImage img = slideImage->smoothScale(w, h);
#endif
// slightly larger, to accomodate for the reflection
int hs = h * 2;
int hofs = h / 3;
// offscreen buffer: black is sweet
#ifdef PICTUREFLOW_QT4
QImage* result = new QImage(hs, w, QImage::Format_RGB32);
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
QImage* result = new QImage;
result->create(hs, w, 32);
#endif
result->fill(bgcolor);
// transpose the image, this is to speed-up the rendering
// because we process one column at a time
// (and much better and faster to work row-wise, i.e in one scanline)
for(int x = 0; x < w; x++)
for(int y = 0; y < h; y++)
result->setPixel(hofs + y, x, img.pixel(x, y));
if(reflectionEffect != NoReflection)
{
// create the reflection
int ht = hs - h - hofs;
int hte = ht;
for(int x = 0; x < w; x++)
for(int y = 0; y < ht; y++)
{
QRgb color = img.pixel(x, img.height()-y-1);
result->setPixel(h+hofs+y, x, blendColor(color,bgcolor,128*(hte-y)/hte));
}
if(reflectionEffect == BlurredReflection)
{
// blur the reflection everything first
// Based on exponential blur algorithm by Jani Huhtanen
QRect rect(hs/2, 0, hs/2, w);
rect &= result->rect();
int r1 = rect.top();
int r2 = rect.bottom();
int c1 = rect.left();
int c2 = rect.right();
int bpl = result->bytesPerLine();
int rgba[4];
unsigned char* p;
// how many times blur is applied?
// for low-end system, limit this to only 1 loop
for(int loop = 0; loop < 2; loop++)
{
for(int col = c1; col <= c2; col++)
{
p = result->scanLine(r1) + col*4;
for(int i = 0; i < 3; i++)
rgba[i] = p[i] << 4;
p += bpl;
for(int j = r1; j < r2; j++, p += bpl)
for(int i = 0; i < 3; i++)
p[i] = (rgba[i] += (((p[i]<<4)-rgba[i])) >> 1) >> 4;
}
for(int row = r1; row <= r2; row++)
{
p = result->scanLine(row) + c1*4;
for(int i = 0; i < 3; i++)
rgba[i] = p[i] << 4;
p += 4;
for(int j = c1; j < c2; j++, p+=4)
for(int i = 0; i < 3; i++)
p[i] = (rgba[i] += (((p[i]<<4)-rgba[i])) >> 1) >> 4;
}
for(int col = c1; col <= c2; col++)
{
p = result->scanLine(r2) + col*4;
for(int i = 0; i < 3; i++)
rgba[i] = p[i] << 4;
p -= bpl;
for(int j = r1; j < r2; j++, p -= bpl)
for(int i = 0; i < 3; i++)
p[i] = (rgba[i] += (((p[i]<<4)-rgba[i])) >> 1) >> 4;
}
for(int row = r1; row <= r2; row++)
{
p = result->scanLine(row) + c2*4;
for(int i = 0; i < 3; i++)
rgba[i] = p[i] << 4;
p -= 4;
for(int j = c1; j < c2; j++, p-=4)
for(int i = 0; i < 3; i++)
p[i] = (rgba[i] += (((p[i]<<4)-rgba[i])) >> 1) >> 4;
}
}
// overdraw to leave only the reflection blurred (but not the actual image)
for(int x = 0; x < w; x++)
for(int y = 0; y < h; y++)
result->setPixel(hofs + y, x, img.pixel(x, y));
}
}
return result;
}
QImage* PictureFlowSoftwareRenderer::surface(int slideIndex)
{
if(!state)
return 0;
if(slideIndex < 0)
return 0;
if(slideIndex >= (int)state->slideImages.count())
return 0;
#ifdef PICTUREFLOW_QT4
int key = slideIndex;
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
QString key = QString::number(slideIndex);
#endif
QImage* img = state->slideImages.at(slideIndex);
bool empty = img ? img->isNull() : true;
if(empty)
{
surfaceCache.remove(key);
imageHash.remove(slideIndex);
if(!blankSurface)
{
int sw = state->slideWidth;
int sh = state->slideHeight;
#ifdef PICTUREFLOW_QT4
QImage img = QImage(sw, sh, QImage::Format_RGB32);
QPainter painter(&img);
QPoint p1(sw*4/10, 0);
QPoint p2(sw*6/10, sh);
QLinearGradient linearGrad(p1, p2);
linearGrad.setColorAt(0, Qt::black);
linearGrad.setColorAt(1, Qt::white);
painter.setBrush(linearGrad);
painter.fillRect(0, 0, sw, sh, QBrush(linearGrad));
painter.setPen(QPen(QColor(64,64,64), 4));
painter.setBrush(QBrush());
painter.drawRect(2, 2, sw-3, sh-3);
painter.end();
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
QPixmap pixmap(sw, sh, 32);
QPainter painter(&pixmap);
painter.fillRect(pixmap.rect(), QColor(192,192,192));
painter.fillRect(5, 5, sw-10, sh-10, QColor(64,64,64));
painter.end();
QImage img = pixmap.convertToImage();
#endif
blankSurface = prepareSurface(&img, sw, sh, bgcolor, state->reflectionEffect);
}
return blankSurface;
}
#ifdef PICTUREFLOW_QT4
bool exist = imageHash.contains(slideIndex);
if(exist)
if(img == imageHash.find(slideIndex).value())
#endif
#ifdef PICTUREFLOW_QT3
bool exist = imageHash.find(slideIndex) != imageHash.end();
if(exist)
if(img == imageHash.find(slideIndex).data())
#endif
#ifdef PICTUREFLOW_QT2
if(img == imageHash[slideIndex])
#endif
if(surfaceCache.contains(key))
return surfaceCache[key];
QImage* sr = prepareSurface(img, state->slideWidth, state->slideHeight, bgcolor, state->reflectionEffect);
surfaceCache.insert(key, sr);
imageHash.insert(slideIndex, img);
return sr;
}
// Renders a slide to offscreen buffer. Returns a rect of the rendered area.
// col1 and col2 limit the column for rendering.
QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, int col2)
{
int blend = slide.blend;
if(!blend)
return QRect();
QImage* src = surface(slide.slideIndex);
if(!src)
return QRect();
QRect rect(0, 0, 0, 0);
int sw = src->height();
int sh = src->width();
int h = buffer.height();
int w = buffer.width();
if(col1 > col2)
{
int c = col2;
col2 = col1;
col1 = c;
}
col1 = (col1 >= 0) ? col1 : 0;
col2 = (col2 >= 0) ? col2 : w-1;
col1 = qMin(col1, w-1);
col2 = qMin(col2, w-1);
int zoom = 100;
int distance = h * 100 / zoom;
PFreal sdx = fcos(slide.angle);
PFreal sdy = fsin(slide.angle);
PFreal xs = slide.cx - state->slideWidth * sdx/2;
PFreal ys = slide.cy - state->slideWidth * sdy/2;
PFreal dist = distance * PFREAL_ONE;
int xi = qMax((PFreal)0, (w*PFREAL_ONE/2) + fdiv(xs*h, dist+ys) >> PFREAL_SHIFT);
if(xi >= w)
return rect;
bool flag = false;
rect.setLeft(xi);
int centerY = 0;
for(int x = qMax(xi, col1); x <= col2; x++)
{
PFreal hity = 0;
PFreal fk = rays[x];
if(sdy)
{
fk = fk - fdiv(sdx,sdy);
hity = -fdiv((rays[x]*distance - slide.cx + slide.cy*sdx/sdy), fk);
}
dist = distance*PFREAL_ONE + hity;
if(dist < 0)
continue;
PFreal hitx = fmul(dist, rays[x]);
PFreal hitdist = fdiv(hitx - slide.cx, sdx);
int column = sw/2 + (hitdist >> PFREAL_SHIFT);
if(column >= sw)
break;
if(column < 0)
continue;
rect.setRight(x);
if(!flag)
rect.setLeft(x);
flag = true;
int y1 = h/2;
int y2 = y1+ 1;
centerY = y1;
QRgb* pixel1 = (QRgb*)(buffer.scanLine(y1)) + x;
QRgb* pixel2 = (QRgb*)(buffer.scanLine(y2)) + x;
QRgb pixelstep = pixel2 - pixel1;
int center = (sh/2);
int dy = dist / h;
int p1 = center*PFREAL_ONE - dy/2;
int p2 = center*PFREAL_ONE + dy/2;
const QRgb *ptr = (const QRgb*)(src->scanLine(column));
if(blend == 256)
while((y1 >= 0) && (y2 < h) && (p1 >= 0))
{
*pixel1 = ptr[p1 >> PFREAL_SHIFT];
*pixel2 = ptr[p2 >> PFREAL_SHIFT];
p1 -= dy;
p2 += dy;
y1--;
y2++;
pixel1 -= pixelstep;
pixel2 += pixelstep;
}
else
while((y1 >= 0) && (y2 < h) && (p1 >= 0))
{
QRgb c1 = ptr[p1 >> PFREAL_SHIFT];
QRgb c2 = ptr[p2 >> PFREAL_SHIFT];
*pixel1 = blendColor(c1, bgcolor, blend);
*pixel2 = blendColor(c2, bgcolor, blend);
p1 -= dy;
p2 += dy;
y1--;
y2++;
pixel1 -= pixelstep;
pixel2 += pixelstep;
}
}
int yTop = (3 * centerY - 2 * state->slideHeight) / 3;
rect.setTop(yTop);
rect.setBottom(state->slideHeight + yTop);
return rect;
}
void PictureFlowSoftwareRenderer::renderSlides()
{
int nleft = state->leftSlides.count();
int nright = state->rightSlides.count();
QRect r = renderSlide(state->centerSlide);
int c1 = r.left();
int c2 = r.right();
cRect = r;
for(int index = 0; index < nleft; index++)
{
QRect rs = renderSlide(state->leftSlides[index], 0, c1-1);
if(!rs.isEmpty())
c1 = rs.left();
}
for(int index = 0; index < nright; index++)
{
QRect rs = renderSlide(state->rightSlides[index], c2+1, buffer.width());
if(!rs.isEmpty())
c2 = rs.right();
}
}
// Render the slides. Updates only the offscreen buffer.
void PictureFlowSoftwareRenderer::render()
{
buffer.fill(state->backgroundColor);
renderSlides();
dirty = false;
}
// -----------------------------------------
class PictureFlowPrivate
{
public:
PictureFlowState* state;
PictureFlowAnimator* animator;
PictureFlowAbstractRenderer* renderer;
QTimer triggerTimer;
QTimer scrollTimer;
};
PictureFlow::PictureFlow(QWidget* parent): FlowInterface(parent)
{
d = new PictureFlowPrivate;
d->state = new PictureFlowState;
d->state->reset();
d->state->reposition();
d->renderer = new PictureFlowSoftwareRenderer;
d->renderer->state = d->state;
d->renderer->widget = this;
d->renderer->init();
d->animator = new PictureFlowAnimator;
d->animator->state = d->state;
QObject::connect(&d->animator->animateTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
QObject::connect(&d->triggerTimer, SIGNAL(timeout()), this, SLOT(render()));
QObject::connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(scroll()));
#ifdef PICTUREFLOW_QT4
setAttribute(Qt::WA_StaticContents, true);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setAttribute(Qt::WA_NoSystemBackground, true);
#endif
#ifdef PICTUREFLOW_QT3
setWFlags(getWFlags() | Qt::WStaticContents);
setWFlags(getWFlags() | Qt::WNoAutoErase);
#endif
#ifdef PICTUREFLOW_QT2
setWFlags(getWFlags() | Qt::WPaintClever);
setWFlags(getWFlags() | Qt::WRepaintNoErase);
setWFlags(getWFlags() | Qt::WResizeNoErase);
#endif
}
PictureFlow::~PictureFlow()
{
delete d->renderer;
delete d->animator;
delete d->state;
delete d;
}
int PictureFlow::slideCount() const
{
return d->state->slideImages.count();
}
QColor PictureFlow::backgroundColor() const
{
return QColor(d->state->backgroundColor);
}
void PictureFlow::setBackgroundColor(const QColor& c)
{
d->state->backgroundColor = c.rgb();
triggerRender();
}
QSize PictureFlow::slideSize() const
{
return QSize(d->state->slideWidth, d->state->slideHeight);
}
void PictureFlow::setSlideSize(QSize size)
{
d->state->slideWidth = size.width();
d->state->slideHeight = size.height();
d->state->reposition();
triggerRender();
}
ReflectionEffect PictureFlow::reflectionEffect() const
{
return d->state->reflectionEffect;
}
void PictureFlow::setReflectionEffect(ReflectionEffect effect)
{
d->state->reflectionEffect = effect;
triggerRender();
}
QImage PictureFlow::slide(int index) const
{
QImage* i = 0;
if((index >= 0) && (index < slideCount()))
i = d->state->slideImages[index];
return i ? QImage(*i) : QImage();
}
void PictureFlow::addSlide(const QImage& image)
{
int c = d->state->slideImages.count();
d->state->slideImages.resize(c+1);
d->state->slideImages[c] = new QImage(image);
triggerRender();
}
void PictureFlow::addSlide(const QPixmap& pixmap)
{
addSlide(pixmap.toImage());
}
void PictureFlow::setSlide(int index, const QImage& image)
{
if((index >= 0) && (index < slideCount()))
{
QImage* i = image.isNull() ? 0 : new QImage(image);
delete d->state->slideImages[index];
d->state->slideImages[index] = i;
triggerRender();
}
}
void PictureFlow::setSlide(int index, const QPixmap& pixmap)
{
setSlide(index, pixmap.toImage());
}
int PictureFlow::centerIndex() const
{
return d->state->centerIndex;
}
bool PictureFlow::slideAnimationOngoing() const
{
return d->animator->animateTimer.isActive();
}
void PictureFlow::setCenterIndex(int index)
{
index = qMin(index, slideCount()-1);
index = qMax(index, 0);
d->state->centerIndex = index;
d->state->reset();
d->animator->stop(index);
triggerRender();
}
void PictureFlow::clear()
{
int c = d->state->slideImages.count();
for(int i = 0; i < c; i++)
delete d->state->slideImages[i];
d->state->slideImages.resize(0);
d->state->reset();
triggerRender();
}
void PictureFlow::render()
{
d->renderer->dirty = true;
update();
}
void PictureFlow::triggerRender()
{
#ifdef PICTUREFLOW_QT4
d->triggerTimer.setSingleShot(true);
d->triggerTimer.start(0);
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
d->triggerTimer.start(0, true);
#endif
}
void PictureFlow::showPrevious()
{
int step = d->animator->step;
int center = d->state->centerIndex;
if(step > 0)
d->animator->start(center);
if(step == 0)
if(center > 0)
d->animator->start(center - 1);
if(step < 0)
d->animator->target = qMax(0, center - 2);
}
void PictureFlow::showNext()
{
int step = d->animator->step;
int center = d->state->centerIndex;
if(step < 0)
d->animator->start(center);
if(step == 0)
if(center < slideCount()-1)
d->animator->start(center + 1);
if(step > 0)
d->animator->target = qMin(center + 2, slideCount()-1);
}
void PictureFlow::showSlide(int index)
{
index = qMax(index, 0);
index = qMin(slideCount()-1, index);
if(index == d->state->centerSlide.slideIndex)
return;
d->animator->start(index);
}
void PictureFlow::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Escape:
emit cancel();
return;
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Select:
emit ok(centerIndex());
return;
case Qt::Key_Left:
if(event->modifiers() == Qt::ControlModifier)
showSlide(centerIndex()-10);
else
showPrevious();
event->accept();
return;
case Qt::Key_Right:
if(event->modifiers() == Qt::ControlModifier)
showSlide(centerIndex()+10);
else
showNext();
event->accept();
return;
}
event->ignore();
}
void PictureFlow::mousePressEvent(QMouseEvent* event)
{
m_lastMoveEventPos = event->pos();
if (d->scrollTimer.isActive())
d->scrollTimer.stop();
d->scrollTimer.start(KScrollTimeout);
scroll();
}
void PictureFlow::mouseMoveEvent(QMouseEvent* event)
{
m_lastMoveEventPos = event->pos();
}
void PictureFlow::mouseReleaseEvent(QMouseEvent* event)
{
d->scrollTimer.stop();
if (slideAnimationOngoing()) {
// qDebug() << "pf:mouseReleaseEvent slideanimation running, ignoring click";
return;
}
if(event->x() > ((width() * (slideRatio2 - slideRatio1)) / (2 * slideRatio2)) && event->x() < ((width() * (slideRatio2 + slideRatio1)) / (2 * slideRatio2))) {
emit ok(centerIndex());
return;
}
}
void PictureFlow::scroll()
{
if(m_lastMoveEventPos.x() < ((width() * (slideRatio2 - slideRatio1)) / (2 * slideRatio2))) {
showPrevious();
}
else if (m_lastMoveEventPos.x() > ((width() * (slideRatio2 + slideRatio1)) / (2 * slideRatio2))) {
showNext();
}
}
void PictureFlow::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
d->renderer->paint();
}
void PictureFlow::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QSize s = event->size(); //parentWidget()->rect().size();
setSlideSize(QSize((s.width() * slideRatio1) / slideRatio2, (s.height() * slideRatio1) / slideRatio2));
}
void PictureFlow::updateAnimation()
{
int old_center = d->state->centerIndex;
d->animator->update();
triggerRender();
if(d->state->centerIndex != old_center)
emit centerIndexChanged(d->state->centerIndex);
}
void PictureFlow::init()
{
QSize s = size(); //parentWidget()->rect().size();
setSlideSize(QSize((s.width() * slideRatio1) / slideRatio2, (s.height() * slideRatio1) / slideRatio2));
//resize(s);
//TODO: Disable refrection ?
// setReflectionEffect(PictureFlow::NoReflection);
setBackgroundColor(Qt::black);
// ensure that system cursor is an arrow, not a random icon
// This is not an issue if the platform does not have a system cursor
#ifndef __SYMBIAN32__
setCursor(Qt::ArrowCursor);
#endif
setFocusPolicy(Qt::WheelFocus);
setFocus(Qt::OtherFocusReason);
}
QRect PictureFlow::centralRect() const
{
if (d->renderer) {
/* Render the slide to get the rectangle */
SlideInfo s = d->state->centerSlide;
QRect r = ((PictureFlowSoftwareRenderer*)d->renderer)->renderSlide(s);
return r;
}
else
return QRect();
}
//-----------------------------------------------
// GraphicsPictureFlow class
GraphicsPictureFlow::GraphicsPictureFlow(QObject* parent): GraphicsFlowInterface(NULL)
{
setParent(parent);
d = new PictureFlowPrivate;
d->state = new PictureFlowState;
d->state->reset();
d->state->reposition();
d->renderer = new PictureFlowSoftwareRenderer;
d->renderer->state = d->state;
d->renderer->gWidget = this;
d->renderer->init();
d->animator = new PictureFlowAnimator;
d->animator->state = d->state;
QObject::connect(&d->animator->animateTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
QObject::connect(&d->triggerTimer, SIGNAL(timeout()), this, SLOT(render()));
QObject::connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(scroll()));
#ifdef PICTUREFLOW_QT4
setAttribute(Qt::WA_StaticContents, true);
setAttribute(Qt::WA_OpaquePaintEvent, true);
setAttribute(Qt::WA_NoSystemBackground, true);
#endif
#ifdef PICTUREFLOW_QT3
setWFlags(getWFlags() | Qt::WStaticContents);
setWFlags(getWFlags() | Qt::WNoAutoErase);
#endif
#ifdef PICTUREFLOW_QT2
setWFlags(getWFlags() | Qt::WPaintClever);
setWFlags(getWFlags() | Qt::WRepaintNoErase);
setWFlags(getWFlags() | Qt::WResizeNoErase);
#endif
}
GraphicsPictureFlow::~GraphicsPictureFlow()
{
delete d->renderer;
delete d->animator;
delete d->state;
delete d;
}
int GraphicsPictureFlow::slideCount() const
{
return d->state->slideImages.count();
}
QColor GraphicsPictureFlow::backgroundColor() const
{
return QColor(d->state->backgroundColor);
}
void GraphicsPictureFlow::setBackgroundColor(const QColor& c)
{
d->state->backgroundColor = c.rgb();
triggerRender();
}
QSize GraphicsPictureFlow::slideSize() const
{
return QSize(d->state->slideWidth, d->state->slideHeight);
}
void GraphicsPictureFlow::setSlideSize(QSize size)
{
d->state->slideWidth = size.width();
d->state->slideHeight = size.height();
d->state->reposition();
triggerRender();
}
ReflectionEffect GraphicsPictureFlow::reflectionEffect() const
{
return d->state->reflectionEffect;
}
void GraphicsPictureFlow::setReflectionEffect(ReflectionEffect effect)
{
d->state->reflectionEffect = effect;
triggerRender();
}
QImage GraphicsPictureFlow::slide(int index) const
{
QImage* i = 0;
if((index >= 0) && (index < slideCount()))
i = d->state->slideImages[index];
return i ? QImage(*i) : QImage();
}
void GraphicsPictureFlow::addSlide(const QImage& image)
{
int c = d->state->slideImages.count();
d->state->slideImages.resize(c+1);
d->state->slideImages[c] = new QImage(image);
triggerRender();
}
void GraphicsPictureFlow::addSlide(const QPixmap& pixmap)
{
addSlide(pixmap.toImage());
}
void GraphicsPictureFlow::setSlide(int index, const QImage& image)
{
if((index >= 0) && (index < slideCount()))
{
QImage* i = image.isNull() ? 0 : new QImage(image);
delete d->state->slideImages[index];
d->state->slideImages[index] = i;
triggerRender();
}
}
void GraphicsPictureFlow::setSlide(int index, const QPixmap& pixmap)
{
setSlide(index, pixmap.toImage());
}
int GraphicsPictureFlow::centerIndex() const
{
return d->state->centerIndex;
}
bool GraphicsPictureFlow::slideAnimationOngoing() const
{
return d->animator->animateTimer.isActive();
}
void GraphicsPictureFlow::setCenterIndex(int index)
{
index = qMin(index, slideCount()-1);
index = qMax(index, 0);
d->state->centerIndex = index;
d->state->reset();
d->animator->stop(index);
triggerRender();
}
void GraphicsPictureFlow::clear()
{
int c = d->state->slideImages.count();
for(int i = 0; i < c; i++)
delete d->state->slideImages[i];
d->state->slideImages.resize(0);
d->state->reset();
triggerRender();
}
void GraphicsPictureFlow::render()
{
d->renderer->dirty = true;
update();
}
void GraphicsPictureFlow::triggerRender()
{
#ifdef PICTUREFLOW_QT4
d->triggerTimer.setSingleShot(true);
d->triggerTimer.start(0);
#endif
#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2)
d->triggerTimer.start(0, true);
#endif
}
void GraphicsPictureFlow::showPrevious()
{
int step = d->animator->step;
int center = d->state->centerIndex;
if(step > 0)
d->animator->start(center);
if(step == 0)
if(center > 0)
d->animator->start(center - 1);
if(step < 0)
d->animator->target = qMax(0, center - 2);
}
void GraphicsPictureFlow::showNext()
{
int step = d->animator->step;
int center = d->state->centerIndex;
if(step < 0)
d->animator->start(center);
if(step == 0)
if(center < slideCount()-1)
d->animator->start(center + 1);
if(step > 0)
d->animator->target = qMin(center + 2, slideCount()-1);
}
void GraphicsPictureFlow::showSlide(int index)
{
index = qMax(index, 0);
index = qMin(slideCount()-1, index);
if(index == d->state->centerSlide.slideIndex)
return;
d->animator->start(index);
}
void GraphicsPictureFlow::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Escape:
emit cancel();
return;
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Select:
emit ok(centerIndex());
return;
case Qt::Key_Left:
if(event->modifiers() == Qt::ControlModifier)
showSlide(centerIndex()-10);
else
showPrevious();
event->accept();
return;
case Qt::Key_Right:
if(event->modifiers() == Qt::ControlModifier)
showSlide(centerIndex()+10);
else
showNext();
event->accept();
return;
}
event->ignore();
}
void GraphicsPictureFlow::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
m_lastMoveEventPos = event->pos().toPoint();
if (d->scrollTimer.isActive())
d->scrollTimer.stop();
d->scrollTimer.start(KScrollTimeout);
scroll();
}
void GraphicsPictureFlow::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
m_lastMoveEventPos = event->pos().toPoint();
}
void GraphicsPictureFlow::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
d->scrollTimer.stop();
if (slideAnimationOngoing()) {
// qDebug() << "pf:mouseReleaseEvent slideanimation running, ignoring click";
return;
}
if(event->pos().x() > ((size().width() * (slideRatio2 - slideRatio1)) / (2 * slideRatio2)) && event->pos().x() < ((size().width() * (slideRatio2 + slideRatio1)) / (2 * slideRatio2))) {
emit ok(centerIndex());
return;
}
}
void GraphicsPictureFlow::scroll()
{
if(m_lastMoveEventPos.x() < ((size().width() * (slideRatio2 - slideRatio1)) / (2 * slideRatio2))) {
showPrevious();
}
else if (m_lastMoveEventPos.x() > ((size().width() * (slideRatio2 + slideRatio1)) / (2 * slideRatio2))) {
showNext();
}
}
void GraphicsPictureFlow::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
d->renderer->gPainter = painter;
d->renderer->paint();
d->renderer->gPainter = NULL;
}
void GraphicsPictureFlow::resizeEvent(QGraphicsSceneResizeEvent* event)
{
QGraphicsWidget::resizeEvent(event);
QSize s = event->newSize().toSize();
setSlideSize(QSize((s.width() * slideRatio1) / slideRatio2, (s.height() * slideRatio1) / slideRatio2));
}
void GraphicsPictureFlow::updateAnimation()
{
int old_center = d->state->centerIndex;
d->animator->update();
triggerRender();
if(d->state->centerIndex != old_center)
emit centerIndexChanged(d->state->centerIndex);
}
void GraphicsPictureFlow::init()
{
QSize s = size().toSize(); //parentWidget()->rect().size();
setSlideSize(QSize((s.width() * slideRatio1) / slideRatio2, (s.height() * slideRatio1) / slideRatio2));
//resize(s);
//TODO: Disable refrection ?
// setReflectionEffect(PictureFlow::NoReflection);
setBackgroundColor(Qt::black);
// ensure that system cursor is an arrow, not a random icon
// This is not an issue if the platform does not have a system cursor
#ifndef __SYMBIAN32__
setCursor(Qt::ArrowCursor);
#endif
setFocusPolicy(Qt::WheelFocus);
setFocus(Qt::OtherFocusReason);
}
QRect GraphicsPictureFlow::centralRect() const
{
if (d->renderer) {
/* Render the slide to get the rectangle */
SlideInfo s = d->state->centerSlide;
QRect r = ((PictureFlowSoftwareRenderer*)d->renderer)->renderSlide(s);
return r;
}
else
return QRect();
}
}