Qt: Improved GIF recording customization

This commit is contained in:
Jeffrey Pfau 2015-10-27 20:09:56 -07:00
parent d5b352a696
commit 8af2172782
6 changed files with 142 additions and 14 deletions

View File

@ -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

View File

@ -19,26 +19,40 @@ void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
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);
free(encoder->outfile);
free(encoder->frame);
MagickBooleanType success = MagickWriteImages(encoder->wand, encoder->outfile, MagickTrue);
DestroyMagickWand(encoder->wand);
encoder->wand = 0;
free(encoder->outfile);
free(encoder->frame);
MagickWandTerminus();
return success == MagickTrue;
}
bool ImageMagickGIFEncoderIsOpen(struct ImageMagickGIFEncoder* encoder) {
@ -64,10 +78,17 @@ static void _magickPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRen
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;
}

View File

@ -21,11 +21,13 @@ struct ImageMagickGIFEncoder {
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

View File

@ -19,13 +19,15 @@ GIFView::GIFView(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 @@ GIFView::~GIFView() {
}
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 @@ void GIFView::stopRecording() {
ImageMagickGIFEncoderClose(&m_encoder);
m_ui.stop->setEnabled(false);
m_ui.start->setEnabled(true);
m_ui.groupBox->setEnabled(true);
}
void GIFView::selectFile() {
@ -64,4 +70,15 @@ 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

View File

@ -38,6 +38,7 @@ signals:
private slots:
void selectFile();
void setFilename(const QString&);
void updateDelay();
private:
Ui::GIFView m_ui;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>342</width>
<height>124</height>
<width>278</width>
<height>247</height>
</rect>
</property>
<property name="windowTitle">
@ -89,6 +89,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">
@ -99,5 +152,38 @@
</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>