File indexing completed on 2024-05-12 16:23:32

0001 /***************************************************************************
0002  *   Copyright (C) 2005-2017 by Linuxstopmotion contributors;              *
0003  *   see the AUTHORS file for details.                                     *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
0019  ***************************************************************************/
0020 
0021 #include "frameview.h"
0022 
0023 #include <algorithm>
0024 #include <cstring>
0025 
0026 #include <QApplication>
0027 #include <QMessageBox>
0028 #include <QPainter>
0029 
0030 #include "imagecache.h"
0031 #include "logger.h"
0032 #include "frontends/frontend.h"
0033 #include "workspacefile.h"
0034 #include "technical/grabber/imagegrabber.h"
0035 #include "src/domain/domainfacade.h"
0036 #include "src/foundation/preferencestool.h"
0037 #include "src/presentation/frontends/qtfrontend/imagegrabthread.h"
0038 #include "src/technical/grabber/commandlinegrabber.h"
0039 
0040 enum { IMAGE_CACHE_SIZE = 10 };
0041 
0042 namespace {
0043 void getMaximumScaledRectangle(QRect& out, int w, int h, const QRect& window) {
0044     int vw = window.width();
0045     int vh = window.height();
0046     int dw = vw;
0047     int dh = vh;
0048     // Find out whether to scale by width or height.
0049     // Width if w/dw < h/dh <=> w*dh < h*dw
0050     if (w*dh < h*dw) {
0051         dw = (w * dh + h/2)/h;
0052         int left = (vw - dw)/2;
0053         out.setLeft(left);
0054         out.setRight(left + dw);
0055         out.setTop(window.top());
0056         out.setBottom(window.bottom());
0057     } else {
0058         dh = (h * dw + w/2)/w;
0059         int top = (vh - dh)/2;
0060         out.setTop(top);
0061         out.setBottom(top + dh);
0062         out.setLeft(window.left());
0063         out.setRight(window.right());
0064     }
0065 }
0066 void drawBorders(QPainter& painter, const QRect& inner, const QRect& outer) {
0067     int topBorder = inner.top() - outer.top();
0068     if (0 < topBorder) {
0069         painter.drawRect(outer.left(), outer.top(), outer.width(), topBorder);
0070     }
0071     int bottomBorder = outer.bottom() - inner.bottom();
0072     if (0 < bottomBorder) {
0073         painter.drawRect(outer.left(), inner.bottom(), outer.width(), bottomBorder);
0074     }
0075     int leftBorder = inner.left() - outer.left();
0076     if (0 < leftBorder) {
0077         painter.drawRect(outer.left(), inner.top(), leftBorder, inner.height());
0078     }
0079     int rightBorder = outer.right() - inner.right();
0080     if (0 < rightBorder) {
0081         painter.drawRect(inner.right(), inner.top(), rightBorder, inner.height());
0082     }
0083 }
0084 }
0085 
0086 FrameView::FrameView(QWidget *parent, const char *name, int playbackSpeed)
0087         : QWidget(parent),
0088           imageCache(IMAGE_CACHE_SIZE), grabThread(0), grabber(0),
0089           capturedFile(WorkspaceFile::capturedImage) {
0090     facade = DomainFacade::getFacade();
0091 
0092     isPlayingVideo = false;
0093     mode = imageModeMix;
0094     this->playbackSpeed = PreferencesTool::get()->getPreference("fps",
0095             playbackSpeed);
0096     activeScene = -1;
0097     activeFrame = -1;
0098     mixCount = 2;
0099     playbackModeFrame = -1;
0100 
0101     connect(&grabTimer, SIGNAL(timeout()), this, SLOT(redraw()));
0102     connect(&playbackTimer, SIGNAL(timeout()),this, SLOT(nextPlayBack()));
0103 
0104     setMinimumSize(400, 300);
0105     setAttribute(Qt::WA_NoSystemBackground);
0106     setObjectName(name);
0107     update();
0108 
0109     Logger::get().logDebug("FrameView is attached to the model and the model to FrameView");
0110 }
0111 
0112 
0113 FrameView::~FrameView() {
0114     // Turn off camera if it's on
0115     if (isPlayingVideo) {
0116         off();
0117     }
0118     delete grabber;
0119     grabber = 0;
0120 }
0121 
0122 
0123 void FrameView::initCompleted() {
0124     emit cameraReady();
0125 }
0126 
0127 void FrameView::updateNewActiveFrame(int sceneNumber, int frameNumber) {
0128     setActiveFrame(sceneNumber, frameNumber);
0129 }
0130 
0131 
0132 void FrameView::updatePlayFrame(int sceneNumber, int frameNumber) {
0133     if (frameNumber > -1) {
0134         setActiveFrame(sceneNumber, frameNumber);
0135     } else {
0136         this->update();
0137     }
0138 }
0139 
0140 
0141 void FrameView::workspaceCleared() {
0142     imageCache.clear();
0143 }
0144 
0145 
0146 void FrameView::resizeEvent(QResizeEvent*) {
0147     update();
0148 }
0149 
0150 void FrameView::drawOnionSkins() {
0151     int w = cameraOutput.width();
0152     int h = cameraOutput.height();
0153     QRect dst;
0154     getMaximumScaledRectangle(dst, w, h, rect());
0155     QPainter painter(this);
0156     painter.setBackgroundMode(Qt::OpaqueMode);
0157     painter.setBrush(QColor::fromRgb(0, 0, 0, 255));
0158     drawBorders(painter, dst, rect());
0159     DomainFacade* anim = DomainFacade::getFacade();
0160     if (isPlayingVideo && 0 <= activeScene) {
0161         switch (mode) {
0162         case imageModeMix:
0163         {
0164             painter.drawPixmap(dst, cameraOutput);
0165             const int frameCount = std::min(mixCount, activeFrame + 1);
0166             for (int i = 0; i != frameCount; ++i) {
0167                 const char* path = anim->getImagePath(
0168                         activeScene, activeFrame - i);
0169                 QPixmap* image = imageCache.get(path);
0170                 if (image) {
0171                     painter.setOpacity(qreal(1)/(i + 2));
0172                     painter.drawPixmap(dst, *image);
0173                 }
0174             }
0175             break;
0176         }
0177         case imageModeDiff:
0178         {
0179             QSize differenceSize(cameraOutput.size());
0180             QRect differenceRect(QPoint(0, 0), differenceSize);
0181             QImage difference(differenceSize, QImage::Format_RGB888);
0182             QPainter differencePainter(&difference);
0183             differencePainter.drawPixmap(differenceRect, cameraOutput);
0184             if (0 <= activeFrame) {
0185                 const char* path = anim->getImagePath(activeScene, activeFrame);
0186                 QPixmap* image = imageCache.get(path);
0187                 if (image) {
0188                     differencePainter.setCompositionMode(
0189                             QPainter::CompositionMode_Difference);
0190                     differencePainter.drawPixmap(differenceRect, *image);
0191                 }
0192             }
0193             differencePainter.end();
0194             painter.drawImage(dst, difference);
0195             break;
0196         }
0197         case imageModePlayback:
0198             if (playbackModeFrame < 0) {
0199                 painter.drawPixmap(dst, cameraOutput);
0200             } else {
0201                 const char* path = anim->getImagePath(activeScene, playbackModeFrame);
0202                 QPixmap* image = imageCache.get(path);
0203                 if (image) {
0204                     painter.drawPixmap(dst, *image);
0205                 }
0206             }
0207             break;
0208         default:
0209             painter.drawPixmap(dst, cameraOutput);
0210             break;
0211         }
0212     } else {
0213         painter.drawPixmap(dst, cameraOutput);
0214     }
0215 }
0216 
0217 void FrameView::paintEvent(QPaintEvent *) {
0218     if (isPlayingVideo) {
0219         if (!cameraOutput.isNull()) {
0220             drawOnionSkins();
0221         }
0222     } else {
0223         QPainter painter(this);
0224         painter.setBackgroundMode(Qt::OpaqueMode);
0225         painter.setBrush(QColor::fromRgb(0, 0, 0, 255));
0226         DomainFacade* anim = DomainFacade::getFacade();
0227         if (0 <= activeScene && 0 <= activeFrame) {
0228             const char* path = anim->getImagePath(activeScene, activeFrame);
0229             QPixmap* image = imageCache.get(path);
0230             if (image) {
0231                 QRect destination;
0232                 getMaximumScaledRectangle(destination, image->width(), image->height(), rect());
0233                 drawBorders(painter, destination, rect());
0234                 painter.drawPixmap(destination, *image);
0235                 return;
0236             }
0237         }
0238         painter.drawRect(rect());
0239     }
0240 }
0241 
0242 void FrameView::setActiveFrame(int sceneNumber, int frameNumber) {
0243     activeScene = sceneNumber;
0244     activeFrame = frameNumber;
0245     Logger::get().logDebug("Setting new active frame %d and scene %d in FrameView",
0246             activeFrame, activeScene);
0247     update();
0248 }
0249 
0250 
0251 // TODO: Refactor this terrible ugly method. This one is really bad!!
0252 bool FrameView::on() {
0253     const char* DEVICE_TOKEN = "$VIDEODEVICE";
0254     PreferencesTool *prefs = PreferencesTool::get();
0255     int activeCmd = prefs->getPreference("activedevice", 0);
0256 
0257     Preference prepoll(QString("importprepoll%1")
0258             .arg(activeCmd).toLatin1().constData(), "");
0259     Preference startDaemon(QString("importstartdaemon%1")
0260             .arg(activeCmd).toLatin1().constData(), "");
0261     Preference stopDaemon(QString("importstopdaemon%1")
0262             .arg(activeCmd).toLatin1().constData(), "");
0263 
0264     bool prepollRequiresDevice = strstr(prepoll.get(), DEVICE_TOKEN);
0265     bool startDaemonRequiresDevice = strstr(startDaemon.get(), DEVICE_TOKEN);
0266     QString device;
0267     if (prepollRequiresDevice || startDaemonRequiresDevice) {
0268         int activeDev = prefs->getPreference("activeVideoDevice", -1);
0269         if (0 <= activeDev) {
0270             Preference deviceP(QString("device%1")
0271                     .arg(activeDev).toLatin1().constData(), "");
0272             device = QString(deviceP.get()).trimmed();
0273         }
0274         if (device.isEmpty()) {
0275             QMessageBox::warning(this, tr("Warning"), tr(
0276                 "No video device selected in the preferences menu."),
0277                 QMessageBox::Ok,
0278                 Qt::NoButton,
0279                 Qt::NoButton);
0280             return false;
0281         }
0282     }
0283     QString pre = QString(prepoll.get()).replace(DEVICE_TOKEN, device);
0284     bool isProcess = startDaemon.get() && *startDaemon.get() != '\0';
0285 
0286     bool isCameraReady = true;
0287     grabber = new CommandLineGrabber(capturedFile.path());
0288     if ( !grabber->setPrePollCommand(pre.toLatin1().constData()) ) {
0289         QMessageBox::warning(this, tr("Warning"), tr(
0290                     "Pre poll command does not exists"),
0291                     QMessageBox::Ok,
0292                     Qt::NoButton,
0293                     Qt::NoButton);
0294         //return false;
0295         isCameraReady = false;
0296     }
0297 
0298     if (isProcess) {
0299         QString sd = QString(startDaemon.get()).replace(DEVICE_TOKEN, device);
0300         if ( !grabber->setStartCommand(sd.toLatin1().constData()) ) {
0301             DomainFacade::getFacade()->getFrontend()->hideProgress();
0302             QMessageBox::warning(this, tr("Warning"), tr(
0303                         "You do not have the given grabber installed on your system"),
0304                         QMessageBox::Ok,
0305                         Qt::NoButton,
0306                         Qt::NoButton);
0307             isCameraReady = false;
0308             //return false;
0309         }
0310     }
0311 
0312     grabber->setStopCommand(stopDaemon.get());
0313 
0314     if (!isCameraReady) {
0315         return false;
0316     }
0317     initCompleted();
0318     isPlayingVideo = true;
0319 
0320     if ( prefs->getPreference("numberofimports", 1) > 0 ) {
0321         // If the grabber is running in it's own process we use a timer.
0322         if (grabber->isGrabberProcess() == true) {
0323             if ( grabber->init() ) {
0324                 grabTimer.start(150);
0325             }
0326             else {
0327                 QMessageBox::warning(this, tr("Warning"), tr(
0328                     "Grabbing failed. This may happen if you try\n"
0329                     "to grab from an invalid device. Please check\n"
0330                     "your grabber settings in the preferences menu."),
0331                     QMessageBox::Ok,
0332                     Qt::NoButton,
0333                     Qt::NoButton);
0334                 return false;
0335             }
0336         }
0337         // Otherwise a thread is needed
0338         else {
0339             grabThread = new ImageGrabThread(this, grabber);
0340             connect(grabThread, SIGNAL(grabbed()), this, SLOT(redraw()));
0341             grabThread->start();
0342             grabThread->wait(500);
0343 
0344             if (grabThread->wasGrabbingSuccess() == false) {
0345                 QMessageBox::warning(this, tr("Warning"), tr(
0346                     "Grabbing failed. This may happen if you try\n"
0347                     "to grab from an invalid device. Please check\n"
0348                     "your grabber settings in the preferences menu."),
0349                     QMessageBox::Ok,
0350                     Qt::NoButton,
0351                     Qt::NoButton);
0352                 return false;
0353             }
0354         }
0355     }
0356     else {
0357         QMessageBox::warning(this, tr("Warning"), tr(
0358                 "You have to define an image grabber to use.\n"
0359                 "This can be set in the preferences menu."),
0360                 QMessageBox::Ok,
0361                 Qt::NoButton,
0362                 Qt::NoButton);
0363         return false;
0364     }
0365     return true;
0366 }
0367 
0368 
0369 void FrameView::off() {
0370     if ( grabber != 0 ) {
0371         if ( grabber->isGrabberProcess() ) {
0372             grabber->tearDown();
0373             grabTimer.stop();
0374             playbackTimer.stop();
0375         }
0376     }
0377     if (grabThread != 0) {
0378         grabThread->terminate();
0379         grabThread->wait();
0380         delete grabThread;
0381         grabThread = 0;
0382     }
0383     delete grabber;
0384     grabber = 0;
0385 
0386     isPlayingVideo = false;
0387     setActiveFrame(activeScene, activeFrame);
0388     update();
0389 }
0390 
0391 
0392 void FrameView::redraw() {
0393     cameraOutput.load(capturedFile.path());
0394     update();
0395 }
0396 
0397 
0398 void FrameView::nextPlayBack() {
0399     if (0 <= activeScene && 0 <= activeFrame) {
0400         int firstFrame = std::max(activeFrame - mixCount + 1, 0);
0401         if (playbackModeFrame < firstFrame) {
0402             // restart playback
0403             playbackModeFrame = firstFrame;
0404         } else {
0405             ++playbackModeFrame;
0406         }
0407         if (activeFrame < playbackModeFrame) {
0408             // negative number means that the camera output should be shown
0409             playbackModeFrame = -1;
0410         }
0411         update();
0412         return;
0413     }
0414     // Set display image to camera output
0415     playbackModeFrame = -1;
0416     update();
0417 }
0418 
0419 
0420 bool FrameView::setViewMode(ImageMode mode) {
0421     if (mode == this->mode)
0422         return true;
0423     if (!grabber)
0424         return true;
0425     if (mode == imageModePlayback) {
0426         if ( grabber->isGrabberProcess() ) {
0427             playbackTimer.start(1000/playbackSpeed);
0428         } else {
0429             return false;
0430         }
0431     } else if (mode != imageModePlayback) {
0432         if ( grabber->isGrabberProcess() ) {
0433             playbackTimer.stop();
0434         }
0435     }
0436     this->mode = mode;
0437     return true;
0438 }
0439 
0440 void FrameView::setMixCount(int mixCount) {
0441     this->mixCount = mixCount;
0442 }
0443 
0444 int FrameView::getViewMode() const {
0445     return mode;
0446 }
0447 
0448 void FrameView::setPlaybackSpeed(int playbackSpeed) {
0449     this->playbackSpeed = playbackSpeed;
0450     if ( playbackTimer.isActive() ) {
0451         playbackTimer.setInterval(1000 / playbackSpeed);
0452     }
0453 }
0454 
0455 int FrameView::getPlaybackSpeed() const {
0456     return playbackSpeed;
0457 }
0458 
0459 void FrameView::fileChanged(const QString& path) {
0460     const char* p = path.toLocal8Bit();
0461     imageCache.drop(p);
0462     if (isPlayingVideo && 0 <= activeScene) {
0463         update();
0464     }
0465 }