mirror of https://github.com/mgba-emu/mgba.git
Qt: Improved GIF recording customization
This commit is contained in:
parent
d5b352a696
commit
8af2172782
1
CHANGES
1
CHANGES
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -38,6 +38,7 @@ signals:
|
|||
private slots:
|
||||
void selectFile();
|
||||
void setFilename(const QString&);
|
||||
void updateDelay();
|
||||
|
||||
private:
|
||||
Ui::GIFView m_ui;
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue