Add AniDir parameter to Export dialog (fix #1505)

This commit is contained in:
David Capello 2018-03-16 08:59:34 -03:00
parent ca31a79ca5
commit 589d775d10
14 changed files with 265 additions and 43 deletions

View File

@ -357,6 +357,7 @@
<option id="resize_scale" type="double" default="1" />
<option id="layer" type="std::string" />
<option id="frame_tag" type="std::string" />
<option id="ani_dir" type="doc::AniDir" default="doc::AniDir::FORWARD" />
<option id="apply_pixel_ratio" type="bool" default="false" />
</section>
<section id="sprite_sheet">

View File

@ -461,6 +461,7 @@ output_file = Output File:
resize = Resize:
layers = Layers:
frames = Frames:
anidir = Animation Direction:
pixel_ratio = Apply pixel ratio
export = &Export
cancel = &Cancel

View File

@ -28,6 +28,9 @@
<label id="frames_label" text="@.frames" />
<combobox id="frames" text="" cell_align="horizontal" />
<label id="anidir_label" text="@.anidir" />
<combobox id="anidir" text="" cell_align="horizontal" />
<check id="pixel_ratio" text="@.pixel_ratio" cell_hspan="2" />
<hbox cell_hspan="2">

View File

@ -90,6 +90,7 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
m_filename = params.get("filename");
m_filenameFormat = params.get("filename-format");
m_frameTag = params.get("frame-tag");
m_aniDir = params.get("ani-dir");
m_slice = params.get("slice");
if (params.has_param("from-frame") ||
@ -160,13 +161,22 @@ void SaveFileBaseCommand::saveDocumentInBackground(
const Context* context,
app::Document* document,
const std::string& filename,
const bool markAsSaved) const
const bool markAsSaved)
{
if (!m_aniDir.empty()) {
if (m_aniDir == "reverse")
m_selFrames = m_selFrames.makeReverse();
else if (m_aniDir == "ping-pong")
m_selFrames = m_selFrames.makePingPong();
}
FileOpROI roi(document, m_slice, m_frameTag,
m_selFrames, m_adjustFramesByFrameTag);
base::UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context,
FileOpROI(document, m_slice, m_frameTag,
m_selFrames, m_adjustFramesByFrameTag),
roi,
filename,
m_filenameFormat));
if (!fop)
@ -366,8 +376,17 @@ again:;
m_selFrames = selFrames;
m_adjustFramesByFrameTag = false;
m_aniDir.clear();
switch (win.aniDirValue()) {
case doc::AniDir::FORWARD: m_aniDir = "forward"; break;
case doc::AniDir::REVERSE: m_aniDir = "reverse"; break;
case doc::AniDir::PING_PONG: m_aniDir = "ping-pong"; break;
}
saveDocumentInBackground(
context, doc, win.outputFilenameValue(), false);
m_aniDir.clear();
}
// Undo resize

View File

@ -35,11 +35,12 @@ namespace app {
const Context* context,
app::Document* document,
const std::string& filename,
const bool markAsSaved) const;
const bool markAsSaved);
std::string m_filename;
std::string m_filenameFormat;
std::string m_frameTag;
std::string m_aniDir;
std::string m_slice;
doc::SelectedFrames m_selFrames;
bool m_adjustFramesByFrameTag;

View File

@ -157,8 +157,9 @@ FileOpROI::FileOpROI(const app::Document* doc,
else if (adjustByFrameTag)
m_selFrames.displace(m_frameTag->fromFrame());
m_selFrames.filter(MAX(0, m_frameTag->fromFrame()),
MIN(m_frameTag->toFrame(), doc->sprite()->lastFrame()));
m_selFrames =
m_selFrames.filter(MAX(0, m_frameTag->fromFrame()),
MIN(m_frameTag->toFrame(), doc->sprite()->lastFrame()));
}
// All frames if selected frames is empty
else if (m_selFrames.empty())
@ -743,7 +744,6 @@ void FileOp::operate(IFileOpProgress* progress)
}
m_filename = *m_seq.filename_list.begin();
m_document->setFilename(m_filename);
// Destroy the image
m_seq.image.reset(NULL);

View File

@ -12,9 +12,13 @@
#include "app/document.h"
#include "app/ui/layer_frame_comboboxes.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "doc/frame_tag.h"
#include "doc/selected_frames.h"
#include "doc/site.h"
namespace app {
@ -44,8 +48,11 @@ ExportFileWindow::ExportFileWindow(const Document* doc)
base::convert_to<std::string>(m_docPref.saveCopy.resizeScale()));
fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer());
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio());
updateAniDir();
outputFilename()->Click.connect(base::Bind<void>(
[this]{
std::string fn = SelectOutputFile();
@ -54,6 +61,8 @@ ExportFileWindow::ExportFileWindow(const Document* doc)
updateOutputFilenameButton();
}
}));
frames()->Change.connect(base::Bind<void>(&ExportFileWindow::updateAniDir, this));
}
bool ExportFileWindow::show()
@ -86,6 +95,11 @@ std::string ExportFileWindow::framesValue() const
return frames()->getValue();
}
doc::AniDir ExportFileWindow::aniDirValue() const
{
return (doc::AniDir)anidir()->getSelectedItemIndex();
}
bool ExportFileWindow::applyPixelRatio() const
{
return pixelRatio()->isSelected();
@ -96,4 +110,20 @@ void ExportFileWindow::updateOutputFilenameButton()
outputFilename()->setText(base::get_file_name(m_outputFilename));
}
void ExportFileWindow::updateAniDir()
{
std::string framesValue = this->framesValue();
if (!framesValue.empty() &&
framesValue != kAllFrames &&
framesValue != kSelectedFrames) {
SelectedFrames selFrames;
FrameTag* frameTag = calculate_selected_frames(
UIContext::instance()->activeSite(), framesValue, selFrames);
if (frameTag)
anidir()->setSelectedItemIndex(int(frameTag->aniDir()));
}
else
anidir()->setSelectedItemIndex(int(doc::AniDir::FORWARD));
}
} // namespace app

View File

@ -29,12 +29,14 @@ namespace app {
double resizeValue() const;
std::string layersValue() const;
std::string framesValue() const;
doc::AniDir aniDirValue() const;
bool applyPixelRatio() const;
obs::signal<std::string()> SelectOutputFile;
private:
void updateOutputFilenameButton();
void updateAniDir();
const Document* m_doc;
DocumentPreferences& m_docPref;

View File

@ -12,6 +12,7 @@
#include "app/document.h"
#include "app/pref/preferences.h"
#include "app/ui/layer_frame_comboboxes.h"
#include "doc/frame_tag.h"
#include "doc/sprite.h"
@ -30,14 +31,7 @@ FrameTagWindow::FrameTagWindow(const doc::Sprite* sprite, const doc::FrameTag* f
doc::rgba_getg(frameTag->color()),
doc::rgba_getb(frameTag->color())));
static_assert(
int(doc::AniDir::FORWARD) == 0 &&
int(doc::AniDir::REVERSE) == 1 &&
int(doc::AniDir::PING_PONG) == 2, "doc::AniDir has changed");
anidir()->addItem("Forward");
anidir()->addItem("Reverse");
anidir()->addItem("Ping-pong");
anidir()->setSelectedItemIndex(int(frameTag->aniDir()));
fill_anidir_combobox(anidir(), frameTag->aniDir());
}
bool FrameTagWindow::show()

View File

@ -11,6 +11,7 @@
#include "app/ui/layer_frame_comboboxes.h"
#include "app/restore_visible_layers.h"
#include "doc/anidir.h"
#include "doc/frame_tag.h"
#include "doc/layer.h"
#include "doc/selected_frames.h"
@ -95,6 +96,19 @@ void fill_frames_combobox(const doc::Sprite* sprite, ui::ComboBox* frames, const
}
}
void fill_anidir_combobox(ui::ComboBox* anidir, doc::AniDir defAnidir)
{
static_assert(
int(doc::AniDir::FORWARD) == 0 &&
int(doc::AniDir::REVERSE) == 1 &&
int(doc::AniDir::PING_PONG) == 2, "doc::AniDir has changed");
anidir->addItem("Forward");
anidir->addItem("Reverse");
anidir->addItem("Ping-pong");
anidir->setSelectedItemIndex(int(defAnidir));
}
void calculate_visible_layers(doc::Site& site,
const std::string& layersValue,
RestoreVisibleLayers& layersVisibility)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2016 David Capello
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,6 +8,7 @@
#define APP_UI_LAYER_FRAME_COMBOBOXES_H_INCLUDED
#pragma once
#include "doc/anidir.h"
#include "ui/listitem.h"
#include <string>
@ -53,6 +54,7 @@ namespace app {
void fill_layers_combobox(const doc::Sprite* sprite, ui::ComboBox* layers, const std::string& defLayer);
void fill_frames_combobox(const doc::Sprite* sprite, ui::ComboBox* frames, const std::string& defFrame);
void fill_anidir_combobox(ui::ComboBox* anidir, doc::AniDir defAnidir);
void calculate_visible_layers(doc::Site& site,
const std::string& layersValue,

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2016 David Capello
// Copyright (c) 2016-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -18,7 +18,7 @@ std::size_t SelectedFrames::size() const
{
std::size_t size = 0;
for (auto& range : m_ranges)
size += (range.toFrame - range.fromFrame + 1);
size += ABS(range.toFrame - range.fromFrame) + 1;
return size;
}
@ -27,6 +27,7 @@ void SelectedFrames::clear()
m_ranges.clear();
}
// TODO this works only for forward ranges
void SelectedFrames::insert(frame_t frame)
{
ASSERT(frame >= 0);
@ -76,19 +77,35 @@ void SelectedFrames::insert(frame_t fromFrame, frame_t toFrame)
}
}
void SelectedFrames::filter(frame_t fromFrame, frame_t toFrame)
SelectedFrames SelectedFrames::filter(frame_t fromFrame, frame_t toFrame) const
{
SelectedFrames f;
if (fromFrame > toFrame)
std::swap(fromFrame, toFrame);
// TODO improve this, avoid copying
SelectedFrames original = *this;
for (frame_t frame : original) {
if (frame >= fromFrame && frame <= toFrame)
insert(frame);
for (const auto& range : m_ranges) {
FrameRange r(range);
const bool isForward = (r.fromFrame <= r.toFrame);
if (isForward) {
if (r.fromFrame < fromFrame) r.fromFrame = fromFrame;
if (r.toFrame > toFrame) r.toFrame = toFrame;
}
else {
if (r.fromFrame > toFrame) r.fromFrame = toFrame;
if (r.toFrame < fromFrame) r.toFrame = fromFrame;
}
if (( isForward && r.fromFrame <= r.toFrame) ||
(!isForward && r.fromFrame >= r.toFrame))
f.m_ranges.push_back(r);
}
return f;
}
// TODO this works only for forward ranges
bool SelectedFrames::contains(frame_t frame) const
{
return std::binary_search(
@ -116,4 +133,40 @@ void SelectedFrames::displace(frame_t frameDelta)
}
}
SelectedFrames SelectedFrames::makeReverse() const
{
SelectedFrames newFrames;
for (const FrameRange& range : m_ranges)
newFrames.m_ranges.insert(
newFrames.m_ranges.begin(),
FrameRange(range.toFrame, range.fromFrame));
return newFrames;
}
SelectedFrames SelectedFrames::makePingPong() const
{
SelectedFrames newFrames = *this;
const int n = m_ranges.size();
int i = 0;
int j = m_ranges.size()-1;
for (const FrameRange& range : m_ranges) {
FrameRange reversedRange(range.toFrame,
range.fromFrame);
if (i == 0) reversedRange.toFrame++;
if (j == 0) reversedRange.fromFrame--;
if (reversedRange.fromFrame >= reversedRange.toFrame)
newFrames.m_ranges.insert(
newFrames.m_ranges.begin() + n,
reversedRange);
++i;
--j;
}
return newFrames;
}
} // namespace doc

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2016 David Capello
// Copyright (c) 2016-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -20,27 +20,38 @@ namespace doc {
public:
class const_iterator : public std::iterator<std::forward_iterator_tag, frame_t> {
static const int kNullFrame = -2;
public:
const_iterator(const Ranges::const_iterator& it)
: m_it(it), m_frame(-1) {
: m_it(it), m_frame(kNullFrame) {
}
const_iterator& operator++() {
if (m_frame < 0)
if (m_frame == kNullFrame)
m_frame = m_it->fromFrame;
if (m_frame < m_it->toFrame)
++m_frame;
if (m_it->fromFrame <= m_it->toFrame) {
if (m_frame < m_it->toFrame)
++m_frame;
else {
m_frame = kNullFrame;
++m_it;
}
}
else {
m_frame = -1;
++m_it;
if (m_frame > m_it->toFrame)
--m_frame;
else {
m_frame = kNullFrame;
++m_it;
}
}
return *this;
}
frame_t operator*() const {
if (m_frame < 0)
if (m_frame == kNullFrame)
m_frame = m_it->fromFrame;
return m_frame;
}
@ -124,7 +135,7 @@ namespace doc {
void clear();
void insert(frame_t frame);
void insert(frame_t fromFrame, frame_t toFrame);
void filter(frame_t fromFrame, frame_t toFrame);
SelectedFrames filter(frame_t fromFrame, frame_t toFrame) const;
bool contains(frame_t frame) const;
@ -134,6 +145,9 @@ namespace doc {
void displace(frame_t frameDelta);
Reversed reversed() const { return Reversed(*this); }
SelectedFrames makeReverse() const;
SelectedFrames makePingPong() const;
bool operator==(const SelectedFrames& o) const {
return m_ranges == o.m_ranges;
}

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2016 David Capello
// Copyright (c) 2016-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -17,6 +17,13 @@
using namespace doc;
static std::vector<frame_t> to_vector(const SelectedFrames& f)
{
std::vector<frame_t> v;
std::copy(f.begin(), f.end(), std::back_inserter(v));
return v;
}
TEST(SelectedFrames, BasicOneRange)
{
SelectedFrames f;
@ -28,9 +35,7 @@ TEST(SelectedFrames, BasicOneRange)
EXPECT_EQ(3, f.size());
EXPECT_EQ(1, f.ranges());
std::vector<frame_t> res;
std::copy(f.begin(), f.end(), std::back_inserter(res));
auto res = to_vector(f);
ASSERT_EQ(3, res.size());
EXPECT_EQ(1, res[0]);
EXPECT_EQ(2, res[1]);
@ -46,9 +51,7 @@ TEST(SelectedFrames, BasicThreeRanges)
EXPECT_EQ(3, f.size());
EXPECT_EQ(3, f.ranges());
std::vector<frame_t> res;
std::copy(f.begin(), f.end(), std::back_inserter(res));
auto res = to_vector(f);
ASSERT_EQ(3, res.size());
EXPECT_EQ(1, res[0]);
EXPECT_EQ(3, res[1]);
@ -66,9 +69,7 @@ TEST(SelectedFrames, InsertSelectedFrameInsideSelectedRange)
EXPECT_EQ(3, f.firstFrame());
EXPECT_EQ(8, f.lastFrame());
std::vector<frame_t> res;
std::copy(f.begin(), f.end(), std::back_inserter(res));
auto res = to_vector(f);
ASSERT_EQ(5, res.size());
EXPECT_EQ(3, res[0]);
EXPECT_EQ(5, res[1]);
@ -123,6 +124,93 @@ TEST(SelectedFrames, ReverseIterators)
EXPECT_EQ(res, res2);
}
TEST(SelectedFrames, MakeReverseSimple)
{
SelectedFrames f;
f.insert(4, 9);
EXPECT_EQ(6, f.size());
EXPECT_EQ(1, f.ranges());
f = f.makeReverse();
EXPECT_EQ(6, f.size());
EXPECT_EQ(1, f.ranges());
auto res = to_vector(f);
ASSERT_EQ(6, res.size());
EXPECT_EQ(9, res[0]);
EXPECT_EQ(8, res[1]);
EXPECT_EQ(7, res[2]);
EXPECT_EQ(6, res[3]);
EXPECT_EQ(5, res[4]);
EXPECT_EQ(4, res[5]);
}
TEST(SelectedFrames, MakeReverse)
{
SelectedFrames f;
f.insert(1);
f.insert(4, 5);
f.insert(7, 9);
EXPECT_EQ(6, f.size());
EXPECT_EQ(3, f.ranges());
f = f.makeReverse();
EXPECT_EQ(3, f.ranges());
auto res = to_vector(f);
ASSERT_EQ(6, res.size());
EXPECT_EQ(9, res[0]);
EXPECT_EQ(8, res[1]);
EXPECT_EQ(7, res[2]);
EXPECT_EQ(5, res[3]);
EXPECT_EQ(4, res[4]);
EXPECT_EQ(1, res[5]);
}
TEST(SelectedFrames, MakePingPongAndFilter)
{
SelectedFrames f;
f.insert(1);
f.insert(4, 5);
f.insert(7, 9);
EXPECT_EQ(6, f.size());
EXPECT_EQ(3, f.ranges());
f = f.makePingPong();
EXPECT_EQ(5, f.ranges());
auto res = to_vector(f);
ASSERT_EQ(10, res.size());
EXPECT_EQ(1, res[0]);
EXPECT_EQ(4, res[1]);
EXPECT_EQ(5, res[2]);
EXPECT_EQ(7, res[3]);
EXPECT_EQ(8, res[4]);
EXPECT_EQ(9, res[5]);
EXPECT_EQ(8, res[6]);
EXPECT_EQ(7, res[7]);
EXPECT_EQ(5, res[8]);
EXPECT_EQ(4, res[9]);
f = f.filter(5, 8);
EXPECT_EQ(4, f.ranges());
res = to_vector(f);
EXPECT_EQ(6, res.size());
EXPECT_EQ(5, res[0]);
EXPECT_EQ(7, res[1]);
EXPECT_EQ(8, res[2]);
EXPECT_EQ(8, res[3]);
EXPECT_EQ(7, res[4]);
EXPECT_EQ(5, res[5]);
f = f.filter(7, 7);
EXPECT_EQ(2, f.ranges());
res = to_vector(f);
EXPECT_EQ(2, res.size());
EXPECT_EQ(7, res[0]);
EXPECT_EQ(7, res[1]);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);