GLEGram 12.5 — Initial public release

Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
This commit is contained in:
Leeksov
2026-04-06 09:48:12 +03:00
commit 4647310322
39685 changed files with 11052678 additions and 0 deletions
@@ -0,0 +1,15 @@
#include "Interpolatable.hpp"
namespace lottie {
float remapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh) {
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
}
float clampFloat(float value, float a, float b) {
float minValue = a <= b ? a : b;
float maxValue = a <= b ? b : a;
return std::max(std::min(value, maxValue), minValue);
}
}
@@ -0,0 +1,14 @@
#ifndef Interpolatable_hpp
#define Interpolatable_hpp
#include <algorithm>
namespace lottie {
float remapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh);
float clampFloat(float value, float a, float b);
}
#endif /* Interpolatable_hpp */
@@ -0,0 +1,83 @@
#include "Keyframe.hpp"
#include <cfloat>
#include <cmath>
#include <algorithm>
namespace lottie {
static float eval_poly(float t, float m, float b) {
return std::fmaf(m, t, b);
}
static float eval_poly(float t, float m, float b, float c) {
float a = std::fmaf(m, t, b);
return std::fmaf(t, a, c);
}
static float eval_poly(float t, float m, float b, float c, float d) {
float e = std::fmaf(m, t, b);
float a = std::fmaf(e, t, c);
return std::fmaf(t, a, d);
}
static float cubic_solver(float A, float B, float C, float D) {
float t = -D;
float A3 = 3.0f * A;
float A6 = 6.0f * A;
float B2 = 2.0f * B;
const int MAX_ITERS = 8;
for (int iters = 0; iters < MAX_ITERS; iters++) {
float f = eval_poly(t, A, B, C, D); // f = At^3 + Bt^2 + Ct + D
if (std::fabs(f) <= 0.00005f) {
break;
}
float fp = eval_poly(t, A3, B2, C); // f' = 3At^2 + 2Bt + C
float fpp = eval_poly(t, A6, B2); // f'' = 6At + 2B
float numer = 2.0f * fp * f;
float denom = std::fma(2.0f * fp, fp, -(f * fpp));
t -= numer / denom;
}
t = std::clamp(t, 0.0f, 1.0f);
return t;
}
inline bool isApproximatelyEqual(float value, float other) {
return std::abs(value - other) <= FLT_EPSILON;
}
float cubicBezierInterpolate(float value, Vector2D const &P1, Vector2D const &P2) {
float t = 0.0;
if (isApproximatelyEqual(value, 0.0f)) {
// Handle corner cases explicitly to prevent rounding errors
t = 0.0;
} else if (isApproximatelyEqual(value, 1.0f)) {
t = 1.0;
} else {
// Calculate t
float a = 3 * P1.x - 3 * P2.x + 1.0f;
float b = -6 * P1.x + 3 * P2.x;
float c = 3 * P1.x;
float tTemp = cubic_solver(a, b, c, -value);
if (isApproximatelyEqual(tTemp, -1.0f)) {
return -1.0;
}
t = tTemp;
}
// Calculate y from t
float oneMinusT = 1.0 - t;
float result = 3 * t * (oneMinusT * oneMinusT) * P1.y + 3 * (t * t) * (1 - t) * P2.y + (t * t * t);
return result;
}
}
@@ -0,0 +1,261 @@
#ifndef Keyframe_hpp
#define Keyframe_hpp
#include "Lottie/Public/Primitives/AnimationTime.hpp"
#include <LottieCpp/Vectors.h>
#include "Lottie/Public/Keyframes/Interpolatable.hpp"
#include "Lottie/Public/Keyframes/ValueInterpolators.hpp"
#include <optional>
namespace lottie {
float cubicBezierInterpolate(float value, Vector2D const &P1, Vector2D const &P2);
/// A keyframe with a single value, and timing information
/// about when the value should be displayed and how it
/// should be interpolated.
template<typename T>
class Keyframe {
public:
/// Initialize a value-only keyframe with no time data.
Keyframe(
T const &value_,
std::optional<Vector3D> spatialInTangent_,
std::optional<Vector3D> spatialOutTangent_
) :
value(value_),
time(0),
isHold(true),
inTangent(std::nullopt),
outTangent(std::nullopt),
spatialInTangent(spatialInTangent_),
spatialOutTangent(spatialOutTangent_) {
}
/// Initialize a keyframe
Keyframe(
T value_,
AnimationFrameTime time_,
bool isHold_,
std::optional<Vector2D> inTangent_,
std::optional<Vector2D> outTangent_,
std::optional<Vector3D> spatialInTangent_,
std::optional<Vector3D> spatialOutTangent_
) :
value(value_),
time(time_),
isHold(isHold_),
inTangent(inTangent_),
outTangent(outTangent_),
spatialInTangent(spatialInTangent_),
spatialOutTangent(spatialOutTangent_) {
}
bool operator==(Keyframe const &rhs) {
return value == rhs.value
&& time == rhs.time
&& isHold == rhs.isHold
&& inTangent == rhs.inTangent
&& outTangent == rhs.outTangent
&& spatialInTangent == rhs.spatialInTangent
&& spatialOutTangent == rhs.spatialOutTangent;
}
bool operator!=(Keyframe const &rhs) {
return !(*this == rhs);
}
public:
T interpolate(Keyframe<T> const &to, float progress) {
std::optional<Vector2D> spatialOutTangent2d;
if (spatialOutTangent) {
spatialOutTangent2d = Vector2D(spatialOutTangent->x, spatialOutTangent->y);
}
std::optional<Vector2D> spatialInTangent2d;
if (to.spatialInTangent) {
spatialInTangent2d = Vector2D(to.spatialInTangent->x, to.spatialInTangent->y);
}
return ValueInterpolator<T>::interpolate(value, to.value, progress, spatialOutTangent2d, spatialInTangent2d);
}
/// Interpolates the keyTime into a value from 0-1
float interpolatedProgress(Keyframe<T> const &to, float keyTime) {
float startTime = time;
float endTime = to.time;
if (keyTime <= startTime) {
return 0.0;
}
if (endTime <= keyTime) {
return 1.0;
}
if (isHold) {
return 0.0;
}
Vector2D outTanPoint = Vector2D::Zero();
if (outTangent.has_value()) {
outTanPoint = outTangent.value();
}
Vector2D inTanPoint = Vector2D(1.0, 1.0);
if (to.inTangent.has_value()) {
inTanPoint = to.inTangent.value();
}
float progress = remapFloat(keyTime, startTime, endTime, 0.0f, 1.0f);
if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0f, 1.0f)) {
/// Cubic interpolation
progress = cubicBezierInterpolate(progress, outTanPoint, inTanPoint);
}
return progress;
}
public:
/// The value of the keyframe
T value;
/// The time in frames of the keyframe.
AnimationFrameTime time;
/// A hold keyframe freezes interpolation until the next keyframe that is not a hold.
bool isHold;
/// The in tangent for the time interpolation curve.
std::optional<Vector2D> inTangent;
/// The out tangent for the time interpolation curve.
std::optional<Vector2D> outTangent;
/// The spatial in tangent of the vector.
std::optional<Vector3D> spatialInTangent;
/// The spatial out tangent of the vector.
std::optional<Vector3D> spatialOutTangent;
};
template<typename T>
class KeyframeData {
public:
KeyframeData(
std::optional<T> startValue_,
std::optional<T> endValue_,
std::optional<AnimationFrameTime> time_,
std::optional<int> hold_,
std::optional<Vector2D> inTangent_,
std::optional<Vector2D> outTangent_,
std::optional<Vector3D> spatialInTangent_,
std::optional<Vector3D> spatialOutTangent_
) :
startValue(startValue_),
endValue(endValue_),
time(time_),
hold(hold_),
inTangent(inTangent_),
outTangent(outTangent_),
spatialInTangent(spatialInTangent_),
spatialOutTangent(spatialOutTangent_) {
}
explicit KeyframeData(lottiejson11::Json const &json) noexcept(false) {
if (!json.is_object()) {
throw LottieParsingException();
}
if (const auto startValueData = getOptionalAny(json.object_items(), "s")) {
startValue = T(startValueData.value());
}
if (const auto endValueData = getOptionalAny(json.object_items(), "e")) {
endValue = T(endValueData.value());
}
if (const auto timeValue = getOptionalDouble(json.object_items(), "t")) {
time = (float)timeValue.value();
}
hold = getOptionalInt(json.object_items(), "h");
if (const auto inTangentData = getOptionalObject(json.object_items(), "i")) {
inTangent = Vector2D(inTangentData.value());
}
if (const auto outTangentData = getOptionalObject(json.object_items(), "o")) {
outTangent = Vector2D(outTangentData.value());
}
if (const auto spatialInTangentData = getOptionalAny(json.object_items(), "ti")) {
spatialInTangent = Vector3D(spatialInTangentData.value());
}
if (const auto spatialOutTangentData = getOptionalAny(json.object_items(), "to")) {
spatialOutTangent = Vector3D(spatialOutTangentData.value());
}
if (const auto nDataValue = getOptionalAny(json.object_items(), "n")) {
nData = nDataValue.value();
}
}
lottiejson11::Json::object toJson() const {
lottiejson11::Json::object result;
if (startValue.has_value()) {
result.insert(std::make_pair("s", startValue->toJson()));
}
if (endValue.has_value()) {
result.insert(std::make_pair("e", endValue->toJson()));
}
if (time.has_value()) {
result.insert(std::make_pair("t", time.value()));
}
if (hold.has_value()) {
result.insert(std::make_pair("h", hold.value()));
}
if (inTangent.has_value()) {
result.insert(std::make_pair("i", inTangent->toJson()));
}
if (outTangent.has_value()) {
result.insert(std::make_pair("o", outTangent->toJson()));
}
if (spatialInTangent.has_value()) {
result.insert(std::make_pair("ti", spatialInTangent->toJson()));
}
if (spatialOutTangent.has_value()) {
result.insert(std::make_pair("to", spatialOutTangent->toJson()));
}
if (nData.has_value()) {
result.insert(std::make_pair("n", nData.value()));
}
return result;
}
public:
/// The start value of the keyframe
std::optional<T> startValue;
/// The End value of the keyframe. Note: Newer versions animation json do not have this field.
std::optional<T> endValue;
/// The time in frames of the keyframe.
std::optional<AnimationFrameTime> time;
/// A hold keyframe freezes interpolation until the next keyframe that is not a hold.
std::optional<int> hold;
/// The in tangent for the time interpolation curve.
std::optional<Vector2D> inTangent;
/// The out tangent for the time interpolation curve.
std::optional<Vector2D> outTangent;
/// The spacial in tangent of the vector.
std::optional<Vector3D> spatialInTangent;
/// The spacial out tangent of the vector.
std::optional<Vector3D> spatialOutTangent;
std::optional<lottiejson11::Json> nData;
bool isHold() const {
if (hold.has_value()) {
return hold.value() > 0;
} else {
return false;
}
}
};
}
#endif /* Keyframe_hpp */
@@ -0,0 +1,48 @@
#include "ValueInterpolators.hpp"
#if __APPLE__
#include <Accelerate/Accelerate.h>
#endif
namespace lottie {
#if __APPLE__
void batchInterpolate(std::vector<PathElement> const &from, std::vector<PathElement> const &to, BezierPath &resultPath, float amount) {
int elementCount = (int)from.size();
if (elementCount > (int)to.size()) {
elementCount = (int)to.size();
}
static_assert(sizeof(PathElement) == 4 * 2 * 3);
resultPath.setElementCount(elementCount);
float floatAmount = (float)amount;
vDSP_vintb((float *)&from[0], 1, (float *)&to[0], 1, &floatAmount, (float *)&resultPath.elements()[0], 1, elementCount * 2 * 3);
}
#else
void batchInterpolate(std::vector<PathElement> const &from, std::vector<PathElement> const &to, BezierPath &resultPath, float amount) {
int elementCount = (int)from.size();
if (elementCount > (int)to.size()) {
elementCount = (int)to.size();
}
static_assert(sizeof(PathElement) == 4 * 2 * 3);
resultPath.setElementCount(elementCount);
float *fromValues = (float *)&from[0];
float *toValues = (float *)&to[0];
float *outValues = (float *)&resultPath.elements()[0];
int numValues = elementCount * 2 * 3;
for (int i = 0; i < numValues; i++) {
outValues[i] = fromValues[i] + ((toValues[i] - fromValues[i]) * amount);
}
}
#endif
}
@@ -0,0 +1,231 @@
#ifndef ValueInterpolators_hpp
#define ValueInterpolators_hpp
#include <LottieCpp/Vectors.h>
#import <LottieCpp/Color.h>
#include <LottieCpp/BezierPath.h>
#include "Lottie/Private/Model/Text/TextDocument.hpp"
#include "Lottie/Public/Primitives/GradientColorSet.hpp"
#include "Lottie/Public/Primitives/DashPattern.hpp"
#include <optional>
#include <cassert>
namespace lottie {
template<typename T>
struct ValueInterpolator {
};
template<>
struct ValueInterpolator<float> {
public:
static float interpolate(float value, float to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
return value + ((to - value) * amount);
}
};
template<>
struct ValueInterpolator<Vector1D> {
public:
static Vector1D interpolate(Vector1D const &value, Vector1D const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
return Vector1D(ValueInterpolator<float>::interpolate(value.value, to.value, amount, spatialOutTangent, spatialInTangent));
}
};
template<>
struct ValueInterpolator<Vector2D> {
public:
static Vector2D interpolate(Vector2D const &value, Vector2D const &to, float amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) {
auto cp1 = value + spatialOutTangent;
auto cp2 = to + spatialInTangent;
return value.interpolate(to, cp1, cp2, amount);
}
static Vector2D interpolate(Vector2D const &value, Vector2D const &to, float amount) {
return value.interpolate(to, amount);
}
};
template<>
struct ValueInterpolator<Vector3D> {
public:
static Vector3D interpolate(Vector3D const &value, Vector3D const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
if (spatialOutTangent && spatialInTangent) {
Vector2D from2d(value.x, value.y);
Vector2D to2d(to.x, to.y);
auto cp1 = from2d + spatialOutTangent.value();
auto cp2 = to2d + spatialInTangent.value();
Vector2D result2d = from2d.interpolate(to2d, cp1, cp2, amount);
return Vector3D(
result2d.x,
result2d.y,
ValueInterpolator<float>::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent)
);
}
return Vector3D(
ValueInterpolator<float>::interpolate(value.x, to.x, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<float>::interpolate(value.y, to.y, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<float>::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent)
);
}
};
template<>
struct ValueInterpolator<Color> {
public:
static Color interpolate(Color const &value, Color const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
return Color(
ValueInterpolator<float>::interpolate(value.r, to.r, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<float>::interpolate(value.g, to.g, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<float>::interpolate(value.b, to.b, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<float>::interpolate(value.a, to.a, amount, spatialOutTangent, spatialInTangent)
);
}
};
void batchInterpolate(std::vector<PathElement> const &from, std::vector<PathElement> const &to, BezierPath &resultPath, float amount);
template<>
struct ValueInterpolator<CurveVertex> {
public:
static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, float amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) {
return CurveVertex::absolute(
ValueInterpolator<Vector2D>::interpolate(value.point, to.point, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<Vector2D>::interpolate(value.inTangent, to.inTangent, amount, spatialOutTangent, spatialInTangent),
ValueInterpolator<Vector2D>::interpolate(value.outTangent, to.outTangent, amount, spatialOutTangent, spatialInTangent)
);
}
static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, float amount) {
return CurveVertex::absolute(
ValueInterpolator<Vector2D>::interpolate(value.point, to.point, amount),
ValueInterpolator<Vector2D>::interpolate(value.inTangent, to.inTangent, amount),
ValueInterpolator<Vector2D>::interpolate(value.outTangent, to.outTangent, amount)
);
}
};
template<>
struct ValueInterpolator<BezierPath> {
public:
static BezierPath interpolate(BezierPath const &value, BezierPath const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
BezierPath newPath;
newPath.reserveCapacity(std::max(value.elements().size(), to.elements().size()));
//TODO:probably a bug in the upstream code, uncomment
//newPath.setClosed(value.closed());
size_t elementCount = std::min(value.elements().size(), to.elements().size());
if (spatialInTangent && spatialOutTangent) {
Vector2D spatialInTangentValue = spatialInTangent.value();
Vector2D spatialOutTangentValue = spatialOutTangent.value();
for (size_t i = 0; i < elementCount; i++) {
const auto &fromVertex = value.elements()[i].vertex;
const auto &toVertex = to.elements()[i].vertex;
newPath.addVertex(ValueInterpolator<CurveVertex>::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue));
}
} else {
for (size_t i = 0; i < elementCount; i++) {
const auto &fromVertex = value.elements()[i].vertex;
const auto &toVertex = to.elements()[i].vertex;
newPath.addVertex(ValueInterpolator<CurveVertex>::interpolate(fromVertex, toVertex, amount));
}
}
return newPath;
}
static void setInplace(BezierPath const &value, BezierPath &resultPath) {
resultPath.reserveCapacity(value.elements().size());
resultPath.setElementCount(value.elements().size());
resultPath.invalidateLength();
memcpy(resultPath.mutableElements().data(), value.elements().data(), value.elements().size() * sizeof(PathElement));
}
static void interpolateInplace(BezierPath const &value, BezierPath const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent, BezierPath &resultPath) {
/*if (value.elements().size() != to.elements().size()) {
return to;
}*/
//TODO:probably a bug in the upstream code, uncomment
//newPath.setClosed(value.closed());
int elementCount = (int)std::min(value.elements().size(), to.elements().size());
resultPath.reserveCapacity(std::max(value.elements().size(), to.elements().size()));
resultPath.setElementCount(elementCount);
resultPath.invalidateLength();
if (spatialInTangent && spatialOutTangent) {
Vector2D spatialInTangentValue = spatialInTangent.value();
Vector2D spatialOutTangentValue = spatialOutTangent.value();
for (int i = 0; i < elementCount; i++) {
const auto &fromVertex = value.elements()[i].vertex;
const auto &toVertex = to.elements()[i].vertex;
auto vertex = ValueInterpolator<CurveVertex>::interpolate(fromVertex, toVertex, amount, spatialOutTangentValue, spatialInTangentValue);
resultPath.updateVertex(vertex, i, false);
}
} else {
batchInterpolate(value.elements(), to.elements(), resultPath, amount);
}
}
};
template<>
struct ValueInterpolator<TextDocument> {
public:
static TextDocument interpolate(TextDocument const &value, TextDocument const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
if (amount == 1.0) {
return to;
} else {
return value;
}
}
};
template<>
struct ValueInterpolator<GradientColorSet> {
public:
static GradientColorSet interpolate(GradientColorSet const &value, GradientColorSet const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
#if DEBUG
assert(value.colors.size() == to.colors.size());
#endif
std::vector<float> colors;
size_t colorCount = std::min(value.colors.size(), to.colors.size());
for (size_t i = 0; i < colorCount; i++) {
colors.push_back(ValueInterpolator<float>::interpolate(value.colors[i], to.colors[i], amount, spatialOutTangent, spatialInTangent));
}
return GradientColorSet(colors);
}
};
template<>
struct ValueInterpolator<DashPattern> {
public:
static DashPattern interpolate(DashPattern const &value, DashPattern const &to, float amount, std::optional<Vector2D> spatialOutTangent, std::optional<Vector2D> spatialInTangent) {
#if DEBUG
assert(value.values.size() == to.values.size());
#endif
std::vector<float> values;
size_t colorCount = std::min(value.values.size(), to.values.size());
for (size_t i = 0; i < colorCount; i++) {
values.push_back(ValueInterpolator<float>::interpolate(value.values[i], to.values[i], amount, spatialOutTangent, spatialInTangent));
}
return DashPattern(std::move(values));
}
};
}
#endif /* ValueInterpolators_hpp */