/* * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved. * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #ifndef LOTModel_H #define LOTModel_H #include #include #include #include #include #include #include #include "vector_varenaalloc.h" #include "vector_vbezier.h" #include "vector_vbrush.h" #include "vector_vinterpolator.h" #include "vector_vmatrix.h" #include "vector_vpath.h" #include "vector_vpoint.h" #include "vector_vrect.h" V_USE_NAMESPACE namespace rlottie { namespace internal { using Marker = std::tuple; using LayerInfo = Marker; template inline T lerp(const T &start, const T &end, float t) { return start + t * (end - start); } namespace model { enum class MatteType : uchar { None = 0, Alpha = 1, AlphaInv, Luma, LumaInv }; enum class BlendMode : uchar { Normal = 0, Multiply = 1, Screen = 2, OverLay = 3 }; class Color { public: Color() = default; Color(float red, float green, float blue) : r(red), g(green), b(blue) {} VColor toColor(float a = 1) { return VColor(uchar(255 * r), uchar(255 * g), uchar(255 * b), uchar(255 * a)); } friend inline Color operator+(const Color &c1, const Color &c2); friend inline Color operator-(const Color &c1, const Color &c2); public: float r{1}; float g{1}; float b{1}; }; inline Color operator-(const Color &c1, const Color &c2) { return Color(c1.r - c2.r, c1.g - c2.g, c1.b - c2.b); } inline Color operator+(const Color &c1, const Color &c2) { return Color(c1.r + c2.r, c1.g + c2.g, c1.b + c2.b); } inline const Color operator*(const Color &c, float m) { return Color(c.r * m, c.g * m, c.b * m); } inline const Color operator*(float m, const Color &c) { return Color(c.r * m, c.g * m, c.b * m); } struct PathData { std::vector mPoints; bool mClosed = false; /* "c" */ void reserve(size_t size) { mPoints.reserve(mPoints.size() + size); } static void lerp(const PathData &start, const PathData &end, float t, VPath &result) { result.reset(); // test for empty animation data. if (start.mPoints.empty() || end.mPoints.empty()) { return; } auto size = std::min(start.mPoints.size(), end.mPoints.size()); /* reserve exact memory requirement at once * ptSize = size + 1(size + close) * elmSize = size/3 cubic + 1 move + 1 close */ result.reserve(size + 1, size / 3 + 2); result.moveTo(start.mPoints[0] + t * (end.mPoints[0] - start.mPoints[0])); for (size_t i = 1; i < size; i += 3) { result.cubicTo( start.mPoints[i] + t * (end.mPoints[i] - start.mPoints[i]), start.mPoints[i + 1] + t * (end.mPoints[i + 1] - start.mPoints[i + 1]), start.mPoints[i + 2] + t * (end.mPoints[i + 2] - start.mPoints[i + 2])); } if (start.mClosed) result.close(); } void toPath(VPath &path) const { path.reset(); if (mPoints.empty()) return; auto size = mPoints.size(); auto points = mPoints.data(); /* reserve exact memory requirement at once * ptSize = size + 1(size + close) * elmSize = size/3 cubic + 1 move + 1 close */ path.reserve(size + 1, size / 3 + 2); path.moveTo(points[0]); for (size_t i = 1; i < size; i += 3) { path.cubicTo(points[i], points[i + 1], points[i + 2]); } if (mClosed) path.close(); } }; template struct Value { T start_; T end_; T at(float t) const { return lerp(start_, end_, t); } float angle(float) const { return 0; } void cache() {} }; struct Position; template struct Value { T start_; T end_; T inTangent_; T outTangent_; float length_{0}; bool hasTangent_{false}; void cache() { if (hasTangent_) { inTangent_ = end_ + inTangent_; outTangent_ = start_ + outTangent_; length_ = VBezier::fromPoints(start_, outTangent_, inTangent_, end_) .length(); if (vIsZero(length_)) { // this segment has zero length. // so disable expensive path computaion. hasTangent_ = false; } } } T at(float t) const { if (hasTangent_) { /* * position along the path calcualated * using bezier at progress length (t * bezlen) */ VBezier b = VBezier::fromPoints(start_, outTangent_, inTangent_, end_); return b.pointAt(b.tAtLength(t * length_, length_)); } return lerp(start_, end_, t); } float angle(float t) const { if (hasTangent_) { VBezier b = VBezier::fromPoints(start_, outTangent_, inTangent_, end_); return b.angleAt(b.tAtLength(t * length_, length_)); } return 0; } }; template class KeyFrames { public: struct Frame { float progress(int frameNo) const { return interpolator_ ? interpolator_->value((frameNo - start_) / (end_ - start_)) : 0; } T value(int frameNo) const { return value_.at(progress(frameNo)); } float angle(int frameNo) const { return value_.angle(progress(frameNo)); } float start_{0}; float end_{0}; VInterpolator *interpolator_{nullptr}; Value value_; }; T value(int frameNo) const { if (frames_.front().start_ >= frameNo) return frames_.front().value_.start_; if (frames_.back().end_ <= frameNo) return frames_.back().value_.end_; for (const auto &keyFrame : frames_) { if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) return keyFrame.value(frameNo); } return {}; } float angle(int frameNo) const { if ((frames_.front().start_ >= frameNo) || (frames_.back().end_ <= frameNo)) return 0; for (const auto &frame : frames_) { if (frameNo >= frame.start_ && frameNo < frame.end_) return frame.angle(frameNo); } return 0; } bool changed(int prevFrame, int curFrame) const { auto first = frames_.front().start_; auto last = frames_.back().end_; return !((first > prevFrame && first > curFrame) || (last < prevFrame && last < curFrame)); } void cache() { for (auto &e : frames_) e.value_.cache(); } public: std::vector frames_; }; template class Property { public: using Animation = KeyFrames; Property() { construct(impl_.value_, {}); } explicit Property(T value) { construct(impl_.value_, std::move(value)); } const Animation &animation() const { return *(impl_.animation_.get()); } const T & value() const { return impl_.value_; } Animation &animation() { if (isValue_) { destroy(); construct(impl_.animation_, std::make_unique()); isValue_ = false; } return *(impl_.animation_.get()); } T &value() { assert(isValue_); return impl_.value_; } Property(Property &&other) noexcept { if (!other.isValue_) { construct(impl_.animation_, std::move(other.impl_.animation_)); isValue_ = false; } else { construct(impl_.value_, std::move(other.impl_.value_)); isValue_ = true; } } // delete special member functions Property(const Property &) = delete; Property &operator=(const Property &) = delete; Property &operator=(Property &&) = delete; ~Property() { destroy(); } bool isStatic() const { return isValue_; } T value(int frameNo) const { return isStatic() ? value() : animation().value(frameNo); } // special function only for type T=PathData template auto value(int frameNo, VPath &path) const -> typename std::enable_if_t::value, void> { if (isStatic()) { value().toPath(path); } else { const auto &vec = animation().frames_; if (vec.front().start_ >= frameNo) return vec.front().value_.start_.toPath(path); if (vec.back().end_ <= frameNo) return vec.back().value_.end_.toPath(path); for (const auto &keyFrame : vec) { if (frameNo >= keyFrame.start_ && frameNo < keyFrame.end_) { T::lerp(keyFrame.value_.start_, keyFrame.value_.end_, keyFrame.progress(frameNo), path); } } } } float angle(int frameNo) const { return isStatic() ? 0 : animation().angle(frameNo); } bool changed(int prevFrame, int curFrame) const { return isStatic() ? false : animation().changed(prevFrame, curFrame); } void cache() { if (!isStatic()) animation().cache(); } private: template void construct(Tp &member, Tp &&val) { new (&member) Tp(std::move(val)); } void destroy() { if (isValue_) { impl_.value_.~T(); } else { using std::unique_ptr; impl_.animation_.~unique_ptr(); } } union details { std::unique_ptr animation_; T value_; details(){}; details(const details &) = delete; details(details &&) = delete; details &operator=(details &&) = delete; details &operator=(const details &) = delete; ~details() noexcept {}; } impl_; bool isValue_{true}; }; class Path; struct PathData; struct Dash { std::vector> mData; bool empty() const { return mData.empty(); } size_t size() const { return mData.size(); } bool isStatic() const { for (const auto &elm : mData) if (!elm.isStatic()) return false; return true; } void getDashInfo(int frameNo, std::vector &result) const; }; class Mask { public: enum class Mode { None, Add, Substarct, Intersect, Difference }; float opacity(int frameNo) const { return mOpacity.value(frameNo) / 100.0f; } bool isStatic() const { return mIsStatic; } public: Property mShape; Property mOpacity{100}; bool mInv{false}; bool mIsStatic{true}; Mask::Mode mMode; }; class Object { public: enum class Type : unsigned char { Composition = 1, Layer, Group, Transform, Fill, Stroke, GFill, GStroke, Rect, Ellipse, Path, Polystar, Trim, Repeater, RoundedCorner }; explicit Object(Object::Type type) : mPtr(nullptr) { mData._type = type; mData._static = true; mData._shortString = true; mData._hidden = false; } ~Object() noexcept { if (!shortString() && mPtr) free(mPtr); } Object(const Object &) = delete; Object &operator=(const Object &) = delete; void setStatic(bool value) { mData._static = value; } bool isStatic() const { return mData._static; } bool hidden() const { return mData._hidden; } void setHidden(bool value) { mData._hidden = value; } void setType(Object::Type type) { mData._type = type; } Object::Type type() const { return mData._type; } void setName(const char *name) { if (name) { auto len = strlen(name); if (len < maxShortStringLength) { setShortString(true); strncpy(mData._buffer, name, len + 1); } else { setShortString(false); mPtr = strdup(name); } } } const char *name() const { return shortString() ? mData._buffer : mPtr; } private: static constexpr unsigned char maxShortStringLength = 14; void setShortString(bool value) { mData._shortString = value; } bool shortString() const { return mData._shortString; } struct Data { char _buffer[maxShortStringLength]; Object::Type _type; bool _static : 1; bool _hidden : 1; bool _shortString : 1; }; union { Data mData; char *mPtr{nullptr}; }; }; struct Asset { enum class Type : unsigned char { Precomp, Image, Char }; bool isStatic() const { return mStatic; } void setStatic(bool value) { mStatic = value; } VBitmap bitmap() const { return mBitmap; } void loadImageData(std::string data); void loadImagePath(std::string Path); Type mAssetType{Type::Precomp}; bool mStatic{true}; std::string mRefId; // ref id std::vector mLayers; // image asset data int mWidth{0}; int mHeight{0}; VBitmap mBitmap; }; class Layer; class Composition : public Object { public: Composition() : Object(Object::Type::Composition) {} std::vector layerInfoList() const; const std::vector &markers() const { return mMarkers; } double duration() const { return frameDuration() / frameRate(); // in second } size_t frameAtPos(double pos) const { if (pos < 0) pos = 0; if (pos > 1) pos = 1; return size_t(round(pos * frameDuration())); } long frameAtTime(double timeInSec) const { return long(frameAtPos(timeInSec / duration())); } size_t totalFrame() const { return mEndFrame - mStartFrame; } long frameDuration() const { return mEndFrame - mStartFrame - 1; } float frameRate() const { return mFrameRate; } size_t startFrame() const { return mStartFrame; } size_t endFrame() const { return mEndFrame; } VSize size() const { return mSize; } void processRepeaterObjects(); void updateStats(); public: struct Stats { uint16_t precompLayerCount{0}; uint16_t solidLayerCount{0}; uint16_t shapeLayerCount{0}; uint16_t imageLayerCount{0}; uint16_t nullLayerCount{0}; }; public: std::string mVersion; VSize mSize; long mStartFrame{0}; long mEndFrame{0}; float mFrameRate{60}; BlendMode mBlendMode{BlendMode::Normal}; Layer * mRootLayer{nullptr}; std::unordered_map mAssets; std::vector mMarkers; VArenaAlloc mArenaAlloc{2048}; Stats mStats; }; class Transform : public Object { public: struct Data { struct Extra { Property m3DRx{0}; Property m3DRy{0}; Property m3DRz{0}; Property mSeparateX{0}; Property mSeparateY{0}; bool mSeparate{false}; bool m3DData{false}; }; VMatrix matrix(int frameNo, bool autoOrient = false) const; float opacity(int frameNo) const { return mOpacity.value(frameNo) / 100.0f; } void createExtraData() { if (!mExtra) mExtra = std::make_unique(); } Property mRotation{0}; /* "r" */ Property mScale{{100, 100}}; /* "s" */ Property mPosition; /* "p" */ Property mAnchor; /* "a" */ Property mOpacity{100}; /* "o" */ std::unique_ptr mExtra; }; Transform() : Object(Object::Type::Transform) {} void set(Transform::Data *data, bool staticFlag) { setStatic(staticFlag); if (isStatic()) { new (&impl.mStaticData) StaticData(data->matrix(0), data->opacity(0)); } else { impl.mData = data; } } VMatrix matrix(int frameNo, bool autoOrient = false) const { if (isStatic()) return impl.mStaticData.mMatrix; return impl.mData->matrix(frameNo, autoOrient); } float opacity(int frameNo) const { if (isStatic()) return impl.mStaticData.mOpacity; return impl.mData->opacity(frameNo); } Transform(const Transform &) = delete; Transform(Transform &&) = delete; Transform &operator=(Transform &) = delete; Transform &operator=(Transform &&) = delete; ~Transform() noexcept { destroy(); } private: void destroy() { if (isStatic()) { impl.mStaticData.~StaticData(); } } struct StaticData { StaticData(VMatrix &&m, float opacity) : mOpacity(opacity), mMatrix(std::move(m)) { } float mOpacity; VMatrix mMatrix; }; union details { Data * mData{nullptr}; StaticData mStaticData; details(){}; details(const details &) = delete; details(details &&) = delete; details &operator=(details &&) = delete; details &operator=(const details &) = delete; ~details() noexcept {}; } impl; }; class Group : public Object { public: Group() : Object(Object::Type::Group) {} explicit Group(Object::Type type) : Object(type) {} public: std::vector mChildren; Transform * mTransform{nullptr}; }; class Layer : public Group { public: enum class Type : uchar { Precomp = 0, Solid = 1, Image = 2, Null = 3, Shape = 4, Text = 5 }; Layer() : Group(Object::Type::Layer) {} bool hasRoundedCorner() const noexcept { return mHasRoundedCorner; } bool hasPathOperator() const noexcept { return mHasPathOperator; } bool hasGradient() const noexcept { return mHasGradient; } bool hasMask() const noexcept { return mHasMask; } bool hasRepeater() const noexcept { return mHasRepeater; } int id() const noexcept { return mId; } int parentId() const noexcept { return mParentId; } bool hasParent() const noexcept { return mParentId != -1; } int inFrame() const noexcept { return mInFrame; } int outFrame() const noexcept { return mOutFrame; } int startFrame() const noexcept { return mStartFrame; } Color solidColor() const noexcept { return mExtra ? mExtra->mSolidColor : Color(); } bool autoOrient() const noexcept { return mAutoOrient; } int timeRemap(int frameNo) const; VSize layerSize() const { return mLayerSize; } bool precompLayer() const { return mLayerType == Type::Precomp; } VMatrix matrix(int frameNo) const { return mTransform ? mTransform->matrix(frameNo, autoOrient()) : VMatrix{}; } float opacity(int frameNo) const { return mTransform ? mTransform->opacity(frameNo) : 1.0f; } Asset *asset() const { return mExtra ? mExtra->mAsset : nullptr; } struct Extra { Color mSolidColor; std::string mPreCompRefId; Property mTimeRemap; /* "tm" */ Composition * mCompRef{nullptr}; Asset * mAsset{nullptr}; std::vector mMasks; }; Layer::Extra *extra() { if (!mExtra) mExtra = std::make_unique(); return mExtra.get(); } public: MatteType mMatteType{MatteType::None}; Type mLayerType{Layer::Type::Null}; BlendMode mBlendMode{BlendMode::Normal}; bool mHasRoundedCorner{false}; bool mHasPathOperator{false}; bool mHasMask{false}; bool mHasRepeater{false}; bool mHasGradient{false}; bool mAutoOrient{false}; VSize mLayerSize; int mParentId{-1}; // Lottie the id of the parent in the composition int mId{-1}; // Lottie the group id used for parenting. float mTimeStreatch{1.0f}; int mInFrame{0}; int mOutFrame{0}; int mStartFrame{0}; std::unique_ptr mExtra{nullptr}; }; /** * TimeRemap has the value in time domain(in sec) * To get the proper mapping first we get the mapped time at the current frame * Number then we need to convert mapped time to frame number using the * composition time line Ex: at frame 10 the mappend time is 0.5(500 ms) which * will be convert to frame number 30 if the frame rate is 60. or will result to * frame number 15 if the frame rate is 30. */ inline int Layer::timeRemap(int frameNo) const { /* * only consider startFrame() when there is no timeRemap. * when a layer has timeremap bodymovin updates the startFrame() * of all child layer so we don't have to take care of it. */ if (!mExtra || mExtra->mTimeRemap.isStatic()) frameNo = frameNo - startFrame(); else frameNo = mExtra->mCompRef->frameAtTime(mExtra->mTimeRemap.value(frameNo)); /* Apply time streatch if it has any. * Time streatch is just a factor by which the animation will speedup or * slow down with respect to the overal animation. Time streach factor is * already applied to the layers inFrame and outFrame. * @TODO need to find out if timestreatch also affects the in and out frame * of the child layers or not. */ return int(frameNo / mTimeStreatch); } class Stroke : public Object { public: Stroke() : Object(Object::Type::Stroke) {} Color color(int frameNo) const { return mColor.value(frameNo); } float opacity(int frameNo) const { return mOpacity.value(frameNo) / 100.0f; } float strokeWidth(int frameNo) const { return mWidth.value(frameNo); } CapStyle capStyle() const { return mCapStyle; } JoinStyle joinStyle() const { return mJoinStyle; } float miterLimit() const { return mMiterLimit; } bool hasDashInfo() const { return !mDash.empty(); } void getDashInfo(int frameNo, std::vector &result) const { return mDash.getDashInfo(frameNo, result); } public: Property mColor; /* "c" */ Property mOpacity{100}; /* "o" */ Property mWidth{0}; /* "w" */ CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ float mMiterLimit{0}; /* "ml" */ Dash mDash; bool mEnabled{true}; /* "fillEnabled" */ }; class Gradient : public Object { public: class Data { public: friend inline Gradient::Data operator+(const Gradient::Data &g1, const Gradient::Data &g2); friend inline Gradient::Data operator-(const Gradient::Data &g1, const Gradient::Data &g2); friend inline Gradient::Data operator*(float m, const Gradient::Data &g); public: std::vector mGradient; }; explicit Gradient(Object::Type type) : Object(type) {} inline float opacity(int frameNo) const { return mOpacity.value(frameNo) / 100.0f; } void update(std::unique_ptr &grad, int frameNo); private: void populate(VGradientStops &stops, int frameNo); public: int mGradientType{1}; /* "t" Linear=1 , Radial = 2*/ Property mStartPoint; /* "s" */ Property mEndPoint; /* "e" */ Property mHighlightLength{0}; /* "h" */ Property mHighlightAngle{0}; /* "a" */ Property mOpacity{100}; /* "o" */ Property mGradient; /* "g" */ int mColorPoints{-1}; bool mEnabled{true}; /* "fillEnabled" */ }; class GradientStroke : public Gradient { public: GradientStroke() : Gradient(Object::Type::GStroke) {} float width(int frameNo) const { return mWidth.value(frameNo); } CapStyle capStyle() const { return mCapStyle; } JoinStyle joinStyle() const { return mJoinStyle; } float miterLimit() const { return mMiterLimit; } bool hasDashInfo() const { return !mDash.empty(); } void getDashInfo(int frameNo, std::vector &result) const { return mDash.getDashInfo(frameNo, result); } public: Property mWidth; /* "w" */ CapStyle mCapStyle{CapStyle::Flat}; /* "lc" */ JoinStyle mJoinStyle{JoinStyle::Miter}; /* "lj" */ float mMiterLimit{0}; /* "ml" */ Dash mDash; }; class GradientFill : public Gradient { public: GradientFill() : Gradient(Object::Type::GFill) {} FillRule fillRule() const { return mFillRule; } public: FillRule mFillRule{FillRule::Winding}; /* "r" */ }; class Fill : public Object { public: Fill() : Object(Object::Type::Fill) {} Color color(int frameNo) const { return mColor.value(frameNo); } float opacity(int frameNo) const { return mOpacity.value(frameNo) / 100.0f; } FillRule fillRule() const { return mFillRule; } public: FillRule mFillRule{FillRule::Winding}; /* "r" */ bool mEnabled{true}; /* "fillEnabled" */ Property mColor; /* "c" */ Property mOpacity{100}; /* "o" */ }; class Shape : public Object { public: explicit Shape(Object::Type type) : Object(type) {} VPath::Direction direction() { return (mDirection == 3) ? VPath::Direction::CCW : VPath::Direction::CW; } public: int mDirection{1}; }; class Path : public Shape { public: Path() : Shape(Object::Type::Path) {} public: Property mShape; }; class RoundedCorner : public Object { public: RoundedCorner() : Object(Object::Type::RoundedCorner) {} float radius(int frameNo) const { return mRadius.value(frameNo);} public: Property mRadius{0}; }; class Rect : public Shape { public: Rect() : Shape(Object::Type::Rect) {} float roundness(int frameNo) { return mRoundedCorner ? mRoundedCorner->radius(frameNo) : mRound.value(frameNo); } bool roundnessChanged(int prevFrame, int curFrame) { return mRoundedCorner ? mRoundedCorner->mRadius.changed(prevFrame, curFrame) : mRound.changed(prevFrame, curFrame); } public: RoundedCorner* mRoundedCorner{nullptr}; Property mPos; Property mSize; Property mRound{0}; }; class Ellipse : public Shape { public: Ellipse() : Shape(Object::Type::Ellipse) {} public: Property mPos; Property mSize; }; class Polystar : public Shape { public: enum class PolyType { Star = 1, Polygon = 2 }; Polystar() : Shape(Object::Type::Polystar) {} public: Polystar::PolyType mPolyType{PolyType::Polygon}; Property mPos; Property mPointCount{0}; Property mInnerRadius{0}; Property mOuterRadius{0}; Property mInnerRoundness{0}; Property mOuterRoundness{0}; Property mRotation{0}; }; class Repeater : public Object { public: struct Transform { VMatrix matrix(int frameNo, float multiplier) const; float startOpacity(int frameNo) const { return mStartOpacity.value(frameNo) / 100; } float endOpacity(int frameNo) const { return mEndOpacity.value(frameNo) / 100; } bool isStatic() const { return mRotation.isStatic() && mScale.isStatic() && mPosition.isStatic() && mAnchor.isStatic() && mStartOpacity.isStatic() && mEndOpacity.isStatic(); } Property mRotation{0}; /* "r" */ Property mScale{{100, 100}}; /* "s" */ Property mPosition; /* "p" */ Property mAnchor; /* "a" */ Property mStartOpacity{100}; /* "so" */ Property mEndOpacity{100}; /* "eo" */ }; Repeater() : Object(Object::Type::Repeater) {} Group *content() const { return mContent ? mContent : nullptr; } void setContent(Group *content) { mContent = content; } int maxCopies() const { return int(mMaxCopies); } float copies(int frameNo) const { return mCopies.value(frameNo); } float offset(int frameNo) const { return mOffset.value(frameNo); } bool processed() const { return mProcessed; } void markProcessed() { mProcessed = true; } public: Group * mContent{nullptr}; Transform mTransform; Property mCopies{0}; Property mOffset{0}; float mMaxCopies{0.0}; bool mProcessed{false}; }; class Trim : public Object { public: struct Segment { float start{0}; float end{0}; Segment() = default; explicit Segment(float s, float e) : start(s), end(e) {} }; enum class TrimType { Simultaneously, Individually }; Trim() : Object(Object::Type::Trim) {} /* * if start > end vector trims the path as a loop ( 2 segment) * if start < end vector trims the path without loop ( 1 segment). * if no offset then there is no loop. */ Segment segment(int frameNo) const { float start = mStart.value(frameNo) / 100.0f; float end = mEnd.value(frameNo) / 100.0f; float offset = std::fmod(mOffset.value(frameNo), 360.0f) / 360.0f; float diff = std::abs(start - end); if (vCompare(diff, 0.0f)) return Segment(0, 0); if (vCompare(diff, 1.0f)) return Segment(0, 1); if (offset > 0) { start += offset; end += offset; if (start <= 1 && end <= 1) { return noloop(start, end); } else if (start > 1 && end > 1) { return noloop(start - 1, end - 1); } else { return (start > 1) ? loop(start - 1, end) : loop(start, end - 1); } } else { start += offset; end += offset; if (start >= 0 && end >= 0) { return noloop(start, end); } else if (start < 0 && end < 0) { return noloop(1 + start, 1 + end); } else { return (start < 0) ? loop(1 + start, end) : loop(start, 1 + end); } } } Trim::TrimType type() const { return mTrimType; } private: Segment noloop(float start, float end) const { assert(start >= 0); assert(end >= 0); Segment s; s.start = std::min(start, end); s.end = std::max(start, end); return s; } Segment loop(float start, float end) const { assert(start >= 0); assert(end >= 0); Segment s; s.start = std::max(start, end); s.end = std::min(start, end); return s; } public: Property mStart{0}; Property mEnd{0}; Property mOffset{0}; Trim::TrimType mTrimType{TrimType::Simultaneously}; }; inline Gradient::Data operator+(const Gradient::Data &g1, const Gradient::Data &g2) { if (g1.mGradient.size() != g2.mGradient.size()) return g1; Gradient::Data newG; newG.mGradient = g1.mGradient; auto g2It = g2.mGradient.begin(); for (auto &i : newG.mGradient) { i = i + *g2It; g2It++; } return newG; } inline Gradient::Data operator-(const Gradient::Data &g1, const Gradient::Data &g2) { if (g1.mGradient.size() != g2.mGradient.size()) return g1; Gradient::Data newG; newG.mGradient = g1.mGradient; auto g2It = g2.mGradient.begin(); for (auto &i : newG.mGradient) { i = i - *g2It; g2It++; } return newG; } inline Gradient::Data operator*(float m, const Gradient::Data &g) { Gradient::Data newG; newG.mGradient = g.mGradient; for (auto &i : newG.mGradient) { i = i * m; } return newG; } using ColorFilter = std::function; void configureModelCacheSize(size_t cacheSize); std::shared_ptr loadFromFile(const std::string &filePath, bool cachePolicy); std::shared_ptr loadFromData(std::string jsonData, const std::string &key, std::string resourcePath, bool cachePolicy); std::shared_ptr loadFromData(std::string jsonData, std::string resourcePath, ColorFilter filter); std::shared_ptr parse(char *str, std::string dir_path, ColorFilter filter = {}); } // namespace model } // namespace internal } // namespace rlottie #endif // LOTModel_H