yacreader/YACReader/render.cpp

1187 lines
32 KiB
C++

#include "render.h"
#include <cmath>
#include <QList>
#include <algorithm>
#include <QByteArray>
#include <QPixmap>
#include <QApplication>
#include <QImage>
#include <typeinfo>
#include "comic_db.h"
#include "yacreader_global_gui.h"
template<class T>
inline const T& kClamp( const T& x, const T& low, const T& high )
{
if ( x < low ) return low;
else if ( high < x ) return high;
else return x;
}
inline
int changeBrightness( int value, int brightness )
{
return kClamp( value + brightness * 255 / 100, 0, 255 );
}
inline
int changeContrast( int value, int contrast )
{
return kClamp((( value - 127 ) * contrast / 100 ) + 127, 0, 255 );
}
inline
int changeGamma( int value, int gamma )
{
return kClamp( int( pow( value / 255.0, 100.0 / gamma ) * 255 ), 0, 255 );
}
inline
int changeUsingTable( int value, const int table[] )
{
return table[ value ];
}
template< int operation( int, int ) >
static
QImage changeImage( const QImage& image, int value )
{
QImage im = image;
im.detach();
if( im.colorCount() == 0 ) /* truecolor */
{
if( im.format() != QImage::Format_RGB32 ) /* just in case */
im = im.convertToFormat( QImage::Format_RGB32 );
int table[ 256 ];
for( int i = 0;
i < 256;
++i )
table[ i ] = operation( i, value );
if( im.hasAlphaChannel() )
{
for( int y = 0;
y < im.height();
++y )
{
QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y ));
for( int x = 0;
x < im.width();
++x )
line[ x ] = qRgba( changeUsingTable( qRed( line[ x ] ), table ),
changeUsingTable( qGreen( line[ x ] ), table ),
changeUsingTable( qBlue( line[ x ] ), table ),
changeUsingTable( qAlpha( line[ x ] ), table ));
}
}
else
{
for( int y = 0;
y < im.height();
++y )
{
QRgb* line = reinterpret_cast< QRgb* >( im.scanLine( y ));
for( int x = 0;
x < im.width();
++x )
line[ x ] = qRgb( changeUsingTable( qRed( line[ x ] ), table ),
changeUsingTable( qGreen( line[ x ] ), table ),
changeUsingTable( qBlue( line[ x ] ), table ));
}
}
}
else
{
QVector<QRgb> colors = im.colorTable();
for( int i = 0;
i < im.colorCount();
++i )
colors[ i ] = qRgb( operation( qRed( colors[ i ] ), value ),
operation( qGreen( colors[ i ] ), value ),
operation( qBlue( colors[ i ] ), value ));
im.setColorTable(colors);
}
return im;
}
// brightness is multiplied by 100 in order to avoid floating point numbers
QImage changeBrightness( const QImage& image, int brightness )
{
if( brightness == 0 ) // no change
return image;
return changeImage< changeBrightness >( image, brightness );
}
// contrast is multiplied by 100 in order to avoid floating point numbers
QImage changeContrast( const QImage& image, int contrast )
{
if( contrast == 100 ) // no change
return image;
return changeImage< changeContrast >( image, contrast );
}
// gamma is multiplied by 100 in order to avoid floating point numbers
QImage changeGamma( const QImage& image, int gamma )
{
if( gamma == 100 ) // no change
return image;
return changeImage< changeGamma >( image, gamma );
}
//-----------------------------------------------------------------------------
// MeanNoiseReductionFilter
//-----------------------------------------------------------------------------
MeanNoiseReductionFilter::MeanNoiseReductionFilter(enum NeighborghoodSize ns)
:neighborghoodSize(ns)
{
}
QImage MeanNoiseReductionFilter::setFilter(const QImage & image)
{
int width = image.width();
int height = image.height();
QImage result(width,height,image.format());
int filterSize = sqrt((float)neighborghoodSize);
int bound = filterSize/2;
QRgb pix;
int r,g,b;
for(int j=bound;j<height-bound;j++){
for(int i=bound;i<width-bound;i++){
r=g=b=0;
for(int y=j-bound;y<=j+bound;y++)
{
for(int x=i-bound;x<=i+bound;x++)
{
pix = image.pixel(x,y);
r += qRed(pix);
g += qGreen(pix);
b += qBlue(pix);
}
}
result.setPixel(i,j,QColor(r/neighborghoodSize,g/neighborghoodSize,b/neighborghoodSize).rgb());
//qDebug((QString::number(redChannel.at(4))+" "+QString::number(greenChannel.at(4))+" "+QString::number(blueChannel.at(4))).toAscii());
//qDebug((QString::number(redChannel.size())+" "+QString::number(greenChannel.size())+" "+QString::number(blueChannel.size())).toAscii());
}
}
return result;
}
//-----------------------------------------------------------------------------
// MedianNoiseReductionFilter
//-----------------------------------------------------------------------------
MedianNoiseReductionFilter::MedianNoiseReductionFilter(enum NeighborghoodSize ns)
:neighborghoodSize(ns)
{
}
QImage MedianNoiseReductionFilter::setFilter(const QImage & image)
{
int width = image.width();
int height = image.height();
QImage result(width,height,image.format());
int filterSize = sqrt((float)neighborghoodSize);
int bound = filterSize/2;
QRgb pix;
QList<int> redChannel;
QList<int> greenChannel;
QList<int> blueChannel;
for(int j=bound;j<height-bound;j++){
for(int i=bound;i<width-bound;i++){
redChannel.clear();
greenChannel.clear();
blueChannel.clear();
for(int y=j-bound;y<=j+bound;y++)
{
for(int x=i-bound;x<=i+bound;x++)
{
pix = image.pixel(x,y);
redChannel.push_back(qRed(pix));
greenChannel.push_back(qGreen(pix));
blueChannel.push_back(qBlue(pix));
}
}
std::sort(redChannel.begin(),redChannel.end());
std::sort(greenChannel.begin(),greenChannel.end());
std::sort(blueChannel.begin(),blueChannel.end());
result.setPixel(i,j,QColor(redChannel.at(4),greenChannel.at(4),blueChannel.at(4)).rgb());
}
}
return result;
}
//-----------------------------------------------------------------------------
// BrightnessFilter
//-----------------------------------------------------------------------------
BrightnessFilter::BrightnessFilter(int l)
:ImageFilter()
{
level = l;
}
QImage BrightnessFilter::setFilter(const QImage & image)
{
/*int width = image.width();
int height = image.height();
QImage result(width,height,image.format());
for(int j=0;j<height;j++){
for(int i=0;i<width;i++){
result.setPixel(i,j,QColor(image.pixel(i,j)).light(level).rgb());
}
}
return result;*/
if(level ==-1)
{
QSettings settings(YACReader::getSettingsPath()+"/YACReader.ini",QSettings::IniFormat);
return changeBrightness(image,settings.value(BRIGHTNESS,0).toInt());
}
else
{
return changeBrightness(image,level);
}
}
//-----------------------------------------------------------------------------
// ContrastFilter
//-----------------------------------------------------------------------------
ContrastFilter::ContrastFilter(int l)
:ImageFilter()
{
level = l;
}
QImage ContrastFilter::setFilter(const QImage & image)
{
/*int width = image.width();
int height = image.height();
QImage result(width,height,image.format());
int min,max,v;
min = 0;
max = 255;
int sum = 0;
QVector<int> hist(256,0);
for(int j=0;j<height;j++){
for(int i=0;i<width;i++){
hist[QColor(image.pixel(i,j)).lightness()]++;
sum++;
}
}
long double count = sum;
long double new_count = 0.0;
long double percentage,next_percentage;
for (int i = 0; i < 254; i++)
{
new_count += hist[i];
percentage = new_count/count;
next_percentage = (new_count+hist[i+1])/count;
if(fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
{
min = i+1;
break;
}
}
new_count=0.0;
for (int i = 255; i > 1; i--)
{
new_count += hist[i];
percentage = new_count/count;
next_percentage = (new_count+hist[i-1])/count;
if(fabs (percentage - 0.006) < fabs (next_percentage - 0.006))
{
max = i-1;
break;
}
}
QColor c;
int range = max - min;
for(int j=0;j<height;j++){
for(int i=0;i<width;i++){
c = QColor(image.pixel(i,j));
result.setPixel(i,j,c.light(((c.lightness()-min)/range*1.0)*255).rgb());
}
}
return result;*/
if(level ==-1)
{
QSettings settings(YACReader::getSettingsPath()+"/YACReader.ini",QSettings::IniFormat);
return changeContrast(image,settings.value(CONTRAST,100).toInt());
}
else
{
return changeContrast(image,level);
}
}
//-----------------------------------------------------------------------------
// ContrastFilter
//-----------------------------------------------------------------------------
GammaFilter::GammaFilter(int l)
:ImageFilter()
{
level = l;
}
QImage GammaFilter::setFilter(const QImage & image)
{
if(level ==-1)
{
QSettings settings(YACReader::getSettingsPath()+"/YACReader.ini",QSettings::IniFormat);
return changeGamma(image,settings.value(GAMMA,100).toInt());
}
else
{
return changeGamma(image,level);
}
}
//-----------------------------------------------------------------------------
// PageRender
//-----------------------------------------------------------------------------
PageRender::PageRender()
:QThread()
{
}
PageRender::PageRender(Render * r,int np, const QByteArray & rd, QImage * p,unsigned int d, QVector<ImageFilter *> f)
:QThread(),
render(r),
numPage(np),
data(rd),
page(p),
degrees(d),
filters(f)
{
}
void PageRender::run()
{
QMutexLocker locker(&(render->mutex));
QImage img;
img.loadFromData(data);
if(degrees > 0)
{
QMatrix m;
m.rotate(degrees);
img = img.transformed(m,Qt::SmoothTransformation);
}
for(int i=0;i<filters.size();i++)
{
img = filters[i]->setFilter(img);
}
*page = img;
emit pageReady(numPage);
}
//-----------------------------------------------------------------------------
// Render
//-----------------------------------------------------------------------------
Render::Render()
:currentIndex(0),doublePage(false),doubleMangaPage(false),comic(0),loadedComic(false),imageRotation(0),numLeftPages(4),numRightPages(4)
{
int size = numLeftPages+numRightPages+1;
currentPageBufferedIndex = numLeftPages;
for(int i = 0; i<size; i++)
{
buffer.push_back(new QImage());
pageRenders.push_back(0);
}
filters.push_back(new BrightnessFilter());
filters.push_back(new ContrastFilter());
filters.push_back(new GammaFilter());
}
Render::~Render()
{
if(comic!=0)
{
comic->moveToThread(QApplication::instance()->thread());
comic->deleteLater();
}
foreach(ImageFilter * filter, filters)
delete filter;
foreach(PageRender * pr,pageRenders)
if(pr !=0)
{
if(pr->wait())
delete pr;
}
}
//Este método se encarga de forzar el renderizado de las páginas.
//Actualiza el buffer según es necesario.
//si la pagina actual no está renderizada, se lanza un hilo que la renderize (double or single page mode) y se emite una señal que indica que se está renderizando.
void Render::render()
{
updateBuffer();
if(buffer[currentPageBufferedIndex]->isNull())
{
if(pagesReady.size()>0)
{
if(pagesReady[currentIndex])
{
pageRenders[currentPageBufferedIndex] = new PageRender(this,currentIndex,comic->getRawData()->at(currentIndex),buffer[currentPageBufferedIndex],imageRotation,filters);
}
else
//las páginas no están listas, y se están cargando en el cómic
emit processingPage(); //para evitar confusiones esta señal debería llamarse de otra forma
//si se ha creado un hilo para renderizar la página actual, se arranca
if(pageRenders[currentPageBufferedIndex]!=0)
{
//se conecta la señal pageReady del hilo, con el SLOT prepareAvailablePage
connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
//se emite la señal de procesando, debido a que los hilos se arrancan aquí
if(filters.size()>0)
emit processingPage();
pageRenders[currentPageBufferedIndex]->start();
pageRenders[currentPageBufferedIndex]->setPriority(QThread::TimeCriticalPriority);
}
else
//en qué caso sería necesario hacer esto??? //TODO: IMPORTANTE, puede que no sea necesario.
emit processingPage();
}
else
//no hay ninguna página lista para ser renderizada, es necesario esperar.
emit processingPage();
}
else
// la página actual está lista
{
//emit currentPageReady();
//make prepareAvailablePage the only function that emits currentPageReady()
prepareAvailablePage(currentIndex);
}
fillBuffer();
}
QPixmap * Render::getCurrentPage()
{
QPixmap * page = new QPixmap();
*page = page->fromImage(*buffer[currentPageBufferedIndex]);
return page;
}
QPixmap * Render::getCurrentDoublePage()
{
if (currentPageIsDoublePage())
{
QPoint leftpage(0,0);
QPoint rightpage(0,0);
QSize leftsize = buffer[currentPageBufferedIndex]->size();
QSize rightsize = buffer[currentPageBufferedIndex+1]->size();
int totalWidth,totalHeight;
switch (imageRotation)
{
case 0:
totalHeight = qMax(leftsize.rheight(),rightsize.rheight());
leftsize.scale(leftsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
rightsize.scale(rightsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
totalWidth = leftsize.rwidth() + rightsize.rwidth();
rightpage.setX(leftsize.rwidth());
break;
case 90:
totalWidth = qMax(leftsize.rwidth(), rightsize.rwidth());
leftsize.scale(totalWidth, leftsize.rheight(), Qt::KeepAspectRatioByExpanding);
rightsize.scale(totalWidth, rightsize.rheight(), Qt::KeepAspectRatioByExpanding);
totalHeight = leftsize.rheight() + rightsize.rheight();
rightpage.setY(leftsize.rheight());
break;
case 180:
totalHeight = qMax(leftsize.rheight(),rightsize.rheight());
leftsize.scale(leftsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
rightsize.scale(rightsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
totalWidth = leftsize.rwidth() + rightsize.rwidth();
leftpage.setX(rightsize.rwidth());
break;
case 270:
totalWidth = qMax(leftsize.rwidth(), rightsize.rwidth());
leftsize.scale(totalWidth, leftsize.rheight(), Qt::KeepAspectRatioByExpanding);
rightsize.scale(totalWidth, rightsize.rheight(), Qt::KeepAspectRatioByExpanding);
totalHeight = leftsize.rheight() + rightsize.rheight();
leftpage.setY(rightsize.rheight());
break;
default:
return NULL;
}
QPixmap * page = new QPixmap(totalWidth, totalHeight);
QPainter painter(page);
painter.drawImage(QRect(leftpage,leftsize), *buffer[currentPageBufferedIndex]);
painter.drawImage(QRect(rightpage,rightsize), *buffer[currentPageBufferedIndex+1]);
return page;
}
else
{
return NULL;
}
}
QPixmap * Render::getCurrentDoubleMangaPage()
{
if (currentPageIsDoublePage())
{
QPoint leftpage(0,0);
QPoint rightpage(0,0);
QSize leftsize = buffer[currentPageBufferedIndex+1]->size();
QSize rightsize = buffer[currentPageBufferedIndex]->size();
int totalWidth,totalHeight;
switch (imageRotation)
{
case 0:
totalHeight = qMax(leftsize.rheight(),rightsize.rheight());
leftsize.scale(leftsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
rightsize.scale(rightsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
totalWidth = leftsize.rwidth() + rightsize.rwidth();
rightpage.setX(leftsize.rwidth());
break;
case 90:
totalWidth = qMax(leftsize.rwidth(), rightsize.rwidth());
leftsize.scale(totalWidth, leftsize.rheight(), Qt::KeepAspectRatioByExpanding);
rightsize.scale(totalWidth, rightsize.rheight(), Qt::KeepAspectRatioByExpanding);
totalHeight = leftsize.rheight() + rightsize.rheight();
rightpage.setY(leftsize.rheight());
break;
case 180:
totalHeight = qMax(leftsize.rheight(),rightsize.rheight());
leftsize.scale(leftsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
rightsize.scale(rightsize.rwidth(), totalHeight, Qt::KeepAspectRatioByExpanding);
totalWidth = leftsize.rwidth() + rightsize.rwidth();
leftpage.setX(rightsize.rwidth());
break;
case 270:
totalWidth = qMax(leftsize.rwidth(), rightsize.rwidth());
leftsize.scale(totalWidth, leftsize.rheight(), Qt::KeepAspectRatioByExpanding);
rightsize.scale(totalWidth, rightsize.rheight(), Qt::KeepAspectRatioByExpanding);
totalHeight = leftsize.rheight() + rightsize.rheight();
leftpage.setY(rightsize.rheight());
break;
default:
return NULL;
}
QPixmap * page = new QPixmap(totalWidth, totalHeight);
QPainter painter(page);
painter.drawImage(QRect(rightpage, rightsize), *buffer[currentPageBufferedIndex]);
painter.drawImage(QRect(leftpage, leftsize), *buffer[currentPageBufferedIndex+1]);
return page;
}
else
{
return NULL;
}
}
bool Render::currentPageIsDoublePage()
{
if (buffer[currentPageBufferedIndex]->isNull() || buffer[currentPageBufferedIndex+1]->isNull())
{
return false;
}
if (imageRotation == 0 || imageRotation == 180)
{
if (buffer[currentPageBufferedIndex]->height() > buffer[currentPageBufferedIndex]->width() &&
buffer[currentPageBufferedIndex+1]->height() > buffer[currentPageBufferedIndex+1]->width())
{
return true;
}
}
else if (imageRotation == 90 || imageRotation == 270)
{
if (buffer[currentPageBufferedIndex]->width() > buffer[currentPageBufferedIndex]->height() &&
buffer[currentPageBufferedIndex+1]->width() > buffer[currentPageBufferedIndex+1]->height())
{
return true;
}
}
return false;
}
bool Render::nextPageIsDoublePage()
{
//this function is not used right now
if (buffer[currentPageBufferedIndex+2]->isNull() || buffer[currentPageBufferedIndex+3]->isNull())
{
return false;
}
if (imageRotation == 0 || imageRotation == 180)
{
if (buffer[currentPageBufferedIndex+2]->height() > buffer[currentPageBufferedIndex+2]->width() &&
buffer[currentPageBufferedIndex+3]->height() > buffer[currentPageBufferedIndex+3]->width())
{
return true;
}
}
else if (imageRotation == 90 || imageRotation == 270)
{
if (buffer[currentPageBufferedIndex]->width() > buffer[currentPageBufferedIndex]->height() &&
buffer[currentPageBufferedIndex+1]->width() > buffer[currentPageBufferedIndex+1]->height())
{
return true;
}
}
return false;
}
bool Render::previousPageIsDoublePage()
{
if (buffer[currentPageBufferedIndex-1]->isNull() || buffer[currentPageBufferedIndex-2]->isNull())
{
return false;
}
if (imageRotation == 0 || imageRotation == 180)
{
if (buffer[currentPageBufferedIndex-1]->height() > buffer[currentPageBufferedIndex-1]->width() &&
buffer[currentPageBufferedIndex-2]->height() > buffer[currentPageBufferedIndex-2]->width())
{
return true;
}
}
else if (imageRotation == 90 || imageRotation == 270)
{
if (buffer[currentPageBufferedIndex-1]->width() > buffer[currentPageBufferedIndex-1]->height() &&
buffer[currentPageBufferedIndex-2]->width() > buffer[currentPageBufferedIndex-2]->height())
{
return true;
}
}
return false;
}
void Render::setRotation(int degrees)
{
Q_UNUSED(degrees)
}
void Render::setComic(Comic * c)
{
if(comic !=0)
{
comic->moveToThread(QApplication::instance()->thread());
comic->disconnect();
comic->deleteLater();
}
comic = c;
}
void Render::prepareAvailablePage(int page)
{
if(!doublePage)
{
if (currentIndex == page)
{
emit currentPageReady();
}
}
else
{
//check for last page in double page mode
if ((currentIndex == page) && (currentIndex + 1) >= (int)comic->numPages())
{
emit currentPageReady();
}
else if ((currentIndex == page && !buffer[currentPageBufferedIndex+1]->isNull()) ||
(currentIndex+1 == page && !buffer[currentPageBufferedIndex]->isNull()))
{
emit currentPageReady();
}
}
}
void Render::update()
{
render();
}
//-----------------------------------------------------------------------------
// Comic interface
//-----------------------------------------------------------------------------
void Render::load(const QString & path, int atPage)
{
createComic(path);
if (comic !=0)
{
loadComic(path,atPage);
startLoad();
}
}
//-----------------------------------------------------------------------------
void Render::load(const QString & path, const ComicDB & comicDB)
{
//TODO prepare filters
for(int i = 0; i < filters.count(); i++)
{
if(typeid(*filters[i]) == typeid(BrightnessFilter))
if(comicDB.info.brightness == -1)
filters[i]->setLevel(0);
else
filters[i]->setLevel(comicDB.info.brightness);
if(typeid(*filters[i]) == typeid(ContrastFilter))
if(comicDB.info.contrast == -1)
filters[i]->setLevel(100);
else
filters[i]->setLevel(comicDB.info.contrast);
if(typeid(*filters[i]) == typeid(GammaFilter))
if(comicDB.info.gamma == -1)
filters[i]->setLevel(100);
else
filters[i]->setLevel(comicDB.info.gamma);
}
createComic(path);
if (comic!=0)
{
loadComic(path,comicDB);
startLoad();
}
}
void Render::createComic(const QString & path)
{
if(comic!=0)
{
//comic->moveToThread(QApplication::instance()->thread());
comic->disconnect();
comic->deleteLater();
}
//comic->moveToThread(QApplication::instance()->thread());
comic = FactoryComic::newComic(path);
if(comic == NULL)//archivo no encontrado o no válido
{
emit errorOpening();
reset();
return;
}
previousIndex = currentIndex = 0;
connect(comic,SIGNAL(errorOpening()),this,SIGNAL(errorOpening()));
connect(comic,SIGNAL(errorOpening(QString)),this,SIGNAL(errorOpening(QString)));
connect(comic,SIGNAL(crcErrorFound(QString)),this,SIGNAL(crcError(QString)));
connect(comic,SIGNAL(errorOpening()),this,SLOT(reset()));
connect(comic,SIGNAL(imageLoaded(int)),this,SIGNAL(imageLoaded(int)));
connect(comic,SIGNAL(imageLoaded(int)),this,SLOT(pageRawDataReady(int)));
connect(comic,SIGNAL(openAt(int)),this,SLOT(renderAt(int)));
connect(comic,SIGNAL(numPages(unsigned int)),this,SIGNAL(numPages(unsigned int)));
connect(comic,SIGNAL(numPages(unsigned int)),this,SLOT(setNumPages(unsigned int)));
connect(comic,SIGNAL(imageLoaded(int,QByteArray)),this,SIGNAL(imageLoaded(int,QByteArray)));
connect(comic,SIGNAL(isBookmark(bool)),this,SIGNAL(currentPageIsBookmark(bool)));
connect(comic,SIGNAL(bookmarksUpdated()),this,SIGNAL(bookmarksUpdated()));
//connect(comic,SIGNAL(isLast()),this,SIGNAL(isLast()));
//connect(comic,SIGNAL(isCover()),this,SIGNAL(isCover()));
pagesReady.clear();
}
void Render::loadComic(const QString & path,const ComicDB & comicDB)
{
comic->load(path,comicDB);
}
void Render::loadComic(const QString & path, int atPage)
{
comic->load(path,atPage);
}
void Render::startLoad()
{
QThread * thread = NULL;
thread = new QThread();
comic->moveToThread(thread);
connect(comic, SIGNAL(errorOpening()), thread, SLOT(quit()));
connect(comic, SIGNAL(errorOpening(QString)), thread, SLOT(quit()));
connect(comic, SIGNAL(imagesLoaded()), thread, SLOT(quit()));
connect(thread, SIGNAL(started()), comic, SLOT(process()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
if(thread != NULL)
thread->start();
invalidate();
loadedComic = true;
update();
}
void Render::renderAt(int page)
{
previousIndex = currentIndex = page;
emit pageChanged(page);
}
void Render::reset()
{
loadedComic = false;
invalidate();
}
//si se solicita la siguiente página, se calcula cuál debe ser en función de si se lee en modo a doble página o no.
//la página sólo se renderiza, si realmente ha cambiado.
void Render::nextPage()
{
int nextPage; //indica cuál será la próxima página
nextPage = comic->nextPage();
//se fuerza renderizado si la página ha cambiado
if(currentIndex != nextPage)
{
previousIndex = currentIndex;
currentIndex = nextPage;
update();
emit pageChanged(currentIndex);
}
else if (hasLoadedComic() && (currentIndex == numPages()-1))
{
emit isLast();
}
}
void Render::nextDoublePage()
{
int nextPage;
if (currentIndex +2 < (int)comic->numPages())
{
nextPage = currentIndex+2;
}
else
{
nextPage = currentIndex;
}
if(currentIndex != nextPage)
{
comic->setIndex(nextPage);
previousIndex = currentIndex;
currentIndex = nextPage;
update();
emit pageChanged(currentIndex);
}
else if (hasLoadedComic() && ((unsigned int)currentIndex >= numPages()-2))
{
emit isLast();
}
}
//si se solicita la página anterior, se calcula cuál debe ser en función de si se lee en modo a doble página o no.
//la página sólo se renderiza, si realmente ha cambiado.
void Render::previousPage()
{
int previousPage; //indica cuál será la próxima página
previousPage = comic->previousPage();
//se fuerza renderizado si la página ha cambiado
if(currentIndex != previousPage)
{
previousIndex = currentIndex;
currentIndex = previousPage;
update();
emit pageChanged(currentIndex);
}
else if (hasLoadedComic() && (currentIndex == 0))
{
emit isCover();
}
}
void Render::previousDoublePage()
{
int previousPage; //indica cuál será la próxima página
previousPage = qMax(currentIndex-2,0);
if(currentIndex != previousPage)
{
comic->setIndex(previousPage);
previousIndex = currentIndex;
currentIndex = previousPage;
update();
emit pageChanged(currentIndex);
}
}
unsigned int Render::getIndex()
{
return comic->getIndex();
}
unsigned int Render::numPages()
{
return comic->numPages();
}
bool Render::hasLoadedComic()
{
if(comic!=0)
return comic->loaded();
return false;
}
void Render::setNumPages(unsigned int numPages)
{
pagesReady.fill(false,numPages);
}
void Render::pageRawDataReady(int page)
{
if (!hasLoadedComic())
return;
pagesEmited.push_back(page);
if(pageRenders.size()>0)
{
for(int i=0;i<pagesEmited.size();i++)
{
if(pagesEmited.at(i)>= pagesReady.size())
return; //Oooops, something went wrong
pagesReady[pagesEmited.at(i)] = true;
if(pagesEmited.at(i) == currentIndex)
update();
else
{
if ( ((pagesEmited.at(i) < currentIndex) && (pagesEmited.at(i) > currentIndex-numLeftPages)) ||
((pagesEmited.at(i) > currentIndex) && (pagesEmited.at(i) < currentIndex+numRightPages)) )
{
fillBuffer();
}
}
}
pagesEmited.clear();
}
}
//sólo se renderiza la página, si ha habido un cambio de página
void Render::goTo(int index)
{
if(currentIndex != index)
{
comic->setIndex(index);
previousIndex = currentIndex;
currentIndex = index;
update();
emit pageChanged(currentIndex);
}
}
void Render::rotateRight()
{
imageRotation = (imageRotation+90) % 360;
reload();
}
void Render::rotateLeft()
{
if(imageRotation == 0)
imageRotation = 270;
else
imageRotation = imageRotation - 90;
reload();
}
//Actualiza el buffer, añadiendo las imágenes (vacías) necesarias para su posterior renderizado y
//eliminado aquellas que ya no sean necesarias. También libera los hilos (no estoy seguro de que sea responsabilidad suya)
//Calcula el número de nuevas páginas que hay que buferear y si debe hacerlo por la izquierda o la derecha (según sea el sentido de la lectura)
void Render::updateBuffer()
{
QMutexLocker locker(&mutex);
int windowSize = currentIndex - previousIndex;
if(windowSize > 0)//add pages to right pages and remove on the left
{
windowSize = qMin(windowSize,buffer.size());
for(int i = 0; i < windowSize; i++)
{
//renders
PageRender * pr = pageRenders.front();
pageRenders.pop_front();
if(pr !=0)
{
if(pr->wait())
delete pr;
}
pageRenders.push_back(0);
//images
if(buffer.front()!=0)
delete buffer.front();
buffer.pop_front();
buffer.push_back(new QImage());
}
}
else //add pages to left pages and remove on the right
if(windowSize<0)
{
windowSize = -windowSize;
windowSize = qMin(windowSize,buffer.size());
for(int i = 0; i < windowSize; i++)
{
//renders
PageRender * pr = pageRenders.back();
pageRenders.pop_back();
if(pr !=0)
{
if(pr->wait())
delete pr;
}
pageRenders.push_front(0);
//images
buffer.push_front(new QImage());
QImage * p = buffer.back();
if(p!=0)
delete p;
buffer.pop_back();
}
}
previousIndex = currentIndex;
}
void Render::fillBuffer()
{
for(int i = 1; i <= qMax(numLeftPages,numRightPages); i++)
{
if ((currentIndex+i < (int)comic->numPages()) &&
buffer[currentPageBufferedIndex+i]->isNull() &&
i <= numRightPages &&
pageRenders[currentPageBufferedIndex+i]==0 &&
pagesReady[currentIndex+i]) //preload next pages
{
pageRenders[currentPageBufferedIndex+i] = new PageRender(this,currentIndex+i,comic->getRawData()->at(currentIndex+i),buffer[currentPageBufferedIndex+i],imageRotation,filters);
connect(pageRenders[currentPageBufferedIndex+i],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
pageRenders[currentPageBufferedIndex+i]->start();
}
if ((currentIndex-i > 0) &&
buffer[currentPageBufferedIndex-i]->isNull() &&
i <= numLeftPages &&
pageRenders[currentPageBufferedIndex-i]==0 &&
pagesReady[currentIndex-i]) //preload previous pages
{
pageRenders[currentPageBufferedIndex-i] = new PageRender(this,currentIndex-i,comic->getRawData()->at(currentIndex-i),buffer[currentPageBufferedIndex-i],imageRotation,filters);
connect(pageRenders[currentPageBufferedIndex-i],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int)));
pageRenders[currentPageBufferedIndex-i]->start();
}
}
}
//Método que debe ser llamado cada vez que la estructura del buffer se vuelve inconsistente con el modo de lectura actual.
//se terminan todos los hilos en ejecución y se libera la memoria (de hilos e imágenes)
void Render::invalidate()
{
for(int i=0;i<pageRenders.size();i++)
{
if(pageRenders[i]!=0)
{
pageRenders[i]->wait();
delete pageRenders[i];
pageRenders[i] = 0;
}
}
for(int i=0;i<buffer.size();i++)
{
delete buffer[i];
buffer[i] = new QImage();
}
}
void Render::doublePageSwitch()
{
doublePage = !doublePage;
if(comic)
{
//invalidate();
update();
}
}
void Render::doubleMangaPageSwitch()
{
doubleMangaPage = !doubleMangaPage;
if(comic&&doublePage)
{
//invalidate();
update();
}
}
QString Render::getCurrentPagesInformation()
{
QString s = QString::number(currentIndex+1);
if (doublePage && (currentIndex+1 < (int)comic->numPages()))
{
if (currentPageIsDoublePage())
{
if (doubleMangaPage)
s = QString::number(currentIndex+2) + "-" + s;
else
s += "-"+QString::number(currentIndex+2);
}
}
s += "/"+QString::number(comic->numPages());
return s;
}
void Render::setBookmark()
{
comic->setBookmark();
}
void Render::removeBookmark()
{
comic->removeBookmark();
}
void Render::save()
{
comic->saveBookmarks();
}
Bookmarks * Render::getBookmarks()
{
return comic->bm;
}
void Render::reload()
{
if(comic)
{
invalidate();
update();
}
}
void Render::updateFilters(int brightness, int contrast, int gamma)
{
for(int i = 0; i < filters.count(); i++)
{
if(typeid(*filters[i]) == typeid(BrightnessFilter))
filters[i]->setLevel(brightness);
if(typeid(*filters[i]) == typeid(ContrastFilter))
filters[i]->setLevel(contrast);
if(typeid(*filters[i]) == typeid(GammaFilter))
filters[i]->setLevel(gamma);
}
reload();
}