Skip to content

Commit

Permalink
Allow scaling animation exports
Browse files Browse the repository at this point in the history
By entering a percentage scale and picking whether you want it bilinear
or nearest-neighbor.
  • Loading branch information
askmeaboutlo0m committed Aug 12, 2024
1 parent 4a0d110 commit c55d90b
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 124 deletions.
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Unreleased Version 2.2.2-pre
* Fix: Solve rendering glitches with selection outlines that happen on some systems. Thanks xxxx for reporting.
* Feature: Allow scaling animation exports. Thanks Hopfel for animating across a giant canvas.

2024-08-09 Version 2.2.2-beta.3
* Fix: Use more accurate timers for performance profiles if the platform supports it.
Expand Down
66 changes: 61 additions & 5 deletions src/desktop/dialogs/animationexportdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "libclient/canvas/documentmetadata.h"
#include "libclient/canvas/paintengine.h"
#include "libclient/export/animationformat.h"
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
Expand All @@ -21,7 +22,8 @@

namespace dialogs {

AnimationExportDialog::AnimationExportDialog(QWidget *parent)
AnimationExportDialog::AnimationExportDialog(
int scalePercent, bool scaleSmooth, QWidget *parent)
: QDialog(parent)
{
setWindowTitle(tr("Export Animation"));
Expand Down Expand Up @@ -62,6 +64,22 @@ AnimationExportDialog::AnimationExportDialog(QWidget *parent)
m_loopsSpinner->setRange(1, 99);
outputForm->addRow(m_loopsLabel, m_loopsSpinner);

m_scaleSpinner = new QSpinBox;
m_scaleSpinner->setRange(1, 1000);
m_scaleSpinner->setValue(scalePercent);
m_scaleSpinner->setSuffix(tr("%"));

m_scaleSmoothBox = new QCheckBox(tr("Smooth"));
m_scaleSmoothBox->setChecked(scaleSmooth);

QHBoxLayout *scaleLayout = new QHBoxLayout;
scaleLayout->addWidget(m_scaleSpinner);
scaleLayout->addWidget(m_scaleSmoothBox);
outputForm->addRow(tr("Scaling:"), scaleLayout);

m_scaleLabel = new QLabel;
outputForm->addRow(m_scaleLabel);

QWidget *inputWidget = new QWidget;
QFormLayout *inputForm = new QFormLayout(inputWidget);
tabs->addTab(inputWidget, tr("Input"));
Expand Down Expand Up @@ -128,6 +146,9 @@ AnimationExportDialog::AnimationExportDialog(QWidget *parent)
connect(
m_formatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &AnimationExportDialog::updateOutputUi);
connect(
m_scaleSpinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateScalingUi);
connect(
m_startSpinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateEndRange);
Expand All @@ -137,15 +158,27 @@ AnimationExportDialog::AnimationExportDialog(QWidget *parent)
connect(
m_x1Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateX2Range);
connect(
m_x1Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateScalingUi);
connect(
m_x2Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateX1Range);
connect(
m_x2Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateScalingUi);
connect(
m_y1Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateY2Range);
connect(
m_y1Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateScalingUi);
connect(
m_y2Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateY1Range);
connect(
m_y2Spinner, QOverload<int>::of(&QSpinBox::valueChanged), this,
&AnimationExportDialog::updateScalingUi);
connect(
m_inputResetButton, &QPushButton::clicked, this,
&AnimationExportDialog::resetInputs);
Expand All @@ -163,6 +196,7 @@ AnimationExportDialog::AnimationExportDialog(QWidget *parent)
&AnimationExportDialog::requestExport, Qt::DirectConnection);

updateOutputUi();
updateScalingUi();
}

void AnimationExportDialog::setCanvas(canvas::CanvasModel *canvas)
Expand Down Expand Up @@ -201,6 +235,13 @@ void AnimationExportDialog::setFlipbookState(
}
}

QSize AnimationExportDialog::getScaledSizeFor(
int scalePercent, const QRect &rect)
{
QSize size = rect.size() * (scalePercent / 100.0);
return QSize(qMax(size.width(), 1), qMax(size.height(), 1));
}

#ifndef __EMSCRIPTEN__
void AnimationExportDialog::accept()
{
Expand All @@ -220,6 +261,15 @@ void AnimationExportDialog::updateOutputUi()
m_loopsSpinner->setVisible(showLoops);
}

void AnimationExportDialog::updateScalingUi()
{
int scalePercent = m_scaleSpinner->value();
QSize size = getScaledSizeFor(scalePercent, getCropRect());
m_scaleLabel->setText(tr("Output resolution will be %1x%2 pixels.")
.arg(size.width())
.arg(size.height()));
}

#ifndef __EMSCRIPTEN__
QString AnimationExportDialog::choosePath()
{
Expand Down Expand Up @@ -340,6 +390,7 @@ void AnimationExportDialog::setCanvasSize(const QSize &size)
}
m_canvasWidth = size.width();
m_canvasHeight = size.height();
updateScalingUi();
}

void AnimationExportDialog::setCanvasFrameCount(int frameCount)
Expand Down Expand Up @@ -376,10 +427,15 @@ void AnimationExportDialog::requestExport()
m_path,
#endif
format, m_loopsSpinner->value(), m_startSpinner->value() - 1,
m_endSpinner->value() - 1, m_framerateSpinner->value(),
QRect(
QPoint(m_x1Spinner->value(), m_y1Spinner->value()),
QPoint(m_x2Spinner->value(), m_y2Spinner->value())));
m_endSpinner->value() - 1, m_framerateSpinner->value(), getCropRect(),
m_scaleSpinner->value(), m_scaleSmoothBox->isChecked());
}

QRect AnimationExportDialog::getCropRect() const
{
return QRect(
QPoint(m_x1Spinner->value(), m_y1Spinner->value()),
QPoint(m_x2Spinner->value(), m_y2Spinner->value()));
}

}
15 changes: 13 additions & 2 deletions src/desktop/dialogs/animationexportdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <QDialog>
#include <QHash>

class KisSliderSpinBox;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
class QLabel;
Expand All @@ -20,14 +22,17 @@ namespace dialogs {
class AnimationExportDialog final : public QDialog {
Q_OBJECT
public:
explicit AnimationExportDialog(QWidget *parent = nullptr);
explicit AnimationExportDialog(
int scalePercent, bool scaleSmooth, QWidget *parent = nullptr);

void setCanvas(canvas::CanvasModel *canvas);

void setFlipbookState(
int start, int end, double speedPercent, const QRectF &crop,
bool apply);

static QSize getScaledSizeFor(int scalePercent, const QRect &rect);

public slots:
#ifndef __EMSCRIPTEN__
void accept() override;
Expand All @@ -39,10 +44,11 @@ public slots:
const QString &path,
#endif
int format, int loops, int start, int end, int framerate,
const QRect &crop);
const QRect &crop, int scalePercent, bool scaleSmooth);

private:
void updateOutputUi();
void updateScalingUi();
#ifndef __EMSCRIPTEN__
QString choosePath();
#endif
Expand All @@ -62,11 +68,16 @@ public slots:

void requestExport();

QRect getCropRect() const;

#ifndef __EMSCRIPTEN__
QString m_path;
#endif
QComboBox *m_formatCombo;
QLabel *m_loopsLabel;
QSpinBox *m_scaleSpinner;
QCheckBox *m_scaleSmoothBox;
QLabel *m_scaleLabel;
QSpinBox *m_loopsSpinner;
QSpinBox *m_startSpinner;
QSpinBox *m_endSpinner;
Expand Down
23 changes: 19 additions & 4 deletions src/desktop/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1927,7 +1927,8 @@ void MainWindow::showAnimationExportDialog(bool fromFlipbook)
dlg->activateWindow();
dlg->raise();
} else {
dlg = new dialogs::AnimationExportDialog(this);
dlg = new dialogs::AnimationExportDialog(
m_animationExportScalePercent, m_animationExportScaleSmooth, this);
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setObjectName(objectName);
dlg->setCanvas(m_doc->canvas());
Expand Down Expand Up @@ -1958,19 +1959,33 @@ void MainWindow::exportAnimation(
#ifndef __EMSCRIPTEN__
const QString &path,
#endif
int format, int loops, int start, int end, int framerate, const QRect &crop)
int format, int loops, int start, int end, int framerate, const QRect &crop,
int scalePercent, bool scaleSmooth)
{
m_animationExportScalePercent = scalePercent;
m_animationExportScaleSmooth = scaleSmooth;

QProgressDialog *progressDialog = new QProgressDialog(
tr("Saving animation..."), tr("Cancel"), 0, 100, this);
progressDialog->setMinimumDuration(500);
progressDialog->setValue(0);

drawdance::CanvasState canvasState =
m_doc->canvas()->paintEngine()->viewCanvasState();
QRect canvasRect = QRect(QPoint(0, 0), canvasState.size());
QRect effectiveCrop = crop & canvasRect;
if(effectiveCrop.isEmpty()) {
effectiveCrop = canvasRect;
}
QSize size = dialogs::AnimationExportDialog::getScaledSizeFor(
scalePercent, effectiveCrop);

AnimationSaverRunnable *saver = new AnimationSaverRunnable(
#ifndef __EMSCRIPTEN__
path,
#endif
format, loops, start, end, framerate, crop,
m_doc->canvas()->paintEngine()->viewCanvasState(), this);
format, size.width(), size.height(), loops, start, end, framerate,
effectiveCrop, scaleSmooth, canvasState, this);
saver->setAutoDelete(true);

connect(
Expand Down
4 changes: 3 additions & 1 deletion src/desktop/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ private slots:
const QString &path,
#endif
int format, int loops, int start, int end, int framerate,
const QRect &crop);
const QRect &crop, int scalePercent, bool scaleSmooth);

ActionBuilder makeAction(const char *name, const QString &text = QString{});
QAction *getAction(const QString &name);
Expand Down Expand Up @@ -404,6 +404,8 @@ private slots:
dialogs::SessionSettingsDialog *m_sessionSettings;
dialogs::ServerLogDialog *m_serverLogDialog;
dialogs::Flipbook::State m_flipbookState;
int m_animationExportScalePercent = 100;
bool m_animationExportScaleSmooth = true;

#ifndef __EMSCRIPTEN__
QMenu *m_recentMenu;
Expand Down
28 changes: 28 additions & 0 deletions src/drawdance/libengine/dpengine/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,34 @@ bool DP_image_thumbnail(DP_Image *img, DP_DrawContext *dc, int max_width,
}
}

DP_Image *DP_image_scale(DP_Image *img, DP_DrawContext *dc, int width,
int height, int interpolation)
{
DP_ASSERT(img);
DP_ASSERT(dc);
if (width > 0 && height > 0) {
int src_width = DP_image_width(img);
int src_height = DP_image_height(img);
DP_Transform tf = DP_transform_scale(
DP_transform_identity(),
DP_int_to_double(width) / DP_int_to_double(src_width),
DP_int_to_double(height) / DP_int_to_double(src_height));
DP_Image *result = DP_image_new(width, height);
if (DP_image_transform_draw(src_width, src_height, DP_image_pixels(img),
dc, result, tf, interpolation)) {
return result;
}
else {
DP_image_free(result);
return NULL;
}
}
else {
DP_error_set("Can't scale to zero dimensions");
return NULL;
}
}

bool DP_image_same_pixel(DP_Image *img, DP_Pixel8 *out_pixel)
{
DP_Pixel8 *pixels = DP_image_pixels(img);
Expand Down
3 changes: 3 additions & 0 deletions src/drawdance/libengine/dpengine/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ DP_Image *DP_image_transform(DP_Image *img, DP_DrawContext *dc,
bool DP_image_thumbnail(DP_Image *img, DP_DrawContext *dc, int max_width,
int max_height, DP_Image **out_thumb) DP_MUST_CHECK;

DP_Image *DP_image_scale(DP_Image *img, DP_DrawContext *dc, int width,
int height, int interpolation);

bool DP_image_same_pixel(DP_Image *img, DP_Pixel8 *out_pixel);

DP_UPixelFloat DP_image_sample_color_at_with(int width, int height,
Expand Down
Loading

0 comments on commit c55d90b

Please sign in to comment.