#include "render.h" #include #include #include #include #include #include #include #include #include "comic_db.h" #include "yacreader_global_gui.h" #include "configuration.h" template 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 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(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(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 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(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(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(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 redChannel; QList greenChannel; QList 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 hist(256,0); for(int j=0;j 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 f) : QThread(), numPage(np), data(rd), page(p), degrees(d), filters(f), render(r) { } void PageRender::run() { QMutexLocker locker(&(render->mutex)); QImage img; img.loadFromData(data); if (degrees > 0) { QTransform 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() : comic(nullptr), doublePage(false), doubleMangaPage(false), currentIndex(0), numLeftPages(4), numRightPages(4), loadedComic(false), imageRotation(0) { 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() { for (auto *pr : pageRenders) { if (pr != nullptr && pr->wait()) { delete pr; } } // TODO move to share_ptr for (auto *filter : filters) { delete filter; } if (comic != nullptr) { comic->invalidate(); comic->deleteLater(); comic->thread()->quit(); comic->thread()->wait(); } } // 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], &PageRender::pageReady, this, &Render::prepareAvailablePage); // 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() { auto 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 nullptr; } auto 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 nullptr; } } 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 nullptr; } auto 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 nullptr; } } bool Render::currentPageIsDoublePage() { if (currentIndex == 0 && Configuration::getConfiguration().getSettings()->value(COVER_IS_SP, true).toBool()) { return false; } 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 (currentIndex == 2 && Configuration::getConfiguration().getSettings()->value(COVER_IS_SP, true).toBool()) { return false; } 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 != nullptr) { 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 != nullptr) { 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 != nullptr) { loadComic(path, comicDB); startLoad(); } } void Render::createComic(const QString &path) { previousIndex = currentIndex = 0; pagesEmited.clear(); if (comic != nullptr) { comic->invalidate(); comic->disconnect(); // Dispatch pending events to guard against race conditons QCoreApplication::sendPostedEvents(this); comic->deleteLater(); } comic = FactoryComic::newComic(path); if (comic == nullptr) // archivo no encontrado o no válido { emit errorOpening(); reset(); return; } connect(comic, QOverload<>::of(&Comic::errorOpening), this, QOverload<>::of(&Render::errorOpening), Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::errorOpening), this, QOverload::of(&Render::errorOpening), Qt::QueuedConnection); connect(comic, &Comic::crcErrorFound, this, &Render::crcError, Qt::QueuedConnection); connect(comic, QOverload<>::of(&Comic::errorOpening), this, &Render::reset, Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::imageLoaded), this, &Render::pageRawDataReady, Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::imageLoaded), this, QOverload::of(&Render::imageLoaded), Qt::QueuedConnection); connect(comic, &Comic::openAt, this, &Render::renderAt, Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::numPages), this, QOverload::of(&Render::numPages), Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::numPages), this, QOverload::of(&Render::setNumPages), Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::imageLoaded), this, QOverload::of(&Render::imageLoaded), Qt::QueuedConnection); connect(comic, &Comic::isBookmark, this, &Render::currentPageIsBookmark, Qt::QueuedConnection); connect(comic, &Comic::bookmarksUpdated, this, &Render::bookmarksUpdated, Qt::QueuedConnection); // 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 = nullptr; thread = new QThread(); comic->moveToThread(thread); connect(comic, QOverload<>::of(&Comic::errorOpening), thread, &QThread::quit, Qt::QueuedConnection); connect(comic, QOverload::of(&Comic::errorOpening), thread, &QThread::quit, Qt::QueuedConnection); connect(comic, &Comic::imagesLoaded, thread, &QThread::quit, Qt::QueuedConnection); connect(comic, &Comic::destroyed, thread, &QThread::quit, Qt::QueuedConnection); connect(comic, &Comic::invalidated, thread, &QThread::quit, Qt::QueuedConnection); connect(thread, &QThread::started, comic, &Comic::process); connect(thread, &QThread::finished, thread, &QObject::deleteLater); if (thread != nullptr) 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() && ((unsigned int)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 != nullptr) 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()) { pagesEmited.clear(); 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 != nullptr) { 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 != nullptr) { if (pr->wait()) delete pr; } pageRenders.push_front(0); // images buffer.push_front(new QImage()); QImage *p = buffer.back(); if (p != nullptr) delete p; buffer.pop_back(); } } } previousIndex = currentIndex; } void Render::fillBuffer() { if (pagesReady.size() < 1) { return; } 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], &PageRender::pageReady, this, &Render::prepareAvailablePage); 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], &PageRender::pageReady, this, &Render::prepareAvailablePage); 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::setManga(bool manga) { doubleMangaPage = manga; if (comic && doublePage) { // 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(); }