#include
#include
#include
#include
#include
namespace
{
struct callJsLogArgs
{
sd_log_level_t level = SD_LOG_DEBUG;
std::string text;
};
void callJsLog(Napi::Env env, Napi::Function callback, std::nullptr_t*, callJsLogArgs* data)
{
auto dataPtr = std::unique_ptr(data);
Napi::HandleScope hs(env);
Napi::String logLevel;
switch (dataPtr->level)
{
case SD_LOG_ERROR: logLevel = Napi::String::From(env, "error"); break;
case SD_LOG_WARN: logLevel = Napi::String::From(env, "warn"); break;
case SD_LOG_INFO: logLevel = Napi::String::From(env, "info"); break;
case SD_LOG_DEBUG:
default: logLevel = Napi::String::From(env, "debug"); break;
}
dataPtr->text.erase(dataPtr->text.find_last_not_of("\n\r") + 1);
auto text = Napi::String::From(env, dataPtr->text);
callback.Call({ logLevel, text });
}
struct callJsProgressArgs
{
int step = 0;
int steps = 0;
float time = 0;
};
void callJsProgress(Napi::Env env, Napi::Function callback, std::nullptr_t*, callJsProgressArgs* data)
{
auto dataPtr = std::unique_ptr(data);
Napi::HandleScope hs(env);
callback.Call({ Napi::Number::From(env, dataPtr->step), Napi::Number::From(env, dataPtr->steps) , Napi::Number::From(env, dataPtr->time) });
}
struct CPPContextData : public std::enable_shared_from_this
{
std::shared_ptr sdCtx;
std::shared_ptr upscalerCtx;
Napi::TypedThreadSafeFunction<:nullptr_t calljslogargs calljslog> logCallback;
Napi::TypedThreadSafeFunction<:nullptr_t calljsprogressargs calljsprogress> progressCallback;
std::vector<:unique_ptr>> pendingTasks;
CPPContextData() = default;
CPPContextData(const CPPContextData& ctx) = delete;
CPPContextData(CPPContextData&& ctx) = delete;
CPPContextData& operator=(const CPPContextData& ctx) = delete;
CPPContextData& operator=(CPPContextData&& ctx) = delete;
~CPPContextData()
{
reset();
}
void nextTask()
{
if (!pendingTasks.empty())
{
auto begin = pendingTasks.begin();
begin->release()->Queue();
pendingTasks.erase(begin);
}
}
void reset()
{
sdCtx.reset();
upscalerCtx.reset();
if (progressCallback)
{
progressCallback.Abort();
progressCallback = {};
}
if (logCallback)
{
logCallback.Abort();
logCallback = {};
}
}
};
constinit thread_local CPPContextData* tl_current = nullptr;
void stableDiffusionLogFunc(enum sd_log_level_t level, const char* text, void* data)
{
const auto ctx = tl_current;
if (ctx && ctx->logCallback)
{
ctx->logCallback.BlockingCall(new callJsLogArgs{ .level = level, .text = text });
}
}
void stableDiffusionProgressFunc(int step, int steps, float time, void* data)
{
const auto ctx = tl_current;
if (ctx && ctx->progressCallback)
{
ctx->progressCallback.BlockingCall(new callJsProgressArgs{ .step = step, .steps = steps, .time = time });
}
}
class freeSdImageList
{
size_t imageCount;
public:
freeSdImageList(size_t imageCount) noexcept : imageCount(imageCount) {};
void operator()(sd_image_t* ptr) const
{
if (ptr)
{
for (size_t i = 0; i < imageCount; i++)
{
free(ptr[i].data);
}
free(ptr);
}
}
};
class freeSdImage
{
public:
void operator()(sd_image_t* ptr) const
{
if (ptr)
{
free(ptr->data);
free(ptr);
}
}
};
using SdImageList = std::unique_ptr;
using SdImage = std::unique_ptr;
Napi::Object wrapSdImage(Napi::Env env, const sd_image_t& img)
{
auto imgObj = Napi::Object::New(env);
imgObj.DefineProperties({
Napi::PropertyDescriptor::Value("width", Napi::Number::From(env, img.width)),
Napi::PropertyDescriptor::Value("height", Napi::Number::From(env, img.height)),
Napi::PropertyDescriptor::Value("channel", Napi::Number::From(env, img.channel)),
Napi::PropertyDescriptor::Value("data", Napi::Buffer::Copy(env, img.data, size_t(img.width) * img.height * img.channel))
});
imgObj.Freeze();
return imgObj;
}
SdImage extractSdImage(Napi::Object imgObj)
{
const auto width = imgObj.Get("width").ToNumber().Int32Value();
const auto height = imgObj.Get("height").ToNumber().Int32Value();
const auto channel = imgObj.Get("channel").ToNumber().Int32Value();
Napi::Buffer::CheckCast(imgObj.Env(), imgObj.Get("data"));
const auto data = imgObj.Get("data").As<:buffer>>();
if (width <= 0 || height <= 0 || channel <= 0)
{
throw Napi::Error::New(imgObj.Env(), "Invalid size");
}
const size_t expectedSize = size_t(width) * height * channel;
if (expectedSize != data.Length())
{
throw Napi::Error::New(imgObj.Env(), "Invalid size");
}
auto img = (sd_image_t*)calloc(1, sizeof(sd_image_t));
img->width = width;
img->height = height;
img->channel = channel;
img->data = (uint8_t*)malloc(expectedSize);
memcpy(img->data, data.Data(), data.Length());
return SdImage(img);
}
template
Napi::Promise queueStableDiffusionWorker(Napi::Env env, const std::shared_ptr& ctx, T&& func, C&& convFunc)
{
class StableDiffusionWorker : public Napi::AsyncWorker
{
//copy this on purpose to snapshot it
std::shared_ptr ctx;
Napi::Promise::Deferred def;
std::decay_t func;
std::decay_t convFunc;
std::optional<:invoke_result_t cppcontextdata>> result;
public:
StableDiffusionWorker(Napi::Env env, const std::shared_ptr& ctx, T&& func, C&& convFunc) : Napi::AsyncWorker(env, "node-stable-diffusion-cpp-worker"),
ctx(ctx), def(env), func(std::forward(func)), convFunc(std::forward(convFunc))
{
}
void Execute() override
{
auto prev = std::exchange(tl_current, ctx.get());
result.emplace(func(*ctx));
tl_current = prev;
}
void OnOK() override
{
def.Resolve(convFunc(Env(), std::move(result).value()));
ctx->nextTask();
}
void OnError(const Napi::Error& e) override
{
def.Reject(e.Value());
ctx->nextTask();
}
Napi::Promise Promise() const { return def.Promise(); }
};
const bool isFirst = ctx->pendingTasks.empty();
auto worker = std::make_unique(env, ctx, std::forward(func), std::forward(convFunc));
const auto ret = worker->Promise();
ctx->pendingTasks.emplace_back(std::move(worker));
if (isFirst)
ctx->nextTask();
return ret;
}
class NodeStableDiffusionCpp : public Napi::Addon
{
public:
NodeStableDiffusionCpp(Napi::Env env, Napi::Object exports)
{
sd_set_log_callback(&stableDiffusionLogFunc, nullptr);
sd_set_progress_callback(&stableDiffusionProgressFunc, nullptr);
auto sampleMethodEnum = Napi::Object::New(env);
sampleMethodEnum.DefineProperties(
{
Napi::PropertyDescriptor::Value("EulerA", Napi::Number::New(env, EULER_A)),
Napi::PropertyDescriptor::Value("Euler", Napi::Number::New(env, EULER)),
Napi::PropertyDescriptor::Value("Heun", Napi::Number::New(env, HEUN)),
Napi::PropertyDescriptor::Value("DPM2", Napi::Number::New(env, DPM2)),
Napi::PropertyDescriptor::Value("DPMPP2SA", Napi::Number::New(env, DPMPP2S_A)),
Napi::PropertyDescriptor::Value("DPMPP2M", Napi::Number::New(env, DPMPP2M)),
Napi::PropertyDescriptor::Value("DPMPP2Mv2", Napi::Number::New(env, DPMPP2Mv2)),
Napi::PropertyDescriptor::Value("LCM", Napi::Number::New(env, LCM)),
Napi::PropertyDescriptor::Value("IPNDM", Napi::Number::New(env, IPNDM)),
Napi::PropertyDescriptor::Value("IPNDM_V", Napi::Number::New(env, IPNDM_V)),
});
sampleMethodEnum.Freeze();
auto scheduleEnum = Napi::Object::New(env);
scheduleEnum.DefineProperties(
{
Napi::PropertyDescriptor::Value("Default", Napi::Number::New(env, DEFAULT)),
Napi::PropertyDescriptor::Value("Discrete", Napi::Number::New(env, DISCRETE)),
Napi::PropertyDescriptor::Value("Karras", Napi::Number::New(env, KARRAS)),
Napi::PropertyDescriptor::Value("AYS", Napi::Number::New(env, AYS)),
Napi::PropertyDescriptor::Value("GITS", Napi::Number::New(env, GITS)),
});
scheduleEnum.Freeze();
auto typeEnum = Napi::Object::New(env);
typeEnum.DefineProperties(
{
Napi::PropertyDescriptor::Value("F32", Napi::Number::New(env, SD_TYPE_F32)),
Napi::PropertyDescriptor::Value("F16", Napi::Number::New(env, SD_TYPE_F16)),
Napi::PropertyDescriptor::Value("Q4_0", Napi::Number::New(env, SD_TYPE_Q4_0)),
Napi::PropertyDescriptor::Value("Q4_1", Napi::Number::New(env, SD_TYPE_Q4_1)),
Napi::PropertyDescriptor::Value("Q5_0", Napi::Number::New(env, SD_TYPE_Q5_0)),
Napi::PropertyDescriptor::Value("Q5_1", Napi::Number::New(env, SD_TYPE_Q5_1)),
Napi::PropertyDescriptor::Value("Q8_0", Napi::Number::New(env, SD_TYPE_Q8_0)),
Napi::PropertyDescriptor::Value("Q8_1", Napi::Number::New(env, SD_TYPE_Q8_1)),
Napi::PropertyDescriptor::Value("Q2_K", Napi::Number::New(env, SD_TYPE_Q2_K)),
Napi::PropertyDescriptor::Value("Q3_K", Napi::Number::New(env, SD_TYPE_Q3_K)),
Napi::PropertyDescriptor::Value("Q4_K", Napi::Number::New(env, SD_TYPE_Q4_K)),
Napi::PropertyDescriptor::Value("Q5_K", Napi::Number::New(env, SD_TYPE_Q5_K)),
Napi::PropertyDescriptor::Value("Q6_K", Napi::Number::New(env, SD_TYPE_Q6_K)),
Napi::PropertyDescriptor::Value("Q8_K", Napi::Number::New(env, SD_TYPE_Q8_K)),
Napi::PropertyDescriptor::Value("IQ2_XXS", Napi::Number::New(env, SD_TYPE_IQ2_XXS)),
Napi::PropertyDescriptor::Value("IQ2_XS", Napi::Number::New(env, SD_TYPE_IQ2_XS)),
Napi::PropertyDescriptor::Value("IQ3_XXS", Napi::Number::New(env, SD_TYPE_IQ3_XXS)),
Napi::PropertyDescriptor::Value("IQ1_S", Napi::Number::New(env, SD_TYPE_IQ1_S)),
Napi::PropertyDescriptor::Value("IQ4_NL", Napi::Number::New(env, SD_TYPE_IQ4_NL)),
Napi::PropertyDescriptor::Value("IQ3_S", Napi::Number::New(env, SD_TYPE_IQ3_S)),
Napi::PropertyDescriptor::Value("IQ2_S", Napi::Number::New(env, SD_TYPE_IQ2_S)),
Napi::PropertyDescriptor::Value("IQ4_XS", Napi::Number::New(env, SD_TYPE_IQ4_XS)),
Napi::PropertyDescriptor::Value("I8", Napi::Number::New(env, SD_TYPE_I8)),
Napi::PropertyDescriptor::Value("I16", Napi::Number::New(env, SD_TYPE_I16)),
Napi::PropertyDescriptor::Value("I32", Napi::Number::New(env, SD_TYPE_I32)),
Napi::PropertyDescriptor::Value("I64", Napi::Number::New(env, SD_TYPE_I64)),
Napi::PropertyDescriptor::Value("F64", Napi::Number::New(env, SD_TYPE_F64)),
Napi::PropertyDescriptor::Value("IQ1_M", Napi::Number::New(env, SD_TYPE_IQ1_M)),
Napi::PropertyDescriptor::Value("BF16", Napi::Number::New(env, SD_TYPE_BF16)),
Napi::PropertyDescriptor::Value("Q4_0_4_4", Napi::Number::New(env, SD_TYPE_Q4_0_4_4)),
Napi::PropertyDescriptor::Value("Q4_0_4_8", Napi::Number::New(env, SD_TYPE_Q4_0_4_8)),
Napi::PropertyDescriptor::Value("Q4_0_8_8", Napi::Number::New(env, SD_TYPE_Q4_0_8_8)),
});
typeEnum.Freeze();
DefineAddon(exports,
{
InstanceValue("SampleMethod", sampleMethodEnum),
InstanceValue("Schedule", scheduleEnum),
InstanceValue("Type", typeEnum),
InstanceMethod("createContext", &NodeStableDiffusionCpp::createContext),
InstanceMethod("createUpscaler", &NodeStableDiffusionCpp::createUpscaler),
InstanceMethod("getSystemInfo", &NodeStableDiffusionCpp::getSystemInfo),
InstanceMethod("getNumPhysicalCores", &NodeStableDiffusionCpp::getNumPhysicalCores),
InstanceMethod("weightTypeName", &NodeStableDiffusionCpp::weightTypeName),
});
}
protected:
Napi::Value createContext(const Napi::CallbackInfo& info)
{
Napi::Value tmp;
const auto params = info[0].ToObject();
const auto model = (tmp = params.Get("model"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto clipL = (tmp = params.Get("clipL"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto clipG = (tmp = params.Get("clipG"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto t5xxl = (tmp = params.Get("t5xxl"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto diffusionModel = (tmp = params.Get("diffusionModel"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto vae = (tmp = params.Get("vae"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto taesd = (tmp = params.Get("taesd"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto controlNet = (tmp = params.Get("controlNet"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto loraDir = (tmp = params.Get("loraDir"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto embedDir = (tmp = params.Get("embedDir"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto stackedIdEmbedDir = (tmp = params.Get("stackedIdEmbedDir"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto vaeDecodeOnly = (tmp = params.Get("vaeDecodeOnly"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto vaeTiling = (tmp = params.Get("vaeTiling"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto freeParamsImmediately = (tmp = params.Get("freeParamsImmediately"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto numThreads = (tmp = params.Get("numThreads"), tmp.IsUndefined() ? GGML_DEFAULT_N_THREADS : tmp.ToNumber().Int32Value());
const auto weightType = (tmp = params.Get("weightType"), tmp.IsUndefined() ? SD_TYPE_F32 : sd_type_t(tmp.ToNumber().Uint32Value()));
const auto cudaRng = (tmp = params.Get("cudaRng"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto schedule = (tmp = params.Get("schedule"), tmp.IsUndefined() ? DEFAULT : schedule_t(tmp.ToNumber().Uint32Value()));
const auto keepClipOnCpu = (tmp = params.Get("keepClipOnCpu"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto keepControlNetOnCpu = (tmp = params.Get("keepControlNetOnCpu"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto keepVaeOnCpu = (tmp = params.Get("keepVaeOnCpu"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
if (weightType >= SD_TYPE_COUNT)
throw Napi::Error::New(info.Env(), "Invalid weightType");
if (schedule >= N_SCHEDULES)
throw Napi::Error::New(info.Env(), "Invalid schedule");
auto cppContextData = std::make_shared();
if (!info[1].IsUndefined())
{
Napi::Function::CheckCast(info.Env(), info[1]);
cppContextData->logCallback = decltype(CPPContextData::logCallback)::New(info.Env(), info[1].As<:function>(), "node-stable-diffusion-cpp-log-callback", 1, 1);
}
if (!info[2].IsUndefined())
{
Napi::Function::CheckCast(info.Env(), info[2]);
cppContextData->logCallback = decltype(CPPContextData::logCallback)::New(info.Env(), info[2].As<:function>(), "node-stable-diffusion-cpp-progress-callback", 1, 1);
}
return queueStableDiffusionWorker(info.Env(), cppContextData, [=](CPPContextData& ctx)
{
ctx.sdCtx = {
new_sd_ctx(
model.c_str(),
clipL.c_str(),
clipG.c_str(),
t5xxl.c_str(),
diffusionModel.c_str(),
vae.c_str(),
taesd.c_str(),
controlNet.c_str(),
loraDir.c_str(),
embedDir.c_str(),
stackedIdEmbedDir.c_str(),
vaeDecodeOnly,
vaeTiling,
freeParamsImmediately,
numThreads,
weightType,
cudaRng ? CUDA_RNG : STD_DEFAULT_RNG,
schedule,
keepClipOnCpu,
keepControlNetOnCpu,
keepVaeOnCpu
),
[](sd_ctx_t* c) {
if (c) free_sd_ctx(c);
}
};
if (!ctx.sdCtx)
throw std::runtime_error("Context creation failed");
return ctx.shared_from_this();
},
[](Napi::Env env, const std::shared_ptr& cppContextData)
{
auto ctx = Napi::Object::New(env);
ctx.DefineProperties({
Napi::PropertyDescriptor::Function(env, Napi::Object(), "dispose", [cppContextData](const Napi::CallbackInfo& info)
{
if (!cppContextData->sdCtx)
throw Napi::Error::New(info.Env(), "Context disposed");
cppContextData->sdCtx.reset();
return queueStableDiffusionWorker(info.Env(), cppContextData, [](CPPContextData& ctx)
{
return ctx.shared_from_this();
},
[](Napi::Env env, const std::shared_ptr& cppContextData)
{
cppContextData->reset();
return env.Undefined();
});
}),
Napi::PropertyDescriptor::Function(env, Napi::Object(), "txt2img", [cppContextData](const Napi::CallbackInfo& info)
{
if (!cppContextData->sdCtx)
throw Napi::Error::New(info.Env(), "Context disposed");
Napi::Value tmp;
const auto params = info[0].ToObject();
const auto prompt = params.Get("prompt").ToString().Utf8Value();
const auto negativePrompt = (tmp = params.Get("negativePrompt"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto clipSkip = (tmp = params.Get("clipSkip"), tmp.IsUndefined() ? -1 : tmp.ToNumber().Int32Value());
const auto cfgScale = (tmp = params.Get("cfgScale"), tmp.IsUndefined() ? 7.0f : tmp.ToNumber().FloatValue());
const auto width = (tmp = params.Get("width"), tmp.IsUndefined() ? 512 : tmp.ToNumber().Int32Value());
const auto height = (tmp = params.Get("height"), tmp.IsUndefined() ? 512 : tmp.ToNumber().Int32Value());
const auto sampleMethod = (tmp = params.Get("sampleMethod"), tmp.IsUndefined() ? EULER_A : sample_method_t(tmp.ToNumber().Uint32Value()));
const auto sampleSteps = (tmp = params.Get("sampleSteps"), tmp.IsUndefined() ? 20 : tmp.ToNumber().Int32Value());
const auto seed = (tmp = params.Get("seed"), tmp.IsUndefined() ? 42 : tmp.ToNumber().Int64Value());
const auto batchCount = (tmp = params.Get("batchCount"), tmp.IsUndefined() ? 1 : tmp.ToNumber().Int32Value());
auto controlCond = (tmp = params.Get("controlCond"), tmp.IsUndefined() ? SdImage() : extractSdImage(tmp.ToObject()));
const auto controlStrength = (tmp = params.Get("controlStrength"), tmp.IsUndefined() ? 0.0f : tmp.ToNumber().FloatValue());
const auto styleRatio = (tmp = params.Get("styleRatio"), tmp.IsUndefined() ? 20.0f : tmp.ToNumber().FloatValue());
const auto normalizeInput = (tmp = params.Get("normalizeInput"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto inputIdImagesPath = (tmp = params.Get("inputIdImagesPath"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto guidance = (tmp = params.Get("guidance"), tmp.IsUndefined() ? 0.0f : tmp.ToNumber().FloatValue());
if (sampleMethod >= N_SAMPLE_METHODS)
throw Napi::Error::New(info.Env(), "Invalid sampleMethod");
return queueStableDiffusionWorker(info.Env(), cppContextData, [=, sdCtx = cppContextData->sdCtx, controlCond = std::move(controlCond)](CPPContextData& ctx)
{
return SdImageList(txt2img(
sdCtx.get(),
prompt.c_str(),
negativePrompt.c_str(),
clipSkip,
cfgScale,
guidance,
width,
height,
sampleMethod,
sampleSteps,
seed,
batchCount,
controlCond.get(),
controlStrength,
styleRatio,
normalizeInput,
inputIdImagesPath.c_str()
), batchCount);
},
[batchCount](Napi::Env env, SdImageList&& images)
{
auto arr = Napi::Array::New(env, batchCount);
for (int b = 0; b < batchCount; b++)
{
arr[b] = wrapSdImage(env, images[b]);
}
return arr;
});
}),
Napi::PropertyDescriptor::Function(env, Napi::Object(), "img2img", [cppContextData](const Napi::CallbackInfo& info)
{
if (!cppContextData->sdCtx)
throw Napi::Error::New(info.Env(), "Context disposed");
Napi::Value tmp;
const auto params = info[0].ToObject();
auto initImage = (tmp = params.Get("initImage"), tmp.IsUndefined() ? SdImage() : extractSdImage(tmp.ToObject()));
const auto prompt = params.Get("prompt").ToString().Utf8Value();
const auto negativePrompt = (tmp = params.Get("negativePrompt"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto clipSkip = (tmp = params.Get("clipSkip"), tmp.IsUndefined() ? -1 : tmp.ToNumber().Int32Value());
const auto cfgScale = (tmp = params.Get("cfgScale"), tmp.IsUndefined() ? 7.0f : tmp.ToNumber().FloatValue());
const auto width = (tmp = params.Get("width"), tmp.IsUndefined() ? initImage->width : tmp.ToNumber().Int32Value());
const auto height = (tmp = params.Get("height"), tmp.IsUndefined() ? initImage->height : tmp.ToNumber().Int32Value());
const auto sampleMethod = (tmp = params.Get("sampleMethod"), tmp.IsUndefined() ? EULER_A : sample_method_t(tmp.ToNumber().Uint32Value()));
const auto sampleSteps = (tmp = params.Get("sampleSteps"), tmp.IsUndefined() ? 20 : tmp.ToNumber().Int32Value());
const auto strength = (tmp = params.Get("strength"), tmp.IsUndefined() ? 0.75f : tmp.ToNumber().FloatValue());
const auto seed = (tmp = params.Get("seed"), tmp.IsUndefined() ? 42 : tmp.ToNumber().Int64Value());
const auto batchCount = (tmp = params.Get("batchCount"), tmp.IsUndefined() ? 1 : tmp.ToNumber().Int32Value());
auto controlCond = (tmp = params.Get("controlCond"), tmp.IsUndefined() ? SdImage() : extractSdImage(tmp.ToObject()));
const auto controlStrength = (tmp = params.Get("controlStrength"), tmp.IsUndefined() ? 0.0f : tmp.ToNumber().FloatValue());
const auto styleRatio = (tmp = params.Get("styleRatio"), tmp.IsUndefined() ? 20.0f : tmp.ToNumber().FloatValue());
const auto normalizeInput = (tmp = params.Get("normalizeInput"), tmp.IsUndefined() ? false : tmp.ToBoolean().Value());
const auto inputIdImagesPath = (tmp = params.Get("inputIdImagesPath"), tmp.IsUndefined() ? "" : tmp.ToString().Utf8Value());
const auto guidance = (tmp = params.Get("guidance"), tmp.IsUndefined() ? 0.0f : tmp.ToNumber().FloatValue());
if (sampleMethod >= N_SAMPLE_METHODS)
throw Napi::Error::New(info.Env(), "Invalid sampleMethod");
return queueStableDiffusionWorker(info.Env(), cppContextData, [=, sdCtx = cppContextData->sdCtx, initImage = std::move(initImage), controlCond = std::move(controlCond)](CPPContextData& ctx)
{
return SdImageList(img2img(
sdCtx.get(),
*initImage,
prompt.c_str(),
negativePrompt.c_str(),
clipSkip,
cfgScale,
guidance,
width,
height,
sampleMethod,
sampleSteps,
strength,
seed,
batchCount,
controlCond.get(),
controlStrength,
styleRatio,
normalizeInput,
inputIdImagesPath.c_str()
), batchCount);
},
[batchCount](Napi::Env env, SdImageList&& images)
{
auto arr = Napi::Array::New(env, batchCount);
for (int b = 0; b < batchCount; b++)
{
arr[b] = wrapSdImage(env, images[b]);
}
return arr;
});
}),
Napi::PropertyDescriptor::Function(env, Napi::Object(), "img2vid", [cppContextData](const Napi::CallbackInfo& info)
{
if (!cppContextData->sdCtx)
throw Napi::Error::New(info.Env(), "Context disposed");
Napi::Value tmp;
const auto params = info[0].ToObject();
auto initImage = (tmp = params.Get("initImage"), tmp.IsUndefined() ? SdImage() : extractSdImage(tmp.ToObject()));
const auto width = (tmp = params.Get("width"), tmp.IsUndefined() ? initImage->width : tmp.ToNumber().Int32Value());
const auto height = (tmp = params.Get("height"), tmp.IsUndefined() ? initImage->height : tmp.ToNumber().Int32Value());
const auto videoFrames = (tmp = params.Get("videoFrames"), tmp.IsUndefined() ? 6 : tmp.ToNumber().Int32Value());
const auto motionBucketId = (tmp = params.Get("motionBucketId"), tmp.IsUndefined() ? 127 : tmp.ToNumber().Int32Value());
const auto fps = (tmp = params.Get("fps"), tmp.IsUndefined() ? 6 : tmp.ToNumber().Int32Value());
const auto augmentationLevel = (tmp = params.Get("augmentationLevel"), tmp.IsUndefined() ? 0.0f : tmp.ToNumber().FloatValue());
const auto minCfg = (tmp = params.Get("minCfg"), tmp.IsUndefined() ? 1.0f : tmp.ToNumber().FloatValue());
const auto cfgScale = (tmp = params.Get("cfgScale"), tmp.IsUndefined() ? 7.0f : tmp.ToNumber().FloatValue());
const auto sampleMethod = (tmp = params.Get("sampleMethod"), tmp.IsUndefined() ? EULER_A : sample_method_t(tmp.ToNumber().Uint32Value()));
const auto sampleSteps = (tmp = params.Get("sampleSteps"), tmp.IsUndefined() ? 20 : tmp.ToNumber().Int32Value());
const auto strength = (tmp = params.Get("strength"), tmp.IsUndefined() ? 0.75f : tmp.ToNumber().FloatValue());
const auto seed = (tmp = params.Get("seed"), tmp.IsUndefined() ? 42 : tmp.ToNumber().Int64Value());
if (sampleMethod >= N_SAMPLE_METHODS)
throw Napi::Error::New(info.Env(), "Invalid sampleMethod");
return queueStableDiffusionWorker(info.Env(), cppContextData, [=, sdCtx = cppContextData->sdCtx, initImage = std::move(initImage)](CPPContextData& ctx)
{
return SdImageList(img2vid(sdCtx.get(), *initImage, width, height, videoFrames, motionBucketId, fps, augmentationLevel, minCfg, cfgScale, sampleMethod, sampleSteps, strength, seed), videoFrames);
},
[videoFrames](Napi::Env env, SdImageList&& images)
{
auto arr = Napi::Array::New(env, videoFrames);
for (int b = 0; b < videoFrames; b++)
{
arr[b] = wrapSdImage(env, images[b]);
}
return arr;
});
}),
});
ctx.Freeze();
return ctx;
});
}
Napi::Value getSystemInfo(const Napi::CallbackInfo& info)
{
return Napi::String::New(info.Env(), sd_get_system_info());
}
Napi::Value getNumPhysicalCores(const Napi::CallbackInfo& info)
{
return Napi::Number::New(info.Env(), get_num_physical_cores());
}
Napi::Value weightTypeName(const Napi::CallbackInfo& info)
{
const auto weightType = sd_type_t(info[0].ToNumber().Uint32Value());
if (weightType >= SD_TYPE_COUNT)
throw Napi::Error::New(info.Env(), "Invalid weightType");
return Napi::String::New(info.Env(), sd_type_name(weightType));
}
Napi::Value createUpscaler(const Napi::CallbackInfo& info)
{
const auto esrganPath = info[0].ToString().Utf8Value();
const auto numThreads = info[1].IsUndefined() ? GGML_DEFAULT_N_THREADS : info[1].ToNumber().Int32Value();
const auto weightType = info[2].IsUndefined() ? SD_TYPE_F32 : sd_type_t(info[2].ToNumber().Uint32Value());
if (weightType >= SD_TYPE_COUNT)
throw Napi::Error::New(info.Env(), "Invalid weightType");
auto cppContextData = std::make_shared();
if (!info[3].IsUndefined())
{
Napi::Function::CheckCast(info.Env(), info[3]);
cppContextData->logCallback = decltype(CPPContextData::logCallback)::New(info.Env(), info[3].As<:function>(), "node-stable-diffusion-cpp-log-callback", 1, 1);
}
if (!info[4].IsUndefined())
{
Napi::Function::CheckCast(info.Env(), info[4]);
cppContextData->logCallback = decltype(CPPContextData::logCallback)::New(info.Env(), info[4].As<:function>(), "node-stable-diffusion-cpp-progress-callback", 1, 1);
}
return queueStableDiffusionWorker(info.Env(), cppContextData, [=](CPPContextData& ctx)
{
ctx.upscalerCtx = { new_upscaler_ctx(esrganPath.c_str(), numThreads, weightType), [](upscaler_ctx_t* c) { if (c) free_upscaler_ctx(c); } };
if (!ctx.upscalerCtx)
throw std::runtime_error("Context creation failed");
return ctx.shared_from_this();
},
[](Napi::Env env, const std::shared_ptr& cppContextData)
{
auto ctx = Napi::Object::New(env);
ctx.DefineProperties({
Napi::PropertyDescriptor::Function(env, Napi::Object(), "dispose", [cppContextData](const Napi::CallbackInfo& info)
{
if (!cppContextData->upscalerCtx)
throw Napi::Error::New(info.Env(), "Context disposed");
cppContextData->upscalerCtx.reset();
return queueStableDiffusionWorker(info.Env(), cppContextData, [](CPPContextData& ctx)
{
return ctx.shared_from_this();
},
[](Napi::Env env, const std::shared_ptr& cppContextData)
{
cppContextData->reset();
return env.Undefined();
});
}),
Napi::PropertyDescriptor::Function(env, Napi::Object(), "upscale", [cppContextData](const Napi::CallbackInfo& info)
{
if (!cppContextData->upscalerCtx)
throw Napi::Error::New(info.Env(), "Context disposed");
auto inputImage = extractSdImage(info[0].ToObject());
const auto upscaleFactor = info[1].ToNumber().Uint32Value();
return queueStableDiffusionWorker(info.Env(), cppContextData, [=, upscalerCtx = cppContextData->upscalerCtx, inputImage = std::move(inputImage)](CPPContextData& ctx)
{
auto img = (sd_image_t*)calloc(1, sizeof(sd_image_t));
*img = upscale(upscalerCtx.get(), *inputImage, upscaleFactor);
return SdImage(img);
},
[](Napi::Env env, SdImage&& image)
{
return wrapSdImage(env, *image);
});
}),
});
ctx.Freeze();
return ctx;
});
}
};
}
NODE_API_ADDON(NodeStableDiffusionCpp)