Add support to "play subtags & repetitions" when exporting an animation (fix #4173)

This commit is contained in:
Martín Capello 2023-12-07 17:31:49 -03:00
parent a6556bb4f2
commit eb45c4adf5
20 changed files with 604 additions and 194 deletions

View File

@ -31,7 +31,7 @@ FileOpROI CliOpenFile::roi() const
document->sprite()->bounds(),
slice,
tag,
selFrames,
FramesSequence(selFrames),
true);
}

View File

@ -126,7 +126,7 @@ void PreviewCliDelegate::saveFile(Context* ctx, const CliOpenFile& cof)
}
if (cof.hasFrameRange()) {
const auto& selFrames = cof.roi().selectedFrames();
const auto& selFrames = cof.roi().framesSequence();
if (!selFrames.empty()) {
if (selFrames.ranges() == 1)
std::cout << " - Frame range from "

View File

@ -100,11 +100,11 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
this->params().toFrame.isSet()) {
doc::frame_t fromFrame = this->params().fromFrame();
doc::frame_t toFrame = this->params().toFrame();
m_selFrames.insert(fromFrame, toFrame);
m_framesSeq.insert(fromFrame, toFrame);
m_adjustFramesByTag = true;
}
else {
m_selFrames.clear();
m_framesSeq.clear();
m_adjustFramesByTag = false;
}
}
@ -212,21 +212,6 @@ void SaveFileBaseCommand::saveDocumentInBackground(
}
#endif // ENABLE_UI
if (params().aniDir.isSet()) {
switch (params().aniDir()) {
case AniDir::REVERSE:
m_selFrames = m_selFrames.makeReverse();
break;
case AniDir::PING_PONG:
m_selFrames = m_selFrames.makePingPong();
break;
case AniDir::PING_PONG_REVERSE:
m_selFrames = m_selFrames.makeReverse();
m_selFrames = m_selFrames.makePingPong();
break;
}
}
gfx::Rect bounds;
if (params().bounds.isSet()) {
// Export the specific given bounds (e.g. the selection bounds)
@ -239,7 +224,7 @@ void SaveFileBaseCommand::saveDocumentInBackground(
FileOpROI roi(document, bounds,
params().slice(), params().tag(),
m_selFrames, m_adjustFramesByTag);
m_framesSeq, m_adjustFramesByTag);
std::unique_ptr<FileOp> fop(
FileOp::createSaveDocumentOperation(
@ -520,14 +505,14 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
// m_selFrames is not empty if fromFrame/toFrame parameters are
// specified.
if (m_selFrames.empty()) {
// Selected frames to export
SelectedFrames selFrames;
Tag* tag = calculate_selected_frames(
site, frames, selFrames);
if (m_framesSeq.empty()) {
// Frames sequence to export
FramesSequence framesSeq;
Tag* tag = calculate_frames_sequence(
site, frames, framesSeq, isPlaySubtags, aniDirValue);
if (tag)
params().tag(tag->name());
m_selFrames = selFrames;
m_framesSeq = framesSeq;
}
m_adjustFramesByTag = false;
}

View File

@ -12,7 +12,7 @@
#include "app/commands/command.h"
#include "app/commands/new_params.h"
#include "doc/anidir.h"
#include "doc/selected_frames.h"
#include "doc/frames_sequence.h"
#include "gfx/point.h"
#include "gfx/rect.h"
@ -63,7 +63,7 @@ namespace app {
const ResizeOnTheFly resizeOnTheFly = ResizeOnTheFly::Off,
const gfx::PointF& scale = gfx::PointF(1.0, 1.0));
doc::SelectedFrames m_selFrames;
doc::FramesSequence m_framesSeq;
bool m_adjustFramesByTag;
};

View File

@ -364,7 +364,7 @@ bool AseFormat::onSave(FileOp* fop)
// Write frames
int outputFrame = 0;
dio::AsepriteExternalFiles ext_files;
for (frame_t frame : fop->roi().selectedFrames()) {
for (frame_t frame : fop->roi().framesSequence()) {
// Prepare the frame header
dio::AsepriteFrameHeader frame_header;
ase_file_prepare_frame_header(f, &frame_header);
@ -1361,7 +1361,7 @@ static void ase_file_write_external_files_chunk(
layers.insert(layers.end(), childLayers.begin(), childLayers.end());
}
else if (layer->isImage()) {
for (frame_t frame : fop->roi().selectedFrames()) {
for (frame_t frame : fop->roi().framesSequence()) {
const Cel* cel = layer->cel(frame);
if (cel && !cel->link()) {
putExtentionIds(cel->data()->userData().propertiesMaps(), ext_files);

View File

@ -258,7 +258,7 @@ int save_document(Context* context, Doc* document)
FileOp::createSaveDocumentOperation(
context,
FileOpROI(document, document->sprite()->bounds(),
"", "", SelectedFrames(), false),
"", "", FramesSequence(), false),
document->filename(), "",
false));
if (!fop)
@ -307,13 +307,13 @@ FileOpROI::FileOpROI(const Doc* doc,
const gfx::Rect& bounds,
const std::string& sliceName,
const std::string& tagName,
const doc::SelectedFrames& selFrames,
const doc::FramesSequence& framesSeq,
const bool adjustByTag)
: m_document(doc)
, m_bounds(bounds)
, m_slice(nullptr)
, m_tag(nullptr)
, m_selFrames(selFrames)
, m_framesSeq(framesSeq)
{
if (doc) {
if (!sliceName.empty())
@ -324,18 +324,18 @@ FileOpROI::FileOpROI(const Doc* doc,
m_tag = doc->sprite()->tags().getByName(tagName);
if (m_tag) {
if (m_selFrames.empty())
m_selFrames.insert(m_tag->fromFrame(), m_tag->toFrame());
if (m_framesSeq.empty())
m_framesSeq.insert(m_tag->fromFrame(), m_tag->toFrame());
else if (adjustByTag)
m_selFrames.displace(m_tag->fromFrame());
m_framesSeq.displace(m_tag->fromFrame());
m_selFrames =
m_selFrames.filter(std::max(0, m_tag->fromFrame()),
m_framesSeq =
m_framesSeq.filter(std::max(0, m_tag->fromFrame()),
std::min(m_tag->toFrame(), doc->sprite()->lastFrame()));
}
// All frames if selected frames is empty
else if (m_selFrames.empty())
m_selFrames.insert(0, doc->sprite()->lastFrame());
else if (m_framesSeq.empty())
m_framesSeq.insert(0, doc->sprite()->lastFrame());
}
}
@ -360,7 +360,7 @@ gfx::Size FileOpROI::fileCanvasSize() const
{
if (m_slice) {
gfx::Size size;
for (auto frame : m_selFrames)
for (auto frame : m_framesSeq)
size |= frameBounds(frame).size();
return size;
}
@ -759,7 +759,7 @@ FileOp* FileOp::createSaveDocumentOperation(const Context* context,
frame_t outputFrame = 0;
for (frame_t frame : fop->m_roi.selectedFrames()) {
for (frame_t frame : fop->m_roi.framesSequence()) {
Tag* innerTag = (fop->m_roi.tag() ? fop->m_roi.tag(): sprite->tags().innerTag(frame));
Tag* outerTag = (fop->m_roi.tag() ? fop->m_roi.tag(): sprite->tags().outerTag(frame));
FilenameInfo fnInfo;
@ -1027,7 +1027,7 @@ void FileOp::operate(IFileOpProgress* progress)
render.setNewBlend(m_config.newBlend);
frame_t outputFrame = 0;
for (frame_t frame : m_roi.selectedFrames()) {
for (frame_t frame : m_roi.framesSequence()) {
gfx::Rect bounds = m_roi.frameBounds(frame);
if (bounds.isEmpty())
continue; // Skip frame because there is no slice key

View File

@ -18,7 +18,7 @@
#include "doc/frame.h"
#include "doc/image_ref.h"
#include "doc/pixel_format.h"
#include "doc/selected_frames.h"
#include "doc/frames_sequence.h"
#include "os/color_space.h"
#include <cstdio>
@ -75,18 +75,18 @@ namespace app {
const gfx::Rect& bounds,
const std::string& sliceName,
const std::string& tagName,
const doc::SelectedFrames& selFrames,
const doc::FramesSequence& frames,
const bool adjustByTag);
const Doc* document() const { return m_document; }
doc::Slice* slice() const { return m_slice; }
doc::Tag* tag() const { return m_tag; }
doc::frame_t fromFrame() const { return m_selFrames.firstFrame(); }
doc::frame_t toFrame() const { return m_selFrames.lastFrame(); }
const doc::SelectedFrames& selectedFrames() const { return m_selFrames; }
doc::frame_t fromFrame() const { return m_framesSeq.firstFrame(); }
doc::frame_t toFrame() const { return m_framesSeq.lastFrame(); }
const doc::FramesSequence& framesSequence() const { return m_framesSeq; }
doc::frame_t frames() const {
return (doc::frame_t)m_selFrames.size();
return (doc::frame_t)m_framesSeq.size();
}
// Returns an empty rectangle only when exporting a slice and the
@ -103,7 +103,7 @@ namespace app {
gfx::Rect m_bounds;
doc::Slice* m_slice;
doc::Tag* m_tag;
doc::SelectedFrames m_selFrames;
doc::FramesSequence m_framesSeq;
};
// Used by file formats with FILE_ENCODE_ABSTRACT_IMAGE flag, to

View File

@ -176,12 +176,12 @@ bool FliFormat::onLoad(FileOp* fop)
#ifdef ENABLE_SAVE
static int get_time_precision(const FileAbstractImage* sprite,
const doc::SelectedFrames& selFrames)
const doc::FramesSequence& framesSeq)
{
// Check if all frames have the same duration
bool constantFrameRate = true;
frame_t prevFrame = -1;
for (frame_t frame : selFrames) {
for (frame_t frame : framesSeq) {
if (prevFrame >= 0) {
if (sprite->frameDuration(prevFrame) != sprite->frameDuration(frame)) {
constantFrameRate = false;
@ -194,7 +194,7 @@ static int get_time_precision(const FileAbstractImage* sprite,
return sprite->frameDuration(0);
int precision = 1000;
for (frame_t frame : selFrames) {
for (frame_t frame : framesSeq) {
int len = sprite->frameDuration(frame);
while (len / precision == 0)
precision /= 10;
@ -218,7 +218,7 @@ bool FliFormat::onSave(FileOp* fop)
header.frames = 0;
header.width = sprite->width();
header.height = sprite->height();
header.speed = get_time_precision(sprite, fop->roi().selectedFrames());
header.speed = get_time_precision(sprite, fop->roi().framesSequence());
encoder.writeHeader(header);
// Create the bitmaps
@ -231,8 +231,8 @@ bool FliFormat::onSave(FileOp* fop)
fliFrame.pixels = bmp->getPixelAddress(0, 0);
fliFrame.rowstride = bmp->rowBytes();
auto frame_beg = fop->roi().selectedFrames().begin();
auto frame_end = fop->roi().selectedFrames().end();
auto frame_beg = fop->roi().framesSequence().begin();
auto frame_end = fop->roi().framesSequence().end();
auto frame_it = frame_beg;
frame_t nframes = fop->roi().frames();
for (frame_t f=0; f<=nframes; ++f, ++frame_it) {

View File

@ -1104,9 +1104,9 @@ public:
m_currentImage = m_images[1].get();
m_nextImage = m_images[2].get();
auto frame_beg = m_fop->roi().selectedFrames().begin();
auto frame_beg = m_fop->roi().framesSequence().begin();
#if _DEBUG
auto frame_end = m_fop->roi().selectedFrames().end();
auto frame_end = m_fop->roi().framesSequence().end();
#endif
auto frame_it = frame_beg;

View File

@ -319,7 +319,7 @@ bool WebPFormat::onSave(FileOp* fop)
WebPAnimEncoder* enc = WebPAnimEncoderNew(w, h, &enc_options);
int timestamp_ms = 0;
for (frame_t frame : fop->roi().selectedFrames()) {
for (frame_t frame : fop->roi().framesSequence()) {
// Render the frame in the bitmap
clear_image(image.get(), image->maskColor());
sprite->renderFrame(frame, fop->roi().frameBounds(frame), image.get());

View File

@ -11,16 +11,19 @@
#include "app/ui/layer_frame_comboboxes.h"
#include "app/doc.h"
#include "app/i18n/strings.h"
#include "app/restore_visible_layers.h"
#include "app/site.h"
#include "doc/anidir.h"
#include "doc/frames_sequence.h"
#include "doc/layer.h"
#include "doc/selected_frames.h"
#include "doc/selected_layers.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "doc/tag.h"
#include "doc/playback.h"
#include "ui/combobox.h"
namespace app {
@ -180,6 +183,96 @@ void calculate_visible_layers(const Site& site,
}
}
doc::Tag* calculate_frames_sequence(const Site& site,
const std::string& framesValue,
doc::FramesSequence& framesSeq,
bool playSubtags,
AniDir aniDir)
{
doc::Tag* tag = nullptr;
if (!playSubtags || framesValue == kSelectedFrames) {
SelectedFrames selFrames;
tag = calculate_selected_frames(site, framesValue, selFrames);
framesSeq = FramesSequence(selFrames);
}
else {
frame_t start = 0;
AniDir origAniDir = aniDir;
int forward = 1;
if (framesValue == kAllFrames) {
tag = nullptr;
forward = (aniDir == AniDir::FORWARD || aniDir == AniDir::PING_PONG ? 1 : -1);
if (forward < 0)
start = site.sprite()->lastFrame();
// Look for a tag containing the first frame and determine the start frame
// according to the selected animation direction.
auto startTag = site.sprite()->tags().innerTag(start);
if (startTag) {
int startTagForward = (startTag->aniDir() == AniDir::FORWARD ||
startTag->aniDir() == AniDir::PING_PONG
? 1 : -1);
start = forward * startTagForward > 0
? startTag->fromFrame()
: startTag->toFrame();
}
}
else {
tag = site.sprite()->tags().getByName(framesValue);
// User selected a specific tag, then set its anidir to the selected
// direction. We save the original direction to restore it later.
if (tag) {
origAniDir = tag->aniDir();
tag->setAniDir(aniDir);
start = (aniDir == AniDir::FORWARD || aniDir == AniDir::PING_PONG
? tag->fromFrame()
: tag->toFrame());
}
}
auto playback = doc::Playback(
site.document()->sprite(),
site.document()->sprite()->tags().getInternalList(),
start,
doc::Playback::PlayAll,
tag,
forward);
framesSeq.insert(playback.frame());
auto frame = playback.nextFrame();
while(!playback.isStopped()) {
framesSeq.insert(frame);
frame = playback.nextFrame();
}
if (framesValue == kAllFrames) {
// If the user is playing all frames and selected some of the ping-pong
// animation direction, then modify the animation as needed.
if (aniDir == AniDir::PING_PONG || aniDir == AniDir::PING_PONG_REVERSE)
framesSeq = framesSeq.makePingPong();
}
else if (tag) {
// If exported tag is ping-pong, remove last frame of the sequence to
// avoid playing the first frame twice.
if (aniDir == AniDir::PING_PONG || aniDir == AniDir::PING_PONG_REVERSE) {
doc::FramesSequence newSeq;
int i = 0;
int frames = framesSeq.size()-1;
for (auto f : framesSeq) {
if (i < frames)
newSeq.insert(f);
++i;
}
framesSeq = newSeq;
}
// Restore tag's original animation direction.
tag->setAniDir(origAniDir);
}
}
return tag;
}
doc::Tag* calculate_selected_frames(const Site& site,
const std::string& framesValue,
doc::SelectedFrames& selFrames)

View File

@ -17,6 +17,7 @@
namespace doc {
class Layer;
class SelectedFrames;
class FramesSequence;
class SelectedLayers;
class Slice;
class Sprite;
@ -75,6 +76,11 @@ namespace app {
const int layersIndex,
RestoreVisibleLayers& layersVisibility);
doc::Tag* calculate_frames_sequence(const Site& site,
const std::string& framesValue,
doc::FramesSequence& selFrames,
bool playSubtags,
doc::AniDir aniDir);
doc::Tag* calculate_selected_frames(const Site& site,
const std::string& framesValue,
doc::SelectedFrames& selFrames);

View File

@ -38,6 +38,7 @@ add_library(doc-lib
file/gpl_file.cpp
file/hex_file.cpp
file/pal_file.cpp
frames_sequence.cpp
grid.cpp
grid_io.cpp
image.cpp

View File

@ -62,7 +62,7 @@ namespace doc {
private:
Cel* m_cel;
const SelectedFrames& m_selFrames;
SelectedFrames::const_iterator m_frameIterator;
frames::const_iterator m_frameIterator;
Flags m_flags;
std::set<ObjectId> m_visited;
};

145
src/doc/frames_iterators.h Normal file
View File

@ -0,0 +1,145 @@
// Aseprite Document Library
// Copyright (c) 2024 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_FRAMES_ITERATORS_H_INCLUDED
#define DOC_FRAMES_ITERATORS_H_INCLUDED
#pragma once
#include "doc/frame_range.h"
#include <iosfwd>
#include <vector>
namespace doc {
namespace frames {
typedef std::vector<FrameRange> Ranges;
class const_iterator {
static const int kNullFrame = -2;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = frame_t;
using difference_type = std::ptrdiff_t;
using pointer = frame_t*;
using reference = frame_t&;
const_iterator(const frames::Ranges::const_iterator& it)
: m_it(it), m_frame(kNullFrame) {
}
const_iterator& operator++() {
if (m_frame == kNullFrame)
m_frame = m_it->fromFrame;
if (m_it->fromFrame <= m_it->toFrame) {
if (m_frame < m_it->toFrame)
++m_frame;
else {
m_frame = kNullFrame;
++m_it;
}
}
else {
if (m_frame > m_it->toFrame)
--m_frame;
else {
m_frame = kNullFrame;
++m_it;
}
}
return *this;
}
frame_t operator*() const {
if (m_frame == kNullFrame)
m_frame = m_it->fromFrame;
return m_frame;
}
bool operator==(const const_iterator& o) const {
return (m_it == o.m_it && m_frame == o.m_frame);
}
bool operator!=(const const_iterator& it) const {
return !operator==(it);
}
private:
mutable frames::Ranges::const_iterator m_it;
mutable frame_t m_frame;
};
class const_reverse_iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = frame_t;
using difference_type = std::ptrdiff_t;
using pointer = frame_t*;
using reference = frame_t&;
const_reverse_iterator(const frames::Ranges::const_reverse_iterator& it)
: m_it(it), m_frame(-1) {
}
const_reverse_iterator& operator++() {
if (m_frame < 0)
m_frame = m_it->toFrame;
int step = (m_it->fromFrame > m_it->toFrame) -
(m_it->fromFrame < m_it->toFrame);
if ((step < 0 && m_frame > m_it->fromFrame) ||
(step > 0 && m_frame < m_it->fromFrame))
m_frame += step;
else {
m_frame = -1;
++m_it;
}
return *this;
}
frame_t operator*() const {
if (m_frame < 0)
m_frame = m_it->toFrame;
return m_frame;
}
bool operator==(const const_reverse_iterator& o) const {
return (m_it == o.m_it && m_frame == o.m_frame);
}
bool operator!=(const const_reverse_iterator& it) const {
return !operator==(it);
}
private:
mutable frames::Ranges::const_reverse_iterator m_it;
mutable frame_t m_frame;
};
template<typename T>
class Reversed {
public:
typedef const_reverse_iterator const_iterator;
const_iterator begin() const { return m_frames.rbegin(); }
const_iterator end() const { return m_frames.rend(); }
Reversed(const T& frames)
: m_frames(frames) {
}
private:
const T& m_frames;
};
} // namespace frames
} // namespace doc
#endif // DOC_FRAMES_ITERATORS_H_INCLUDED

207
src/doc/frames_sequence.cpp Normal file
View File

@ -0,0 +1,207 @@
// Aseprite Document Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "doc/frames_sequence.h"
#include "base/base.h"
#include "base/debug.h"
#include "base/serialization.h"
#include <algorithm>
#include <iostream>
namespace doc {
using namespace base::serialization;
using namespace base::serialization::little_endian;
FramesSequence::FramesSequence(const SelectedFrames& selectedFrames)
{
// TODO: This might be optimized by copying each range directly, but
// for this I would need that SelectedFrames make this class its friend.
for(auto frame : selectedFrames) {
this->insert(frame);
}
}
std::size_t FramesSequence::size() const
{
std::size_t size = 0;
for (auto& range : m_ranges)
size += ABS(range.toFrame - range.fromFrame) + 1;
return size;
}
void FramesSequence::clear()
{
m_ranges.clear();
}
void FramesSequence::insert(frame_t frame)
{
ASSERT(frame >= 0);
if (m_ranges.empty()) {
m_ranges.push_back(FrameRange(frame));
return;
}
auto& lastRange = m_ranges.back();
int rangeStep = (lastRange.fromFrame < lastRange.toFrame) -
(lastRange.fromFrame > lastRange.toFrame);
int step = (lastRange.toFrame < frame) - (lastRange.toFrame > frame);
if (step &&
(rangeStep == step || !rangeStep) &&
frame-lastRange.toFrame == step)
lastRange.toFrame = frame;
else
m_ranges.push_back(FrameRange(frame));
}
void FramesSequence::insert(frame_t fromFrame, frame_t toFrame)
{
int step = (fromFrame <= toFrame ? 1 : -1);
for (frame_t frame = fromFrame; frame != toFrame; frame+=step) {
insert(frame);
}
insert(toFrame);
}
FramesSequence FramesSequence::filter(frame_t fromFrame, frame_t toFrame) const
{
FramesSequence f;
if (fromFrame > toFrame)
std::swap(fromFrame, toFrame);
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;
}
bool FramesSequence::contains(frame_t frame) const
{
return std::find_if(
m_ranges.begin(),
m_ranges.end(),
[frame](const FrameRange& r) -> bool {
return (r.fromFrame <= frame && frame <= r.toFrame) ||
(r.fromFrame >= frame && frame >= r.toFrame);
}) != m_ranges.end();
}
frame_t FramesSequence::lowestFrame() const
{
frame_t lof = this->firstFrame();
for (auto range : m_ranges) {
lof = std::min<frame_t>(range.fromFrame, std::min<frame_t>(range.toFrame, lof));
}
return lof;
}
void FramesSequence::displace(frame_t frameDelta)
{
// Avoid setting negative numbers in frame ranges
auto lof = lowestFrame();
if (lof+frameDelta < 0)
frameDelta = -lof;
for (auto& range : m_ranges) {
range.fromFrame += frameDelta;
range.toFrame += frameDelta;
ASSERT(range.fromFrame >= 0);
ASSERT(range.toFrame >= 0);
}
}
FramesSequence FramesSequence::makeReverse() const
{
FramesSequence newFrames;
for (const FrameRange& range : m_ranges)
newFrames.m_ranges.insert(
newFrames.m_ranges.begin(),
FrameRange(range.toFrame, range.fromFrame));
return newFrames;
}
FramesSequence FramesSequence::makePingPong() const
{
FramesSequence newFrames = *this;
const int n = m_ranges.size();
int i = 0;
int j = m_ranges.size()-1;
for (const FrameRange& range : m_ranges) {
// Discard first or last range if it contains just one frame.
if (range.toFrame == range.fromFrame && (i == 0 || j == 0)) {
++i;
--j;
continue;
}
FrameRange reversedRange(range.toFrame,
range.fromFrame);
int step = (range.fromFrame < range.toFrame) -
(range.fromFrame > range.toFrame);
if (i == 0) reversedRange.toFrame+=step;
if (j == 0) reversedRange.fromFrame-=step;
if (reversedRange.fromFrame != reversedRange.toFrame)
newFrames.m_ranges.insert(
newFrames.m_ranges.begin() + n,
reversedRange);
++i;
--j;
}
return newFrames;
}
bool FramesSequence::write(std::ostream& os) const
{
write32(os, size());
for (const frame_t frame : *this) {
write32(os, frame);
}
return os.good();
}
bool FramesSequence::read(std::istream& is)
{
clear();
int nframes = read32(is);
for (int i=0; i<nframes && is; ++i) {
frame_t frame = read32(is);
insert(frame);
}
return is.good();
}
} // namespace doc

69
src/doc/frames_sequence.h Normal file
View File

@ -0,0 +1,69 @@
// Aseprite Document Library
// Copyright (c) 2023 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
#ifndef DOC_FRAMES_SEQUENCE_H_INCLUDED
#define DOC_FRAMES_SEQUENCE_H_INCLUDED
#pragma once
#include "doc/selected_frames.h"
using namespace doc::frames;
namespace doc {
// This class is based in several code of the SelectedFrames class.
// TODO: At some point we should remove the duplicated code between these
// classes.
class FramesSequence {
public:
FramesSequence() {}
FramesSequence(const SelectedFrames& selectedFrames);
const_iterator begin() const { return const_iterator(m_ranges.begin()); }
const_iterator end() const { return const_iterator(m_ranges.end()); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(m_ranges.rbegin()); }
const_reverse_iterator rend() const { return const_reverse_iterator(m_ranges.rend()); }
std::size_t size() const;
std::size_t ranges() const { return m_ranges.size(); }
bool empty() const { return m_ranges.empty(); }
void clear();
void insert(frame_t frame);
void insert(frame_t fromFrame, frame_t toFrame);
FramesSequence filter(frame_t fromFrame, frame_t toFrame) const;
bool contains(frame_t frame) const;
frame_t firstFrame() const { return (!m_ranges.empty() ? m_ranges.front().fromFrame: -1); }
frame_t lastFrame() const { return (!m_ranges.empty() ? m_ranges.back().toFrame: -1); }
frame_t lowestFrame() const;
void displace(frame_t frameDelta);
Reversed<FramesSequence> reversed() const { return Reversed(*this); }
FramesSequence makeReverse() const;
FramesSequence makePingPong() const;
bool operator==(const FramesSequence& o) const {
return m_ranges == o.m_ranges;
}
bool operator!=(const FramesSequence& o) const {
return !operator==(o);
}
bool write(std::ostream& os) const;
bool read(std::istream& is);
private:
Ranges m_ranges;
};
} // namespace doc
#endif // DOC_FRAMES_SEQUENCE_H_INCLUDED

View File

@ -59,12 +59,14 @@ Playback::Playback(const Sprite* sprite,
const TagsList& tags,
const frame_t frame,
const Mode playMode,
const Tag* tag)
const Tag* tag,
const int forward)
: m_sprite(sprite)
, m_tags(tags)
, m_initialFrame(frame)
, m_frame(frame)
, m_playMode(playMode)
, m_forward(forward)
{
PLAY_TRACE("--Playback-- tag=", (tag ? tag->name(): ""), "mode=", mode_to_string(m_playMode));
@ -86,7 +88,7 @@ Playback::Playback(const Sprite* sprite,
if (tag) {
addTag(tag, false, 1);
// Loop the given tag in the constructor infite times
// Loop the given tag in the constructor infinite times
m_playing.back()->repeat = std::numeric_limits<int>::max();
}
}
@ -248,11 +250,19 @@ bool Playback::handleExitFrame(const frame_t frameDelta)
break;
}
if (frameDelta > 0 && m_frame == m_sprite->lastFrame()) {
if (frameDelta > 0 &&
((m_frame == m_sprite->lastFrame() && m_forward > 0) ||
(m_frame == 0 && m_forward < 0))) {
if (m_playMode == PlayInLoop) {
PLAY_TRACE(" Going back to frame=0 (PlayInLoop)", m_frame,
m_sprite->lastFrame());
m_frame = 0;
if (m_forward > 0) {
PLAY_TRACE(" Going back to frame=0 (PlayInLoop)", m_frame,
m_sprite->lastFrame());
m_frame = 0;
}
else {
PLAY_TRACE(" Going back to frame=last frame (PlayInLoop)");
m_frame = m_sprite->lastFrame();
}
return false;
}
else {
@ -261,10 +271,18 @@ bool Playback::handleExitFrame(const frame_t frameDelta)
return false;
}
}
else if (frameDelta < 0 && m_frame == 0) {
else if (frameDelta < 0 &&
((m_frame == 0 && m_forward > 0) ||
(m_frame == m_sprite->lastFrame() && m_forward < 0))) {
if (m_playMode == PlayInLoop) {
PLAY_TRACE(" Going back to frame=last frame (PlayInLoop)");
m_frame = m_sprite->lastFrame();
if (m_forward > 0) {
PLAY_TRACE(" Going back to frame=last frame (PlayInLoop)");
m_frame = m_sprite->lastFrame();
}
else {
PLAY_TRACE(" Going back to frame=0 (PlayInLoop)");
m_frame = 0;
}
return false;
}
else {
@ -433,7 +451,7 @@ bool Playback::decrementRepeat(const frame_t frameDelta)
m_playing.pop_back();
// Forward direction of the parent tag
int forward = (m_playing.empty() ? +1: m_playing.back()->forward);
int forward = (m_playing.empty() ? m_forward: m_playing.back()->forward);
bool rewind = (m_playing.empty() ? false: m_playing.back()->rewind);
// New frame outside the tag
@ -522,7 +540,7 @@ void Playback::goToFirstTagFrame(const Tag* tag)
int Playback::getParentForward() const
{
if (m_playing.empty())
return 1;
return m_forward;
else
return m_playing.back()->forward;
}

View File

@ -50,7 +50,8 @@ namespace doc {
const TagsList& tagsList,
const frame_t frame,
const Mode playMode,
const Tag* tag);
const Tag* tag,
const int forward = 1);
Playback(const Sprite* sprite = nullptr,
const frame_t frame = 0,
@ -128,6 +129,7 @@ namespace doc {
frame_t m_initialFrame;
frame_t m_frame;
Mode m_playMode;
int m_forward;
// Queue of tags to play and tags that are being played
std::vector<std::unique_ptr<PlayTag>> m_playing;

View File

@ -9,133 +9,17 @@
#define DOC_SELECTED_FRAMES_H_INCLUDED
#pragma once
#include "doc/frame_range.h"
#include "doc/frames_iterators.h"
#include <iosfwd>
#include <vector>
using namespace doc::frames;
namespace doc {
// The FramesSequence class is based in several code of this class.
// TODO: At some point we should remove the duplicated code between these
// classes.
class SelectedFrames {
typedef std::vector<FrameRange> Ranges;
public:
class const_iterator {
static const int kNullFrame = -2;
public:
using iterator_category = std::forward_iterator_tag;
using value_type = frame_t;
using difference_type = std::ptrdiff_t;
using pointer = frame_t*;
using reference = frame_t&;
const_iterator(const Ranges::const_iterator& it)
: m_it(it), m_frame(kNullFrame) {
}
const_iterator& operator++() {
if (m_frame == kNullFrame)
m_frame = m_it->fromFrame;
if (m_it->fromFrame <= m_it->toFrame) {
if (m_frame < m_it->toFrame)
++m_frame;
else {
m_frame = kNullFrame;
++m_it;
}
}
else {
if (m_frame > m_it->toFrame)
--m_frame;
else {
m_frame = kNullFrame;
++m_it;
}
}
return *this;
}
frame_t operator*() const {
if (m_frame == kNullFrame)
m_frame = m_it->fromFrame;
return m_frame;
}
bool operator==(const const_iterator& o) const {
return (m_it == o.m_it && m_frame == o.m_frame);
}
bool operator!=(const const_iterator& it) const {
return !operator==(it);
}
private:
mutable Ranges::const_iterator m_it;
mutable frame_t m_frame;
};
class const_reverse_iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = frame_t;
using difference_type = std::ptrdiff_t;
using pointer = frame_t*;
using reference = frame_t&;
const_reverse_iterator(const Ranges::const_reverse_iterator& it)
: m_it(it), m_frame(-1) {
}
const_reverse_iterator& operator++() {
if (m_frame < 0)
m_frame = m_it->toFrame;
if (m_frame > m_it->fromFrame)
--m_frame;
else {
m_frame = -1;
++m_it;
}
return *this;
}
frame_t operator*() const {
if (m_frame < 0)
m_frame = m_it->toFrame;
return m_frame;
}
bool operator==(const const_reverse_iterator& o) const {
return (m_it == o.m_it && m_frame == o.m_frame);
}
bool operator!=(const const_reverse_iterator& it) const {
return !operator==(it);
}
private:
mutable Ranges::const_reverse_iterator m_it;
mutable frame_t m_frame;
};
class Reversed {
public:
typedef const_reverse_iterator const_iterator;
const_iterator begin() const { return m_selectedFrames.rbegin(); }
const_iterator end() const { return m_selectedFrames.rend(); }
Reversed(const SelectedFrames& selectedFrames)
: m_selectedFrames(selectedFrames) {
}
private:
const SelectedFrames& m_selectedFrames;
};
const_iterator begin() const { return const_iterator(m_ranges.begin()); }
const_iterator end() const { return const_iterator(m_ranges.end()); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(m_ranges.rbegin()); }
@ -156,7 +40,7 @@ namespace doc {
frame_t lastFrame() const { return (!m_ranges.empty() ? m_ranges.back().toFrame: -1); }
void displace(frame_t frameDelta);
Reversed reversed() const { return Reversed(*this); }
Reversed<SelectedFrames> reversed() const { return Reversed(*this); }
SelectedFrames makeReverse() const;
SelectedFrames makePingPong() const;