diff --git a/.bzrignore b/.bzrignore index aa6f0da3a9..4063530843 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,7 +1,7 @@ *_ui.py moc_*.cpp src/calibre/plugins -src/calibre/gui2/pictureflow/.build/ +src/calibre/gui2/pictureflow/*.so* src/calibre/gui2/pictureflow/PyQt/.build/ src/calibre/gui2/pictureflow/Makefile src/calibre.egg-info/ diff --git a/Makefile b/Makefile index 6087586232..faec70b566 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ manual: pictureflow : mkdir -p src/calibre/plugins && rm -f src/calibre/plugins/*pictureflow* && \ - cd src/calibre/gui2/pictureflow && rm *.o && \ + cd src/calibre/gui2/pictureflow && rm -f *.o *.so* && \ mkdir -p .build && cd .build && rm -f * && \ qmake ../pictureflow-lib.pro && make && \ cd ../PyQt && \ @@ -33,7 +33,7 @@ pictureflow : cd .build && rm -f * && \ python ../configure.py && make && \ cd ../../../../../.. && \ - cp src/calibre/gui2/pictureflow/.build/libpictureflow.so.?.?.? src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \ + cp src/calibre/gui2/pictureflow/libpictureflow.so.?.?.? src/calibre/gui2/pictureflow/PyQt/.build/pictureflow.so src/calibre/plugins/ && \ python -c "import os, glob; lp = glob.glob('src/calibre/plugins/libpictureflow.so.*')[0]; os.rename(lp, lp[:-4])" diff --git a/osx_installer.py b/osx_installer.py index 42553cbed2..bed0b5b0e7 100644 --- a/osx_installer.py +++ b/osx_installer.py @@ -178,7 +178,7 @@ _check_symlinks_prescript() print 'Building pictureflow' os.chdir('src/calibre/gui2/pictureflow') for f in glob.glob('*.o'): os.unlink(f) - subprocess.check_call([qmake, 'pictureflow-lib.pro']) + subprocess.check_call([qmake, 'pictureflow.pro']) subprocess.check_call(['make']) files.append((os.path.abspath(os.path.realpath('libpictureflow.dylib')), 'libpictureflow.dylib')) os.chdir('PyQt/.build') diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py new file mode 100644 index 0000000000..ad41749210 --- /dev/null +++ b/src/calibre/gui2/cover_flow.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +''' +Module to implement the Cover Flow feature +''' + +import sys, os +from collections import deque + +from PyQt4.QtGui import QImage +from PyQt4.QtCore import Qt, QSize, QTimer, SIGNAL + +from calibre import pictureflow + +if pictureflow is not None: + class FileSystemImages(pictureflow.FlowImages): + + def __init__(self, dirpath): + pictureflow.FlowImages.__init__(self) + self.images = [] + self.captions = [] + for f in os.listdir(dirpath): + f = os.path.join(dirpath, f) + img = QImage(f) + if not img.isNull(): + self.images.append(img) + self.captions.append(os.path.basename(f)) + + def count(self): + return len(self.images) + + def image(self, index): + return self.images[index] + + def caption(self, index): + return self.captions[index] + + def currentChanged(self, index): + print 'current changed:', index + + class DatabaseImages(pictureflow.FlowImages): + + def __init__(self, model, buffer=20): + pictureflow.FlowImages.__init__(self) + self.model = model + self.default_image = QImage(':/images/book.svg') + self.buffer_size = buffer + self.timer = QTimer() + self.connect(self.timer, SIGNAL('timeout()'), self.load) + self.timer.start(50) + self.clear() + + def count(self): + return self.model.rowCount(None) + + def caption(self, index): + return self.model.title(index) + + def clear(self): + self.buffer = {} + self.load_queue = deque() + + def load(self): + if self.load_queue: + index = self.load_queue.popleft() + if self.buffer.has_key(index): + return + img = QImage() + img.loadFromData(self.model.cover(index)) + if img.isNull(): + img = self.default_image + self.buffer[index] = img + + def image(self, index): + img = self.buffer.get(index) + if img is None: + img = QImage() + img.loadFromData(self.model.cover(index)) + if img.isNull(): + img = self.default_image + self.buffer[index] = img + return img + + def currentChanged(self, index): + for key in self.buffer.keys(): + if abs(key - index) > self.buffer_size: + self.buffer.pop(key) + for i in range(max(0, index-self.buffer_size), min(self.count(), index+self.buffer_size)): + if not self.buffer.has_key(i): + self.load_queue.append(i) + + class CoverFlow(pictureflow.PictureFlow): + + def __init__(self, height=300, parent=None): + pictureflow.PictureFlow.__init__(self, parent) + self.setSlideSize(QSize(int(2/3. * height), height)) + self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height)) +else: + CoverFlow = None + +def main(args=sys.argv): + return 0 + +if __name__ == '__main__': + from PyQt4.QtGui import QApplication, QMainWindow + app = QApplication([]) + w = QMainWindow() + cf = CoverFlow() + cf.resize(cf.minimumSize()) + w.resize(cf.minimumSize()+QSize(30, 20)) + path = sys.argv[1] + if path.endswith('.db'): + from calibre.library.database import LibraryDatabase + from calibre.gui2.library import BooksModel + from calibre.gui2 import images_rc + bm = BooksModel() + bm.set_database(LibraryDatabase(path)) + bm.sort(1, Qt.AscendingOrder) + model = DatabaseImages(bm) + else: + model = FileSystemImages(sys.argv[1]) + cf.setImages(model) + cf.connect(cf, SIGNAL('currentChanged(int)'), model.currentChanged) + w.setCentralWidget(cf) + + w.show() + cf.setFocus(Qt.OtherFocusReason) + sys.exit(app.exec_()) \ No newline at end of file diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index e9e62c336e..8f00097ae5 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -92,7 +92,7 @@ class BooksModel(QAbstractTableModel): num -= d return ''.join(result) - def __init__(self, parent): + def __init__(self, parent=None): QAbstractTableModel.__init__(self, parent) self.db = None self.cols = ['title', 'authors', 'size', 'date', 'rating', 'publisher', 'tags', 'series'] @@ -292,6 +292,12 @@ class BooksModel(QAbstractTableModel): def id(self, row): return self.db.id(row.row()) + def title(self, row_number): + return self.db.title(row_number) + + def cover(self, row_number): + return self.db.cover(row_number) + def data(self, index, role): if role == Qt.DisplayRole or role == Qt.EditRole: row, col = index.row(), index.column() diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index c15b1ea3ca..fb45815ce0 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -8,7 +8,8 @@ from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \ QToolButton, QDialog from PyQt4.QtSvg import QSvgRenderer -from calibre import __version__, __appname__, islinux, sanitize_file_name, launch, Settings +from calibre import __version__, __appname__, islinux, sanitize_file_name, launch, \ + Settings, pictureflowerror, iswindows, isosx from calibre.ptempfile import PersistentTemporaryFile from calibre.ebooks.metadata.meta import get_metadata, get_filename_pat, set_filename_pat from calibre.devices.errors import FreeSpaceError @@ -18,7 +19,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ pixmap_to_data, choose_dir, ORG_NAME, \ qstring_to_unicode, set_sidebar_directories, \ SingleApplication, Application -from calibre import iswindows, isosx +from calibre.gui2.cover_flow import CoverFlow from calibre.library.database import LibraryDatabase from calibre.gui2.update import CheckForUpdates from calibre.gui2.main_window import MainWindow diff --git a/src/calibre/gui2/pictureflow/PyQt/configure.py b/src/calibre/gui2/pictureflow/PyQt/configure.py index 29075e3d2a..3549941329 100644 --- a/src/calibre/gui2/pictureflow/PyQt/configure.py +++ b/src/calibre/gui2/pictureflow/PyQt/configure.py @@ -30,7 +30,7 @@ makefile = pyqtconfig.QtGuiModuleMakefile ( # Add the library we are wrapping. The name doesn't include any platform # specific prefixes or extensions (e.g. the "lib" prefix on UNIX, or the # ".dll" extension on Windows). -makefile.extra_lib_dirs = ['../../.build', '..\\..\\release', '../../'] +makefile.extra_lib_dirs = ['../..', '..\\..\\release'] makefile.extra_libs = ['pictureflow0' if 'win' in sys.platform and 'darwin' not in sys.platform else "pictureflow"] makefile.extra_cflags = ['-arch i386', '-arch ppc'] if 'darwin' in sys.platform else [] makefile.extra_cxxflags = makefile.extra_cflags diff --git a/src/calibre/gui2/pictureflow/PyQt/pictureflow.sip b/src/calibre/gui2/pictureflow/PyQt/pictureflow.sip index ed337772f8..74901c95bc 100644 --- a/src/calibre/gui2/pictureflow/PyQt/pictureflow.sip +++ b/src/calibre/gui2/pictureflow/PyQt/pictureflow.sip @@ -6,48 +6,18 @@ %Import QtCore/QtCoremod.sip %Import QtGui/QtGuimod.sip -class AbstractDelayedImage { +class FlowImages : QObject { %TypeHeaderCode #include "../../pictureflow.h" %End public: - AbstractDelayedImage(); - virtual bool load(); - virtual void unload(); - virtual void free(); - virtual bool isLoaded(); - virtual QImage* image(); - + virtual int count(); + virtual QImage image(int index); + virtual QString caption(int index); }; -class FileDelayedImage : AbstractDelayedImage -{ - -%TypeHeaderCode -#include "../../pictureflow.h" -%End - -public: - FileDelayedImage(QString path); - bool load(); -}; - -class PreLoadedImage : AbstractDelayedImage -{ - -%TypeHeaderCode -#include "../../pictureflow.h" -%End - -public: - - PreLoadedImage(const QImage& image); - PreLoadedImage(const QPixmap& image); - bool load(); - void free(); -}; class PictureFlow : QWidget { @@ -58,102 +28,50 @@ class PictureFlow : QWidget { public : - PictureFlow(QWidget *parent /TransferThis/ = 0, int loadBuffer = 10); + PictureFlow(QWidget *parent /TransferThis/ = 0); - /*! - Returns the background color. - */ - QColor backgroundColor() const; - - /*! - Sets the background color. By default it is black. - */ - void setBackgroundColor(const QColor& c); - - /*! - Returns the dimension of each slide (in pixels). - */ + void setImages(FlowImages *images); + QSize slideSize() const; - /*! - Sets the dimension of each slide (in pixels). - */ void setSlideSize(QSize size); - /*! - Returns the total number of slides. - */ - int slideCount() const; + void setZoomFactor(int zoom); - /*! - Returns AbstractDelayedImage of specified slide. - */ - AbstractDelayedImage slide(int index) const; + int zoomFactor() const; - /*! - Returns the index of slide currently shown in the middle of the viewport. - */ - int centerIndex() const; + void clearCaches(); - public slots: + virtual QImage slide(int index) const; - /*! - Adds a new slide. - */ - void addSlide(AbstractDelayedImage *image); + int currentSlide() const; - /*! - Sets an image for specified slide. If the slide already exists, - it will be replaced. - */ - void setSlide(int index, AbstractDelayedImage *image); +public slots: - /*! - Sets slide to be shown in the middle of the viewport. No animation - effect will be produced, unlike using showSlide. - */ - void setCenterIndex(int index); + void setCurrentSlide(int index); - /*! - Clears all slides. - */ - void clear(); - - /*! - Shows previous slide using animation effect. - */ - void showPrevious(); - - /*! - Shows next slide using animation effect. - */ - void showNext(); - - /*! - Go to specified slide using animation effect. - */ - void showSlide(int index); - - /*! - Rerender the widget. Normally this function will be automatically invoked - whenever necessary, e.g. during the transition animation. - */ void render(); - /*! - Schedules a rendering update. Unlike render(), this function does not cause - immediate rendering. - */ - void triggerRender(); + void showPrevious(); + + void showNext(); + + void showSlide(int index); + + void dataChanged(); signals: - void centerIndexChanged(int index); + void itemActivated(int index); + void inputReceived(); + void currentChanged(int index); protected: void paintEvent(QPaintEvent *event); void keyPressEvent(QKeyEvent* event); + void mouseMoveEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); void resizeEvent(QResizeEvent* event); - + void timerEvent(QTimerEvent* event); }; diff --git a/src/calibre/gui2/pictureflow/main.cpp b/src/calibre/gui2/pictureflow/main.cpp deleted file mode 100644 index 582cc53dc5..0000000000 --- a/src/calibre/gui2/pictureflow/main.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - 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 -#include -#include -#include -#include - -#if QT_VERSION >= 0x040000 -#include -#endif - -#include "pictureflow.h" - -QStringList findFiles(const QString& path = QString()) -{ - QStringList files; - - QDir dir = QDir::current(); - if(!path.isEmpty()) - dir = QDir(path); - - dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); -#if QT_VERSION >= 0x040000 - QFileInfoList list = dir.entryInfoList(); - for (int i = 0; i < list.size(); ++i) - { - QFileInfo fileInfo = list.at(i); - files.append(dir.absoluteFilePath(fileInfo.fileName())); - } -#else - const QFileInfoList* list = dir.entryInfoList(); - if(list) - { - QFileInfoListIterator it( *list ); - QFileInfo * fi; - while( (fi=it.current()) != 0 ) - { - ++it; - files.append(dir.absFilePath(fi->fileName())); - } - } -#endif - - return files; -} - -#if QT_VERSION < 0x040000 -#define modifiers state -#define AltModifier AltButton -#define setWindowTitle setCaption -#endif - -#if QT_VERSION < 0x030000 -#define flush flushX -#endif - -class Browser: public PictureFlow -{ -public: - Browser(): PictureFlow() - { - setWindowTitle("PictureFlow"); - } - - void keyPressEvent(QKeyEvent* event) - { - if(event->key() == Qt::Key_Escape || event->key() == Qt::Key_Enter || - event->key() == Qt::Key_Return) - { - event->accept(); - close(); - } - - // checking the speed of rendering - if(event->key() == Qt::Key_F10) - if(event->modifiers() == Qt::AltModifier) - { - qDebug("benchmarking.... please wait"); - const int blit_count = 10; - - QTime stopwatch; - stopwatch.start(); - for(int i = 0; i < blit_count; i++) - { - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - render(); repaint(); QApplication::flush(); QApplication::syncX(); - } - QString msg; - int elapsed = stopwatch.elapsed(); - if( elapsed > 0 ) - msg = QString("FPS: %1").arg( blit_count*10*1000.0/elapsed ); - else - msg = QString("Too fast. Increase blit_count"); - setWindowTitle( msg ); - event->accept(); - return; - } - - // for debugging only: Alt+F11 cycles the reflection effect - if(event->key() == Qt::Key_F11) - if(event->modifiers() == Qt::AltModifier) - { - qDebug("changing reflection effect..."); - switch(reflectionEffect()) - { - //case NoReflection: setReflectionEffect(PlainReflection); break; - case PlainReflection: setReflectionEffect(BlurredReflection); break; - case BlurredReflection: setReflectionEffect(PlainReflection); break; - default: setReflectionEffect(PlainReflection); break; - } - event->accept(); - return; - } - - PictureFlow::keyPressEvent(event); - } -}; - -int main( int argc, char ** argv ) -{ - QApplication* app = new QApplication( argc, argv ); - Browser* w = new Browser; - -#if defined(_WS_QWS) || defined(Q_WS_QWS) - w->showFullScreen(); - int ww = w->width(); - int wh = w->height(); - int dim = (ww > wh) ? wh : ww; - dim = dim * 3 / 4; - w->setSlideSize(QSize(3*dim/5, dim)); -#else - w->setSlideSize(QSize(3*80, 5*80)); - w->resize(750, 470); -#endif - - QStringList files = (argc > 1) ? findFiles(QString(argv[1])) : findFiles(); - - for(int i = 0; i < (int)files.count(); i++) - w->addSlide(new FileDelayedImage(files[i])); - - w->setCenterIndex(w->slideCount()/2); - w->setBackgroundColor(Qt::white); - w->show(); - - app->connect( app, SIGNAL(lastWindowClosed()), app, SLOT(quit()) ); - int result = app->exec(); - - delete w; - delete app; - - return result; -} diff --git a/src/calibre/gui2/pictureflow/pictureflow-lib.pro b/src/calibre/gui2/pictureflow/pictureflow-lib.pro deleted file mode 100644 index 6632b7eede..0000000000 --- a/src/calibre/gui2/pictureflow/pictureflow-lib.pro +++ /dev/null @@ -1,6 +0,0 @@ -TARGET = pictureflow -TEMPLATE = lib -HEADERS = pictureflow.h -SOURCES = pictureflow.cpp -VERSION = 0.2.0 -CONFIG += x86 ppc diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp index a1d0245362..4e0e773a16 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.cpp +++ b/src/calibre/gui2/pictureflow/pictureflow.cpp @@ -1,11 +1,43 @@ -/* +/**************************************************************************** +** +* (C) Copyright 2007 Trolltech ASA +* All rights reserved. +** +* This is version of the Pictureflow animated image show widget modified by Trolltech ASA. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY TROLLTECH ASA ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +****************************************************************************/ + +/* + ORIGINAL COPYRIGHT HEADER 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, a of charge, to any person obtaining a copy + 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 @@ -26,32 +58,45 @@ #include "pictureflow.h" -#include -using namespace std; - - -#include +#include #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 qAbs(x,y) ((x) > (y)) ? (x)-(y) : (y)-(x) +#ifdef Q_WS_QWS +#include +#endif +#include + +// uncomment this to enable bilinear filtering for texture mapping +// gives much better rendering, at the cost of memory space +// #define PICTUREFLOW_BILINEAR_FILTER // 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 +typedef unsigned short QRgb565; + +#define RGB565_RED_MASK 0xF800 +#define RGB565_GREEN_MASK 0x07E0 +#define RGB565_BLUE_MASK 0x001F + +#define RGB565_RED(col) ((col&RGB565_RED_MASK)>>11) +#define RGB565_GREEN(col) ((col&RGB565_GREEN_MASK)>>5) +#define RGB565_BLUE(col) (col&RGB565_BLUE_MASK) + +#define PFREAL_SHIFT 10 +#define PFREAL_FACTOR (1 << PFREAL_SHIFT) +#define PFREAL_ONE (1 << PFREAL_SHIFT) +#define PFREAL_HALF (PFREAL_ONE >> 1) inline PFreal fmul(PFreal a, PFreal b) { @@ -67,627 +112,703 @@ inline PFreal fdiv(PFreal num, PFreal den) return r; } +inline float fixedToFloat(PFreal val) +{ + return ((float)val) / (float)PFREAL_ONE; +} + +inline PFreal floatToFixed(float val) +{ + return (PFreal)(val*PFREAL_ONE); +} + +#define IANGLE_MAX 1024 +#define IANGLE_MASK 1023 + +// warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! +static const PFreal sinTable[IANGLE_MAX] = { + 3, 9, 15, 21, 28, 34, 40, 47, + 53, 59, 65, 72, 78, 84, 90, 97, + 103, 109, 115, 122, 128, 134, 140, 147, + 153, 159, 165, 171, 178, 184, 190, 196, + 202, 209, 215, 221, 227, 233, 239, 245, + 251, 257, 264, 270, 276, 282, 288, 294, + 300, 306, 312, 318, 324, 330, 336, 342, + 347, 353, 359, 365, 371, 377, 383, 388, + 394, 400, 406, 412, 417, 423, 429, 434, + 440, 446, 451, 457, 463, 468, 474, 479, + 485, 491, 496, 501, 507, 512, 518, 523, + 529, 534, 539, 545, 550, 555, 561, 566, + 571, 576, 581, 587, 592, 597, 602, 607, + 612, 617, 622, 627, 632, 637, 642, 647, + 652, 656, 661, 666, 671, 675, 680, 685, + 690, 694, 699, 703, 708, 712, 717, 721, + 726, 730, 735, 739, 743, 748, 752, 756, + 760, 765, 769, 773, 777, 781, 785, 789, + 793, 797, 801, 805, 809, 813, 816, 820, + 824, 828, 831, 835, 839, 842, 846, 849, + 853, 856, 860, 863, 866, 870, 873, 876, + 879, 883, 886, 889, 892, 895, 898, 901, + 904, 907, 910, 913, 916, 918, 921, 924, + 927, 929, 932, 934, 937, 939, 942, 944, + 947, 949, 951, 954, 956, 958, 960, 963, + 965, 967, 969, 971, 973, 975, 977, 978, + 980, 982, 984, 986, 987, 989, 990, 992, + 994, 995, 997, 998, 999, 1001, 1002, 1003, + 1004, 1006, 1007, 1008, 1009, 1010, 1011, 1012, + 1013, 1014, 1015, 1015, 1016, 1017, 1018, 1018, + 1019, 1019, 1020, 1020, 1021, 1021, 1022, 1022, + 1022, 1023, 1023, 1023, 1023, 1023, 1023, 1023, + 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1022, + 1022, 1022, 1021, 1021, 1020, 1020, 1019, 1019, + 1018, 1018, 1017, 1016, 1015, 1015, 1014, 1013, + 1012, 1011, 1010, 1009, 1008, 1007, 1006, 1004, + 1003, 1002, 1001, 999, 998, 997, 995, 994, + 992, 990, 989, 987, 986, 984, 982, 980, + 978, 977, 975, 973, 971, 969, 967, 965, + 963, 960, 958, 956, 954, 951, 949, 947, + 944, 942, 939, 937, 934, 932, 929, 927, + 924, 921, 918, 916, 913, 910, 907, 904, + 901, 898, 895, 892, 889, 886, 883, 879, + 876, 873, 870, 866, 863, 860, 856, 853, + 849, 846, 842, 839, 835, 831, 828, 824, + 820, 816, 813, 809, 805, 801, 797, 793, + 789, 785, 781, 777, 773, 769, 765, 760, + 756, 752, 748, 743, 739, 735, 730, 726, + 721, 717, 712, 708, 703, 699, 694, 690, + 685, 680, 675, 671, 666, 661, 656, 652, + 647, 642, 637, 632, 627, 622, 617, 612, + 607, 602, 597, 592, 587, 581, 576, 571, + 566, 561, 555, 550, 545, 539, 534, 529, + 523, 518, 512, 507, 501, 496, 491, 485, + 479, 474, 468, 463, 457, 451, 446, 440, + 434, 429, 423, 417, 412, 406, 400, 394, + 388, 383, 377, 371, 365, 359, 353, 347, + 342, 336, 330, 324, 318, 312, 306, 300, + 294, 288, 282, 276, 270, 264, 257, 251, + 245, 239, 233, 227, 221, 215, 209, 202, + 196, 190, 184, 178, 171, 165, 159, 153, + 147, 140, 134, 128, 122, 115, 109, 103, + 97, 90, 84, 78, 72, 65, 59, 53, + 47, 40, 34, 28, 21, 15, 9, 3, + -4, -10, -16, -22, -29, -35, -41, -48, + -54, -60, -66, -73, -79, -85, -91, -98, + -104, -110, -116, -123, -129, -135, -141, -148, + -154, -160, -166, -172, -179, -185, -191, -197, + -203, -210, -216, -222, -228, -234, -240, -246, + -252, -258, -265, -271, -277, -283, -289, -295, + -301, -307, -313, -319, -325, -331, -337, -343, + -348, -354, -360, -366, -372, -378, -384, -389, + -395, -401, -407, -413, -418, -424, -430, -435, + -441, -447, -452, -458, -464, -469, -475, -480, + -486, -492, -497, -502, -508, -513, -519, -524, + -530, -535, -540, -546, -551, -556, -562, -567, + -572, -577, -582, -588, -593, -598, -603, -608, + -613, -618, -623, -628, -633, -638, -643, -648, + -653, -657, -662, -667, -672, -676, -681, -686, + -691, -695, -700, -704, -709, -713, -718, -722, + -727, -731, -736, -740, -744, -749, -753, -757, + -761, -766, -770, -774, -778, -782, -786, -790, + -794, -798, -802, -806, -810, -814, -817, -821, + -825, -829, -832, -836, -840, -843, -847, -850, + -854, -857, -861, -864, -867, -871, -874, -877, + -880, -884, -887, -890, -893, -896, -899, -902, + -905, -908, -911, -914, -917, -919, -922, -925, + -928, -930, -933, -935, -938, -940, -943, -945, + -948, -950, -952, -955, -957, -959, -961, -964, + -966, -968, -970, -972, -974, -976, -978, -979, + -981, -983, -985, -987, -988, -990, -991, -993, + -995, -996, -998, -999, -1000, -1002, -1003, -1004, + -1005, -1007, -1008, -1009, -1010, -1011, -1012, -1013, + -1014, -1015, -1016, -1016, -1017, -1018, -1019, -1019, + -1020, -1020, -1021, -1021, -1022, -1022, -1023, -1023, + -1023, -1024, -1024, -1024, -1024, -1024, -1024, -1024, + -1024, -1024, -1024, -1024, -1024, -1024, -1024, -1023, + -1023, -1023, -1022, -1022, -1021, -1021, -1020, -1020, + -1019, -1019, -1018, -1017, -1016, -1016, -1015, -1014, + -1013, -1012, -1011, -1010, -1009, -1008, -1007, -1005, + -1004, -1003, -1002, -1000, -999, -998, -996, -995, + -993, -991, -990, -988, -987, -985, -983, -981, + -979, -978, -976, -974, -972, -970, -968, -966, + -964, -961, -959, -957, -955, -952, -950, -948, + -945, -943, -940, -938, -935, -933, -930, -928, + -925, -922, -919, -917, -914, -911, -908, -905, + -902, -899, -896, -893, -890, -887, -884, -880, + -877, -874, -871, -867, -864, -861, -857, -854, + -850, -847, -843, -840, -836, -832, -829, -825, + -821, -817, -814, -810, -806, -802, -798, -794, + -790, -786, -782, -778, -774, -770, -766, -761, + -757, -753, -749, -744, -740, -736, -731, -727, + -722, -718, -713, -709, -704, -700, -695, -691, + -686, -681, -676, -672, -667, -662, -657, -653, + -648, -643, -638, -633, -628, -623, -618, -613, + -608, -603, -598, -593, -588, -582, -577, -572, + -567, -562, -556, -551, -546, -540, -535, -530, + -524, -519, -513, -508, -502, -497, -492, -486, + -480, -475, -469, -464, -458, -452, -447, -441, + -435, -430, -424, -418, -413, -407, -401, -395, + -389, -384, -378, -372, -366, -360, -354, -348, + -343, -337, -331, -325, -319, -313, -307, -301, + -295, -289, -283, -277, -271, -265, -258, -252, + -246, -240, -234, -228, -222, -216, -210, -203, + -197, -191, -185, -179, -172, -166, -160, -154, + -148, -141, -135, -129, -123, -116, -110, -104, + -98, -91, -85, -79, -73, -66, -60, -54, + -48, -41, -35, -29, -22, -16, -10, -4 +}; + +// this is the program the generate the above table +#if 0 +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define PFREAL_ONE 1024 +#define IANGLE_MAX 1024 + +int main(int, char**) +{ + FILE*f = fopen("table.c","wt"); + fprintf(f,"PFreal sinTable[] = {\n"); + for(int i = 0; i < 128; i++) + { + for(int j = 0; j < 8; j++) + { + int iang = j+i*8; + double ii = (double)iang + 0.5; + double angle = ii * 2 * M_PI / IANGLE_MAX; + double sinAngle = sin(angle); + fprintf(f,"%6d, ", (int)(floor(PFREAL_ONE*sinAngle))); + } + fprintf(f,"\n"); + } + fprintf(f,"};\n"); + fclose(f); + + return 0; +} +#endif + 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; -} + return sinTable[iangle & IANGLE_MASK]; +} inline PFreal fcos(int iangle) { + // quarter phase shift 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 +class PictureFlowPrivate { public: - PictureFlowState(); - ~PictureFlowState(); + PictureFlowPrivate(PictureFlow* widget); - void reposition(); - void reset(); + int slideCount() const; + void setSlideCount(int count); - QRgb backgroundColor; + QSize slideSize() const; + void setSlideSize(QSize size); + + int zoomFactor() const; + void setZoomFactor(int z); + + QImage slide(int index) const; + void setSlide(int index, const QImage& image); + + int currentSlide() const; + void setCurrentSlide(int index); + + int getTarget() const; + + void showPrevious(); + void showNext(); + void showSlide(int index); + + void resize(int w, int h); + + void render(); + void startAnimation(); + void updateAnimation(); + + void clearSurfaceCache(); + + QImage buffer; + QBasicTimer animateTimer; + + bool singlePress; + int singlePressThreshold; + QPoint firstPress; + QPoint previousPos; + QTime previousPosTimestamp; + int pixelDistanceMoved; + int pixelsToMovePerSlide; + void setImages(FlowImages *images); + void dataChanged(); + +private: + PictureFlow* widget; + + FlowImages *slideImages; + int slideWidth; int slideHeight; - PictureFlow::ReflectionEffect reflectionEffect; - QVector slideImages; + int zoom; - int angle; + int centerIndex; + SlideInfo centerSlide; + QVector leftSlides; + QVector rightSlides; + + QVector rays; + int itilt; int spacing; PFreal offsetX; PFreal offsetY; - SlideInfo centerSlide; - QVector leftSlides; - QVector rightSlides; - int centerIndex; - deque toLoad; -}; + QImage blankSurface; + QCache surfaceCache; + QTimer triggerTimer; -class PictureFlowAnimator -{ -public: - PictureFlowAnimator(); - PictureFlowState* state; - - void start(int slide); - void stop(int slide); - void update(); - - int target; + int slideFrame; int step; - int frame; - QTimer animateTimer; -}; + int target; + int fade; -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(); - -private: - QSize size; - QRgb bgcolor; - int effect; - QImage buffer; - QVector rays; - QImage* blankSurface; - QCache surfaceCache; - QHash imageHash; - - void render(); - void renderSlides(); - QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1); + void recalc(int w, int h); + QRect renderSlide(const SlideInfo &slide, int alpha=256, int col1=-1, int col=-1); QImage* surface(int slideIndex); + void triggerRender(); + void resetSlides(); }; -// ------------- PictureFlowState --------------------------------------- - -PictureFlowState::PictureFlowState(): -backgroundColor(0), slideWidth(150), slideHeight(200), -reflectionEffect(PictureFlow::BlurredReflection), centerIndex(0) +PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w) { + widget = w; + slideImages = new FlowImages(); + + slideWidth = 200; + slideHeight = 200; + zoom = 100; + + centerIndex = 0; + + slideFrame = 0; + step = 0; + target = 0; + fade = 256; + + triggerTimer.setSingleShot(true); + triggerTimer.setInterval(0); + QObject::connect(&triggerTimer, SIGNAL(timeout()), widget, SLOT(render())); + + recalc(200, 200); + resetSlides(); } -PictureFlowState::~PictureFlowState() -{ - for(int i = 0; i < (int)slideImages.count(); i++) - delete slideImages[i]; +void PictureFlowPrivate::dataChanged() { + surfaceCache.clear(); + resetSlides(); + triggerRender(); } -// readjust the settings, call this when slide dimension is changed -void PictureFlowState::reposition() +void PictureFlowPrivate::setImages(FlowImages *images) { - angle = 70 * IANGLE_MAX / 360; // approx. 70 degrees tilted - - offsetX = slideWidth/2 * (PFREAL_ONE-fcos(angle)); - offsetY = slideWidth/2 * fsin(angle); - offsetX += slideWidth * PFREAL_ONE; - offsetY += slideWidth * PFREAL_ONE / 4; - spacing = 40; + QObject::disconnect(slideImages, SIGNAL(dataChanged()), widget, SLOT(dataChanged())); + slideImages = images; + dataChanged(); + QObject::connect(slideImages, SIGNAL(dataChanged()), widget, SLOT(dataChanged())); } +int PictureFlowPrivate::slideCount() const +{ + return slideImages->count(); +} + +QSize PictureFlowPrivate::slideSize() const +{ + return QSize(slideWidth, slideHeight); +} + +void PictureFlowPrivate::setSlideSize(QSize size) +{ + slideWidth = size.width(); + slideHeight = size.height(); + recalc(buffer.width(), buffer.height()); + triggerRender(); +} + +int PictureFlowPrivate::zoomFactor() const +{ + return zoom; +} + +void PictureFlowPrivate::setZoomFactor(int z) +{ + if(z <= 0) + return; + + zoom = z; + recalc(buffer.width(), buffer.height()); + triggerRender(); +} + +QImage PictureFlowPrivate::slide(int index) const +{ + return slideImages->image(index); +} + +int PictureFlowPrivate::getTarget() const +{ + return target; +} + +int PictureFlowPrivate::currentSlide() const +{ + return centerIndex; +} + +void PictureFlowPrivate::setCurrentSlide(int index) +{ + step = 0; + centerIndex = qBound(index, 0, slideImages->count()-1); + target = centerIndex; + slideFrame = index << 16; + resetSlides(); + triggerRender(); + widget->emitcurrentChanged(centerIndex); +} + +void PictureFlowPrivate::showPrevious() +{ + if(step >= 0) + { + if(centerIndex > 0) + { + target--; + startAnimation(); + } + } + else + { + target = qMax(0, centerIndex - 2); + } +} + +void PictureFlowPrivate::showNext() +{ + if(step <= 0) + { + if(centerIndex < slideImages->count()-1) + { + target++; + startAnimation(); + } + } + else + { + target = qMin(centerIndex + 2, slideImages->count()-1); + } +} + +void PictureFlowPrivate::showSlide(int index) +{ + index = qMax(index, 0); + index = qMin(slideImages->count()-1, index); + if(index == centerSlide.slideIndex) + return; + + target = index; + startAnimation(); +} + +void PictureFlowPrivate::resize(int w, int h) +{ + recalc(w, h); + resetSlides(); + triggerRender(); +} + + // adjust slides so that they are in "steady state" position -void PictureFlowState::reset() +void PictureFlowPrivate::resetSlides() { 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++) + leftSlides.clear(); + leftSlides.resize(3); + for(int i = 0; i < leftSlides.count(); i++) { SlideInfo& si = leftSlides[i]; - si.angle = angle; + si.angle = itilt; 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; + //qDebug() << "Left[" << i << "] x=" << fixedToFloat(si.cx) << ", y=" << fixedToFloat(si.cy) ; } - rightSlides.resize(6); - for(int i = 0; i < (int)rightSlides.count(); i++) + rightSlides.clear(); + rightSlides.resize(3); + for(int i = 0; i < rightSlides.count(); i++) { SlideInfo& si = rightSlides[i]; - si.angle = -angle; + si.angle = -itilt; 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; + //qDebug() << "Right[" << i << "] x=" << fixedToFloat(si.cx) << ", y=" << fixedToFloat(si.cy) ; } } -// ------------- PictureFlowAnimator --------------------------------------- +#define BILINEAR_STRETCH_HOR 4 +#define BILINEAR_STRETCH_VER 4 -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; - - - // deaccelerate when approaching the target - const int max = 2 * 65536; - - 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; - - - 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) -{ -} - -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; - - - buffer = QImage(ww, wh, QImage::Format_RGB32); - 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) +static QImage prepareSurface(QImage img, int w, int h) { Qt::TransformationMode mode = Qt::SmoothTransformation; - QImage img = slideImage->scaled(w, h, Qt::IgnoreAspectRatio, mode); + img = img.scaled(w, h, Qt::IgnoreAspectRatio, mode); // slightly larger, to accomodate for the reflection int hs = h * 2; int hofs = h / 3; // offscreen buffer: black is sweet - QImage* result = new QImage(hs, w, QImage::Format_RGB32); - result->fill(bgcolor); + QImage result(hs, w, QImage::Format_RGB16); + result.fill(0); // 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)); + result.setPixel(hofs + y, 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 < 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 == PictureFlow::BlurredReflection) + // 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++) { - // 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)); + QRgb color = img.pixel(x, img.height()-y-1); + //QRgb565 color = img.scanLine(img.height()-y-1) + x*sizeof(QRgb565); //img.pixel(x, img.height()-y-1); + int a = qAlpha(color); + int r = qRed(color) * a / 256 * (hte - y) / hte * 3/5; + int g = qGreen(color) * a / 256 * (hte - y) / hte * 3/5; + int b = qBlue(color) * a / 256 * (hte - y) / hte * 3/5; + result.setPixel(h+hofs+y, x, qRgb(r, g, b)); } - } + +#ifdef PICTUREFLOW_BILINEAR_FILTER + int hh = BILINEAR_STRETCH_VER*hs; + int ww = BILINEAR_STRETCH_HOR*w; + result = result.scaled(hh, ww, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); +#endif return result; } -QImage* PictureFlowSoftwareRenderer::surface(int slideIndex) + +// get transformed image for specified slide +// if it does not exist, create it and place it in the cache +QImage* PictureFlowPrivate::surface(int slideIndex) { - if(!state) - return 0; if(slideIndex < 0) return 0; - if(slideIndex >= (int)state->slideImages.count()) + if(slideIndex >= slideImages->count()) return 0; - int key = slideIndex; - AbstractDelayedImage *cimg = state->slideImages.at(slideIndex); - if (not cimg->isLoaded()) cimg->load(); - QImage* img = cimg->image(); - bool empty = img ? img->isNull() : true; - if(empty) + if(surfaceCache.contains(slideIndex)) + return surfaceCache[slideIndex]; + + QImage img = widget->slide(slideIndex); + if(img.isNull()) { - surfaceCache.remove(key); - imageHash.remove(slideIndex); - if(!blankSurface) + if(blankSurface.isNull()) { - int sw = state->slideWidth; - int sh = state->slideHeight; + blankSurface = QImage(slideWidth, slideHeight, QImage::Format_RGB16); - QImage img = QImage(sw, sh, QImage::Format_RGB32); - - QPainter painter(&img); - QPoint p1(sw*4/10, 0); - QPoint p2(sw*6/10, sh); + QPainter painter(&blankSurface); + QPoint p1(slideWidth*4/10, 0); + QPoint p2(slideWidth*6/10, slideHeight); QLinearGradient linearGrad(p1, p2); linearGrad.setColorAt(0, Qt::black); - linearGrad.setColorAt(1, Qt::white); + linearGrad.setColorAt(1, Qt::white); painter.setBrush(linearGrad); - painter.fillRect(0, 0, sw, sh, QBrush(linearGrad)); + painter.fillRect(0, 0, slideWidth, slideHeight, QBrush(linearGrad)); painter.setPen(QPen(QColor(64,64,64), 4)); painter.setBrush(QBrush()); - painter.drawRect(2, 2, sw-3, sh-3); + painter.drawRect(2, 2, slideWidth-3, slideHeight-3); painter.end(); - - blankSurface = prepareSurface(&img, sw, sh, bgcolor, state->reflectionEffect); + blankSurface = prepareSurface(blankSurface, slideWidth, slideHeight); } - return blankSurface; + return &blankSurface; } - bool exist = imageHash.contains(slideIndex); - if(exist) - if(img == imageHash.find(slideIndex).value()) - 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; + surfaceCache.insert(slideIndex, new QImage(prepareSurface(img, slideWidth, slideHeight))); + return surfaceCache[slideIndex]; } -// 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(); +// Schedules rendering the slides. Call this function to avoid immediate +// render and thus cause less flicker. +void PictureFlowPrivate::triggerRender() +{ + triggerTimer.start(); +} + +// Render the slides. Updates only the offscreen buffer. +void PictureFlowPrivate::render() +{ + buffer.fill(0); + + int nleft = leftSlides.count(); + int nright = rightSlides.count(); + + QRect r = renderSlide(centerSlide); + int c1 = r.left(); + int c2 = r.right(); + + if(step == 0) + { + // no animation, boring plain rendering + for(int index = 0; index < nleft-1; index++) + { + int alpha = (index < nleft-2) ? 256 : 128; + QRect rs = renderSlide(leftSlides[index], alpha, 0, c1-1); + if(!rs.isEmpty()) + c1 = rs.left(); + } + for(int index = 0; index < nright-1; index++) + { + int alpha = (index < nright-2) ? 256 : 128; + QRect rs = renderSlide(rightSlides[index], alpha, c2+1, buffer.width()); + if(!rs.isEmpty()) + c2 = rs.right(); + } + + QPainter painter; + painter.begin(&buffer); + + QFont font("Arial", 14); + font.setBold(true); + painter.setFont(font); + painter.setPen(Qt::white); + //painter.setPen(QColor(255,255,255,127)); + + painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2), + Qt::AlignCenter, slideImages->caption(centerIndex)); + + painter.end(); + + } + else + { + // the first and last slide must fade in/fade out + for(int index = 0; index < nleft; index++) + { + int alpha = 256; + if(index == nleft-1) + alpha = (step > 0) ? 0 : 128-fade/2; + if(index == nleft-2) + alpha = (step > 0) ? 128-fade/2 : 256-fade/2; + if(index == nleft-3) + alpha = (step > 0) ? 256-fade/2 : 256; + QRect rs = renderSlide(leftSlides[index], alpha, 0, c1-1); + if(!rs.isEmpty()) + c1 = rs.left(); + + alpha = (step > 0) ? 256-fade/2 : 256; + } + for(int index = 0; index < nright; index++) + { + int alpha = (index < nright-2) ? 256 : 128; + if(index == nright-1) + alpha = (step > 0) ? fade/2 : 0; + if(index == nright-2) + alpha = (step > 0) ? 128+fade/2 : fade/2; + if(index == nright-3) + alpha = (step > 0) ? 256 : 128+fade/2; + QRect rs = renderSlide(rightSlides[index], alpha, c2+1, buffer.width()); + if(!rs.isEmpty()) + c2 = rs.right(); + } + + + + QPainter painter; + painter.begin(&buffer); + + QFont font("Arial", 14); + font.setBold(true); + painter.setFont(font); + + int leftTextIndex = (step>0) ? centerIndex : centerIndex-1; + + painter.setPen(QColor(255,255,255, (255-fade) )); + painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2), + Qt::AlignCenter, slideImages->caption(leftTextIndex)); + + painter.setPen(QColor(255,255,255, fade)); + painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2), + Qt::AlignCenter, slideImages->caption(leftTextIndex+1)); + + + painter.end(); + } +} + + +static inline uint BYTE_MUL_RGB16(uint x, uint a) { + a += 1; + uint t = (((x & 0x07e0)*a) >> 8) & 0x07e0; + t |= (((x & 0xf81f)*(a>>2)) >> 6) & 0xf81f; + return t; +} + +static inline uint BYTE_MUL_RGB16_32(uint x, uint a) { + uint t = (((x & 0xf81f07e0) >> 5)*a) & 0xf81f07e0; + t |= (((x & 0x07e0f81f)*a) >> 5) & 0x07e0f81f; + return t; +} + + +// Renders a slide to offscreen buffer. Returns a rect of the rendered area. +// alpha=256 means normal, alpha=0 is fully black, alpha=128 half transparent +// col1 and col2 limit the column for rendering. +QRect PictureFlowPrivate::renderSlide(const SlideInfo &slide, int alpha, +int col1, int col2) +{ QImage* src = surface(slide.slideIndex); if(!src) return QRect(); - QRect rect(0, 0, 0, 0); - + QRect rect(0, 0, 0, 0); + +#ifdef PICTUREFLOW_BILINEAR_FILTER + int sw = src->height() / BILINEAR_STRETCH_HOR; + int sh = src->width() / BILINEAR_STRETCH_VER; +#else int sw = src->height(); int sh = src->width(); +#endif int h = buffer.height(); int w = buffer.width(); @@ -703,15 +824,14 @@ QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, 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 xs = slide.cx - slideWidth * sdx/2; + PFreal ys = slide.cy - slideWidth * sdy/2; PFreal dist = distance * PFREAL_ONE; - int xi = qMax((PFreal)0, (w*PFREAL_ONE/2) + fdiv(xs*h, dist+ys) >> PFREAL_SHIFT); + int xi = qMax((PFreal)0, ((w*PFREAL_ONE/2) + fdiv(xs*h, dist+ys)) >> PFREAL_SHIFT); if(xi >= w) return rect; @@ -734,30 +854,41 @@ QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, PFreal hitx = fmul(dist, rays[x]); PFreal hitdist = fdiv(hitx - slide.cx, sdx); +#ifdef PICTUREFLOW_BILINEAR_FILTER + int column = sw*BILINEAR_STRETCH_HOR/2 + (hitdist*BILINEAR_STRETCH_HOR >> PFREAL_SHIFT); + if(column >= sw*BILINEAR_STRETCH_HOR) + break; +#else int column = sw/2 + (hitdist >> PFREAL_SHIFT); if(column >= sw) break; +#endif if(column < 0) continue; - rect.setRight(x); + rect.setRight(x); if(!flag) rect.setLeft(x); - flag = true; + 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; + QRgb565* pixel1 = (QRgb565*)(buffer.scanLine(y1)) + x; + QRgb565* pixel2 = (QRgb565*)(buffer.scanLine(y2)) + x; + int pixelstep = pixel2 - pixel1; +#ifdef PICTUREFLOW_BILINEAR_FILTER + int center = (sh*BILINEAR_STRETCH_VER/2); + int dy = dist*BILINEAR_STRETCH_VER / h; +#else int center = (sh/2); int dy = dist / h; +#endif 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) + const QRgb565 *ptr = (const QRgb565*)(src->scanLine(column)); + if(alpha == 256) while((y1 >= 0) && (y2 < h) && (p1 >= 0)) { *pixel1 = ptr[p1 >> PFREAL_SHIFT]; @@ -768,283 +899,292 @@ QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, 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); + QRgb565 c1 = ptr[p1 >> PFREAL_SHIFT]; + QRgb565 c2 = ptr[p2 >> PFREAL_SHIFT]; + + *pixel1 = BYTE_MUL_RGB16(c1, alpha); + *pixel2 = BYTE_MUL_RGB16(c2, alpha); + +/* + int r1 = qRed(c1) * alpha/256; + int g1 = qGreen(c1) * alpha/256; + int b1 = qBlue(c1) * alpha/256; + int r2 = qRed(c2) * alpha/256; + int g2 = qGreen(c2) * alpha/256; + int b2 = qBlue(c2) * alpha/256; + *pixel1 = qRgb(r1, g1, b1); + *pixel2 = qRgb(r2, g2, b2); +*/ p1 -= dy; p2 += dy; y1--; y2++; pixel1 -= pixelstep; pixel2 += pixelstep; - } - } + } + } rect.setTop(0); rect.setBottom(h-1); return rect; } -void PictureFlowSoftwareRenderer::renderSlides() +// Updates look-up table and other stuff necessary for the rendering. +// Call this when the viewport size or slide dimension is changed. +void PictureFlowPrivate::recalc(int ww, int wh) { - int nleft = state->leftSlides.count(); - int nright = state->rightSlides.count(); + int w = (ww+1)/2; + int h = (wh+1)/2; + buffer = QImage(ww, wh, QImage::Format_RGB16); + buffer.fill(0); - QRect r = renderSlide(state->centerSlide); - int c1 = r.left(); - int c2 = r.right(); + rays.resize(w*2); - for(int index = 0; index < nleft; index++) + for(int i = 0; i < w; i++) { - QRect rs = renderSlide(state->leftSlides[index], 0, c1-1); - if(!rs.isEmpty()) - c1 = rs.left(); + PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2*h); + rays[w-i-1] = -gg; + rays[w+i] = gg; } - for(int index = 0; index < nright; index++) + + // pointer must move more than 1/15 of the window to enter drag mode + singlePressThreshold = ww / 15; +// qDebug() << "singlePressThreshold now set to " << singlePressThreshold; + + pixelsToMovePerSlide = ww / 3; +// qDebug() << "pixelsToMovePerSlide now set to " << pixelsToMovePerSlide; + + itilt = 80 * IANGLE_MAX / 360; // approx. 80 degrees tilted + + offsetY = slideWidth/2 * fsin(itilt); + offsetY += slideWidth * PFREAL_ONE / 4; + +// offsetX = slideWidth/2 * (PFREAL_ONE-fcos(itilt)); +// offsetX += slideWidth * PFREAL_ONE; + + // center slide + side slide + offsetX = slideWidth*PFREAL_ONE; +// offsetX = 150*PFREAL_ONE;//(slideWidth/2)*PFREAL_ONE + ( slideWidth*fcos(itilt) )/2; +// qDebug() << "center width = " << slideWidth; +// qDebug() << "side width = " << fixedToFloat(slideWidth/2 * (PFREAL_ONE-fcos(itilt))); +// qDebug() << "offsetX now " << fixedToFloat(offsetX); + + spacing = slideWidth/5; + + surfaceCache.clear(); + blankSurface = QImage(); +} + +void PictureFlowPrivate::startAnimation() +{ + if(!animateTimer.isActive()) { - QRect rs = renderSlide(state->rightSlides[index], c2+1, buffer.width()); - if(!rs.isEmpty()) - c2 = rs.right(); + step = (target < centerSlide.slideIndex) ? -1 : 1; + animateTimer.start(30, widget); } } -// Render the slides. Updates only the offscreen buffer. -void PictureFlowSoftwareRenderer::render() +// Updates the animation effect. Call this periodically from a timer. +void PictureFlowPrivate::updateAnimation() { - buffer.fill(state->backgroundColor); - renderSlides(); - dirty = false; + if(!animateTimer.isActive()) + return; + if(step == 0) + return; + + int speed = 16384; + + // deaccelerate when approaching the target + if(true) + { + const int max = 2 * 65536; + + int fi = slideFrame; + 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; + } + + slideFrame += speed*step; + + int index = slideFrame >> 16; + int pos = slideFrame & 0xffff; + int neg = 65536 - pos; + int tick = (step < 0) ? neg : pos; + PFreal ftick = (tick * PFREAL_ONE) >> 16; + + // the leftmost and rightmost slide must fade away + fade = pos / 256; + + if(step < 0) + index++; + if(centerIndex != index) + { + centerIndex = index; + slideFrame = index << 16; + centerSlide.slideIndex = centerIndex; + for(int i = 0; i < leftSlides.count(); i++) + leftSlides[i].slideIndex = centerIndex-1-i; + for(int i = 0; i < rightSlides.count(); i++) + rightSlides[i].slideIndex = centerIndex+1+i; + widget->emitcurrentChanged(centerIndex); + } + + centerSlide.angle = (step * tick * itilt) >> 16; + centerSlide.cx = -step * fmul(offsetX, ftick); + centerSlide.cy = fmul(offsetY, ftick); + + if(centerIndex == target) + { + resetSlides(); + animateTimer.stop(); + triggerRender(); + step = 0; + fade = 256; + return; + } + + for(int i = 0; i < leftSlides.count(); i++) + { + SlideInfo& si = leftSlides[i]; + si.angle = itilt; + si.cx = -(offsetX + spacing*i*PFREAL_ONE + step*spacing*ftick); + si.cy = offsetY; + } + + for(int i = 0; i < rightSlides.count(); i++) + { + SlideInfo& si = rightSlides[i]; + si.angle = -itilt; + si.cx = offsetX + spacing*i*PFREAL_ONE - step*spacing*ftick; + si.cy = offsetY; + } + + if(step > 0) + { + PFreal ftick = (neg * PFREAL_ONE) >> 16; + rightSlides[0].angle = -(neg * itilt) >> 16; + rightSlides[0].cx = fmul(offsetX, ftick); + rightSlides[0].cy = fmul(offsetY, ftick); + } + else + { + PFreal ftick = (pos * PFREAL_ONE) >> 16; + leftSlides[0].angle = (pos * itilt) >> 16; + leftSlides[0].cx = -fmul(offsetX, ftick); + leftSlides[0].cy = fmul(offsetY, ftick); + } + + // must change direction ? + if(target < index) if(step > 0) + step = -1; + if(target > index) if(step < 0) + step = 1; + + triggerRender(); +} + + +void PictureFlowPrivate::clearSurfaceCache() +{ + surfaceCache.clear(); } // ----------------------------------------- -class PictureFlowPrivate +PictureFlow::PictureFlow(QWidget* parent): QWidget(parent) { -public: - PictureFlowState* state; - PictureFlowAnimator* animator; - PictureFlowAbstractRenderer* renderer; - QTimer triggerTimer; - QTimer loadTimer; - int loadBuffer; -}; - - -PictureFlow::PictureFlow(QWidget* parent, const int loadBuffer): QWidget(parent) -{ - d = new PictureFlowPrivate; - - d->loadBuffer = loadBuffer; - - 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->loadTimer, SIGNAL(timeout()), this, SLOT(load())); - d->loadTimer.start(10); + d = new PictureFlowPrivate(this); setAttribute(Qt::WA_StaticContents, true); setAttribute(Qt::WA_OpaquePaintEvent, true); setAttribute(Qt::WA_NoSystemBackground, true); + +#ifdef Q_WS_QWS + if (QScreen::instance()->pixelFormat() != QImage::Format_Invalid) + setAttribute(Qt::WA_PaintOnScreen, true); +#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); + return d->slideSize(); } void PictureFlow::setSlideSize(QSize size) { - d->state->slideWidth = size.width(); - d->state->slideHeight = size.height(); - d->state->reposition(); - triggerRender(); + d->setSlideSize(size); } -PictureFlow::ReflectionEffect PictureFlow::reflectionEffect() const +int PictureFlow::zoomFactor() const { - return d->state->reflectionEffect; + return d->zoomFactor(); } -void PictureFlow::setReflectionEffect(ReflectionEffect effect) +void PictureFlow::setZoomFactor(int z) { - d->state->reflectionEffect = effect; - triggerRender(); + d->setZoomFactor(z); } -AbstractDelayedImage PictureFlow::slide(int index) const +QImage PictureFlow::slide(int index) const { - if((index >= 0) && (index < slideCount()) && d->state->slideImages[index]) - return *d->state->slideImages[index]; - return AbstractDelayedImage(); + return d->slide(index); } -void PictureFlow::addSlide(AbstractDelayedImage *image) + +void PictureFlow::setImages(FlowImages *images) { - int c = d->state->slideImages.count(); - d->state->slideImages.resize(c+1); - d->state->slideImages[c] = image; - if (qAbs(d->state->centerIndex, c) <= d->loadBuffer) - d->state->toLoad.push_back(c); - triggerRender(); + d->setImages(images); } -void PictureFlow::setSlide(int index, AbstractDelayedImage *image) +int PictureFlow::currentSlide() const { - if((index >= 0) && (index < slideCount())) - { - delete d->state->slideImages[index]; - d->state->slideImages[index] = image; - if (qAbs(d->state->centerIndex, index) <= d->loadBuffer) - d->state->toLoad.push_back(index); - triggerRender(); - } + return d->currentSlide(); } -int PictureFlow::centerIndex() const +void PictureFlow::setCurrentSlide(int index) { - return d->state->centerIndex; + d->setCurrentSlide(index); } -void PictureFlow::updateBuffer(int old_center, int new_center) +void PictureFlow::clearCaches() { - int c; - for (c = qMax(new_center - d->loadBuffer, 0); c <= qMin(new_center+ d->loadBuffer, slideCount()-1); c++) { - if (not d->state->slideImages[c]->isLoaded()) d->state->toLoad.push_back(c); - } - for (c = qMax(old_center - d->loadBuffer, 0); c <= qMin(old_center + d->loadBuffer, slideCount()-1); c++) { - if ((c < qMax(new_center - d->loadBuffer, 0) or c > qMin(new_center + d->loadBuffer, slideCount()-1)) and d->state->slideImages[c]->isLoaded()) { - d->state->slideImages[c]->unload(); - } - } -} - -void PictureFlow::setCenterIndex(int index) -{ - index = qMin(index, slideCount()-1); - index = qMax(index, 0); - updateBuffer(d->state->centerIndex, index); - 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(); + d->clearSurfaceCache(); } void PictureFlow::render() { - d->renderer->dirty = true; + d->render(); update(); } -void PictureFlow::load() -{ - int index; - if (not d->state->toLoad.empty()) { - index = d->state->toLoad.front(); - d->state->toLoad.pop_front(); - if (index > 0 and index < d->state->slideImages.count() and not d->state->slideImages[index]->isLoaded()) { - d->state->slideImages[index]->load(); triggerRender(); - } - } -} - -void PictureFlow::triggerRender() -{ - d->triggerTimer.setSingleShot(true); - d->triggerTimer.start(0); -} - 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); + d->showPrevious(); } 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); + d->showNext(); } 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); + d->showSlide(index); } void PictureFlow::keyPressEvent(QKeyEvent* event) @@ -1052,8 +1192,8 @@ void PictureFlow::keyPressEvent(QKeyEvent* event) if(event->key() == Qt::Key_Left) { if(event->modifiers() == Qt::ControlModifier) - showSlide(centerIndex()-10); - else + showSlide(currentSlide()-10); + else showPrevious(); event->accept(); return; @@ -1062,7 +1202,7 @@ void PictureFlow::keyPressEvent(QKeyEvent* event) if(event->key() == Qt::Key_Right) { if(event->modifiers() == Qt::ControlModifier) - showSlide(centerIndex()+10); + showSlide(currentSlide()+10); else showNext(); event->accept(); @@ -1072,46 +1212,160 @@ void PictureFlow::keyPressEvent(QKeyEvent* event) event->ignore(); } +#define SPEED_LOWER_THRESHOLD 10 +#define SPEED_UPPER_LIMIT 40 + +void PictureFlow::mouseMoveEvent(QMouseEvent* event) +{ + int distanceMovedSinceLastEvent = event->pos().x() - d->previousPos.x(); + + // Check to see if we need to switch from single press mode to a drag mode + if (d->singlePress) + { + // Increment the distance moved for this event + d->pixelDistanceMoved += distanceMovedSinceLastEvent; + + // Check against threshold + if (qAbs(d->pixelDistanceMoved) > d->singlePressThreshold) + { + d->singlePress = false; +// qDebug() << "DRAG MODE ON"; + } + } + + if (!d->singlePress) + { + int speed; + // Calculate velocity in a 10th of a window width per second + if (d->previousPosTimestamp.elapsed() == 0) + speed = SPEED_LOWER_THRESHOLD; + else + { + speed = ((qAbs(event->pos().x()-d->previousPos.x())*1000) / d->previousPosTimestamp.elapsed()) + / (d->buffer.width() / 10); + + if (speed < SPEED_LOWER_THRESHOLD) + speed = SPEED_LOWER_THRESHOLD; + else if (speed > SPEED_UPPER_LIMIT) + speed = SPEED_UPPER_LIMIT; + else { + speed = SPEED_LOWER_THRESHOLD + (speed / 3); +// qDebug() << "ACCELERATION ENABLED Speed = " << speed << ", Distance = " << distanceMovedSinceLastEvent; + + } + } + + +// qDebug() << "Speed = " << speed; + +// int incr = ((event->pos().x() - d->previousPos.x())/10) * speed; + +// qDebug() << "Incremented by " << incr; + + int incr = (distanceMovedSinceLastEvent * speed); + + //qDebug() << "(distanceMovedSinceLastEvent * speed) = " << incr; + + if (incr > d->pixelsToMovePerSlide*2) { + incr = d->pixelsToMovePerSlide*2; + //qDebug() << "Limiting incr to " << incr; + } + + + d->pixelDistanceMoved += (distanceMovedSinceLastEvent * speed); + // qDebug() << "distance: " << d->pixelDistanceMoved; + + int slideInc; + + slideInc = d->pixelDistanceMoved / (d->pixelsToMovePerSlide * 10); + + if (slideInc != 0) { + int targetSlide = d->getTarget() - slideInc; + showSlide(targetSlide); +// qDebug() << "TargetSlide = " << targetSlide; + + //qDebug() << "Decrementing pixelDistanceMoved by " << (d->pixelsToMovePerSlide *10) * slideInc; + + d->pixelDistanceMoved -= (d->pixelsToMovePerSlide *10) * slideInc; + +/* + if ( (targetSlide <= 0) || (targetSlide >= d->slideCount()-1) ) + d->pixelDistanceMoved = 0; +*/ + } + + + } + + d->previousPos = event->pos(); + d->previousPosTimestamp.restart(); + + emit inputReceived(); +} + void PictureFlow::mousePressEvent(QMouseEvent* event) { - if(event->x() > width()/2) - showNext(); - else - showPrevious(); + d->firstPress = event->pos(); + d->previousPos = event->pos(); + d->previousPosTimestamp.start(); + d->singlePress = true; // Initially assume a single press +// d->dragStartSlide = d->getTarget(); + d->pixelDistanceMoved = 0; + + emit inputReceived(); } +void PictureFlow::mouseReleaseEvent(QMouseEvent* event) +{ + int sideWidth = (d->buffer.width() - slideSize().width()) /2; + + if (d->singlePress) + { + if (event->x() < sideWidth ) + { + showPrevious(); + } else if ( event->x() > sideWidth + slideSize().width() ) { + showNext(); + } else { + emit itemActivated(d->getTarget()); + } + + event->accept(); + } + + emit inputReceived(); +} + + void PictureFlow::paintEvent(QPaintEvent* event) { Q_UNUSED(event); - d->renderer->paint(); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawImage(QPoint(0,0), d->buffer); } void PictureFlow::resizeEvent(QResizeEvent* event) { - triggerRender(); + d->resize(width(), height()); QWidget::resizeEvent(event); } -void PictureFlow::updateAnimation() +void PictureFlow::timerEvent(QTimerEvent* event) { - int old_center = d->state->centerIndex; - d->animator->update(); - triggerRender(); - if(d->state->centerIndex != old_center) { - updateBuffer(old_center, d->state->centerIndex); - emit centerIndexChanged(d->state->centerIndex); + if(event->timerId() == d->animateTimer.timerId()) + { +// QTime now = QTime::currentTime(); + d->updateAnimation(); +// d->animateTimer.start(qMax(0, 30-now.elapsed() ), this); } + else + QWidget::timerEvent(event); } -//-------------Delayed Images------------------------------------------------ +void PictureFlow::dataChanged() { d->dataChanged(); } +void PictureFlow::emitcurrentChanged(int index) { emit currentChanged(index); } -bool AbstractDelayedImage::load() { return bool(0); } -void AbstractDelayedImage::unload() {if (imageptr != 0) {delete imageptr; imageptr = 0;}} -void AbstractDelayedImage::free() { unload(); } -bool AbstractDelayedImage::isLoaded() { return imageptr != 0; } -QImage* AbstractDelayedImage::image() { return imageptr; } -bool FileDelayedImage::load() { imageptr = new QImage(file_path); return not imageptr->isNull(); } -PreLoadedImage::PreLoadedImage(const QImage& image) { memory = new QImage(image); } -PreLoadedImage::PreLoadedImage(const QPixmap& image) { memory = new QImage(image.toImage()); } -bool PreLoadedImage::load() { imageptr = new QImage(*memory); return not imageptr->isNull(); } -void PreLoadedImage::free() { unload(); if (memory != 0) {delete memory; memory = 0;} } +int FlowImages::count() { return 0; } +QImage FlowImages::image(int index) { return QImage(); } +QString FlowImages::caption(int index) {return QString(); } diff --git a/src/calibre/gui2/pictureflow/pictureflow.h b/src/calibre/gui2/pictureflow/pictureflow.h index f733d58697..e6bb0c04f5 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.h +++ b/src/calibre/gui2/pictureflow/pictureflow.h @@ -1,8 +1,39 @@ +/**************************************************************************** +** +* (C) Copyright 2007 Trolltech ASA +* All rights reserved. +** +* This is version of the Pictureflow animated image show widget modified by Trolltech ASA. +* +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY TROLLTECH ASA ``AS IS'' AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +****************************************************************************/ /* + ORIGINAL COPYRIGHT HEADER 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 @@ -27,87 +58,47 @@ #ifndef PICTUREFLOW_H #define PICTUREFLOW_H -#include -#include #include -class AbstractDelayedImage +class FlowImages : public QObject { +Q_OBJECT + public: - AbstractDelayedImage() : imageptr(0) {}; - virtual ~AbstractDelayedImage() { free(); } + virtual int count(); + virtual QImage image(int index); + virtual QString caption(int index); - virtual bool load(); - virtual void unload(); - virtual void free(); - virtual bool isLoaded(); - virtual QImage* image(); - -protected: - QImage *imageptr; +signals: + void dataChanged(); }; -class FileDelayedImage : public AbstractDelayedImage -{ -public: - FileDelayedImage(QString path) : file_path(path) { imageptr = 0; } - - bool load(); - -private: - QString file_path; -}; - -class PreLoadedImage : public AbstractDelayedImage -{ -public: - PreLoadedImage(const QImage& image); - PreLoadedImage(const QPixmap& image); - - bool load(); - void free(); - - -private: - QImage *memory; -}; - - class PictureFlowPrivate; /*! - Class PictureFlow implements an image show widget with animation effect - like Apple's CoverFlow (in iTunes and iPod). Images are arranged in form - of slides, one main slide is shown at the center with few slides on - the left and right sides of the center slide. When the next or previous - slide is brought to the front, the whole slides flow to the right or - the right with smooth animation effect; until the new slide is finally + Class PictureFlow implements an image show widget with animation effect + like Apple's CoverFlow (in iTunes and iPod). Images are arranged in form + of slides, one main slide is shown at the center with few slides on + the left and right sides of the center slide. When the next or previous + slide is brought to the front, the whole slides flow to the right or + the right with smooth animation effect; until the new slide is finally placed at the center. - */ + */ class PictureFlow : public QWidget { Q_OBJECT - Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_PROPERTY(int currentSlide READ currentSlide WRITE setCurrentSlide) Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize) - Q_PROPERTY(int slideCount READ slideCount) - Q_PROPERTY(int centerIndex READ centerIndex WRITE setCenterIndex) + Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor) public: - - enum ReflectionEffect - { - NoReflection, - PlainReflection, - BlurredReflection - }; - /*! Creates a new PictureFlow widget. - */ - PictureFlow(QWidget* parent = 0, int loadBuffer = 10); + */ + PictureFlow(QWidget* parent = 0); /*! Destroys the widget. @@ -115,74 +106,60 @@ public: ~PictureFlow(); /*! - Returns the background color. + Set the images to be displayed by this widget. */ - QColor backgroundColor() const; - - /*! - Sets the background color. By default it is black. - */ - void setBackgroundColor(const QColor& c); - + void setImages(FlowImages *images); + /*! Returns the dimension of each slide (in pixels). - */ + */ QSize slideSize() const; /*! Sets the dimension of each slide (in pixels). - */ + */ void setSlideSize(QSize size); /*! - Returns the total number of slides. - */ - int slideCount() const; + Sets the zoom factor (in percent). + */ + void setZoomFactor(int zoom); /*! - Returns AbstractDelayedImage of specified slide. + Returns the zoom factor (in percent). */ - AbstractDelayedImage slide(int index) const; + int zoomFactor() const; + + /*! + Clears any caches held to free up memory + */ + void clearCaches(); + + /*! + Returns QImage of specified slide. + This function will be called only whenever necessary, e.g. the 100th slide + will not be retrived when only the first few slides are visible. + */ + virtual QImage slide(int index) const; /*! Returns the index of slide currently shown in the middle of the viewport. - */ - int centerIndex() const; - - /*! - Returns the effect applied to the reflection. - */ - ReflectionEffect reflectionEffect() const; - - /*! - Sets the effect applied to the reflection. The default is PlainReflection. - */ - void setReflectionEffect(ReflectionEffect effect); - + */ + int currentSlide() const; public slots: /*! - Adds a new slide. - */ - void addSlide(AbstractDelayedImage *image); - - /*! - Sets an image for specified slide. If the slide already exists, - it will be replaced. - */ - void setSlide(int index, AbstractDelayedImage *image); - - /*! - Sets slide to be shown in the middle of the viewport. No animation + Sets slide to be shown in the middle of the viewport. No animation effect will be produced, unlike using showSlide. - */ - void setCenterIndex(int index); + */ + void setCurrentSlide(int index); /*! - Clears all slides. + Rerender the widget. Normally this function will be automatically invoked + whenever necessary, e.g. during the transition animation. */ - void clear(); + void render(); /*! Shows previous slide using animation effect. @@ -198,41 +175,30 @@ public slots: Go to specified slide using animation effect. */ void showSlide(int index); - + /*! - Rerender the widget. Normally this function will be automatically invoked - whenever necessary, e.g. during the transition animation. + Clear all caches and redraw */ - void render(); - - /*! - Load and images that are in the load buffer. This function is automatically - caled peridically. - */ - void load(); - - /*! - Schedules a rendering update. Unlike render(), this function does not cause - immediate rendering. - */ - void triggerRender(); + void dataChanged(); + + void emitcurrentChanged(int index); signals: - void centerIndexChanged(int index); + void itemActivated(int index); + void inputReceived(); + void currentChanged(int index); protected: void paintEvent(QPaintEvent *event); void keyPressEvent(QKeyEvent* event); + void mouseMoveEvent(QMouseEvent* event); void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); void resizeEvent(QResizeEvent* event); - -private slots: - void updateAnimation(); + void timerEvent(QTimerEvent* event); private: PictureFlowPrivate* d; - void updateBuffer(int old_center, int new_center); }; #endif // PICTUREFLOW_H - diff --git a/src/calibre/gui2/pictureflow/pictureflow.pro b/src/calibre/gui2/pictureflow/pictureflow.pro index 6b3988bddb..6632b7eede 100644 --- a/src/calibre/gui2/pictureflow/pictureflow.pro +++ b/src/calibre/gui2/pictureflow/pictureflow.pro @@ -1,5 +1,6 @@ TARGET = pictureflow -TEMPLATE = app +TEMPLATE = lib HEADERS = pictureflow.h -SOURCES = pictureflow.cpp main.cpp +SOURCES = pictureflow.cpp VERSION = 0.2.0 +CONFIG += x86 ppc diff --git a/windows_installer.py b/windows_installer.py index 6e57003a73..7f15e3c897 100644 --- a/windows_installer.py +++ b/windows_installer.py @@ -545,7 +545,7 @@ class BuildEXE(build_exe): shutil.rmtree('release') if os.path.exists('debug'): shutil.rmtree('debug') - subprocess.check_call(['qmake', 'pictureflow-lib.pro']) + subprocess.check_call(['qmake', 'pictureflow.pro']) subprocess.check_call(['mingw32-make', '-f', 'Makefile.Release']) os.chdir('PyQt') if not os.path.exists('.build'):