all repos — mgba @ 8af2172782b283a07ac40fcc36186c2f52bb1f7b

mGBA Game Boy Advance Emulator

Qt: Improved GIF recording customization
Jeffrey Pfau jeffrey@endrift.com
Tue, 27 Oct 2015 20:09:56 -0700
commit

8af2172782b283a07ac40fcc36186c2f52bb1f7b

parent

d5b352a6962afeb54b1229402c28c1b5425dfa7f

M CHANGESCHANGES

@@ -3,6 +3,7 @@ Features:

- Officially supported ports for the Nintendo 3DS, Wii, and PlayStation Vita - I/O viewer - Booting of multiboot images + - Customization of GIF recording Bugfixes: - Util: Fix PowerPC PNG read/write pixel order - Qt: Use safer isLoaded check in GameController
M src/platform/imagemagick/imagemagick-gif-encoder.csrc/platform/imagemagick/imagemagick-gif-encoder.c

@@ -19,26 +19,40 @@ encoder->d.postAudioFrame = _magickPostAudioFrame;

encoder->d.postAudioBuffer = 0; encoder->frameskip = 2; + encoder->delayMs = -1; +} + +void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs) { + if (ImageMagickGIFEncoderIsOpen(encoder)) { + return; + } + encoder->frameskip = frameskip; + encoder->delayMs = delayMs; } bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder* encoder, const char* outfile) { MagickWandGenesis(); encoder->wand = NewMagickWand(); + MagickSetImageFormat(encoder->wand, "GIF"); + MagickSetImageDispose(encoder->wand, PreviousDispose); encoder->outfile = strdup(outfile); encoder->frame = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); encoder->currentFrame = 0; return true; } -void ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) { + +bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder* encoder) { if (!encoder->wand) { - return; + return false; } - MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue); + + MagickBooleanType success = MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue); + DestroyMagickWand(encoder->wand); + encoder->wand = 0; free(encoder->outfile); free(encoder->frame); - DestroyMagickWand(encoder->wand); - encoder->wand = 0; MagickWandTerminus(); + return success == MagickTrue; } bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder* encoder) {

@@ -64,10 +78,17 @@

MagickConstituteImage(encoder->wand, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, "RGBP", CharPixel, encoder->frame); uint64_t ts = encoder->currentFrame; uint64_t nts = encoder->currentFrame + encoder->frameskip + 1; - ts *= VIDEO_TOTAL_LENGTH * 100; - nts *= VIDEO_TOTAL_LENGTH * 100; - ts /= GBA_ARM7TDMI_FREQUENCY; - nts /= GBA_ARM7TDMI_FREQUENCY; + if (encoder->delayMs >= 0) { + ts *= encoder->delayMs; + nts *= encoder->delayMs; + ts /= 10; + nts /= 10; + } else { + ts *= VIDEO_TOTAL_LENGTH * 100; + nts *= VIDEO_TOTAL_LENGTH * 100; + ts /= GBA_ARM7TDMI_FREQUENCY; + nts /= GBA_ARM7TDMI_FREQUENCY; + } MagickSetImageDelay(encoder->wand, nts - ts); ++encoder->currentFrame; }
M src/platform/imagemagick/imagemagick-gif-encoder.hsrc/platform/imagemagick/imagemagick-gif-encoder.h

@@ -21,11 +21,13 @@ uint32_t* frame;

unsigned currentFrame; int frameskip; + int delayMs; }; void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder*); +void ImageMagickGIFEncoderSetParams(struct ImageMagickGIFEncoder* encoder, int frameskip, int delayMs); bool ImageMagickGIFEncoderOpen(struct ImageMagickGIFEncoder*, const char* outfile); -void ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder*); +bool ImageMagickGIFEncoderClose(struct ImageMagickGIFEncoder*); bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder*); #endif
M src/platform/qt/GIFView.cppsrc/platform/qt/GIFView.cpp

@@ -19,12 +19,14 @@ : QWidget(parent)

{ m_ui.setupUi(this); - connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); connect(m_ui.start, SIGNAL(clicked()), this, SLOT(startRecording())); connect(m_ui.stop, SIGNAL(clicked()), this, SLOT(stopRecording())); connect(m_ui.selectFile, SIGNAL(clicked()), this, SLOT(selectFile())); connect(m_ui.filename, SIGNAL(textChanged(const QString&)), this, SLOT(setFilename(const QString&))); + + connect(m_ui.frameskip, SIGNAL(valueChanged(int)), this, SLOT(updateDelay())); + connect(m_ui.delayAuto, SIGNAL(clicked(bool)), this, SLOT(updateDelay())); ImageMagickGIFEncoderInit(&m_encoder); }

@@ -34,12 +36,15 @@ stopRecording();

} void GIFView::startRecording() { + int delayMs = m_ui.delayAuto->isChecked() ? -1 : m_ui.delayMs->value(); + ImageMagickGIFEncoderSetParams(&m_encoder, m_ui.frameskip->value(), delayMs); if (!ImageMagickGIFEncoderOpen(&m_encoder, m_filename.toUtf8().constData())) { LOG(ERROR) << tr("Failed to open output GIF file: %1").arg(m_filename); return; } m_ui.start->setEnabled(false); m_ui.stop->setEnabled(true); + m_ui.groupBox->setEnabled(false); emit recordingStarted(&m_encoder.d); }

@@ -48,6 +53,7 @@ emit recordingStopped();

ImageMagickGIFEncoderClose(&m_encoder); m_ui.stop->setEnabled(false); m_ui.start->setEnabled(true); + m_ui.groupBox->setEnabled(true); } void GIFView::selectFile() {

@@ -62,6 +68,17 @@ }

void GIFView::setFilename(const QString& fname) { m_filename = fname; +} + +void GIFView::updateDelay() { + if (!m_ui.delayAuto->isChecked()) { + return; + } + + uint64_t s = (m_ui.frameskip->value() + 1); + s *= VIDEO_TOTAL_LENGTH * 1000; + s /= GBA_ARM7TDMI_FREQUENCY; + m_ui.delayMs->setValue(s); } #endif
M src/platform/qt/GIFView.hsrc/platform/qt/GIFView.h

@@ -38,6 +38,7 @@

private slots: void selectFile(); void setFilename(const QString&); + void updateDelay(); private: Ui::GIFView m_ui;
M src/platform/qt/GIFView.uisrc/platform/qt/GIFView.ui

@@ -6,8 +6,8 @@ <property name="geometry">

<rect> <x>0</x> <y>0</y> - <width>342</width> - <height>124</height> + <width>278</width> + <height>247</height> </rect> </property> <property name="windowTitle">

@@ -90,6 +90,59 @@ </item>

</layout> </item> <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string/> + </property> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Frameskip</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="frameskip"> + <property name="value"> + <number>2</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Frame delay (ms)</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="delayAuto"> + <property name="text"> + <string>Automatic</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="delayMs"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maximum"> + <number>5000</number> + </property> + <property name="value"> + <number>50</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="standardButtons"> <set>QDialogButtonBox::Close</set>

@@ -99,5 +152,38 @@ </item>

</layout> </widget> <resources/> - <connections/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>GIFView</receiver> + <slot>close()</slot> + <hints> + <hint type="sourcelabel"> + <x>138</x> + <y>226</y> + </hint> + <hint type="destinationlabel"> + <x>138</x> + <y>123</y> + </hint> + </hints> + </connection> + <connection> + <sender>delayAuto</sender> + <signal>clicked(bool)</signal> + <receiver>delayMs</receiver> + <slot>setDisabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>202</x> + <y>177</y> + </hint> + <hint type="destinationlabel"> + <x>192</x> + <y>148</y> + </hint> + </hints> + </connection> + </connections> </ui>