From 91e97ba8ff77a87cd771761b110c8734bed5b9db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Wed, 3 Oct 2012 08:32:43 +0200 Subject: [PATCH] parte de la integraci?n de YACReaderFlowGL en YACReader terminada se ha introducido un bug que hace que la aplicaci?n cierre anormalmente al pasar al siguiente c?mic --- YACReader/YACReader.pro | 14 +- YACReader/goto_flow.cpp | 2 +- YACReader/goto_flow.h | 5 +- YACReader/images.qrc | 3 +- YACReader/viewer.cpp | 3 +- YACReader/viewer.h | 3 +- YACReaderLibrary/images.qrc | 1 + common/comic_flow_widget.cpp | 2 +- common/comic_flow_widget.h | 2 +- common/yacreader_flow_gl.cpp | 359 ++++++++++++++++++++++++++--------- common/yacreader_flow_gl.h | 78 ++++++-- images/defaultCover.png | Bin 0 -> 10871 bytes 12 files changed, 362 insertions(+), 110 deletions(-) create mode 100644 images/defaultCover.png diff --git a/YACReader/YACReader.pro b/YACReader/YACReader.pro index 96aecc7e..3dd4d1fa 100644 --- a/YACReader/YACReader.pro +++ b/YACReader/YACReader.pro @@ -7,7 +7,7 @@ DEPENDPATH += . \ release INCLUDEPATH += . INCLUDEPATH += ../common -QT += network webkit phonon +QT += network webkit phonon opengl CONFIG += release CONFIG -= flat @@ -28,7 +28,11 @@ HEADERS += comic.h \ bookmarks_dialog.h \ render.h \ shortcuts_dialog.h \ - translator.h + translator.h \ + ../common/goto_flow_widget.h \ + goto_flow_gl.h \ + ../common/yacreader_flow_gl.h + SOURCES += comic.cpp \ configuration.cpp \ goto_dialog.cpp \ @@ -46,7 +50,11 @@ SOURCES += comic.cpp \ bookmarks_dialog.cpp \ render.cpp \ shortcuts_dialog.cpp \ - translator.cpp + translator.cpp \ + ../common/goto_flow_widget.cpp \ + goto_flow_gl.cpp \ + ../common/yacreader_flow_gl.cpp + RESOURCES += images.qrc \ files.qrc RC_FILE = icon.rc diff --git a/YACReader/goto_flow.cpp b/YACReader/goto_flow.cpp index edb8fd17..08fe6afb 100644 --- a/YACReader/goto_flow.cpp +++ b/YACReader/goto_flow.cpp @@ -19,7 +19,7 @@ QMutex mutexGoToFlow; GoToFlow::GoToFlow(QWidget *parent,PictureFlow::FlowType flowType) -:QWidget(parent),ready(false) +:GoToFlowWidget(parent),ready(false) { updateTimer = new QTimer; connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateImageData())); diff --git a/YACReader/goto_flow.h b/YACReader/goto_flow.h index c521f2f4..b4a77247 100644 --- a/YACReader/goto_flow.h +++ b/YACReader/goto_flow.h @@ -12,12 +12,13 @@ #include #include #include "custom_widgets.h" +#include "goto_flow_widget.h" class Comic; class SlideInitializer; class PageLoader; -class GoToFlow : public QWidget +class GoToFlow : public GoToFlowWidget { Q_OBJECT public: @@ -31,7 +32,7 @@ private: QIntValidator * v; QPushButton * centerButton; QPushButton * goToButton; - Comic * comic; + //Comic * comic; QSize imageSize; QVector imagesLoaded; diff --git a/YACReader/images.qrc b/YACReader/images.qrc index 2aab79e4..26a9d978 100644 --- a/YACReader/images.qrc +++ b/YACReader/images.qrc @@ -62,6 +62,7 @@ ../images/helpImages/shortcuts.png ../images/helpImages/keyboard.png ../images/helpImages/mouse.png - ../images/helpImages/speaker.png + ../images/helpImages/speaker.png + ../images/defaultCover.png diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp index daa54a4d..477742d7 100644 --- a/YACReader/viewer.cpp +++ b/YACReader/viewer.cpp @@ -3,6 +3,7 @@ #include "configuration.h" #include "magnifying_glass.h" #include "goto_flow.h" +#include "goto_flow_gl.h" #include "bookmarks_dialog.h" #include "render.h" #include "goto_dialog.h" @@ -65,7 +66,7 @@ drag(false) goToDialog = new GoToDialog(this); - goToFlow = new GoToFlow(this,Configuration::getConfiguration().getFlowType()); + goToFlow = new GoToFlowGL(this,Configuration::getConfiguration().getFlowType()); goToFlow->hide(); showGoToFlowAnimation = new QPropertyAnimation(goToFlow,"pos"); showGoToFlowAnimation->setDuration(150); diff --git a/YACReader/viewer.h b/YACReader/viewer.h index 6da04035..8f16d0bf 100644 --- a/YACReader/viewer.h +++ b/YACReader/viewer.h @@ -24,6 +24,7 @@ class BookmarksDialog; class Render; class GoToDialog; class YACReaderTranslator; +class GoToFlowWidget; class Viewer : public QScrollArea { @@ -78,7 +79,7 @@ virtual void mouseReleaseEvent ( QMouseEvent * event ); QTimer * scroller; int posByStep; int nextPos; - GoToFlow * goToFlow; + GoToFlowWidget * goToFlow; QPropertyAnimation * showGoToFlowAnimation; GoToDialog * goToDialog; //!Image properties diff --git a/YACReaderLibrary/images.qrc b/YACReaderLibrary/images.qrc index 92ef8e47..8658670d 100644 --- a/YACReaderLibrary/images.qrc +++ b/YACReaderLibrary/images.qrc @@ -37,5 +37,6 @@ ../images/importComicsInfo.png ../images/db.png ../images/asignNumber.png + ../images/defaultCover.png \ No newline at end of file diff --git a/common/comic_flow_widget.cpp b/common/comic_flow_widget.cpp index 483d1573..e0891027 100644 --- a/common/comic_flow_widget.cpp +++ b/common/comic_flow_widget.cpp @@ -124,7 +124,7 @@ void ComicFlowWidgetSW::mouseDoubleClickEvent(QMouseEvent* event) ComicFlowWidgetGL::ComicFlowWidgetGL(QWidget * parent) :ComicFlowWidget(parent) { - flow = new YACReaderFlowGL(parent); + flow = new YACReaderComicFlowGL(parent); connect(flow,SIGNAL(centerIndexChanged(int)),this,SIGNAL(centerIndexChanged(int))); connect(flow,SIGNAL(selected(unsigned int)),this,SIGNAL(selected(unsigned int))); diff --git a/common/comic_flow_widget.h b/common/comic_flow_widget.h index 832059f3..d6d53f8b 100644 --- a/common/comic_flow_widget.h +++ b/common/comic_flow_widget.h @@ -69,7 +69,7 @@ class ComicFlowWidgetGL : public ComicFlowWidget { Q_OBJECT private: - YACReaderFlowGL * flow; + YACReaderComicFlowGL * flow; public: ComicFlowWidgetGL(QWidget * parent = 0); diff --git a/common/yacreader_flow_gl.cpp b/common/yacreader_flow_gl.cpp index 4057e88f..70723870 100644 --- a/common/yacreader_flow_gl.cpp +++ b/common/yacreader_flow_gl.cpp @@ -204,9 +204,6 @@ YACReaderFlowGL::YACReaderFlowGL(QWidget *parent,struct Preset p) viewRotateActive = 0; stepBackup = config.animationStep/config.animationSpeedUp; - worker = new ImageLoaderGL(this); - worker->flow = this; - /*QTimer * timer = new QTimer(); connect(timer, SIGNAL(timeout()), this, SLOT(updateImageData())); timer->start(70); @@ -679,7 +676,7 @@ void YACReaderFlowGL::populate(int n) float x = 1; float y = 1 * (700/480.0); int i; - GLuint cover = bindTexture(QImage(":/images/notCover.png"),GL_TEXTURE_2D,GL_RGBA,QGLContext::LinearFilteringBindOption | QGLContext::MipmapBindOption); + GLuint cover = bindTexture(QImage(":/images/defaultCover.png"),GL_TEXTURE_2D,GL_RGBA,QGLContext::LinearFilteringBindOption | QGLContext::MipmapBindOption); markTexture = bindTexture(QImage(":/images/setRead.png"),GL_TEXTURE_2D,GL_RGBA,QGLContext::LinearFilteringBindOption | QGLContext::MipmapBindOption); for(i = 0;ibusy()) - return; - - // set image of last one - int idx = worker->index(); - if( idx >= 0 && !worker->result().isNull()) - { - if(!loaded[idx]) - { - float x = 1; - QImage img = worker->result(); - GLuint cover = bindTexture(img, GL_TEXTURE_2D,GL_RGB,QGLContext::LinearFilteringBindOption); - float y = 1 * (float(img.height())/img.width()); - replace("cover", cover, x, y,idx); - /*CFImages[idx].width = x; - CFImages[idx].height = y; - CFImages[idx].img = worker->resultTexture; - strcpy(CFImages[idx].name,"cover");*/ - loaded[idx] = true; - //numImagesLoaded++; - } - } - - // try to load only few images on the left and right side - // i.e. all visible ones plus some extra -#define COUNT 8 - int indexes[2*COUNT+1]; - int center = currentSelected; - indexes[0] = center; - for(int j = 0; j < COUNT; j++) - { - indexes[j*2+1] = center+j+1; - indexes[j*2+2] = center-j-1; - } - for(int c = 0; c < 2*COUNT+1; c++) - { - int i = indexes[c]; - if((i >= 0) && (i < numObjects)) - if(!loaded[i])//slide(i).isNull()) - { - //loader->loadTexture(i); - //loaded[i]=true; - // schedule thumbnail generation - if(paths.size()>0) - { - QString fname = paths.at(i); - //loaded[i]=true; - - worker->generate(i, fname); - } - return; - } - } -} void YACReaderFlowGL::setPreset(const Preset & p) { @@ -873,15 +807,7 @@ void YACReaderFlowGL::clear() { reset(); } -void YACReaderFlowGL::setImagePaths(QStringList paths) -{ - worker->reset(); - reset(); - numObjects = 0; - populate(paths.size()); - this->paths = paths; - numObjects = paths.size(); -} + void YACReaderFlowGL::setCenterIndex(int index) { setCurrentIndex(index); @@ -986,6 +912,170 @@ void YACReaderFlowGL::mouseDoubleClickEvent(QMouseEvent* event) emit selected(centerIndex()); } +YACReaderComicFlowGL::YACReaderComicFlowGL(QWidget *parent,struct Preset p ) + :YACReaderFlowGL(parent,p) +{ + worker = new ImageLoaderGL(this); + worker->flow = this; +} + +void YACReaderComicFlowGL::setImagePaths(QStringList paths) +{ + worker->reset(); + reset(); + numObjects = 0; + populate(paths.size()); + this->paths = paths; + numObjects = paths.size(); +} + + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +void YACReaderComicFlowGL::updateImageData() +{ + // can't do anything, wait for the next possibility + if(worker->busy()) + return; + + // set image of last one + int idx = worker->index(); + if( idx >= 0 && !worker->result().isNull()) + { + if(!loaded[idx]) + { + float x = 1; + QImage img = worker->result(); + GLuint cover = bindTexture(img, GL_TEXTURE_2D,GL_RGB,QGLContext::LinearFilteringBindOption); + float y = 1 * (float(img.height())/img.width()); + replace("cover", cover, x, y,idx); + /*CFImages[idx].width = x; + CFImages[idx].height = y; + CFImages[idx].img = worker->resultTexture; + strcpy(CFImages[idx].name,"cover");*/ + loaded[idx] = true; + //numImagesLoaded++; + } + } + + // try to load only few images on the left and right side + // i.e. all visible ones plus some extra +#define COUNT 8 + int indexes[2*COUNT+1]; + int center = currentSelected; + indexes[0] = center; + for(int j = 0; j < COUNT; j++) + { + indexes[j*2+1] = center+j+1; + indexes[j*2+2] = center-j-1; + } + for(int c = 0; c < 2*COUNT+1; c++) + { + int i = indexes[c]; + if((i >= 0) && (i < numObjects)) + if(!loaded[i])//slide(i).isNull()) + { + //loader->loadTexture(i); + //loaded[i]=true; + // schedule thumbnail generation + if(paths.size()>0) + { + QString fname = paths.at(i); + //loaded[i]=true; + + worker->generate(i, fname); + } + return; + } + } +} + + +YACReaderPageFlowGL::YACReaderPageFlowGL(QWidget *parent,struct Preset p ) + :YACReaderFlowGL(parent,p) +{ + worker = new ImageLoaderByteArrayGL(this); + worker->flow = this; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +void YACReaderPageFlowGL::updateImageData() +{ + // can't do anything, wait for the next possibility + if(worker->busy()) + return; + + // set image of last one + int idx = worker->index(); + if( idx >= 0 && !worker->result().isNull()) + { + if(!loaded[idx]) + { + float x = 1; + QImage img = worker->result(); + GLuint cover = bindTexture(img, GL_TEXTURE_2D,GL_RGB,QGLContext::LinearFilteringBindOption | QGLContext::MipmapBindOption); + float y = 1 * (float(img.height())/img.width()); + replace("cover", cover, x, y,idx); + /*CFImages[idx].width = x; + CFImages[idx].height = y; + CFImages[idx].img = worker->resultTexture; + strcpy(CFImages[idx].name,"cover");*/ + loaded[idx] = true; + //numImagesLoaded++; + } + } + + // try to load only few images on the left and right side + // i.e. all visible ones plus some extra +#define COUNT 8 + int indexes[2*COUNT+1]; + int center = currentSelected; + indexes[0] = center; + for(int j = 0; j < COUNT; j++) + { + indexes[j*2+1] = center+j+1; + indexes[j*2+2] = center-j-1; + } + for(int c = 0; c < 2*COUNT+1; c++) + { + int i = indexes[c]; + if((i >= 0) && (i < numObjects)) + if(rawImages.size()>0) + + if(!loaded[i]&&imagesReady[i])//slide(i).isNull()) + { + //loader->loadTexture(i); + //loaded[i]=true; + // schedule thumbnail generation + + //loaded[i]=true; + + worker->generate(i, rawImages.at(i)); + + return; + } + } +} + +void YACReaderPageFlowGL::populate(int n) +{ + YACReaderFlowGL::populate(n); + imagesReady = QVector (n,false); + rawImages = QVector (n); + imagesSetted = QVector (n,false); //puede sobrar + +} + + //----------------------------------------------------------------------------- //ImageLoader //----------------------------------------------------------------------------- @@ -1079,17 +1169,110 @@ QImage ImageLoaderGL::result() return img; } -WidgetLoader::WidgetLoader(QWidget *parent, QGLWidget * shared) - :QGLWidget(parent,shared) -{ -} - -void WidgetLoader::loadTexture(int index) +//----------------------------------------------------------------------------- +//ImageLoader +//----------------------------------------------------------------------------- +QImage ImageLoaderByteArrayGL::loadImage(const QByteArray& raw) { QImage image; - bool result = image.load(QString("./cover%1.jpg").arg(index+1)); - //image = image.scaledToWidth(128,Qt::SmoothTransformation); //TODO parametrizar - flow->cfImages[index].width = 0.5; - flow->cfImages[index].height = 0.5 * (float(image.height())/image.width()); - flow->cfImages[index].img = bindTexture(image, GL_TEXTURE_2D,GL_RGBA,QGLContext::LinearFilteringBindOption | QGLContext::MipmapBindOption); -} \ No newline at end of file + bool result = image.loadFromData(raw); + + //QGLPixelBuffer * pb = new QGLPixelBuffer(image.size(),flow->format(),flow); + //resultTexture = pb->bindTexture(image,GL_TEXTURE_2D); + + //resultTexture = flow->bindTexture(image,GL_TEXTURE_2D); + + //TODO parametrizar + image = image.scaledToWidth(128,Qt::SmoothTransformation); + + + if(!result) + return QImage(); + + return image; +} + +ImageLoaderByteArrayGL::ImageLoaderByteArrayGL(YACReaderFlowGL * flow): +QThread(),flow(flow),restart(false), working(false), idx(-1) +{ + +} + +ImageLoaderByteArrayGL::~ImageLoaderByteArrayGL() +{ + mutex.lock(); + condition.wakeOne(); + mutex.unlock(); + wait(); +} + +bool ImageLoaderByteArrayGL::busy() const +{ + return isRunning() ? working : false; +} + +void ImageLoaderByteArrayGL::generate(int index, const QByteArray& raw) +{ + mutex.lock(); + this->idx = index; + this->rawData = raw; + this->size = size; + this->img = QImage(); + mutex.unlock(); + + if (!isRunning()) + start(); + else + { + // already running, wake up whenever ready + restart = true; + condition.wakeOne(); + } +} + +void ImageLoaderByteArrayGL::run() +{ + for(;;) + { + // copy necessary data + mutex.lock(); + this->working = true; + QByteArray raw = this->rawData; + mutex.unlock(); + + QImage image = loadImage(raw); + + // let everyone knows it is ready + mutex.lock(); + this->working = false; + this->img = image; + mutex.unlock(); + + // put to sleep + mutex.lock(); + if (!this->restart) + condition.wait(&mutex); + restart = false; + mutex.unlock(); + } +} + +QImage ImageLoaderByteArrayGL::result() +{ + return img; +} + +//WidgetLoader::WidgetLoader(QWidget *parent, QGLWidget * shared) +// :QGLWidget(parent,shared) +//{ +//} +// +//void WidgetLoader::loadTexture(int index) +//{ +// QImage image; +// bool result = image.load(QString("./cover%1.jpg").arg(index+1)); +// //image = image.scaledToWidth(128,Qt::SmoothTransformation); //TODO parametrizar +// flow->cfImages[index].width = 0.5; +// flow->cfImages[index].height = 0.5 * (float(image.height())/image.width()); +// flow->cfImages[index].img = bindTexture(image, GL_TEXTURE_2D,GL_RGBA,QGLContext::LinearFilteringBindOption | QGLContext::MipmapBindOption); +//} \ No newline at end of file diff --git a/common/yacreader_flow_gl.h b/common/yacreader_flow_gl.h index 2a34efe4..4b018ec5 100644 --- a/common/yacreader_flow_gl.h +++ b/common/yacreader_flow_gl.h @@ -16,6 +16,7 @@ class ImageLoaderGL; class QGLContext; class WidgetLoader; +class ImageLoaderByteArrayGL; //Cover Vector typedef struct RVect{ @@ -109,7 +110,7 @@ private: void calcRV(RVect *RV,int pos); void animate(RVect *Current,RVect to); void drawCover(CFImage *CF); - ImageLoaderGL * worker; + int updateCount; WidgetLoader * loader; int fontSize; @@ -211,7 +212,7 @@ public: void setPreset(const Preset & p); - void updateImageData(); + virtual void updateImageData() = 0; void reset(); @@ -223,7 +224,6 @@ public: void unmarkSlide(int index); void setSlideSize(QSize size); void clear(); - void setImagePaths(QStringList paths); void setCenterIndex(int index); void showSlide(int index); int centerIndex(); @@ -243,15 +243,40 @@ signals: void selected(unsigned int); }; -class WidgetLoader : public QGLWidget -{ - Q_OBJECT -public: - WidgetLoader(QWidget *parent, QGLWidget * shared); - YACReaderFlowGL * flow; -public slots: - void loadTexture(int index); +//class WidgetLoader : public QGLWidget +//{ +// Q_OBJECT +//public: +// WidgetLoader(QWidget *parent, QGLWidget * shared); +// YACReaderFlowGL * flow; +//public slots: +// void loadTexture(int index); +// +//}; +class YACReaderComicFlowGL : public YACReaderFlowGL +{ +public: + YACReaderComicFlowGL(QWidget *parent = 0,struct Preset p = defaultYACReaderFlowConfig); + void setImagePaths(QStringList paths); + void updateImageData(); + +private: + ImageLoaderGL * worker; + +}; + +class YACReaderPageFlowGL : public YACReaderFlowGL +{ +public: + YACReaderPageFlowGL(QWidget *parent = 0,struct Preset p = defaultYACReaderFlowConfig); + void updateImageData(); + void populate(int n); + QVector imagesReady; + QVector rawImages; + QVector imagesSetted; +private: + ImageLoaderByteArrayGL * worker; }; class ImageLoaderGL : public QThread @@ -285,6 +310,37 @@ private: QImage img; }; +class ImageLoaderByteArrayGL : public QThread +{ +public: + ImageLoaderByteArrayGL(YACReaderFlowGL * flow); + ~ImageLoaderByteArrayGL(); + // returns FALSE if worker is still busy and can't take the task + bool busy() const; + void generate(int index, const QByteArray& raw); + void reset(){idx = -1;}; + int index() const { return idx; }; + QImage result(); + YACReaderFlowGL * flow; + GLuint resultTexture; + QImage loadImage(const QByteArray& rawData); + +protected: + void run(); + +private: + QMutex mutex; + QWaitCondition condition; + + + bool restart; + bool working; + int idx; + QByteArray rawData; + QSize size; + QImage img; +}; + //class TextureLoader : public QThread //{ //public: diff --git a/images/defaultCover.png b/images/defaultCover.png new file mode 100644 index 0000000000000000000000000000000000000000..32d6bc195a1c89243dbc308b7124a3673bed4d13 GIT binary patch literal 10871 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 jfq$KMSc`@Q{!D(8%8zPoIR?HYWw0={xm