/* PictureFlow - animated image show widget http://pictureflow.googlecode.com 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" #include // 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 #ifdef PICTUREFLOW_QT4 #include #include #include #include #include #include #include #include #include #include #endif #ifdef PICTUREFLOW_QT3 #include #include #include #include #include #include #include #include #include #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 #include #include #include #include #include #include #include #include #include #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 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(int angle = 50, float spacingRatio = 0); ~PictureFlowState(); void reposition(); void reset(); QRgb backgroundColor; int slideWidth; int slideHeight; PictureFlow::ReflectionEffect reflectionEffect; QVector slideImages; QVector marks; bool showMarks; QImage mark; int angle; int rawAngle; int spacing; float spacingRatio; PFreal offsetX; PFreal offsetY; SlideInfo centerSlide; QVector leftSlides; QVector rightSlides; int centerIndex; bool flowRightToLeft; }; class PictureFlowAnimator { public: PictureFlowAnimator(); PictureFlowState *state; void start(int slide); void stop(int slide); void update(); int target; int step; int frame; QTimer animateTimer; bool animating; }; class PictureFlowAbstractRenderer { public: PictureFlowAbstractRenderer() : state(0), dirty(false), widget(0) { } virtual ~PictureFlowAbstractRenderer() { } PictureFlowState *state; bool dirty; QWidget *widget; virtual void init() = 0; virtual void paint() = 0; }; class PictureFlowSoftwareRenderer : public PictureFlowAbstractRenderer { public: PictureFlowSoftwareRenderer(); ~PictureFlowSoftwareRenderer(); virtual void init(); virtual void paint(); void render(); private: QSize size; QRgb bgcolor; int effect; QImage buffer; QVector rays; QImage *blankSurface; #ifdef PICTUREFLOW_QT4 QCache surfaceCache; QHash imageHash; #endif #ifdef PICTUREFLOW_QT3 QCache surfaceCache; QMap imageHash; #endif #ifdef PICTUREFLOW_QT2 QCache surfaceCache; QIntDict imageHash; #endif void renderSlides(); QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1); QImage *surface(int slideIndex); }; // ------------- PictureFlowState --------------------------------------- PictureFlowState::PictureFlowState(int a, float sr) : backgroundColor(0), slideWidth(150), slideHeight(200), reflectionEffect(PictureFlow::BlurredReflection), rawAngle(a), spacingRatio(sr), centerIndex(0), flowRightToLeft(false) { } 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 = 70 * IANGLE_MAX / 360; // approx. 70 degrees tilted angle = rawAngle * IANGLE_MAX / 360; offsetX = slideWidth / 2 * (PFREAL_ONE - fcos(angle)); offsetY = slideWidth / 2 * fsin(angle); offsetX += slideWidth * PFREAL_ONE; offsetY += slideWidth * PFREAL_ONE / 3; if (rawAngle < 45) offsetX += offsetX / 4; if (angle > 0) spacing = slideWidth * 0.35; else spacing = slideWidth * spacingRatio + slideWidth * (spacingRatio ? 0.10 : 0.2); } // 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; if (angle == 0 && spacingRatio) leftSlides.resize(4); else 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; if (!flowRightToLeft) si.slideIndex = centerIndex - 1 - i; else si.slideIndex = centerIndex + 1 + i; si.blend = 200; if (i == (int)leftSlides.count() - 2) si.blend = 128; if (i == (int)leftSlides.count() - 1) si.blend = 0; } if (angle == 0 && spacingRatio) rightSlides.resize(4); else 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; if (!flowRightToLeft) si.slideIndex = centerIndex + 1 + i; else si.slideIndex = centerIndex - 1 - i; si.blend = 200; 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), animating(false) { } void PictureFlowAnimator::start(int slide) { target = slide; if (!animateTimer.isActive() && state) { step = (target < state->centerSlide.slideIndex) ? -1 : 1; animateTimer.setSingleShot(true); animateTimer.start(30); // TODO comprobar rendimiento, originalmente era 30 animating = true; } } void PictureFlowAnimator::stop(int slide) { step = 0; target = slide; frame = slide << 16; animateTimer.stop(); animating = false; } void PictureFlowAnimator::update() { /*if(!animateTimer.isActive()) return;*/ if (step == 0) return; if (!state) return; int speed = 16384 / 4; // TODO comprobar rendimiento, originalmente era /4 #if 1 // deaccelerate when approaching the target const int max = 2 * 65536; // TODO cambiado de 2 * a 4 * comprobar rendimiento 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++) { if (!state->flowRightToLeft) state->leftSlides[i].slideIndex = state->centerIndex - 1 - i; else state->leftSlides[i].slideIndex = state->centerIndex + 1 + i; } for (int i = 0; i < (int)state->rightSlides.count(); i++) { if (!state->flowRightToLeft) state->rightSlides[i].slideIndex = state->centerIndex + 1 + i; else state->rightSlides[i].slideIndex = state->centerIndex - 1 - i; } } if (!state->flowRightToLeft) { state->centerSlide.angle = (step * tick * state->angle) >> 16; state->centerSlide.cx = -step * fmul(state->offsetX, ftick); } else { 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; if (!state->flowRightToLeft) si.cx = -(state->offsetX + state->spacing * (i)*PFREAL_ONE + step * state->spacing * ftick); else 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; if (!state->flowRightToLeft) si.cx = state->offsetX + state->spacing * (i)*PFREAL_ONE - step * state->spacing * ftick; else si.cx = state->offsetX + state->spacing * (i)*PFREAL_ONE + step * state->spacing * ftick; si.cy = state->offsetY; } if (step > 0 && !state->flowRightToLeft) { 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 if (!state->flowRightToLeft) { 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); } else if (step < 0) { PFreal ftick = (pos * PFREAL_ONE) >> 16; state->rightSlides[0].angle = -(pos * state->angle) >> 16; state->rightSlides[0].cx = fmul(state->offsetX, ftick); state->rightSlides[0].cy = fmul(state->offsetY, ftick); } else { PFreal ftick = (neg * PFREAL_ONE) >> 16; state->leftSlides[0].angle = (neg * 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 = 200; if (index == nleft - 1) blend = (step > 0) ? 0 : 128 - fade / 2; if (index == nleft - 2) blend = (step > 0) ? 128 - fade / 2 : 200 - (0.5625 * fade / 2); if (index == nleft - 3) blend = (step > 0) ? 200 - (0.5625 * fade / 2) : 200; if (index == 0) blend = (step > 0) ? 200 : 200 + 56 - (0.4375 * fade / 2); state->leftSlides[index].blend = blend; } for (int index = 0; index < nright; index++) { int blend = (index < nright - 2) ? 200 : 128; if (index == nright - 1) blend = (step > 0) ? fade / 2 : 0; if (index == nright - 2) blend = (step > 0) ? 128 + (0.5625 * fade / 2) : (0.5625 * fade / 2); if (index == nright - 3) blend = (step > 0) ? 200 : 128 + (0.5625 * fade / 2); if (index == 0) blend = (step > 0) ? 200 + (0.4375 * fade / 2) : 200; state->rightSlides[index].blend = blend; } state->centerSlide.blend = (step > 0) ? 256 - (0.4375 * fade / 2) : 200 + (0.4375 * fade / 2); } // ------------- 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) return; if (widget->size() != 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(); QPainter painter(widget); painter.drawImage(QPoint(0, 0), buffer); } void PictureFlowSoftwareRenderer::init() { if (!widget) return; surfaceCache.clear(); blankSurface = 0; size = widget->size(); int ww = size.width(); int wh = size.height(); int w = (ww + 1) / 2; int h = (wh + 1) / 2; if (h < 10) // TODO a partir de qué h es seguro?? return; #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, PictureFlow::ReflectionEffect reflectionEffect) { int iw, ih; iw = slideImage->width(); ih = slideImage->height(); int psw, psh; if (iw > ih) { psw = w; psh = w * (1.0 * ih / iw); } else { int h1 = h; psw = h1 * (1.0 * iw / ih); psh = h1; while (psw > w) { h1 -= 2; psw = h1 * (1.0 * iw / ih); psh = h1; } } w = psw; #ifdef PICTUREFLOW_QT4 Qt::TransformationMode mode = Qt::SmoothTransformation; QImage img = slideImage->scaled(psw, psh, 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) int lhof = (h - psh); // int lwof = (w-psw)/2; for (int x = 0; x < psw; x++) for (int y = 0; y < psh; y++) result->setPixel(hofs + y + lhof, x, img.pixel(x, y)); if (reflectionEffect != PictureFlow::NoReflection) { // create the reflection int ht = hs - (h + hofs); int hte = ht; for (int x = 0; x < psw; x++) for (int y = 0; y < ht; y++) { QRgb color; if (y < psh) color = img.pixel(x, psh - y - 1); else color = bgcolor; result->setPixel(h + hofs + y, x, blendColor(color, bgcolor, 80 * (hte - y) / hte)); } } 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.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); // check if this slide must be marked // if(marks[slideIndex]) if (state->showMarks) { if (state->marks[slideIndex]) { QPainter painter(sr); painter.setPen(QColor(255, 0, 0).rgb()); int sh = sr->height(); int jInit = sh * 4 / 5; int iInit = state->slideHeight + state->slideHeight / 3; /*for(int j = jInit; j < sh; j ++) { for(int i = iInit-(j-jInit); i < iInit; i ++) { painter.drawPoint(i,j); } }*/ painter.drawImage(QRect(iInit - (sh - jInit), jInit, sh - jInit, sh - jInit), state->mark); } } 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); 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; 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; } } rect.setTop(0); rect.setBottom(h - 1); 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(); 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(); if (state->slideImages.size() > 0) { int size = buffer.width() * 0.015; int start = buffer.width() * 0.010; QPainter painter(&buffer); painter.setPen(QColor(255, 255, 255).rgb() - state->backgroundColor); painter.setFont(QFont("Arial", start + size * 0.5)); painter.drawText(start, start + size, QString().setNum(state->centerIndex + 1) + "/" + QString().setNum(state->slideImages.size())); } dirty = false; } // ----------------------------------------- class PictureFlowPrivate { public: PictureFlowState *state; PictureFlowAnimator *animator; PictureFlowAbstractRenderer *renderer; QTimer triggerTimer; }; PictureFlow::PictureFlow(QWidget *parent, FlowType flowType) : QWidget(parent) { d = new PictureFlowPrivate; switch (flowType) { case CoverFlowLike: d->state = new PictureFlowState(50, 0); break; case Strip: d->state = new PictureFlowState(0, 1); break; case StripOverlapped: d->state = new PictureFlowState(0, 0); break; default: break; } framesSkip = 0; 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, &QTimer::timeout, this, &PictureFlow::updateAnimation); QObject::connect(&d->triggerTimer, &QTimer::timeout, this, QOverload<>::of(&PictureFlow::render)); #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(); } PictureFlow::ReflectionEffect PictureFlow::reflectionEffect() const { return d->state->reflectionEffect; } void PictureFlow::setReflectionEffect(ReflectionEffect effect) { d->state->reflectionEffect = effect; triggerRender(); } void PictureFlow::setFlowRightToLeft(bool b) { d->state->flowRightToLeft = b; d->state->reset(); 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); d->state->marks.resize(c + 1); d->state->marks[c] = YACReader::Unread; triggerRender(); } void PictureFlow::addSlide(const QPixmap &pixmap) { addSlide(pixmap.toImage()); } void PictureFlow::insertSlide(int index) { d->state->slideImages.insert(index, new QImage()); d->state->marks.insert(index, YACReader::Unread); triggerRender(); } void PictureFlow::removeSlide(int index) { int c = d->state->slideImages.count(); if (index >= 0 && index < c) { d->state->slideImages.remove(index); d->state->marks.remove(index); setCenterIndex(index); } } 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; } 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->marks.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); emit centerIndexChanged(center); } if (step == 0) if (center > 0) { d->animator->start(center - 1); emit centerIndexChanged(center - 1); } if (step < 0) { d->animator->target = qMax(0, center - 2); emit centerIndexChanged(qMax(0, center - 2)); } } void PictureFlow::showNext() { int step = d->animator->step; int center = d->state->centerIndex; if (step < 0) { d->animator->start(center); emit centerIndexChanged(center); } if (step == 0) if (center < slideCount() - 1) { d->animator->start(center + 1); emit centerIndexChanged(center + 1); } if (step > 0) { d->animator->target = qMin(center + 2, slideCount() - 1); emit centerIndexChanged(qMin(center + 2, slideCount() - 1)); } } void PictureFlow::showSlide(unsigned int index) { index = qMax(index, 0); index = qMin(slideCount() - 1, index); if ((int)index == d->state->centerSlide.slideIndex) { return; } int distance = centerIndex() - index; if (abs(distance) > 10) { if (distance < 0) setCenterIndex(centerIndex() + (-distance) - 10); else setCenterIndex(centerIndex() - distance + 10); } d->state->centerIndex = index; d->animator->start(index); } void PictureFlow::keyPressEvent(QKeyEvent *event) { if ((event->key() == Qt::Key_Left && !(d->state->flowRightToLeft)) || (event->key() == Qt::Key_Right && d->state->flowRightToLeft)) { /*if(event->modifiers() == Qt::ControlModifier) showSlide(centerIndex()-10); else*/ showPrevious(); event->accept(); return; } if ((event->key() == Qt::Key_Right && !(d->state->flowRightToLeft)) || (event->key() == Qt::Key_Left && d->state->flowRightToLeft)) { /*if(event->modifiers() == Qt::ControlModifier) showSlide(centerIndex()+10); else*/ showNext(); event->accept(); return; } if (event->key() == Qt::Key_Up) { // TODO emit selected signal return; } event->ignore(); } void PictureFlow::mousePressEvent(QMouseEvent *event) { mousePressEvent(event, 0); } void PictureFlow::mousePressEvent(QMouseEvent *event, int slideWidth) { if ((event->x() > (width() + slideWidth) / 2 && !(d->state->flowRightToLeft)) || (event->x() < (width() - slideWidth) / 2 && d->state->flowRightToLeft)) showNext(); else if ((event->x() < (width() - slideWidth) / 2 && !(d->state->flowRightToLeft)) || (event->x() > (width() + slideWidth) / 2 && d->state->flowRightToLeft)) showPrevious(); // else (centered slide space) } void PictureFlow::paintEvent(QPaintEvent *event) { Q_UNUSED(event); d->renderer->paint(); } void PictureFlow::resizeEvent(QResizeEvent *event) { int heightWidget = event->size().height(); int height, width; height = heightWidget * 0.55; width = height * 0.65; setSlideSize(QSize(width, height)); render(); d->animator->start(centerIndex()); QWidget::resizeEvent(event); } #include void PictureFlow::updateAnimation() // bucle principal { QElapsedTimer timer; timer.start(); bool frameSkiped = false; int old_center = d->state->centerIndex; d->animator->update(); if (framesSkip == 0) render(); // triggerRender(); else { framesSkip--; frameSkiped = true; } if (d->state->centerIndex != old_center) emit centerIndexChangedSilent(d->state->centerIndex); if (d->animator->animating == true) { int difference = 10 - timer.elapsed(); if (difference >= 0 && !frameSkiped) QTimer::singleShot(difference, this, &PictureFlow::updateAnimation); else { QTimer::singleShot(0, this, &PictureFlow::updateAnimation); if (!frameSkiped) framesSkip = -((difference - 10) / 10); } } } void PictureFlow::setFlowType(FlowType flowType) { switch (flowType) { case CoverFlowLike: d->state->rawAngle = 50; d->state->spacingRatio = 0, d->state->reposition(); break; case Strip: d->state->rawAngle = 0; d->state->spacingRatio = 1; d->state->reposition(); break; case StripOverlapped: d->state->rawAngle = 0; d->state->spacingRatio = 0; d->state->reposition(); break; default: break; } d->state->reset(); d->renderer->init(); } void PictureFlow::setMarkImage(const QImage &m) { d->state->mark = m; } void PictureFlow::markSlide(int index, YACReader::YACReaderComicReadStatus readStatus) { if (index < d->state->marks.size()) d->state->marks[index] = readStatus; } void PictureFlow::updateMarks() { d->renderer->init(); repaint(); } void PictureFlow::unmarkSlide(int index) { if (index < d->state->marks.size()) d->state->marks[index] = YACReader::Unread; } void PictureFlow::setMarks(const QVector &m) { d->state->marks = m; updateMarks(); } void PictureFlow::setShowMarks(bool enable) { d->state->showMarks = enable; updateMarks(); } QVector PictureFlow::getMarks() { return d->state->marks; } void PictureFlow::resortCovers(QList newOrder) { QVector slideImagesNew; QVector marksNew; QVector slidesInfo; slidesInfo << d->state->leftSlides << d->state->centerSlide << d->state->rightSlides; QVector slidesInfoNew; int order = 0; foreach (int index, newOrder) { slideImagesNew << d->state->slideImages.at(index); marksNew << d->state->marks.at(index); slidesInfoNew << slidesInfo.at(index); slidesInfoNew.last().slideIndex = order++; } d->state->slideImages = slideImagesNew; d->state->marks = marksNew; d->state->leftSlides = slidesInfoNew.mid(0, d->state->leftSlides.length()); d->state->centerSlide = slidesInfoNew.at(d->state->centerIndex); d->state->leftSlides = slidesInfoNew.mid(d->state->centerIndex + 1, d->state->leftSlides.length()); setCenterIndex(d->state->centerIndex); }