mirror of https://github.com/aseprite/aseprite.git
208 lines
4.8 KiB
C++
208 lines
4.8 KiB
C++
// 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
|