Open Frameworks addon that wraps this stable diffusion library: https://github.com/leejet/stable-diffusion.cpp
Branches ahead of main
These branches have commits not yet on main — could be unmerged fixes or work in progress worth a look.
claude/fix-image-variation-crash
claude/improve-interaction-ofxggml
claude/explain-repository-structure
README
ofxStableDiffusion
ofxStableDiffusion is an openFrameworks addon that wraps stable-diffusion.cpp for text-to-image, image-to-image, image-to-video, and upscaling workflows.Current addon version:
1.0.0Requirements
- openFrameworks: 0.11.0 or later (tested with 0.12.0)
- C++ Standard: C++17 or later
- Platform Support: Windows (x64), Linux (x64), macOS (experimental Metal support)
- typed request/config/result objects
- background-thread generation
- owned image/video outputs
- explicit native-library staging
- wrapper-level integration seams for
ofxGgmlinteraction - lightweight unit tests for the new video helper layer
Highlights
- Text-to-image and image-to-image generation
- Native image modes:
TextToImage,ImageToImage, andInpainting - Best-of-N image reranking through a callback seam that can be driven by
ofxGgmlCLIP scoring - Image-to-video generation with
Standard,Loop,PingPong, andBoomerangpresentation modes ofxStableDiffusionRealtimeVideoSessionfor live creative-loop previews using prompt coalescing, low-step frames, and optional previous-frame img2img feedback- ESRGAN upscaling support
- Progress callbacks for diffusion steps
getCapabilities()plus capability/model-family helpers for UI gating and backend-aware workflows- parameter-tuning helpers for image/video defaults, ranges, and clamping by model family
- Optional
ofxStableDiffusionHoloscanBridgescaffold for liveframe -> conditioning -> diffusion -> previewpipelines, with a native Holoscan runtime path on Linux and a clean fallback path when Holoscan is not installed or the platform is not supported yet ofxStableDiffusionVideoWorkflowHelpers.hfor reusable video-generation presets, request validation, and richer render-manifest export on top of the existing request/result layer- Legacy entry points still available for wrapper-level migration
- Standalone native runtime management instead of sharing
ggmlbinaries across addons
Repo Layout
src/
src/core/
src/video/
FastPreview,LowVram,Balanced,Quality, andBatchStoryboardpresets- preflight request validation before a longer render starts
- richer JSON render manifests that capture prompt, dimensions, fps, seed, LoRAs, and clip summary data
libs/stable-diffusion/
scripts/
tests/
docs/
Feature Readiness
| Surface | Status | Notes |
|---|---|---|
Typed image generation (generate) | Stable | Primary production-ready wrapper API |
Typed video generation (generateVideo) | Stable | Includes owned frame results and metadata export |
| Legacy compatibility entry points | Supported | Kept for migration; new work should prefer typed requests |
ofxStableDiffusionRealtimeSession | Stable | Call update() every frame; callbacks fire from update() |
ofxStableDiffusionRealtimeVideoSession | Experimental | Useful preview workflow, but still evolving |
ofxStableDiffusionCreativeWorkflow | Experimental | Unifies realtime preview with queued higher-quality image/video renders and session snapshots |
ofxStableDiffusionHoloscanBridge | Experimental | Linux-first runtime path with fallback behavior elsewhere |
ofxStableDiffusionBatchProcessor | Experimental | Runs generator-backed grids, sweeps, A/B comparisons, and batch artifact export |
Threading Contract
ofxStableDiffusionis internally synchronized unless a method explicitly documents borrowed-pointer or blocking behavior.generate(),generateVideo(),configureContext(), andsetUpscalerSettings()may be called from any thread, but only one long-running task can run at a time.isGenerating(),isBusy(),requestCancellation(),isCancellationRequested(), and copied-result accessors (getLastResult(),getImages(),getVideoClip()) are safe to query from any thread.getImagePixels(),getVideoFramePixels(), andreturnImages()expose borrowed buffers; use them only with care and never cache the returned pointers across generations.setProgressCallback()andsetImageRankCallback()run on the worker thread. Keep those callbacks lightweight and defer UI / texture updates to your mainupdate()/draw()flow.ofxStableDiffusionRealtimeSessionandofxStableDiffusionRealtimeVideoSessiondispatch their callbacks from whichever thread callsupdate().
API Shape
The addon now supports two layers of use:
Modern typed wrapper API
#include "ofxStableDiffusion.h"
ofxStableDiffusion sd;
ofxStableDiffusionContextSettings context;
context.modelPath = "data/models/sd/sd_turbo.safetensors";
context.nThreads = 8;
context.weightType = SD_TYPE_F16;
sd.configureContext(context);
ofxStableDiffusionImageRequest request;
request.prompt = "cinematic portrait, rim lighting";
request.width = 512;
request.height = 512;
request.sampleSteps = 20;
sd.generate(request);
// Optional: apply a stack of LoRA/LoCon adapters per request
ofxStableDiffusionLora loraA;
loraA.path = "data/loras/edge.safetensors";
loraA.strength = 0.7f;
request.loras = {loraA};
sd.generate(request);
// Update the active LoRA stack globally
sd.setLoras({loraA});
// Hot-reload textual-inversion embeddings (reloads the context)
sd.reloadEmbeddings("data/embeddings");
// List currently discoverable embeddings (name, absolute path)
const auto embeddings = sd.listEmbeddings();
// Inspect what the currently configured model/runtime can actually do
const auto capabilities = sd.getCapabilities();
// Save generated video frames plus a JSON sidecar with per-frame prompts/seeds
sd.saveVideoFramesWithMetadata("output/storyboard", "shot");The parameter-tuning helpers can also be used outside the wrapper instance when you want model-family-aware defaults before building a request:
const auto imageProfile =
ofxStableDiffusionParameterTuningHelpers::resolveImageProfile(
context,
ofxStableDiffusionImageMode::ImageToImage);
request.cfgScale = imageProfile.defaultCfgScale;
request.sampleSteps = imageProfile.defaultSampleSteps;
request.strength = imageProfile.defaultStrength;Error Handling
The addon provides advanced error handling with error codes, messages, suggestions, and history tracking:
ofxStableDiffusionImageRequest request;
request.width = 513; // Invalid: not a multiple of 64
request.height = 512;
request.batchCount = 20; // Invalid: exceeds maximum of 16
sd.generate(request);
// Check for errors programmatically
if (sd.getLastErrorCode() != ofxStableDiffusionErrorCode::None) {
// Get detailed error information
const auto& errorInfo = sd.getLastErrorInfo();
ofLogError() << "Error: " << errorInfo.message;
ofLogError() << "Suggestion: " << errorInfo.suggestion;
ofLogError() << "Error code: " << ofxStableDiffusionErrorCodeLabel(errorInfo.code);
}
// Or use the simple string-based error (backward compatible)
if (!sd.getLastError().empty()) {
ofLogError() << "Error: " << sd.getLastError();
}
// Access error history for debugging
const auto& history = sd.getErrorHistory();
for (const auto& error : history) {
ofLogNotice() << "[" << error.timestampMicros << "] "
<< error.message << " -> " << error.suggestion;
}
// Clear error history
sd.clearErrorHistory();Available Error Codes:
None- No errorModelNotFound- Model file not foundModelCorrupted- Model file corruptedModelLoadFailed- Model loading failedOutOfMemory- Insufficient memoryInvalidDimensions- Invalid width/heightInvalidBatchCount- Invalid batch countInvalidFrameCount- Invalid video frame countMissingInputImage- Input image required but not providedGenerationFailed- Generation process failedThreadBusy- Another task is runningUpscaleFailed- Upscaling failedUnknown- Unknown error
Legacy compatibility API
The older
newSdCtx, txt2img, img2img, and img2vid entry points are still available so existing call sites, including addon-to-addon bridges, do not have to migrate immediately.Realtime Creative Loop
ofxStableDiffusionRealtimeVideoSession adapts the interaction pattern of realtime video tools to the local stable-diffusion.cpp backend. It keeps only the latest prompt while a frame is generating, uses low-step preview requests for responsiveness, can refine an idle prompt with a higher step budget, and can feed the previous output frame back through img2img for visual continuity.#include "ofxStableDiffusion.h"
ofxStableDiffusionRealtimeVideoSettings liveSettings;
liveSettings.previewSteps = 4;
liveSettings.refineSteps = 16;
liveSettings.previewStrength = 0.68f;
liveSettings.refineStrength = 0.35f;
liveSettings.usePreviousFrameFeedback = true;
ofxStableDiffusionRealtimeVideoSession liveVideo;
liveVideo.start(liveSettings, sd);
ofxStableDiffusionRealtimeVideoRequest liveRequest;
liveRequest.prompt = "a fashion portrait reacting to colored stage lights";
liveVideo.submit(liveRequest);
// In update():
liveVideo.update();This is a near-realtime preview workflow, not a proprietary HD world model. For best results use fast models, modest preview dimensions, and low preview step counts.
Video Behavior
Video generation returns owned frames through
ofxStableDiffusionVideoClip.Animated requests can also drive per-frame prompt interpolation, parameter animation, and seed sequencing through
ofxStableDiffusionVideoRequest::animationSettings. Each returned frame now carries the prompt / negative prompt / CFG / strength / seed values that were actually used, and clips can export both PNG sequences and JSON metadata.Useful video-export helpers:
ofxStableDiffusionVideoClip::saveMetadataJson(...)ofxStableDiffusionVideoClip::saveFrameSequenceWithMetadata(...)ofxStableDiffusion::saveVideoMetadata(...)ofxStableDiffusion::saveVideoFramesWithMetadata(...)
ofxStableDiffusionLongVideoManifest+ofxStableDiffusionLongVideoChunk
ofxStableDiffusion::renderLongVideo(...)renders all chunks sequentially,
metadata.json.Supported playback/presentation modes:
Standard
Loop
PingPong
Boomerang
This is especially useful when a UI layer or another addon wants a more natural preview clip without re-asking the native runtime for more frames.
Video Performance Recommendations
- Use fast models for previews: Prefer LCM/Turbo-style checkpoints with 4โ8 steps for quick iteration; only re-run hero frames at higher quality.
- Quantize when possible: F16 or Q8/Q5 levels often cut VRAM use by 50โ75% and speed up inference, enabling higher resolutions or longer clips without swapping.
- Keep the model warm and resident: Reuse the same loaded context via the model manager; avoid swapping checkpoints mid-run and consider a throwaway warmup frame to eliminate first-run latency.
- Minimize unique frames: Generate the smallest necessary source frame count, then stretch duration with
PingPong,Boomerang, orLoopplayback instead of regenerating. - Right-size resolution, fps, and steps: Lower resolution and fps where acceptable; clamp
sampleStepsto ~15โ25 for finals (lower for previews). Per-frame time scales directly with unique frames. - Trim preview I/O: Skip metadata/JSON exports on preview passes; save sidecar files only on final renders to avoid extra disk churn.
Coding Conventions (openFrameworks-aligned)
- Use tabs (as in existing headers) with K&R braces; lowerCamelCase for functions/members and PascalCase for types, keeping the
ofxStableDiffusionprefix for public types. - Include order:
ofMain.h, then addon headers, then STL/system headers. - Document public API with
///Doxygen-style comments; avoid mixing//for header docs. - Avoid exceptions; return
boolor error codes and populateofxStableDiffusionErrorfor failures. - Keep small helpers inline in headers; move heavier logic to
.cppfiles to limit inline bloat. - Prefer RAII and STL containers over raw
new/delete. - Keep UI, textures, and other openFrameworks rendering objects on the main thread; worker-thread callbacks should only capture/copy lightweight state.
- Favor openFrameworks core types at the API edge:
ofPixels/ofImagefor images,ofJsonfor metadata,ofVec/ofFloatColor/ofRectanglewhere geometry or color is needed. Convert to STL or native structs internally only when necessary for performance or binding.
Image Modes
The typed image request layer exposes the native image modes:
TextToImageImageToImageInpainting
CLIP-Rerank Integration
ofxStableDiffusion now exposes a wrapper-level image ranking callback for Best-of-N workflows.That is the intended integration point for
ofxGgml CLIP scoring:- generate a batch in
ofxStableDiffusion - score the outputs with
ofxGgmlClipInference - rerank or collapse to the best image without sharing native runtimes
ofxGgml Integration Guidance
This addon can be integrated with
ofxGgml in two ways:1. Addon-Level Integration (Recommended)
- keep
ofxStableDiffusionstandalone at the native-runtime layer - integrate
ofxGgmlwith it through the addon API - do not share the low-level
ggmlbinary directly across addons
- upstream
stable-diffusion.cppmay require a differentggmlrevision - backend flags and ABI expectations can diverge
- wrapper-level integration is more stable than native binary coupling
ofxGgml side through ofxGgmlStableDiffusionAdapters.h. This addon also exposes a small bridge helper header:src/bridges/ofxStableDiffusionGgmlBridge.h
2. System GGML Integration (Optional)
For advanced use cases where you want a single GGML binary shared across addons, you can build
ofxStableDiffusion to consume GGML from ofxGgml:# Linux/macOS
./scripts/build-stable-diffusion.sh --use-system-ggml --ofxggml-path ../ofxGgml
Windows
.\scripts\build-stable-diffusion.ps1 -UseSystemGgml -OfxGgmlPath ..\ofxGgmlThis enables
stable-diffusion.cpp's built-in system GGML support via -DSD_USE_SYSTEM_GGML=ON. The build scripts will: - Detect
ofxGgmlat the specified path (defaults to../../ofxGgml) - Use ofxGgml's GGML headers and libraries instead of bundling GGML
- Validate that ofxGgml is built before proceeding
- ofxGgml must be built first with matching backend flags (CPU/CUDA/Vulkan)
- GGML version compatibility between stable-diffusion.cpp and ofxGgml must be maintained
- This mode is opt-in; the default remains standalone for stability
Native Runtime
The addon stages native artifacts into addon-local paths:
libs/stable-diffusion/includelibs/stable-diffusion/lib/vslibs/stable-diffusion/lib/Linux64libs/ggml/includelibs/ggml/lib/vslibs/variants/<backend>/...
scripts/build-stable-diffusion.ps1scripts/build-stable-diffusion.batscripts/build-stable-diffusion.shscripts/download-stable-diffusion-release.ps1scripts/setup_addon.ps1scripts/setup_windows.bat
Backend flags now follow the same style as
ofxGgml, but the build selects one backend per build:--cpu,--cpu-only/-CpuOnly
--gpu,--cuda/-Cuda
--vulkan/-Vulkan
--metal/-Metal
--all/-All
cuda, then vulkan, then cpu-onlyEach backend build is also snapshotted under
libs/variants/<backend>, so you can switch the canonical addon runtime later without rebuilding everything.Variant selector helpers:
scripts/select-stable-diffusion-backend.ps1 -Backend cudascripts/setup_windows.bat --skip-native --select-backend cuda
scripts/setup_windows.bat and scripts/setup_addon.ps1 now always refresh the vendored source from the latest upstream release-tag source snapshot, then build the native runtime locally.--source-release-tag TAG
When an upstream Windows zip does not include
stable-diffusion.lib, the addon setup script synthesizes the Visual Studio import library from the downloaded DLL exports automatically.Current Native Source Status
The repo now includes a vendored upstream
stable-diffusion.cpp source snapshot under libs/stable-diffusion/source, pinned to:- upstream repo:
https://github.com/leejet/stable-diffusion.cpp - upstream release tag:
master-585-44cca3d - upstream commit:
44cca3d - vendored on:
2026-04-21
master-585-44cca3d, which was the latest upstream release published on 2026-04-19. Override it with --release-tag if you want a different upstream runtime. Source: stable-diffusion.cpp releasesThe addon now includes the upstream header directly through
libs/stable-diffusion/include/stable-diffusion.h, without re-exporting the older enum aliases. Addon code should use the current upstream names such as EULER_A_SAMPLE_METHOD, DPMPP2Mv2_SAMPLE_METHOD, and SCHEDULER_COUNT.Testing
The test suite focuses on wrapper-level logic that should stay stable even when the native runtime changes.
Run:
cmake -S tests -B tests/build
cmake --build tests/build --config Release
ctest --test-dir tests/build -C Release --output-on-failureYou can also use:
scripts/run-tests.ps1scripts/run-tests.sh
Example
The example project lives in
ofxStableDiffusionExample/ and now exposes:- progress/error status
- busy-state gating
- video-mode selection
- a small
Holoscan Bridgesection for the new bridge MVP, including prompt handoff, loaded-image submission, and inline bridge preview - the native Holoscan runtime path is Linux-only for now; Windows and other platforms stay on the addon fallback lane until that runtime is validated there
- frame export plus JSON metadata for generated clips
- optional end-frame morphing
- prompt morph / seed-sequence animation controls
Troubleshooting
Model Loading Issues
Problem: Model fails to load or crashes during loading
- Solution: Verify the model file exists and is not corrupted
- Solution: Check that the model format is compatible (
.safetensors,
ofxAddons