From 9d81580ca0c0377659516dd432c5acbf01d0b727 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Mardelle Date: Sat, 23 Oct 2010 22:10:10 +0000 Subject: [PATCH] Small improvements to stop motion widget svn path=/trunk/kdenlive/; revision=5039 --- src/blackmagic/capture.cpp | 186 +++++++++--------------------- src/mainwindow.cpp | 2 +- src/stopmotion/capturehandler.cpp | 75 ++++++++++++ src/stopmotion/capturehandler.h | 1 + src/stopmotion/stopmotion.cpp | 172 +++++++++++++-------------- src/stopmotion/stopmotion.h | 8 +- src/v4l/v4lcapture.cpp | 131 +++++++++------------ src/v4l/v4lcapture.h | 4 +- src/widgets/stopmotion_ui.ui | 3 + 9 files changed, 280 insertions(+), 302 deletions(-) diff --git a/src/blackmagic/capture.cpp b/src/blackmagic/capture.cpp index edeceb14..03aac813 100644 --- a/src/blackmagic/capture.cpp +++ b/src/blackmagic/capture.cpp @@ -65,80 +65,6 @@ static double g_aspect_ratio = 16.0 / 9.0; static unsigned long frameCount = 0; -void yuv2rgb_int(unsigned char *yuv_buffer, unsigned char *rgb_buffer, int width, int height) -{ - int len; - int r, g, b; - int Y, U, V, Y2; - int rgb_ptr, y_ptr, t; - - len = width * height / 2; - - rgb_ptr = 0; - y_ptr = 0; - - for(t = 0; t < len; t++) { /* process 2 pixels at a time */ - /* Compute parts of the UV components */ - - U = yuv_buffer[y_ptr]; - Y = yuv_buffer[y_ptr+1]; - V = yuv_buffer[y_ptr+2]; - Y2 = yuv_buffer[y_ptr+3]; - y_ptr += 4; - - - /*r = 1.164*(Y-16) + 1.596*(V-128); - g = 1.164*(Y-16) - 0.813*(V-128) - 0.391*(U-128); - b = 1.164*(Y-16) + 2.018*(U-128);*/ - - - r = ((298 * (Y - 16) + 409 * (V - 128) + 128) >> 8); - - g = ((298 * (Y - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8); - - b = ((298 * (Y - 16) + 516 * (U - 128) + 128) >> 8); - - if(r > 255) r = 255; - if(g > 255) g = 255; - if(b > 255) b = 255; - - if(r < 0) r = 0; - if(g < 0) g = 0; - if(b < 0) b = 0; - - rgb_buffer[rgb_ptr] = b; - rgb_buffer[rgb_ptr+1] = g; - rgb_buffer[rgb_ptr+2] = r; - rgb_buffer[rgb_ptr+3] = 255; - - rgb_ptr += 4; - /*r = 1.164*(Y2-16) + 1.596*(V-128); - g = 1.164*(Y2-16) - 0.813*(V-128) - 0.391*(U-128); - b = 1.164*(Y2-16) + 2.018*(U-128);*/ - - - r = ((298 * (Y2 - 16) + 409 * (V - 128) + 128) >> 8); - - g = ((298 * (Y2 - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8); - - b = ((298 * (Y2 - 16) + 516 * (U - 128) + 128) >> 8); - - if(r > 255) r = 255; - if(g > 255) g = 255; - if(b > 255) b = 255; - - if(r < 0) r = 0; - if(g < 0) g = 0; - if(b < 0) b = 0; - - rgb_buffer[rgb_ptr] = b; - rgb_buffer[rgb_ptr+1] = g; - rgb_buffer[rgb_ptr+2] = r; - rgb_buffer[rgb_ptr+3] = 255; - rgb_ptr += 4; - } -} - class CDeckLinkGLWidget : public QGLWidget, public IDeckLinkScreenPreviewCallback { @@ -193,7 +119,7 @@ void CDeckLinkGLWidget::showOverlay(QImage img, bool transparent) m_img = convertToGLFormat(img); m_zx = (double)m_pictureWidth / m_img.width(); m_zy = (double)m_pictureHeight / m_img.height(); - if(m_transparentOverlay) { + if (m_transparentOverlay) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR); } else { @@ -209,7 +135,7 @@ void CDeckLinkGLWidget::hideOverlay() void CDeckLinkGLWidget::initializeGL() { - if(deckLinkScreenPreviewHelper != NULL) { + if (deckLinkScreenPreviewHelper != NULL) { mutex.lock(); deckLinkScreenPreviewHelper->InitializeGL(); glShadeModel(GL_FLAT); @@ -251,7 +177,7 @@ void CDeckLinkGLWidget::paintGL() //glClearColor(1.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT); deckLinkScreenPreviewHelper->PaintGL(); - if(!m_img.isNull()) { + if (!m_img.isNull()) { glPixelZoom(m_zx, m_zy); glDrawPixels(m_img.width(), m_img.height(), GL_RGBA, GL_UNSIGNED_BYTE, m_img.bits()); } @@ -279,10 +205,10 @@ void CDeckLinkGLWidget::resizeGL(int width, int height) m_pictureHeight = height; m_pictureWidth = width; int calculatedWidth = g_aspect_ratio * height; - if(calculatedWidth > width) m_pictureHeight = width / g_aspect_ratio; + if (calculatedWidth > width) m_pictureHeight = width / g_aspect_ratio; else { int calculatedHeight = width / g_aspect_ratio; - if(calculatedHeight > height) m_pictureWidth = height * g_aspect_ratio; + if (calculatedHeight > height) m_pictureWidth = height * g_aspect_ratio; } glViewport((width - m_pictureWidth) / 2, (height - m_pictureHeight) / 2, m_pictureWidth, m_pictureHeight); glMatrixMode(GL_PROJECTION); @@ -290,7 +216,7 @@ void CDeckLinkGLWidget::resizeGL(int width, int height) glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glRasterPos2i(-1, -1); - if(!m_img.isNull()) { + if (!m_img.isNull()) { m_zx = (double)m_pictureWidth / m_img.width(); m_zy = (double)m_pictureHeight / m_img.height(); } @@ -336,7 +262,7 @@ ULONG CDeckLinkGLWidget::Release() int oldValue; oldValue = refCount.fetchAndAddAcquire(-1); - if(oldValue == 1) { + if (oldValue == 1) { delete this; } @@ -345,7 +271,7 @@ ULONG CDeckLinkGLWidget::Release() HRESULT CDeckLinkGLWidget::DrawFrame(IDeckLinkVideoFrame* theFrame) { - if(deckLinkScreenPreviewHelper != NULL && theFrame != NULL) { + if (deckLinkScreenPreviewHelper != NULL && theFrame != NULL) { /*mutex.lock(); m_frame = theFrame; mutex.unlock();*/ @@ -381,7 +307,7 @@ ULONG DeckLinkCaptureDelegate::Release(void) m_refCount--; pthread_mutex_unlock(&m_mutex); - if(m_refCount == 0) { + if (m_refCount == 0) { delete this; return 0; } @@ -397,22 +323,22 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame void* audioFrameBytes; // Handle Video Frame - if(videoFrame) { + if (videoFrame) { // If 3D mode is enabled we retreive the 3D extensions interface which gives. // us access to the right eye frame by calling GetFrameForRightEye() . - if((videoFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **) &threeDExtensions) != S_OK) || + if ((videoFrame->QueryInterface(IID_IDeckLinkVideoFrame3DExtensions, (void **) &threeDExtensions) != S_OK) || (threeDExtensions->GetFrameForRightEye(&rightEyeFrame) != S_OK)) { rightEyeFrame = NULL; } - if(videoFrame->GetFlags() & bmdFrameHasNoInputSource) { + if (videoFrame->GetFlags() & bmdFrameHasNoInputSource) { emit gotMessage(i18n("Frame (%1) - No input signal", frameCount)); fprintf(stderr, "Frame received (#%lu) - No input signal detected\n", frameCount); } else { const char *timecodeString = NULL; - if(g_timecodeFormat != 0) { + if (g_timecodeFormat != 0) { IDeckLinkTimecode *timecode; - if(videoFrame->GetTimecode(g_timecodeFormat, &timecode) == S_OK) { + if (videoFrame->GetTimecode(g_timecodeFormat, &timecode) == S_OK) { timecode->GetString(&timecodeString); } } @@ -424,12 +350,12 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame rightEyeFrame != NULL ? "Valid Frame (3D left/right)" : "Valid Frame", videoFrame->GetRowBytes() * videoFrame->GetHeight());*/ - if(timecodeString) + if (timecodeString) free((void*)timecodeString); - if(!doCaptureFrame.isEmpty()) { + if (!doCaptureFrame.isEmpty()) { videoFrame->GetBytes(&frameBytes); - if(doCaptureFrame.endsWith("raw")) { + if (doCaptureFrame.endsWith("raw")) { // Save as raw uyvy422 imgage videoOutputFile = open(doCaptureFrame.toUtf8().constData(), O_WRONLY | O_CREAT/*|O_TRUNC*/, 0664); write(videoOutputFile, frameBytes, videoFrame->GetRowBytes() * videoFrame->GetHeight()); @@ -438,18 +364,18 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame } else { QImage image(videoFrame->GetWidth(), videoFrame->GetHeight(), QImage::Format_ARGB32_Premultiplied); //convert from uyvy422 to rgba - yuv2rgb_int((uchar *)frameBytes, (uchar *)image.bits(), videoFrame->GetWidth(), videoFrame->GetHeight()); + CaptureHandler::yuv2rgb((uchar *)frameBytes, (uchar *)image.bits(), videoFrame->GetWidth(), videoFrame->GetHeight()); image.save(doCaptureFrame); emit frameSaved(doCaptureFrame); } doCaptureFrame.clear(); } - if(videoOutputFile != -1) { + if (videoOutputFile != -1) { videoFrame->GetBytes(&frameBytes); write(videoOutputFile, frameBytes, videoFrame->GetRowBytes() * videoFrame->GetHeight()); - if(rightEyeFrame) { + if (rightEyeFrame) { rightEyeFrame->GetBytes(&frameBytes); write(videoOutputFile, frameBytes, videoFrame->GetRowBytes() * videoFrame->GetHeight()); } @@ -457,14 +383,14 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame } frameCount++; - if(g_maxFrames > 0 && frameCount >= g_maxFrames) { + if (g_maxFrames > 0 && frameCount >= g_maxFrames) { pthread_cond_signal(&sleepCond); } } // Handle Audio Frame - if(audioFrame) { - if(audioOutputFile != -1) { + if (audioFrame) { + if (audioOutputFile != -1) { audioFrame->GetBytes(&audioFrameBytes); write(audioOutputFile, audioFrameBytes, audioFrame->GetSampleFrameCount() * g_audioChannels *(g_audioSampleDepth / 8)); } @@ -569,7 +495,7 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) pthread_cond_init(&sleepCond, NULL);*/ kDebug() << "/// INIT CAPTURE ON DEV: " << deviceId; - if(!deckLinkIterator) { + if (!deckLinkIterator) { emit gotMessage(i18n("This application requires the DeckLink drivers installed.")); fprintf(stderr, "This application requires the DeckLink drivers installed.\n"); stopCapture(); @@ -577,16 +503,16 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) } /* Connect to selected DeckLink instance */ - for(int i = 0; i < deviceId + 1; i++) + for (int i = 0; i < deviceId + 1; i++) result = deckLinkIterator->Next(&deckLink); - if(result != S_OK) { + if (result != S_OK) { fprintf(stderr, "No DeckLink PCI cards found.\n"); emit gotMessage(i18n("No DeckLink PCI cards found.")); stopCapture(); return; } - if(deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput) != S_OK) { + if (deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput) != S_OK) { stopCapture(); return; } @@ -605,7 +531,7 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output result = deckLinkInput->GetDisplayModeIterator(&displayModeIterator); - if(result != S_OK) { + if (result != S_OK) { emit gotMessage(i18n("Could not obtain the video output display mode iterator - result = ", result)); fprintf(stderr, "Could not obtain the video output display mode iterator - result = %08x\n", result); stopCapture(); @@ -684,7 +610,7 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) } }*/ - if(g_videoModeIndex < 0) { + if (g_videoModeIndex < 0) { emit gotMessage(i18n("No video mode specified")); fprintf(stderr, "No video mode specified\n"); stopCapture(); @@ -712,8 +638,8 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) } }*/ - while(displayModeIterator->Next(&displayMode) == S_OK) { - if(g_videoModeIndex == displayModeCount) { + while (displayModeIterator->Next(&displayMode) == S_OK) { + if (g_videoModeIndex == displayModeCount) { BMDDisplayModeSupport result; const char *displayModeName; @@ -725,15 +651,15 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) deckLinkInput->DoesSupportVideoMode(selectedDisplayMode, pixelFormat, bmdVideoInputFlagDefault, &result, NULL); - if(result == bmdDisplayModeNotSupported) { + if (result == bmdDisplayModeNotSupported) { emit gotMessage(i18n("The display mode %1 is not supported with the selected pixel format", displayModeName)); fprintf(stderr, "The display mode %s is not supported with the selected pixel format\n", displayModeName); stopCapture(); return; } - if(inputFlags & bmdVideoInputDualStream3D) { - if(!(displayMode->GetFlags() & bmdDisplayModeSupports3D)) { + if (inputFlags & bmdVideoInputDualStream3D) { + if (!(displayMode->GetFlags() & bmdDisplayModeSupports3D)) { emit gotMessage(i18n("The display mode %1 is not supported with 3D", displayModeName)); fprintf(stderr, "The display mode %s is not supported with 3D\n", displayModeName); stopCapture(); @@ -747,7 +673,7 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) displayMode->Release(); } - if(!foundDisplayMode) { + if (!foundDisplayMode) { emit gotMessage(i18n("Invalid mode %1 specified", g_videoModeIndex)); fprintf(stderr, "Invalid mode %d specified\n", g_videoModeIndex); stopCapture(); @@ -755,7 +681,7 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) } result = deckLinkInput->EnableVideoInput(selectedDisplayMode, pixelFormat, inputFlags); - if(result != S_OK) { + if (result != S_OK) { emit gotMessage(i18n("Failed to enable video input. Is another application using the card?")); fprintf(stderr, "Failed to enable video input. Is another application using the card?\n"); stopCapture(); @@ -763,13 +689,13 @@ void BmdCaptureHandler::startPreview(int deviceId, int captureMode) } result = deckLinkInput->EnableAudioInput(bmdAudioSampleRate48kHz, g_audioSampleDepth, g_audioChannels); - if(result != S_OK) { + if (result != S_OK) { stopCapture(); return; } deckLinkInput->SetScreenPreviewCallback(previewView); result = deckLinkInput->StartStreams(); - if(result != S_OK) { + if (result != S_OK) { qDebug() << "/// CAPTURE FAILED...."; emit gotMessage(i18n("Capture failed")); } @@ -822,20 +748,20 @@ void BmdCaptureHandler::startCapture(const QString &path) int i = 0; QString videopath = path + "_video_" + QString::number(i).rightJustified(4, '0', false) + ".raw"; QString audiopath = path + "_audio_" + QString::number(i).rightJustified(4, '0', false) + ".raw"; - while(QFile::exists(videopath) || QFile::exists(audiopath)) { + while (QFile::exists(videopath) || QFile::exists(audiopath)) { i++; videopath = path + "_video_" + QString::number(i).rightJustified(4, '0', false) + ".raw"; audiopath = path + "_audio_" + QString::number(i).rightJustified(4, '0', false) + ".raw"; } videoOutputFile = open(videopath.toUtf8().constData(), O_WRONLY | O_CREAT | O_TRUNC, 0664); - if(videoOutputFile < 0) { + if (videoOutputFile < 0) { emit gotMessage(i18n("Could not open video output file %1", videopath)); fprintf(stderr, "Could not open video output file \"%s\"\n", videopath.toUtf8().constData()); return; } - if(KdenliveSettings::hdmicaptureaudio()) { + if (KdenliveSettings::hdmicaptureaudio()) { audioOutputFile = open(audiopath.toUtf8().constData(), O_WRONLY | O_CREAT | O_TRUNC, 0664); - if(audioOutputFile < 0) { + if (audioOutputFile < 0) { emit gotMessage(i18n("Could not open audio output file %1", audiopath)); fprintf(stderr, "Could not open video output file \"%s\"\n", audiopath.toUtf8().constData()); return; @@ -845,9 +771,9 @@ void BmdCaptureHandler::startCapture(const QString &path) void BmdCaptureHandler::stopCapture() { - if(videoOutputFile) + if (videoOutputFile) close(videoOutputFile); - if(audioOutputFile) + if (audioOutputFile) close(audioOutputFile); videoOutputFile = -1; audioOutputFile = -1; @@ -860,49 +786,49 @@ void BmdCaptureHandler::captureFrame(const QString &fname) void BmdCaptureHandler::showOverlay(QImage img, bool transparent) { - if(previewView) previewView->showOverlay(img, transparent); + if (previewView) previewView->showOverlay(img, transparent); } void BmdCaptureHandler::hideOverlay() { - if(previewView) previewView->hideOverlay(); + if (previewView) previewView->hideOverlay(); } void BmdCaptureHandler::hidePreview(bool hide) { - if(previewView) previewView->setHidden(hide); + if (previewView) previewView->setHidden(hide); } void BmdCaptureHandler::stopPreview() { - if(!previewView) return; - if(deckLinkInput != NULL) deckLinkInput->StopStreams(); - if(videoOutputFile) + if (!previewView) return; + if (deckLinkInput != NULL) deckLinkInput->StopStreams(); + if (videoOutputFile) close(videoOutputFile); - if(audioOutputFile) + if (audioOutputFile) close(audioOutputFile); - if(displayModeIterator != NULL) { + if (displayModeIterator != NULL) { displayModeIterator->Release(); displayModeIterator = NULL; } - if(deckLinkInput != NULL) { + if (deckLinkInput != NULL) { deckLinkInput->Release(); deckLinkInput = NULL; } - if(deckLink != NULL) { + if (deckLink != NULL) { deckLink->Release(); deckLink = NULL; } - if(deckLinkIterator != NULL) { + if (deckLinkIterator != NULL) { deckLinkIterator->Release(); deckLinkIterator = NULL; } - if(previewView != NULL) { + if (previewView != NULL) { delete previewView; previewView = NULL; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 25942b19..b8ec9ec5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1535,7 +1535,7 @@ void MainWindow::setupActions() connect(reloadClip , SIGNAL(triggered()), m_projectList, SLOT(slotReloadClip())); reloadClip->setEnabled(false); - QAction *stopMotion = new KAction(KIcon("image-x-generic"), i18n("Stopmotion Animation"), this); + QAction *stopMotion = new KAction(KIcon("image-x-generic"), i18n("Stop Motion Capture"), this); collection->addAction("stopmotion", stopMotion); connect(stopMotion , SIGNAL(triggered()), this, SLOT(slotOpenStopmotion())); diff --git a/src/stopmotion/capturehandler.cpp b/src/stopmotion/capturehandler.cpp index 72a3363c..38ee07e6 100644 --- a/src/stopmotion/capturehandler.cpp +++ b/src/stopmotion/capturehandler.cpp @@ -37,5 +37,80 @@ void CaptureHandler::stopCapture() { } +//static +void CaptureHandler::yuv2rgb(unsigned char *yuv_buffer, unsigned char *rgb_buffer, int width, int height) +{ + int len; + int r, g, b; + int Y, U, V, Y2; + int rgb_ptr, y_ptr, t; + + len = width * height / 2; + + rgb_ptr = 0; + y_ptr = 0; + + for (t = 0; t < len; t++) { /* process 2 pixels at a time */ + /* Compute parts of the UV components */ + + U = yuv_buffer[y_ptr]; + Y = yuv_buffer[y_ptr+1]; + V = yuv_buffer[y_ptr+2]; + Y2 = yuv_buffer[y_ptr+3]; + y_ptr += 4; + + + /*r = 1.164*(Y-16) + 1.596*(V-128); + g = 1.164*(Y-16) - 0.813*(V-128) - 0.391*(U-128); + b = 1.164*(Y-16) + 2.018*(U-128);*/ + + + r = ((298 * (Y - 16) + 409 * (V - 128) + 128) >> 8); + + g = ((298 * (Y - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8); + + b = ((298 * (Y - 16) + 516 * (U - 128) + 128) >> 8); + + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + + rgb_buffer[rgb_ptr] = b; + rgb_buffer[rgb_ptr+1] = g; + rgb_buffer[rgb_ptr+2] = r; + rgb_buffer[rgb_ptr+3] = 255; + + rgb_ptr += 4; + /*r = 1.164*(Y2-16) + 1.596*(V-128); + g = 1.164*(Y2-16) - 0.813*(V-128) - 0.391*(U-128); + b = 1.164*(Y2-16) + 2.018*(U-128);*/ + + + r = ((298 * (Y2 - 16) + 409 * (V - 128) + 128) >> 8); + + g = ((298 * (Y2 - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8); + + b = ((298 * (Y2 - 16) + 516 * (U - 128) + 128) >> 8); + + if (r > 255) r = 255; + if (g > 255) g = 255; + if (b > 255) b = 255; + + if (r < 0) r = 0; + if (g < 0) g = 0; + if (b < 0) b = 0; + + rgb_buffer[rgb_ptr] = b; + rgb_buffer[rgb_ptr+1] = g; + rgb_buffer[rgb_ptr+2] = r; + rgb_buffer[rgb_ptr+3] = 255; + rgb_ptr += 4; + } +} + diff --git a/src/stopmotion/capturehandler.h b/src/stopmotion/capturehandler.h index ef83ebd9..29deea04 100644 --- a/src/stopmotion/capturehandler.h +++ b/src/stopmotion/capturehandler.h @@ -38,6 +38,7 @@ public: virtual void showOverlay(QImage img, bool transparent = true) = 0; virtual void hideOverlay() = 0; virtual void hidePreview(bool hide) = 0; + static void yuv2rgb(unsigned char *yuv_buffer, unsigned char *rgb_buffer, int width, int height); protected: QVBoxLayout *m_layout; diff --git a/src/stopmotion/stopmotion.cpp b/src/stopmotion/stopmotion.cpp index e4c12267..d6d6786d 100644 --- a/src/stopmotion/stopmotion.cpp +++ b/src/stopmotion/stopmotion.cpp @@ -57,7 +57,7 @@ void MyLabel::setImage(QImage img) //virtual void MyLabel::wheelEvent(QWheelEvent * event) { - if(event->delta() > 0) emit seek(true); + if (event->delta() > 0) emit seek(true); else emit seek(false); } @@ -73,10 +73,10 @@ void MyLabel::paintEvent(QPaintEvent * event) int pictureHeight = height(); int pictureWidth = width(); int calculatedWidth = aspect_ratio * height(); - if(calculatedWidth > width()) pictureHeight = width() / aspect_ratio; + if (calculatedWidth > width()) pictureHeight = width() / aspect_ratio; else { int calculatedHeight = width() / aspect_ratio; - if(calculatedHeight > height()) pictureWidth = height() * aspect_ratio; + if (calculatedHeight > height()) pictureWidth = height() * aspect_ratio; } p.drawImage(QRect((width() - pictureWidth) / 2, (height() - pictureHeight) / 2, pictureWidth, pictureHeight), m_img, QRect(0, 0, m_img.width(), m_img.height())); p.end(); @@ -136,9 +136,9 @@ StopmotionWidget::StopmotionWidget(KUrl projectFolder, QWidget *parent) : effectsMenu->addAction(invertEffect); effectsMenu->addAction(thresEffect); QList list = effectsMenu->actions(); - for(int i = 0; i < list.count(); i++) { + for (int i = 0; i < list.count(); i++) { list.at(i)->setCheckable(true); - if(list.at(i)->data().toInt() == m_effectIndex) { + if (list.at(i)->data().toInt() == m_effectIndex) { list.at(i)->setChecked(true); } } @@ -175,13 +175,13 @@ StopmotionWidget::StopmotionWidget(KUrl projectFolder, QWidget *parent) : connect(sequence_name, SIGNAL(textChanged(const QString &)), this, SLOT(sequenceNameChanged(const QString &))); m_layout = new QVBoxLayout; - if(BMInterface::getBlackMagicDeviceList(capture_device, NULL)) { + if (BMInterface::getBlackMagicDeviceList(capture_device, NULL)) { // Found a BlackMagic device m_bmCapture = new BmdCaptureHandler(m_layout); connect(m_bmCapture, SIGNAL(gotMessage(const QString &)), this, SLOT(slotGotHDMIMessage(const QString &))); } - if(QFile::exists(KdenliveSettings::video4vdevice())) { - if(m_bmCapture == NULL) m_bmCapture = new V4lCaptureHandler(m_layout); + if (QFile::exists(KdenliveSettings::video4vdevice())) { + if (m_bmCapture == NULL) m_bmCapture = new V4lCaptureHandler(m_layout); capture_device->addItem(KdenliveSettings::video4vdevice(), "v4l"); } @@ -196,7 +196,7 @@ StopmotionWidget::StopmotionWidget(KUrl projectFolder, QWidget *parent) : button_addsequence->setEnabled(false); connect(live_button, SIGNAL(clicked(bool)), this, SLOT(slotLive(bool))); connect(button_addsequence, SIGNAL(clicked(bool)), this, SLOT(slotAddSequence())); - connect(preview_button, SIGNAL(clicked(bool)), this, SLOT(slotPlayPreview())); + connect(preview_button, SIGNAL(clicked(bool)), this, SLOT(slotPlayPreview(bool))); connect(frame_list, SIGNAL(currentRowChanged(int)), this, SLOT(slotShowSelectedFrame())); connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage, int))); @@ -212,31 +212,29 @@ StopmotionWidget::~StopmotionWidget() void StopmotionWidget::slotUpdateOverlayEffect(QAction *act) { #ifdef QIMAGEBLITZ - if(act) m_effectIndex = act->data().toInt(); + if (act) m_effectIndex = act->data().toInt(); KdenliveSettings::setBlitzeffect(m_effectIndex); - if(m_showOverlay->isChecked()) slotUpdateOverlay(); + if (m_showOverlay->isChecked()) slotUpdateOverlay(); #endif } void StopmotionWidget::closeEvent(QCloseEvent *e) { slotLive(false); - live_button->setChecked(false); QDialog::closeEvent(e); } void StopmotionWidget::slotSetCaptureInterval() { int interval = QInputDialog::getInteger(this, i18n("Set Capture Interval"), i18n("Interval (in seconds)"), KdenliveSettings::captureinterval(), 1); - if(interval > 0 && interval != KdenliveSettings::captureinterval()) + if (interval > 0 && interval != KdenliveSettings::captureinterval()) KdenliveSettings::setCaptureinterval(interval); } void StopmotionWidget::slotShowThumbs(bool show) { KdenliveSettings::setShowstopmotionthumbs(show); - kDebug() << "SHOW THUMBS: " << show; - if(show) { + if (show) { frame_list->clear(); sequenceNameChanged(sequence_name->currentText()); } else { @@ -248,7 +246,7 @@ void StopmotionWidget::slotShowThumbs(bool show) void StopmotionWidget::slotIntervalCapture(bool capture) { - if(capture) slotCaptureFrame(); + if (capture) slotCaptureFrame(); } @@ -258,7 +256,7 @@ void StopmotionWidget::slotUpdateHandler() m_bmCapture->stopPreview(); delete m_bmCapture; m_layout->removeWidget(m_frame_preview); - if(data == "v4l") { + if (data == "v4l") { m_bmCapture = new V4lCaptureHandler(m_layout); } else { m_bmCapture = new BmdCaptureHandler(m_layout); @@ -289,7 +287,7 @@ void StopmotionWidget::parseExistingSequences() void StopmotionWidget::slotLive(bool isOn) { - if(isOn) { + if (isOn) { m_frame_preview->setImage(QImage()); m_frame_preview->setHidden(true); m_bmCapture->startPreview(KdenliveSettings::hdmi_capturedevice(), KdenliveSettings::hdmi_capturemode()); @@ -297,13 +295,14 @@ void StopmotionWidget::slotLive(bool isOn) } else { m_bmCapture->stopPreview(); capture_button->setEnabled(false); + live_button->setChecked(false); } } void StopmotionWidget::slotShowOverlay(bool isOn) { - if(isOn) { - if(live_button->isChecked() && m_sequenceFrame > 0) { + if (isOn) { + if (live_button->isChecked() && m_sequenceFrame > 0) { slotUpdateOverlay(); } } else { @@ -314,18 +313,18 @@ void StopmotionWidget::slotShowOverlay(bool isOn) void StopmotionWidget::slotUpdateOverlay() { QString path = getPathForFrame(m_sequenceFrame - 1); - if(!QFile::exists(path)) return; + if (!QFile::exists(path)) return; QImage img(path); - if(img.isNull()) { + if (img.isNull()) { QTimer::singleShot(1000, this, SLOT(slotUpdateOverlay())); return; } #ifdef QIMAGEBLITZ //img = Blitz::convolveEdge(img, 0, Blitz::Low); - switch(m_effectIndex) { + switch (m_effectIndex) { case 2: - img = Blitz::contrast(img, true); + img = Blitz::contrast(img, true, 6); break; case 3: img = Blitz::edge(img); @@ -337,7 +336,8 @@ void StopmotionWidget::slotUpdateOverlay() Blitz::invert(img); break; case 6: - img = Blitz::threshold(img, 200, Blitz::Grayscale, qRgba(255, 0, 0, 255), qRgba(0, 0, 0, 0)); + img = Blitz::threshold(img, 120, Blitz::Grayscale, qRgba(0, 0, 0, 0), qRgba(255, 0, 0, 255)); + //img = Blitz::flatten(img, QColor(255, 0, 0, 255), QColor(0, 0, 0, 0)); break; default: break; @@ -354,13 +354,13 @@ void StopmotionWidget::sequenceNameChanged(const QString &name) m_filesList.clear(); m_future.waitForFinished(); frame_list->clear(); - if(name.isEmpty()) { + if (name.isEmpty()) { button_addsequence->setEnabled(false); } else { // Check if we are editing an existing sequence QString pattern = SlideshowClip::selectedPath(getPathForFrame(0, sequence_name->currentText()), false, QString(), &m_filesList); m_sequenceFrame = m_filesList.isEmpty() ? 0 : SlideshowClip::getFrameNumberFromPath(m_filesList.last()) + 1; - if(!m_filesList.isEmpty()) { + if (!m_filesList.isEmpty()) { m_sequenceName = sequence_name->currentText(); connect(this, SIGNAL(doCreateThumbs(QImage, int)), this, SLOT(slotCreateThumbs(QImage, int))); m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs); @@ -376,14 +376,14 @@ void StopmotionWidget::sequenceNameChanged(const QString &name) void StopmotionWidget::slotCaptureFrame() { - if(sequence_name->currentText().isEmpty()) { + if (sequence_name->currentText().isEmpty()) { QString seqName = QInputDialog::getText(this, i18n("Create New Sequence"), i18n("Enter sequence name")); - if(seqName.isEmpty()) return; + if (seqName.isEmpty()) return; sequence_name->blockSignals(true); sequence_name->setItemText(sequence_name->currentIndex(), seqName); sequence_name->blockSignals(false); } - if(m_sequenceName != sequence_name->currentText()) { + if (m_sequenceName != sequence_name->currentText()) { m_sequenceName = sequence_name->currentText(); m_sequenceFrame = 0; } @@ -394,21 +394,21 @@ void StopmotionWidget::slotCaptureFrame() KNotification::event("FrameCaptured"); m_sequenceFrame++; button_addsequence->setEnabled(true); - if(m_intervalCapture->isChecked()) QTimer::singleShot(KdenliveSettings::captureinterval() * 1000, this, SLOT(slotCaptureFrame())); + if (m_intervalCapture->isChecked()) QTimer::singleShot(KdenliveSettings::captureinterval() * 1000, this, SLOT(slotCaptureFrame())); } void StopmotionWidget::slotNewThumb(const QString path) { - if(!KdenliveSettings::showstopmotionthumbs()) return; + if (!KdenliveSettings::showstopmotionthumbs()) return; m_filesList.append(path); - if(m_showOverlay->isChecked()) slotUpdateOverlay(); - if(!m_future.isRunning()) m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs); + if (m_showOverlay->isChecked()) slotUpdateOverlay(); + if (!m_future.isRunning()) m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs); } void StopmotionWidget::slotPrepareThumbs() { - if(m_filesList.isEmpty()) return; + if (m_filesList.isEmpty()) return; QString path = m_filesList.takeFirst(); emit doCreateThumbs(QImage(path), SlideshowClip::getFrameNumberFromPath(path)); @@ -416,7 +416,7 @@ void StopmotionWidget::slotPrepareThumbs() void StopmotionWidget::slotCreateThumbs(QImage img, int ix) { - if(img.isNull()) { + if (img.isNull()) { m_future = QtConcurrent::run(this, &StopmotionWidget::slotPrepareThumbs); return; } @@ -440,47 +440,29 @@ void StopmotionWidget::slotCreateThumbs(QImage img, int ix) QString StopmotionWidget::getPathForFrame(int ix, QString seqName) { - if(seqName.isEmpty()) seqName = m_sequenceName; + if (seqName.isEmpty()) seqName = m_sequenceName; return m_projectFolder.path(KUrl::AddTrailingSlash) + seqName + "_" + QString::number(ix).rightJustified(4, '0', false) + ".png"; } -void StopmotionWidget::slotShowFrame(int ix) +void StopmotionWidget::slotShowFrame(const QString &path) { - if(m_sequenceFrame == 0) { - //there are no images in sequence - return; - } - if(ix < m_sequenceFrame) { - // Show previous frame - slotLive(false); - live_button->setChecked(false); - QImage img(getPathForFrame(ix)); - capture_button->setEnabled(false); - if(!img.isNull()) { - //m_bmCapture->showOverlay(img, false); - m_bmCapture->hidePreview(true); - m_frame_preview->setImage(img); - m_frame_preview->setHidden(false); - m_frame_preview->update(); - selectFrame(ix); - } + slotLive(false); + QImage img(path); + capture_button->setEnabled(false); + if (!img.isNull()) { + m_bmCapture->hidePreview(true); + m_frame_preview->setImage(img); + m_frame_preview->setHidden(false); + m_frame_preview->update(); } - /*else { - slotLive(true); - m_frame_preview->setImage(QImage()); - m_frame_preview->setHidden(true); - m_bmCapture->hideOverlay(); - m_bmCapture->hidePreview(false); - capture_button->setEnabled(true); - }*/ } void StopmotionWidget::slotShowSelectedFrame() { QListWidgetItem *item = frame_list->currentItem(); - if(item) { - int ix = item->data(Qt::UserRole).toInt();; - //slotShowFrame(ix); + if (item) { + //int ix = item->data(Qt::UserRole).toInt();; + slotShowFrame(item->toolTip()); } } @@ -489,54 +471,64 @@ void StopmotionWidget::slotAddSequence() emit addOrUpdateSequence(getPathForFrame(0)); } -void StopmotionWidget::slotPlayPreview() +void StopmotionWidget::slotPlayPreview(bool animate) { - if(m_animatedIndex != -1) { + if (!animate) { // stop animation - m_animatedIndex = -1; + m_animationList.clear(); return; } - QListWidgetItem *item = frame_list->currentItem(); - if(item) { - m_animatedIndex = item->data(Qt::UserRole).toInt(); + if (KdenliveSettings::showstopmotionthumbs()) { + frame_list->setCurrentRow(0); + QTimer::singleShot(200, this, SLOT(slotAnimate())); + } else { + SlideshowClip::selectedPath(getPathForFrame(0, sequence_name->currentText()), false, QString(), &m_animationList); + slotAnimate(); } - QTimer::singleShot(200, this, SLOT(slotAnimate())); } void StopmotionWidget::slotAnimate() { - slotShowFrame(m_animatedIndex); - m_animatedIndex++; - if(m_animatedIndex < m_sequenceFrame) QTimer::singleShot(200, this, SLOT(slotAnimate())); - else m_animatedIndex = -1; + //slotShowFrame(m_animatedIndex); + if (KdenliveSettings::showstopmotionthumbs()) { + //TODO: loop + if (frame_list->currentRow() < (frame_list->count() - 1)) { + frame_list->setCurrentRow(frame_list->currentRow() + 1); + QTimer::singleShot(200, this, SLOT(slotAnimate())); + } else preview_button->setChecked(false); + } else if (!m_animationList.isEmpty()) { + slotShowFrame(m_animationList.takeFirst()); + QTimer::singleShot(200, this, SLOT(slotAnimate())); + } else preview_button->setChecked(false); + } QListWidgetItem *StopmotionWidget::getFrameFromIndex(int ix) { QListWidgetItem *item = NULL; int pos = ix; - if(ix >= frame_list->count()) { + if (ix >= frame_list->count()) { pos = frame_list->count() - 1; } - if(ix < 0) pos = 0; + if (ix < 0) pos = 0; item = frame_list->item(pos); int value = item->data(Qt::UserRole).toInt(); - if(value == ix) return item; - else if(value < ix) { + if (value == ix) return item; + else if (value < ix) { pos++; - while(pos < frame_list->count()) { + while (pos < frame_list->count()) { item = frame_list->item(pos); value = item->data(Qt::UserRole).toInt(); - if(value == ix) return item; + if (value == ix) return item; pos++; } } else { pos --; - while(pos >= 0) { + while (pos >= 0) { item = frame_list->item(pos); value = item->data(Qt::UserRole).toInt(); - if(value == ix) return item; + if (value == ix) return item; pos --; } } @@ -548,7 +540,7 @@ void StopmotionWidget::selectFrame(int ix) { frame_list->blockSignals(true); QListWidgetItem *item = getFrameFromIndex(ix); - if(!item) return; + if (!item) return; frame_list->setCurrentItem(item); frame_list->blockSignals(false); } @@ -556,9 +548,9 @@ void StopmotionWidget::selectFrame(int ix) void StopmotionWidget::slotSeekFrame(bool forward) { int ix = frame_list->currentRow(); - if(forward) { - if(ix < frame_list->count() - 1) frame_list->setCurrentRow(ix + 1); - } else if(ix > 0) frame_list->setCurrentRow(ix - 1); + if (forward) { + if (ix < frame_list->count() - 1) frame_list->setCurrentRow(ix + 1); + } else if (ix > 0) frame_list->setCurrentRow(ix - 1); } diff --git a/src/stopmotion/stopmotion.h b/src/stopmotion/stopmotion.h index a3593d46..dbefc944 100644 --- a/src/stopmotion/stopmotion.h +++ b/src/stopmotion/stopmotion.h @@ -107,6 +107,10 @@ private: /** @brief The action triggering display of last frame over current live video feed. */ QAction *m_showOverlay; + /** @brief The list of all frames path. */ + QStringList m_animationList; + + #ifdef QIMAGEBLITZ int m_effectIndex; #endif @@ -130,7 +134,7 @@ private slots: void slotCaptureFrame(); /** @brief Display a previous frame in monitor. */ - void slotShowFrame(int); + void slotShowFrame(const QString &path); /** @brief Get full path for a frame in the sequence. * @param ix the frame number. @@ -144,7 +148,7 @@ private slots: void slotShowSelectedFrame(); /** @brief Start animation preview mode. */ - void slotPlayPreview(); + void slotPlayPreview(bool animate); /** @brief Simulate animation. */ void slotAnimate(); diff --git a/src/v4l/v4lcapture.cpp b/src/v4l/v4lcapture.cpp index 63dc71d4..09ba96ba 100644 --- a/src/v4l/v4lcapture.cpp +++ b/src/v4l/v4lcapture.cpp @@ -38,85 +38,60 @@ static src_t v4lsrc; -void yuv2rgb_int3(unsigned char *yuv_buffer, unsigned char *rgb_buffer, int width, int height) +class MyDisplay : public QLabel { - int len; - int r, g, b; - int Y, U, V, Y2; - int rgb_ptr, y_ptr, t; - - len = width * height / 2; - - rgb_ptr = 0; - y_ptr = 0; - - for(t = 0; t < len; t++) { /* process 2 pixels at a time */ - /* Compute parts of the UV components */ - - U = yuv_buffer[y_ptr]; - Y = yuv_buffer[y_ptr+1]; - V = yuv_buffer[y_ptr+2]; - Y2 = yuv_buffer[y_ptr+3]; - y_ptr += 4; - - - /*r = 1.164*(Y-16) + 1.596*(V-128); - g = 1.164*(Y-16) - 0.813*(V-128) - 0.391*(U-128); - b = 1.164*(Y-16) + 2.018*(U-128);*/ - - - r = ((298 * (Y - 16) + 409 * (V - 128) + 128) >> 8); - - g = ((298 * (Y - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8); - - b = ((298 * (Y - 16) + 516 * (U - 128) + 128) >> 8); - - if(r > 255) r = 255; - if(g > 255) g = 255; - if(b > 255) b = 255; - - if(r < 0) r = 0; - if(g < 0) g = 0; - if(b < 0) b = 0; - - rgb_buffer[rgb_ptr] = b; - rgb_buffer[rgb_ptr+1] = g; - rgb_buffer[rgb_ptr+2] = r; - rgb_buffer[rgb_ptr+3] = 255; - - rgb_ptr += 4; - /*r = 1.164*(Y2-16) + 1.596*(V-128); - g = 1.164*(Y2-16) - 0.813*(V-128) - 0.391*(U-128); - b = 1.164*(Y2-16) + 2.018*(U-128);*/ - - - r = ((298 * (Y2 - 16) + 409 * (V - 128) + 128) >> 8); +public: + MyDisplay(QWidget *parent = 0); + void setImage(QImage img); + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + +private: + QImage m_img; + bool m_clear; +}; + +MyDisplay::MyDisplay(QWidget *parent): + QLabel(parent) + , m_clear(false) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setAttribute(Qt::WA_PaintOnScreen); + setAttribute(Qt::WA_OpaquePaintEvent); +} - g = ((298 * (Y2 - 16) - 100 * (U - 128) - 208 * (V - 128) + 128) >> 8); +void MyDisplay::resizeEvent(QResizeEvent *) +{ + m_clear = true; +} - b = ((298 * (Y2 - 16) + 516 * (U - 128) + 128) >> 8); +void MyDisplay::paintEvent(QPaintEvent *) +{ + QPainter p(this); + if (m_clear) { + // widget resized, cleanup + p.fillRect(0, 0, width(), height(), palette().background()); + m_clear = false; + } + if (m_img.isNull()) return; + QImage img = m_img.scaled(width(), height(), Qt::KeepAspectRatio); + p.drawImage((width() - img.width()) / 2, (height() - img.height()) / 2, img); + p.end(); +} - if(r > 255) r = 255; - if(g > 255) g = 255; - if(b > 255) b = 255; +void MyDisplay::setImage(QImage img) +{ + m_img = img; + update(); +} - if(r < 0) r = 0; - if(g < 0) g = 0; - if(b < 0) b = 0; - rgb_buffer[rgb_ptr] = b; - rgb_buffer[rgb_ptr+1] = g; - rgb_buffer[rgb_ptr+2] = r; - rgb_buffer[rgb_ptr+3] = 255; - rgb_ptr += 4; - } -} V4lCaptureHandler::V4lCaptureHandler(QVBoxLayout *lay, QWidget *parent): CaptureHandler(lay, parent) , m_update(false) { - m_display = new QLabel; + m_display = new MyDisplay; lay->addWidget(m_display); } @@ -126,7 +101,7 @@ void V4lCaptureHandler::startPreview(int /*deviceId*/, int /*captureMode*/) fswebcam_config_t *config; /* Prepare the configuration structure. */ config = (fswebcam_config_t *) calloc(sizeof(fswebcam_config_t), 1); - if(!config) { + if (!config) { /*WARN("Out of memory.");*/ fprintf(stderr, "Out of MEM...."); return; @@ -180,7 +155,7 @@ void V4lCaptureHandler::startPreview(int /*deviceId*/, int /*captureMode*/) v4lsrc.option = config->option; char *source = config->device; - if(src_open(&v4lsrc, source) != 0) return; + if (src_open(&v4lsrc, source) != 0) return; m_update = true; QTimer::singleShot(200, this, SLOT(slotUpdate())); } @@ -192,31 +167,31 @@ V4lCaptureHandler::~V4lCaptureHandler() void V4lCaptureHandler::slotUpdate() { - if(!m_update) return; + if (!m_update) return; src_grab(&v4lsrc); uint8_t *img = (uint8_t *) v4lsrc.img; uint32_t i = v4lsrc.width * v4lsrc.height; - if(v4lsrc.length << 2 < i) return; + if (v4lsrc.length << 2 < i) return; QImage qimg(v4lsrc.width, v4lsrc.height, QImage::Format_RGB32); //Format_ARGB32_Premultiplied //convert from uyvy422 to rgba - yuv2rgb_int3((uchar *)img, (uchar *)qimg.bits(), v4lsrc.width, v4lsrc.height); - if(!m_captureFramePath.isEmpty()) { + CaptureHandler::yuv2rgb((uchar *)img, (uchar *)qimg.bits(), v4lsrc.width, v4lsrc.height); + if (!m_captureFramePath.isEmpty()) { qimg.save(m_captureFramePath); emit frameSaved(m_captureFramePath); m_captureFramePath.clear(); } - if(!m_overlayImage.isNull()) { + if (!m_overlayImage.isNull()) { // overlay image QPainter p(&qimg); p.setOpacity(0.5); p.drawImage(0, 0, m_overlayImage); p.end(); } - m_display->setPixmap(QPixmap::fromImage(qimg)); - if(m_update) QTimer::singleShot(200, this, SLOT(slotUpdate())); + m_display->setImage(qimg); + if (m_update) QTimer::singleShot(200, this, SLOT(slotUpdate())); } void V4lCaptureHandler::startCapture(const QString &/*path*/) @@ -249,7 +224,7 @@ void V4lCaptureHandler::hidePreview(bool hide) void V4lCaptureHandler::stopPreview() { - if(!m_update) return; + if (!m_update) return; m_update = false; src_close(&v4lsrc); } diff --git a/src/v4l/v4lcapture.h b/src/v4l/v4lcapture.h index ef81e54a..36bf28d0 100644 --- a/src/v4l/v4lcapture.h +++ b/src/v4l/v4lcapture.h @@ -28,6 +28,8 @@ #include #include +class MyDisplay; + class V4lCaptureHandler : public CaptureHandler { Q_OBJECT @@ -45,7 +47,7 @@ public: private: bool m_update; - QLabel *m_display; + MyDisplay *m_display; QString m_captureFramePath; QImage m_overlayImage; diff --git a/src/widgets/stopmotion_ui.ui b/src/widgets/stopmotion_ui.ui index f88a01ba..425bd1c8 100644 --- a/src/widgets/stopmotion_ui.ui +++ b/src/widgets/stopmotion_ui.ui @@ -121,6 +121,9 @@ ... + + true + -- 2.39.2