C++ Library
The Lotio C++ library provides headers and static libraries for programmatic use in C++ applications.
Installation
Homebrew (macOS)
brew tap matrunchyk/lotio
brew install lotio
This installs:
- Binary: lotio
- Headers: /opt/homebrew/include/lotio/ (or /usr/local/include/lotio/)
- Libraries: /opt/homebrew/lib/ (Skia static libraries)
- pkg-config: lotio.pc
From Source
See the README for build instructions. The developer package includes:
- Headers in include/lotio/
- Static libraries in lib/
- pkg-config file in lib/pkgconfig/
Headers
Headers are organized by module:
#include <lotio/core/animation_setup.h> // Animation initialization
#include <lotio/core/renderer.h> // Frame rendering
#include <lotio/core/frame_encoder.h> // Frame encoding (PNG)
#include <lotio/text/text_processor.h> // Text processing
#include <lotio/text/layer_overrides.h> // Layer overrides
#include <lotio/text/font_utils.h> // Text measurement modes
#include <lotio/utils/logging.h> // Logging utilities
Text Measurement Mode
enum class TextMeasurementMode {
FAST, // Fastest, basic accuracy
ACCURATE, // Good balance, accounts for kerning and glyph metrics (default)
PIXEL_PERFECT // Most accurate, accounts for anti-aliasing and subpixel rendering
};
The TextMeasurementMode enum controls the accuracy vs performance trade-off for measuring text width:
FAST: Fastest measurement using basic font metrics. Good for most cases but may underestimate width for some fonts.ACCURATE(default): Good balance of accuracy and performance. Uses SkTextBlob bounds which accounts for kerning and glyph metrics. Recommended for most use cases.PIXEL_PERFECT: Most accurate measurement by rendering text and scanning actual pixels. Accounts for anti-aliasing and subpixel rendering. Slower but most precise.
Basic Usage
Setting Up an Animation
#include <lotio/core/animation_setup.h>
#include <lotio/core/renderer.h>
#include <lotio/core/frame_encoder.h>
#include <string>
int main() {
std::string inputJson = "animation.json";
std::string layerOverridesJson = ""; // Optional
// Setup and create animation with default text padding and measurement mode
AnimationSetupResult result = setupAndCreateAnimation(
inputJson,
layerOverridesJson,
0.97f, // textPadding: 97% of width (3% padding)
TextMeasurementMode::ACCURATE // textMeasurementMode: good balance
);
if (!result.success) {
std::cerr << "Failed to setup animation: " << result.errorMessage << std::endl;
return 1;
}
// Use the animation...
return 0;
}
Rendering Frames
#include <lotio/core/renderer.h>
#include <lotio/core/frame_encoder.h>
// Render a single frame
double time = 0.5; // Time in seconds
int width = 800;
int height = 600;
// Render frame
std::vector<uint8_t> rgbaData = renderFrame(result.animation, time, width, height);
// Encode to PNG
std::string outputPath = "frame_0001.png";
bool success = encodeFrameToPNG(rgbaData, width, height, outputPath);
Text Processing
#include <lotio/text/text_processor.h>
#include <lotio/text/layer_overrides.h>
// Load layer overrides
auto layerOverrides = parseLayerOverrides("layer-overrides.json");
auto imageLayers = parseImageLayers("layer-overrides.json");
// Process layer overrides in animation JSON
std::string modifiedJson = processLayerOverrides(
originalJson,
config
);
Linking
Using pkg-config (Recommended)
g++ $(pkg-config --cflags --libs lotio) your_app.cpp -o your_app
Manual Linking
g++ -I/opt/homebrew/include \
-L/opt/homebrew/lib \
-llotio \
-lskottie -lskia -lskparagraph -lsksg -lskshaper \
-lskunicode_icu -lskunicode_core -lskresources -ljsonreader \
your_app.cpp -o your_app
API Reference
Animation Setup
struct AnimationSetupResult {
bool success;
std::string errorMessage;
skottie::Animation* animation; // Skottie animation pointer
};
AnimationSetupResult setupAndCreateAnimation(
const std::string& inputJsonPath,
const std::string& layerOverridesPath,
float textPadding = 0.97f,
TextMeasurementMode textMeasurementMode = TextMeasurementMode::ACCURATE,
const std::string& fontsDir = ""
);
Parameters:
- inputJsonPath: Path to Lottie animation JSON file
- Absolute paths: Used as-is (e.g., /path/to/animation.json)
- Relative paths: Resolved relative to the current working directory (cwd) where your program is executed
- Example: If your program runs from /home/user/project/ and you pass animation.json, it resolves to /home/user/project/animation.json
- The parent directory of this file is used as the base directory for resolving relative image paths in the Lottie JSON
- layerOverridesPath: Path to layer overrides JSON file (empty string if not used)
- Absolute paths: Used as-is (e.g., /path/to/layer-overrides.json)
- Relative paths: Resolved relative to the current working directory (cwd) where your program is executed
- Example: If your program runs from /home/user/project/ and you pass config/overrides.json, it resolves to /home/user/project/config/overrides.json
- The parent directory of this file is used as the base directory for resolving relative image paths in imageLayers.filePath
- textPadding: Text padding factor (0.0-1.0, default: 0.97 = 3% padding). Controls how much of the target text box width is used for text sizing.
- textMeasurementMode: Text measurement accuracy mode (default: ACCURATE). See TextMeasurementMode enum above.
- fontsDir: Optional directory for font files (e.g. .ttf). When non-empty, fonts are looked up here first, then in the JSON file's directory and its fonts/ subdirectory. Empty string (default) uses only the JSON directory and fonts/.
### Frame Rendering
```cpp
std::vector<uint8_t> renderFrame(
skottie::Animation* animation,
double time,
int width,
int height
);
Returns RGBA pixel data as a std::vector<uint8_t> (4 bytes per pixel).
Frame Encoding
bool encodeFrameToPNG(
const std::vector<uint8_t>& rgbaData,
int width,
int height,
const std::string& outputPath
);
Encodes RGBA pixel data to PNG format. The C++ library outputs PNG frames by default.
Text Processing
struct LayerOverride {
float minSize; // Optional: minimum font size (0 = not specified, no auto-fit)
float maxSize; // Optional: maximum font size (0 = not specified, no auto-fit)
std::string fallbackText; // Optional: fallback text if text doesn't fit (defaults to empty)
float textBoxWidth; // Optional: text box width (0 = use from JSON or animation width)
std::string value; // Optional: text value to set (defaults to original text from data.json)
int direction; // Optional: text direction (-1 = not specified/preserve, 0 = LTR, 1 = RTL)
};
struct ImageLayerOverride {
std::string filePath; // Optional: directory path (defaults to assets[].u, empty string = use full path from fileName)
std::string fileName; // Optional: filename (defaults to assets[].p)
};
std::map<std::string, LayerOverride> parseLayerOverrides(const std::string& configPath);
std::map<std::string, ImageLayerOverride> parseImageLayers(const std::string& configPath);
std::string processLayerOverrides(
std::string& animationJson,
const std::string& layerOverridesPath,
float textPadding = 0.97f,
TextMeasurementMode textMeasurementMode = TextMeasurementMode::ACCURATE
);
Text Direction:
- The direction field in LayerOverride controls text direction:
- -1: Not specified - preserves existing direction from Lottie JSON
- 0: Left-to-Right (LTR) - for languages like English, Spanish, etc.
- 1: Right-to-Left (RTL) - for languages like Hebrew, Arabic, etc.
- If direction is not specified in layer-overrides.json, the existing direction from the Lottie JSON file is preserved, maintaining backward compatibility.
Text layer visibility: Overridden text layers must be visible in the Lottie composition. Lotio does not fix invalid or mis-exported comps at runtime. When tp is omitted in the Lottie JSON, lotio's Skottie build uses a track-matte patch: the matte source is the nearest prior layer with td !== 0 (Bodymovin behavior), so comps like sample5 typically render correctly without editing data.json. If a text layer is under a 0-opacity null parent, has track matte (tt !== 0) with wrong or missing matte source, or is masked by a layer with td !== 0 and wrong tp, it may render invisible. Lotio logs [WARNING] to stderr with fix suggestions. Fix the Lottie JSON or re-export from AE/Bodymovin when the built-in track-matte patch does not apply. See CLI docs “Text layer visibility requirements” for details.
Image Layer Path Resolution:
When using imageLayers in layer-overrides.json:
- Absolute paths: Used as-is (e.g., /workspace/images/logo.png)
- Relative paths: Resolved relative to the layer-overrides.json file's directory (NOT the current working directory)
- Example: If layer-overrides.json is at /workspace/config/layer-overrides.json and imageLayers contains "image_0": { "filePath": "images/", "fileName": "logo.png" }, it resolves to /workspace/config/images/logo.png
- Important: This is different from inputJsonPath and layerOverridesPath parameters, which are resolved relative to the current working directory
- URLs are NOT supported: HTTP (http://) and HTTPS (https://) URLs are not supported
- Empty filePath: If filePath is an empty string, fileName must contain the full path
Notes:
- Image paths in the original Lottie JSON are resolved relative to the input JSON file's parent directory
- If an asset ID is not in imageLayers, the original u and p from the Lottie JSON are used
- Both filePath and fileName are optional - if not specified, defaults from assets[].u and assets[].p are used
## Complete Example
```cpp
#include <lotio/core/animation_setup.h>
#include <lotio/core/renderer.h>
#include <lotio/core/frame_encoder.h>
#include <lotio/text/text_processor.h>
#include <iostream>
#include <iomanip>
#include <sstream>
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " --data <input.json> [--fps <fps>] <output_dir>" << std::endl;
return 1;
}
std::string inputJson;
std::string outputDir;
float fps = 30.0f;
// Parse arguments
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "--data" && i + 1 < argc) {
inputJson = argv[++i];
} else if (arg == "--fps" && i + 1 < argc) {
fps = std::stof(argv[++i]);
} else if (arg[0] != '-') {
outputDir = arg;
}
}
if (inputJson.empty() || outputDir.empty()) {
std::cerr << "Error: --data and output_dir are required" << std::endl;
return 1;
}
// Setup animation with custom text padding and measurement mode
AnimationSetupResult result = setupAndCreateAnimation(
inputJson,
"",
0.95f, // textPadding: 95% of width (5% padding)
TextMeasurementMode::PIXEL_PERFECT // Most accurate measurement
);
if (!result.success) {
std::cerr << "Error: " << result.errorMessage << std::endl;
return 1;
}
// Get animation info
double duration = result.animation->duration();
int width = static_cast<int>(result.animation->size().width());
int height = static_cast<int>(result.animation->size().height());
// Use provided fps, or animation fps, or fallback to 30
float animationFps = result.animation->fps();
if (fps == 30.0f && animationFps > 0.0f) {
fps = animationFps;
}
// Render frames
int totalFrames = static_cast<int>(duration * fps);
for (int frame = 0; frame < totalFrames; frame++) {
double time = static_cast<double>(frame) / fps;
// Render frame
std::vector<uint8_t> rgbaData = renderFrame(result.animation, time, width, height);
// Save as PNG
std::ostringstream filename;
filename << outputDir << "/frame_"
<< std::setfill('0') << std::setw(4) << frame << ".png";
if (!encodeFrameToPNG(rgbaData, width, height, filename.str())) {
std::cerr << "Failed to encode frame " << frame << std::endl;
continue;
}
std::cout << "Rendered frame " << frame << "/" << totalFrames << std::endl;
}
std::cout << "Rendering complete!" << std::endl;
return 0;
}
Using Skia Directly
The lotio package includes Skia headers and libraries, so you can use Skia features directly:
// Use Skia directly
#include <skia/core/SkCanvas.h>
#include <skia/core/SkSurface.h>
#include <skia/modules/skottie/include/Skottie.h>
// Use lotio
#include <lotio/core/animation_setup.h>
int main() {
// Use Skia API directly
SkImageInfo info = SkImageInfo::MakeN32(800, 600, kOpaque_SkAlphaType);
auto surface = SkSurfaces::Raster(info);
SkCanvas* canvas = surface->getCanvas();
// Use lotio functions
AnimationSetupResult result = setupAndCreateAnimation("input.json", "");
// Render using Skia
result.animation->render(canvas);
return 0;
}
Include Paths
The pkg-config file includes all necessary include paths:
- -I${includedir} - Lotio headers
- -I${includedir}/skia - Skia core headers
- -I${includedir}/skia/gen - Skia generated headers
Libraries
The following Skia libraries are included:
- libskottie.a - Skottie animation library
- libskia.a - Skia core library
- libskparagraph.a - Text paragraph library
- libsksg.a - Scene graph library
- libskshaper.a - Text shaping library
- libskunicode_icu.a - Unicode support (ICU)
- libskunicode_core.a - Unicode core
- libskresources.a - Resource management
- libjsonreader.a - JSON parsing
Multi-threaded Rendering
Lotio supports multi-threaded rendering for improved performance:
#include <lotio/core/renderer.h>
#include <thread>
#include <vector>
void renderFrameRange(
skottie::Animation* animation,
int startFrame,
int endFrame,
int fps,
int width,
int height,
const std::string& outputDir
) {
for (int frame = startFrame; frame < endFrame; frame++) {
double time = static_cast<double>(frame) / fps;
std::vector<uint8_t> rgbaData = renderFrame(animation, time, width, height);
// Save frame...
}
}
// Render with multiple threads
int numThreads = std::thread::hardware_concurrency();
int framesPerThread = totalFrames / numThreads;
std::vector<std::thread> threads;
for (int i = 0; i < numThreads; i++) {
int start = i * framesPerThread;
int end = (i == numThreads - 1) ? totalFrames : (i + 1) * framesPerThread;
threads.emplace_back(renderFrameRange,
result.animation, start, end, fps, width, height, outputDir);
}
for (auto& thread : threads) {
thread.join();
}
Troubleshooting
Include Errors
- Verify headers are installed:
ls /opt/homebrew/include/lotio/ - Check pkg-config:
pkg-config --cflags --libs lotio - Reload IDE after installation
Linker Errors
- Verify libraries exist:
ls /opt/homebrew/lib/libskia.a - Check library paths in build command
- Ensure all Skia dependencies are linked
Runtime Errors
- Check animation JSON is valid
- Verify font paths in Lottie JSON
- Check file permissions for output directory
- Relative paths for
inputJsonPathandlayerOverridesPathare resolved relative to the current working directory (cwd) where your program runs - Relative paths in
imageLayers.filePathare resolved relative to the layer-overrides.json file's directory (NOT the current working directory) - URLs (
http://,https://) and data URIs (data:) are NOT supported inimageLayers
See Also
- Overview - General information about Lotio
- CLI - Command-line usage
- JS Library - JavaScript/WebAssembly usage