File indexing completed on 2025-03-09 03:52:08
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2007-11-14 0007 * Description : a presentation tool. 0008 * 0009 * SPDX-FileCopyrightText: 2007-2009 by Valerio Fuoglio <valerio dot fuoglio at gmail dot com> 0010 * SPDX-FileCopyrightText: 2012-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * Parts of this code are based on 0013 * smoothslidesaver by Carsten Weinhold <carsten dot weinhold at gmx dot de> 0014 * and slideshowgl by Renchi Raju <renchi dot raju at gmail dot com> 0015 * 0016 * SPDX-License-Identifier: GPL-2.0-or-later 0017 * 0018 * ============================================================ */ 0019 0020 #include "presentationkb_p.h" 0021 0022 // C++ includes 0023 0024 #include <cmath> 0025 0026 // Qt includes 0027 0028 #include <QImage> 0029 #include <QPainter> 0030 #include <QFont> 0031 #include <QCursor> 0032 #include <QPixmap> 0033 #include <QApplication> 0034 #include <QScreen> 0035 #include <QWindow> 0036 0037 // KDE includes 0038 0039 #include <klocalizedstring.h> 0040 #include <ksharedconfig.h> 0041 #include <kconfiggroup.h> 0042 0043 namespace DigikamGenericPresentationPlugin 0044 { 0045 0046 KBViewTrans::KBViewTrans(bool zoomIn, float relAspect) 0047 : m_deltaX (0.0), 0048 m_deltaY (0.0), 0049 m_deltaScale (0.0), 0050 m_baseScale (0.0), 0051 m_baseX (0.0), 0052 m_baseY (0.0), 0053 m_xScale (0.0), 0054 m_yScale (0.0) 0055 { 0056 int i = 0; 0057 0058 // randomly select sizes of start and end viewport 0059 0060 double s[2] = { 0.0 }; 0061 0062 do 0063 { 0064 s[0] = 0.3 * rnd() + 1.0; 0065 s[1] = 0.3 * rnd() + 1.0; 0066 } 0067 while ((fabs(s[0] - s[1]) < 0.15) && (++i < 10)); 0068 0069 if (zoomIn ^ (s[0] > s[1])) 0070 { 0071 double tmp = s[0]; 0072 s[0] = s[1]; 0073 s[1] = tmp; 0074 } 0075 0076 m_deltaScale = s[1] / s[0] - 1.0; 0077 m_baseScale = s[0]; 0078 0079 // additional scale factors to ensure proper m_aspect of the displayed image 0080 0081 double x[2] = { 0.0 }; 0082 double y[2] = { 0.0 }; 0083 double xMargin[2] = { 0.0 }; 0084 double yMargin[2] = { 0.0 }; 0085 double bestDist = 0.0;; 0086 double sx = 0.0; 0087 double sy = 0.0; 0088 0089 if (relAspect > 1.0) 0090 { 0091 sx = 1.0; 0092 sy = relAspect; 0093 } 0094 else 0095 { 0096 sx = 1.0 / relAspect; 0097 sy = 1.0; 0098 } 0099 0100 m_xScale = sx; 0101 m_yScale = sy; 0102 0103 // calculate path 0104 0105 xMargin[0] = (s[0] * sx - 1.0) / 2.0; 0106 yMargin[0] = (s[0] * sy - 1.0) / 2.0; 0107 xMargin[1] = (s[1] * sx - 1.0) / 2.0; 0108 yMargin[1] = (s[1] * sy - 1.0) / 2.0; 0109 0110 i = 0; 0111 bestDist = 0.0; 0112 0113 do 0114 { 0115 double sign = rndSign(); 0116 x[0] = xMargin[0] * (0.2 * rnd() + 0.8) * sign; 0117 y[0] = yMargin[0] * (0.2 * rnd() + 0.8) * -sign; 0118 x[1] = xMargin[1] * (0.2 * rnd() + 0.8) * -sign; 0119 y[1] = yMargin[1] * (0.2 * rnd() + 0.8) * sign; 0120 0121 if (fabs(x[1] - x[0]) + fabs(y[1] - y[0]) > bestDist) 0122 { 0123 m_baseX = x[0]; 0124 m_baseY = y[0]; 0125 m_deltaX = x[1] - x[0]; 0126 m_deltaY = y[1] - y[0]; 0127 bestDist = fabs(m_deltaX) + fabs(m_deltaY); 0128 } 0129 0130 } 0131 while ((bestDist < 0.3) && (++i < 10)); 0132 } 0133 0134 KBViewTrans::KBViewTrans() 0135 : m_deltaX (0.0), 0136 m_deltaY (0.0), 0137 m_deltaScale (0.0), 0138 m_baseScale (0.0), 0139 m_baseX (0.0), 0140 m_baseY (0.0), 0141 m_xScale (0.0), 0142 m_yScale (0.0) 0143 { 0144 } 0145 0146 KBViewTrans::~KBViewTrans() 0147 { 0148 } 0149 0150 float KBViewTrans::transX(float pos) const 0151 { 0152 return (m_baseX + m_deltaX * pos); 0153 } 0154 0155 float KBViewTrans::transY(float pos) const 0156 { 0157 return (m_baseY + m_deltaY * pos); 0158 } 0159 0160 float KBViewTrans::scale (float pos) const 0161 { 0162 return (m_baseScale * (1.0 + m_deltaScale * pos)); 0163 } 0164 0165 float KBViewTrans::xScaleCorrect() const 0166 { 0167 return m_xScale; 0168 } 0169 0170 float KBViewTrans::yScaleCorrect() const 0171 { 0172 return m_yScale; 0173 } 0174 0175 double KBViewTrans::rnd() const 0176 { 0177 return QRandomGenerator::global()->generateDouble(); 0178 } 0179 0180 double KBViewTrans::rndSign() const 0181 { 0182 return ((QRandomGenerator::global()->bounded(2U) == 0) ? 1.0 : -1.0); 0183 } 0184 0185 // ------------------------------------------------------------------------- 0186 0187 KBImage::KBImage(KBViewTrans* const viewTrans, float aspect) 0188 : m_viewTrans (viewTrans), 0189 m_aspect (aspect), 0190 m_pos (0.0), 0191 m_opacity (0.0), 0192 m_texture (nullptr) 0193 { 0194 m_paint = (m_viewTrans) ? true : false; 0195 } 0196 0197 KBImage::~KBImage() 0198 { 0199 if (m_texture) 0200 { 0201 m_texture->destroy(); 0202 } 0203 0204 delete m_viewTrans; 0205 delete m_texture; 0206 } 0207 0208 // ------------------------------------------------------------------------- 0209 0210 PresentationKB::PresentationKB(PresentationContainer* const sharedData) 0211 : QOpenGLWidget(), 0212 d (new Private) 0213 { 0214 setAttribute(Qt::WA_DeleteOnClose); 0215 setContextMenuPolicy(Qt::PreventContextMenu); 0216 0217 #ifdef Q_OS_WIN 0218 0219 setWindowFlags(Qt::Popup | 0220 Qt::FramelessWindowHint | 0221 Qt::WindowStaysOnTopHint); 0222 0223 #else 0224 0225 setWindowState(windowState() | Qt::WindowFullScreen); 0226 0227 #endif 0228 0229 QScreen* screen = qApp->primaryScreen(); 0230 0231 if (QWidget* const widget = qApp->activeWindow()) 0232 { 0233 if (QWindow* const window = widget->windowHandle()) 0234 { 0235 screen = window->screen(); 0236 } 0237 } 0238 0239 QRect deskRect = screen->geometry(); 0240 d->deskX = deskRect.x(); 0241 d->deskY = deskRect.y(); 0242 d->deskWidth = deskRect.width(); 0243 d->deskHeight = deskRect.height(); 0244 0245 move(d->deskX, d->deskY); 0246 resize(d->deskWidth, d->deskHeight); 0247 0248 d->sharedData = sharedData; 0249 d->sharedData->display = this; 0250 0251 readSettings(); 0252 0253 unsigned frameRate; 0254 0255 if (d->forceFrameRate == 0) 0256 { 0257 frameRate = qRound(screen->refreshRate() * 2); 0258 } 0259 else 0260 { 0261 frameRate = d->forceFrameRate; 0262 } 0263 0264 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Frame Rate : " << frameRate; 0265 0266 d->image[0] = new KBImage(nullptr); 0267 d->image[1] = new KBImage(nullptr); 0268 d->step = 1.0 / ((float)(d->delay * frameRate)); 0269 d->enableSameSpeed = d->sharedData->kbEnableSameSpeed; 0270 d->imageLoadThread = new KBImageLoader(d->sharedData, width(), height()); 0271 d->timer = new QTimer(this); 0272 0273 connect(d->timer, SIGNAL(timeout()), 0274 this, SLOT(moveSlot())); 0275 0276 // -- playback widget ------------------------------- 0277 0278 #ifdef HAVE_MEDIAPLAYER 0279 0280 d->playbackWidget = new PresentationAudioWidget(this, d->sharedData->soundtrackUrls, d->sharedData); 0281 d->playbackWidget->hide(); 0282 d->playbackWidget->move(0, 0); 0283 0284 #endif 0285 0286 // -- hide cursor when not moved -------------------- 0287 0288 d->mouseMoveTimer = new QTimer(this); 0289 d->mouseMoveTimer->setSingleShot(true); 0290 0291 connect(d->mouseMoveTimer, SIGNAL(timeout()), 0292 this, SLOT(slotMouseMoveTimeOut())); 0293 0294 setMouseTracking(true); 0295 slotMouseMoveTimeOut(); 0296 0297 // -- load image and let's start 0298 0299 d->imageLoadThread->start(); 0300 d->timer->start(1000 / frameRate); 0301 0302 #ifdef HAVE_MEDIAPLAYER 0303 0304 if (d->sharedData->soundtrackPlay) 0305 { 0306 d->playbackWidget->slotPlay(); 0307 } 0308 0309 #endif 0310 0311 } 0312 0313 PresentationKB::~PresentationKB() 0314 { 0315 0316 #ifdef HAVE_MEDIAPLAYER 0317 0318 d->playbackWidget->slotStop(); 0319 0320 #endif 0321 0322 d->timer->stop(); 0323 d->mouseMoveTimer->stop(); 0324 0325 delete d->effect; 0326 delete d->image[0]; 0327 delete d->image[1]; 0328 0329 if (d->endTexture) 0330 { 0331 d->endTexture->destroy(); 0332 } 0333 0334 delete d->endTexture; 0335 0336 d->imageLoadThread->quit(); 0337 bool terminated = d->imageLoadThread->wait(10000); 0338 0339 if (!terminated) 0340 { 0341 d->imageLoadThread->terminate(); 0342 d->imageLoadThread->wait(3000); 0343 } 0344 0345 delete d->imageLoadThread; 0346 delete d; 0347 } 0348 0349 float PresentationKB::aspect() const 0350 { 0351 return ((float)width() / (float)height()); 0352 } 0353 0354 void PresentationKB::setNewKBEffect() 0355 { 0356 KBEffect::Type type; 0357 bool needFadeIn = ((d->effect == nullptr) || (d->effect->type() == KBEffect::Fade)); 0358 0359 // we currently only have two effects 0360 0361 if (d->disableFadeInOut) 0362 { 0363 type = KBEffect::Blend; 0364 } 0365 else if (d->disableCrossFade) 0366 { 0367 type = KBEffect::Fade; 0368 } 0369 else 0370 { 0371 type = KBEffect::chooseKBEffect((d->effect) ? d->effect->type() : KBEffect::Fade); 0372 } 0373 0374 delete d->effect; 0375 0376 switch (type) 0377 { 0378 case KBEffect::Fade: 0379 { 0380 d->effect = new FadeKBEffect(this, needFadeIn); 0381 break; 0382 } 0383 0384 case KBEffect::Blend: 0385 { 0386 d->effect = new BlendKBEffect(this, needFadeIn); 0387 break; 0388 } 0389 0390 default: 0391 { 0392 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "Unknown transition effect, falling back to crossfade"; 0393 d->effect = new BlendKBEffect(this, needFadeIn); 0394 break; 0395 } 0396 } 0397 } 0398 0399 void PresentationKB::moveSlot() 0400 { 0401 if (d->initialized) 0402 { 0403 if (d->effect->done()) 0404 { 0405 setNewKBEffect(); 0406 d->imageLoadThread->requestNewImage(); 0407 d->endOfShow = !d->haveImages; 0408 } 0409 0410 if (d->enableSameSpeed) 0411 { 0412 d->effect->advanceTime(d->stepSameSpeed); 0413 } 0414 else 0415 { 0416 d->effect->advanceTime(d->step); 0417 } 0418 } 0419 0420 update(); 0421 } 0422 0423 bool PresentationKB::setupNewImage(int idx) 0424 { 0425 Q_ASSERT(idx >= 0 && idx < 2); 0426 0427 if (!d->haveImages) 0428 { 0429 return false; 0430 } 0431 0432 bool ok = false; 0433 d->zoomIn = !d->zoomIn; 0434 0435 if (d->imageLoadThread->grabImage()) 0436 { 0437 delete d->image[idx]; 0438 0439 // we have the image lock and there is an image 0440 0441 float imageAspect = d->imageLoadThread->imageAspect(); 0442 KBViewTrans* const viewTrans = new KBViewTrans(d->zoomIn, aspect() / imageAspect); 0443 d->image[idx] = new KBImage(viewTrans, imageAspect); 0444 0445 applyTexture(d->image[idx], d->imageLoadThread->image()); 0446 ok = true; 0447 0448 } 0449 else 0450 { 0451 d->haveImages = false; 0452 } 0453 0454 // don't forget to release the lock on the copy of the image 0455 // owned by the image loader thread 0456 0457 d->imageLoadThread->ungrabImage(); 0458 0459 return ok; 0460 } 0461 0462 void PresentationKB::startSlideShowOnce() 0463 { 0464 // when the image loader thread is ready, it will already have loaded 0465 // the first image 0466 if ((d->initialized == false) && d->imageLoadThread->ready()) 0467 { 0468 setupNewImage(0); // setup the first image and 0469 d->imageLoadThread->requestNewImage(); // load the next one in background 0470 setNewKBEffect(); // set the initial effect 0471 if (d->enableSameSpeed) 0472 { 0473 d->stepSameSpeed = d->step / d->imageLoadThread->imageAspect(); 0474 } 0475 0476 d->initialized = true; 0477 } 0478 } 0479 0480 void PresentationKB::swapImages() 0481 { 0482 KBImage* const tmp = d->image[0]; 0483 d->image[0] = d->image[1]; 0484 d->image[1] = tmp; 0485 } 0486 0487 void PresentationKB::initializeGL() 0488 { 0489 // Enable Texture Mapping 0490 0491 glEnable(GL_TEXTURE_2D); 0492 0493 // Clear The Background Color 0494 0495 glClearColor(0.0, 0.0, 0.0, 1.0f); 0496 0497 glEnable(GL_TEXTURE_2D); 0498 glShadeModel(GL_SMOOTH); 0499 0500 // Turn Blending On 0501 0502 glEnable(GL_BLEND); 0503 0504 // Blending Function For Translucency Based On Source Alpha Value 0505 0506 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 0507 0508 // Enable perspective vision 0509 0510 glClearDepth(1.0f); 0511 } 0512 0513 void PresentationKB::paintGL() 0514 { 0515 startSlideShowOnce(); 0516 0517 glDisable(GL_DEPTH_TEST); 0518 glDepthMask(GL_FALSE); 0519 0520 // only clear the color buffer, if none of the active images is fully opaque 0521 0522 if (!((d->image[0]->m_paint && (d->image[0]->m_opacity == 1.0)) || 0523 (d->image[1]->m_paint && (d->image[1]->m_opacity == 1.0)))) 0524 { 0525 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 0526 } 0527 0528 glLoadIdentity(); 0529 glMatrixMode(GL_PROJECTION); 0530 glLoadIdentity(); 0531 glMatrixMode(GL_MODELVIEW); 0532 glLoadIdentity(); 0533 0534 if (d->endOfShow) 0535 { 0536 endOfShow(); 0537 d->timer->stop(); 0538 } 0539 else 0540 { 0541 if (d->image[1]->m_paint) 0542 { 0543 paintTexture(d->image[1]); 0544 } 0545 0546 if (d->image[0]->m_paint) 0547 { 0548 paintTexture(d->image[0]); 0549 } 0550 } 0551 0552 glFlush(); 0553 } 0554 0555 void PresentationKB::resizeGL(int w, int h) 0556 { 0557 glViewport(0, 0, (GLint)w, (GLint)h); 0558 } 0559 0560 void PresentationKB::applyTexture(KBImage* const img, const QImage &texture) 0561 { 0562 /* create the texture */ 0563 0564 img->m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); 0565 img->m_texture->setData(texture.mirrored()); 0566 img->m_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); 0567 img->m_texture->setMagnificationFilter(QOpenGLTexture::Linear); 0568 img->m_texture->bind(); 0569 } 0570 0571 void PresentationKB::paintTexture(KBImage* const img) 0572 { 0573 glMatrixMode(GL_MODELVIEW); 0574 glLoadIdentity(); 0575 0576 float sx = img->m_viewTrans->xScaleCorrect(); 0577 float sy = img->m_viewTrans->yScaleCorrect(); 0578 0579 glTranslatef(img->m_viewTrans->transX(img->m_pos) * 2.0, img->m_viewTrans->transY(img->m_pos) * 2.0, 0.0); 0580 glScalef(img->m_viewTrans->scale(img->m_pos), img->m_viewTrans->scale(img->m_pos), 0.0); 0581 0582 img->m_texture->bind(); 0583 0584 glBegin(GL_QUADS); 0585 { 0586 glColor4f(1.0, 1.0, 1.0, img->m_opacity); 0587 glTexCoord2f(0, 0); 0588 glVertex3f(-sx, -sy, 0); 0589 0590 glTexCoord2f(1, 0); 0591 glVertex3f(sx, -sy, 0); 0592 0593 glTexCoord2f(1, 1); 0594 glVertex3f(sx, sy, 0); 0595 0596 glTexCoord2f(0, 1); 0597 glVertex3f(-sx, sy, 0); 0598 } 0599 glEnd(); 0600 } 0601 0602 void PresentationKB::readSettings() 0603 { 0604 KSharedConfigPtr config = KSharedConfig::openConfig(); 0605 KConfigGroup group = config->group(QLatin1String("Presentation Settings")); 0606 0607 d->delay = group.readEntry("Delay", 8000) / 1000; 0608 d->disableFadeInOut = group.readEntry("KB Disable FadeInOut", false); 0609 d->disableCrossFade = group.readEntry("KB Disable Crossfade", false); 0610 d->forceFrameRate = group.readEntry("KB Force Framerate", 0); 0611 0612 if (d->delay < 5) 0613 { 0614 d->delay = 5; 0615 } 0616 0617 if (d->forceFrameRate > 120) 0618 { 0619 d->forceFrameRate = 120; 0620 } 0621 } 0622 0623 void PresentationKB::endOfShow() 0624 { 0625 QPixmap pix(512, 512); 0626 pix.fill(Qt::black); 0627 0628 QFont fn(font()); 0629 fn.setPointSize(fn.pointSize() + 10); 0630 fn.setBold(true); 0631 0632 QPainter p(&pix); 0633 p.setPen(Qt::white); 0634 p.setFont(fn); 0635 p.drawText(20, 50, i18n("SlideShow Completed")); 0636 p.drawText(20, 100, i18n("Click to Exit...")); 0637 p.end(); 0638 0639 /* create the texture */ 0640 0641 d->endTexture = new QOpenGLTexture(QOpenGLTexture::Target2D); 0642 d->endTexture->setData(pix.toImage().mirrored()); 0643 d->endTexture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); 0644 d->endTexture->setMagnificationFilter(QOpenGLTexture::Linear); 0645 d->endTexture->bind(); 0646 0647 /* paint the texture */ 0648 0649 glMatrixMode(GL_MODELVIEW); 0650 glLoadIdentity(); 0651 0652 glBegin(GL_QUADS); 0653 { 0654 glColor4f(1.0, 1.0, 1.0, 1.0); 0655 glTexCoord2f(0, 0); 0656 glVertex3f(-1.0, -1.0, 0); 0657 0658 glTexCoord2f(1, 0); 0659 glVertex3f(1.0, -1.0, 0); 0660 0661 glTexCoord2f(1, 1); 0662 glVertex3f(1.0, 1.0, 0); 0663 0664 glTexCoord2f(0, 1); 0665 glVertex3f(-1.0, 1.0, 0); 0666 } 0667 glEnd(); 0668 0669 d->showingEnd = true; 0670 } 0671 0672 QStringList PresentationKB::effectNames() 0673 { 0674 QStringList effects; 0675 effects.append(QLatin1String("Ken Burns")); 0676 0677 return effects; 0678 } 0679 0680 QMap<QString, QString> PresentationKB::effectNamesI18N() 0681 { 0682 QMap<QString, QString> effects; 0683 effects[QLatin1String("Ken Burns")] = i18n("Ken Burns"); 0684 0685 return effects; 0686 } 0687 0688 void PresentationKB::keyPressEvent(QKeyEvent* event) 0689 { 0690 if (!event) 0691 { 0692 return; 0693 } 0694 0695 #ifdef HAVE_MEDIAPLAYER 0696 0697 d->playbackWidget->keyPressEvent(event); 0698 0699 #endif 0700 0701 if (event->key() == Qt::Key_Escape) 0702 { 0703 close(); 0704 } 0705 } 0706 0707 void PresentationKB::mousePressEvent(QMouseEvent* e) 0708 { 0709 if (!e) 0710 { 0711 return; 0712 } 0713 0714 if (d->endOfShow && d->showingEnd) 0715 { 0716 slotClose(); 0717 } 0718 } 0719 0720 void PresentationKB::mouseMoveEvent(QMouseEvent* e) 0721 { 0722 setCursor(QCursor(Qt::ArrowCursor)); 0723 d->mouseMoveTimer->start(1000); 0724 0725 #ifdef HAVE_MEDIAPLAYER 0726 0727 if (!d->playbackWidget->canHide()) 0728 { 0729 return; 0730 } 0731 0732 QPoint pos(e->pos()); 0733 0734 if ((pos.y() > 20) && 0735 (pos.y() < (d->deskHeight - 20 - 1))) 0736 { 0737 if (d->playbackWidget->isHidden()) 0738 { 0739 return; 0740 } 0741 else 0742 { 0743 d->playbackWidget->hide(); 0744 setFocus(); 0745 } 0746 0747 return; 0748 } 0749 0750 d->playbackWidget->show(); 0751 0752 #else 0753 0754 Q_UNUSED(e); 0755 0756 #endif 0757 } 0758 0759 void PresentationKB::slotMouseMoveTimeOut() 0760 { 0761 QPoint pos(QCursor::pos()); 0762 0763 if ((pos.y() < 20) || 0764 (pos.y() > (d->deskHeight - 20 - 1)) 0765 0766 #ifdef HAVE_MEDIAPLAYER 0767 0768 || d->playbackWidget->underMouse() 0769 0770 #endif 0771 0772 ) 0773 { 0774 return; 0775 } 0776 0777 setCursor(QCursor(Qt::BlankCursor)); 0778 } 0779 0780 bool PresentationKB::checkOpenGL() const 0781 { 0782 // No OpenGL context is found. Are the drivers ok? 0783 0784 if (!isValid()) 0785 { 0786 return false; 0787 } 0788 0789 // GL_EXT_texture3D is not supported 0790 0791 QString s = QString::fromLatin1(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))); 0792 0793 if (!s.contains(QString::fromLatin1("GL_EXT_texture3D"), Qt::CaseInsensitive)) 0794 { 0795 return false; 0796 } 0797 0798 // Everything is ok! 0799 0800 return true; 0801 } 0802 0803 void PresentationKB::slotClose() 0804 { 0805 close(); 0806 } 0807 0808 } // namespace DigikamGenericPresentationPlugin 0809 0810 #include "moc_presentationkb.cpp"