mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-04-01 06:09:49 +00:00
feat(server): add generation metadata to png images (#1217)
This commit is contained in:
parent
4d5232083f
commit
4fe7a35939
@ -125,6 +125,7 @@ Generation Options:
|
||||
--vace-strength <float> wan vace strength
|
||||
--increase-ref-index automatically increase the indices of references images based on the order they are listed (starting with 1).
|
||||
--disable-auto-resize-ref-image disable auto resize of ref images
|
||||
--disable-image-metadata do not embed generation metadata on image files
|
||||
-s, --seed RNG seed (default: 42, use random seed for < 0)
|
||||
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing,
|
||||
tcd, res_multistep, res_2s] (default: euler for Flux/SD3/Wan, euler_a
|
||||
|
||||
@ -225,63 +225,6 @@ void parse_args(int argc, const char** argv, SDCliParams& cli_params, SDContextP
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_image_params(const SDCliParams& cli_params, const SDContextParams& ctx_params, const SDGenerationParams& gen_params, int64_t seed) {
|
||||
std::string parameter_string = gen_params.prompt_with_lora + "\n";
|
||||
if (gen_params.negative_prompt.size() != 0) {
|
||||
parameter_string += "Negative prompt: " + gen_params.negative_prompt + "\n";
|
||||
}
|
||||
parameter_string += "Steps: " + std::to_string(gen_params.sample_params.sample_steps) + ", ";
|
||||
parameter_string += "CFG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
|
||||
if (gen_params.sample_params.guidance.slg.scale != 0 && gen_params.skip_layers.size() != 0) {
|
||||
parameter_string += "SLG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
|
||||
parameter_string += "Skip layers: [";
|
||||
for (const auto& layer : gen_params.skip_layers) {
|
||||
parameter_string += std::to_string(layer) + ", ";
|
||||
}
|
||||
parameter_string += "], ";
|
||||
parameter_string += "Skip layer start: " + std::to_string(gen_params.sample_params.guidance.slg.layer_start) + ", ";
|
||||
parameter_string += "Skip layer end: " + std::to_string(gen_params.sample_params.guidance.slg.layer_end) + ", ";
|
||||
}
|
||||
parameter_string += "Guidance: " + std::to_string(gen_params.sample_params.guidance.distilled_guidance) + ", ";
|
||||
parameter_string += "Eta: " + std::to_string(gen_params.sample_params.eta) + ", ";
|
||||
parameter_string += "Seed: " + std::to_string(seed) + ", ";
|
||||
parameter_string += "Size: " + std::to_string(gen_params.get_resolved_width()) + "x" + std::to_string(gen_params.get_resolved_height()) + ", ";
|
||||
parameter_string += "Model: " + sd_basename(ctx_params.model_path) + ", ";
|
||||
parameter_string += "RNG: " + std::string(sd_rng_type_name(ctx_params.rng_type)) + ", ";
|
||||
if (ctx_params.sampler_rng_type != RNG_TYPE_COUNT) {
|
||||
parameter_string += "Sampler RNG: " + std::string(sd_rng_type_name(ctx_params.sampler_rng_type)) + ", ";
|
||||
}
|
||||
parameter_string += "Sampler: " + std::string(sd_sample_method_name(gen_params.sample_params.sample_method));
|
||||
if (!gen_params.custom_sigmas.empty()) {
|
||||
parameter_string += ", Custom Sigmas: [";
|
||||
for (size_t i = 0; i < gen_params.custom_sigmas.size(); ++i) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(4) << gen_params.custom_sigmas[i];
|
||||
parameter_string += oss.str() + (i == gen_params.custom_sigmas.size() - 1 ? "" : ", ");
|
||||
}
|
||||
parameter_string += "]";
|
||||
} else if (gen_params.sample_params.scheduler != SCHEDULER_COUNT) { // Only show schedule if not using custom sigmas
|
||||
parameter_string += " " + std::string(sd_scheduler_name(gen_params.sample_params.scheduler));
|
||||
}
|
||||
parameter_string += ", ";
|
||||
for (const auto& te : {ctx_params.clip_l_path, ctx_params.clip_g_path, ctx_params.t5xxl_path, ctx_params.llm_path, ctx_params.llm_vision_path}) {
|
||||
if (!te.empty()) {
|
||||
parameter_string += "TE: " + sd_basename(te) + ", ";
|
||||
}
|
||||
}
|
||||
if (!ctx_params.diffusion_model_path.empty()) {
|
||||
parameter_string += "Unet: " + sd_basename(ctx_params.diffusion_model_path) + ", ";
|
||||
}
|
||||
if (!ctx_params.vae_path.empty()) {
|
||||
parameter_string += "VAE: " + sd_basename(ctx_params.vae_path) + ", ";
|
||||
}
|
||||
if (gen_params.clip_skip != -1) {
|
||||
parameter_string += "Clip skip: " + std::to_string(gen_params.clip_skip) + ", ";
|
||||
}
|
||||
parameter_string += "Version: stable-diffusion.cpp";
|
||||
return parameter_string;
|
||||
}
|
||||
|
||||
void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) {
|
||||
SDCliParams* cli_params = (SDCliParams*)data;
|
||||
log_print(level, log, cli_params->verbose, cli_params->color);
|
||||
@ -414,12 +357,14 @@ bool save_results(const SDCliParams& cli_params,
|
||||
if (!img.data)
|
||||
return false;
|
||||
|
||||
std::string params = get_image_params(cli_params, ctx_params, gen_params, gen_params.seed + idx);
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(ctx_params, gen_params, gen_params.seed + idx)
|
||||
: "";
|
||||
int ok = 0;
|
||||
if (is_jpg) {
|
||||
ok = stbi_write_jpg(path.string().c_str(), img.width, img.height, img.channel, img.data, 90, params.c_str());
|
||||
ok = stbi_write_jpg(path.string().c_str(), img.width, img.height, img.channel, img.data, 90, params.size() > 0 ? params.c_str() : nullptr);
|
||||
} else {
|
||||
ok = stbi_write_png(path.string().c_str(), img.width, img.height, img.channel, img.data, 0, params.c_str());
|
||||
ok = stbi_write_png(path.string().c_str(), img.width, img.height, img.channel, img.data, 0, params.size() > 0 ? params.c_str() : nullptr);
|
||||
}
|
||||
LOG_INFO("save result image %d to '%s' (%s)", idx, path.string().c_str(), ok ? "success" : "failure");
|
||||
return ok != 0;
|
||||
|
||||
@ -965,6 +965,7 @@ struct SDGenerationParams {
|
||||
std::string control_video_path;
|
||||
bool auto_resize_ref_image = true;
|
||||
bool increase_ref_index = false;
|
||||
bool embed_image_metadata = true;
|
||||
|
||||
std::vector<int> skip_layers = {7, 8, 9};
|
||||
sd_sample_params_t sample_params;
|
||||
@ -1199,10 +1200,16 @@ struct SDGenerationParams {
|
||||
"disable auto resize of ref images",
|
||||
false,
|
||||
&auto_resize_ref_image},
|
||||
{"",
|
||||
"--disable-image-metadata",
|
||||
"do not embed generation metadata on image files",
|
||||
false,
|
||||
&embed_image_metadata},
|
||||
{"",
|
||||
"--vae-tiling",
|
||||
"process vae in tiles to reduce memory usage",
|
||||
true, &vae_tiling_params.enabled},
|
||||
true,
|
||||
&vae_tiling_params.enabled},
|
||||
};
|
||||
|
||||
auto on_seed_arg = [&](int argc, const char** argv, int index) {
|
||||
@ -1567,6 +1574,7 @@ struct SDGenerationParams {
|
||||
|
||||
load_if_exists("auto_resize_ref_image", auto_resize_ref_image);
|
||||
load_if_exists("increase_ref_index", increase_ref_index);
|
||||
load_if_exists("embed_image_metadata", embed_image_metadata);
|
||||
|
||||
load_if_exists("skip_layers", skip_layers);
|
||||
load_if_exists("high_noise_skip_layers", high_noise_skip_layers);
|
||||
@ -2094,3 +2102,65 @@ uint8_t* load_image_from_memory(const char* image_bytes,
|
||||
int expected_channel = 3) {
|
||||
return load_image_common(true, image_bytes, len, width, height, expected_width, expected_height, expected_channel);
|
||||
}
|
||||
|
||||
std::string get_image_params(const SDContextParams& ctx_params, const SDGenerationParams& gen_params, int64_t seed) {
|
||||
std::string parameter_string;
|
||||
if (gen_params.prompt_with_lora.size() != 0) {
|
||||
parameter_string += gen_params.prompt_with_lora + "\n";
|
||||
} else {
|
||||
parameter_string += gen_params.prompt + "\n";
|
||||
}
|
||||
if (gen_params.negative_prompt.size() != 0) {
|
||||
parameter_string += "Negative prompt: " + gen_params.negative_prompt + "\n";
|
||||
}
|
||||
parameter_string += "Steps: " + std::to_string(gen_params.sample_params.sample_steps) + ", ";
|
||||
parameter_string += "CFG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
|
||||
if (gen_params.sample_params.guidance.slg.scale != 0 && gen_params.skip_layers.size() != 0) {
|
||||
parameter_string += "SLG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
|
||||
parameter_string += "Skip layers: [";
|
||||
for (const auto& layer : gen_params.skip_layers) {
|
||||
parameter_string += std::to_string(layer) + ", ";
|
||||
}
|
||||
parameter_string += "], ";
|
||||
parameter_string += "Skip layer start: " + std::to_string(gen_params.sample_params.guidance.slg.layer_start) + ", ";
|
||||
parameter_string += "Skip layer end: " + std::to_string(gen_params.sample_params.guidance.slg.layer_end) + ", ";
|
||||
}
|
||||
parameter_string += "Guidance: " + std::to_string(gen_params.sample_params.guidance.distilled_guidance) + ", ";
|
||||
parameter_string += "Eta: " + std::to_string(gen_params.sample_params.eta) + ", ";
|
||||
parameter_string += "Seed: " + std::to_string(seed) + ", ";
|
||||
parameter_string += "Size: " + std::to_string(gen_params.width) + "x" + std::to_string(gen_params.height) + ", ";
|
||||
parameter_string += "Model: " + sd_basename(ctx_params.model_path) + ", ";
|
||||
parameter_string += "RNG: " + std::string(sd_rng_type_name(ctx_params.rng_type)) + ", ";
|
||||
if (ctx_params.sampler_rng_type != RNG_TYPE_COUNT) {
|
||||
parameter_string += "Sampler RNG: " + std::string(sd_rng_type_name(ctx_params.sampler_rng_type)) + ", ";
|
||||
}
|
||||
parameter_string += "Sampler: " + std::string(sd_sample_method_name(gen_params.sample_params.sample_method));
|
||||
if (!gen_params.custom_sigmas.empty()) {
|
||||
parameter_string += ", Custom Sigmas: [";
|
||||
for (size_t i = 0; i < gen_params.custom_sigmas.size(); ++i) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(4) << gen_params.custom_sigmas[i];
|
||||
parameter_string += oss.str() + (i == gen_params.custom_sigmas.size() - 1 ? "" : ", ");
|
||||
}
|
||||
parameter_string += "]";
|
||||
} else if (gen_params.sample_params.scheduler != SCHEDULER_COUNT) { // Only show schedule if not using custom sigmas
|
||||
parameter_string += " " + std::string(sd_scheduler_name(gen_params.sample_params.scheduler));
|
||||
}
|
||||
parameter_string += ", ";
|
||||
for (const auto& te : {ctx_params.clip_l_path, ctx_params.clip_g_path, ctx_params.t5xxl_path, ctx_params.llm_path, ctx_params.llm_vision_path}) {
|
||||
if (!te.empty()) {
|
||||
parameter_string += "TE: " + sd_basename(te) + ", ";
|
||||
}
|
||||
}
|
||||
if (!ctx_params.diffusion_model_path.empty()) {
|
||||
parameter_string += "Unet: " + sd_basename(ctx_params.diffusion_model_path) + ", ";
|
||||
}
|
||||
if (!ctx_params.vae_path.empty()) {
|
||||
parameter_string += "VAE: " + sd_basename(ctx_params.vae_path) + ", ";
|
||||
}
|
||||
if (gen_params.clip_skip != -1) {
|
||||
parameter_string += "Clip skip: " + std::to_string(gen_params.clip_skip) + ", ";
|
||||
}
|
||||
parameter_string += "Version: stable-diffusion.cpp";
|
||||
return parameter_string;
|
||||
}
|
||||
|
||||
@ -205,6 +205,7 @@ Default Generation Options:
|
||||
--vace-strength <float> wan vace strength
|
||||
--increase-ref-index automatically increase the indices of references images based on the order they are listed (starting with 1).
|
||||
--disable-auto-resize-ref-image disable auto resize of ref images
|
||||
--disable-image-metadata do not embed generation metadata on image files
|
||||
-s, --seed RNG seed (default: 42, use random seed for < 0)
|
||||
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing,
|
||||
tcd, res_multistep, res_2s] (default: euler for Flux/SD3/Wan, euler_a
|
||||
|
||||
@ -220,13 +220,24 @@ std::string extract_and_remove_sd_cpp_extra_args(std::string& text) {
|
||||
enum class ImageFormat { JPEG,
|
||||
PNG };
|
||||
|
||||
static int stbi_ext_write_png_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data, int stride_bytes, const char* parameters) {
|
||||
int len;
|
||||
unsigned char* png = stbi_write_png_to_mem((const unsigned char*)data, stride_bytes, x, y, comp, &len, parameters);
|
||||
if (png == NULL)
|
||||
return 0;
|
||||
func(context, png, len);
|
||||
STBIW_FREE(png);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> write_image_to_vector(
|
||||
ImageFormat format,
|
||||
const uint8_t* image,
|
||||
int width,
|
||||
int height,
|
||||
int channels,
|
||||
int quality = 90) {
|
||||
std::string params = "",
|
||||
int quality = 90) {
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
auto write_func = [&buffer](void* context, void* data, int size) {
|
||||
@ -249,7 +260,7 @@ std::vector<uint8_t> write_image_to_vector(
|
||||
result = stbi_write_jpg_to_func(c_func, &ctx, width, height, channels, image, quality);
|
||||
break;
|
||||
case ImageFormat::PNG:
|
||||
result = stbi_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels);
|
||||
result = stbi_ext_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels, params.size() > 0 ? params.c_str() : nullptr);
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("invalid image format");
|
||||
@ -497,11 +508,15 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
if (results[i].data == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
|
||||
: "";
|
||||
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
|
||||
results[i].data,
|
||||
results[i].width,
|
||||
results[i].height,
|
||||
results[i].channel,
|
||||
params,
|
||||
output_compression);
|
||||
if (image_bytes.empty()) {
|
||||
LOG_ERROR("write image to mem failed");
|
||||
@ -747,11 +762,15 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
for (int i = 0; i < num_results; i++) {
|
||||
if (results[i].data == nullptr)
|
||||
continue;
|
||||
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
|
||||
: "";
|
||||
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
|
||||
results[i].data,
|
||||
results[i].width,
|
||||
results[i].height,
|
||||
results[i].channel,
|
||||
params,
|
||||
output_compression);
|
||||
std::string b64 = base64_encode(image_bytes);
|
||||
json item;
|
||||
@ -1062,11 +1081,15 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto image_bytes = write_image_to_vector(ImageFormat::PNG,
|
||||
results[i].data,
|
||||
results[i].width,
|
||||
results[i].height,
|
||||
results[i].channel);
|
||||
std::string params = gen_params.embed_image_metadata
|
||||
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
|
||||
: "";
|
||||
auto image_bytes = write_image_to_vector(ImageFormat::PNG,
|
||||
results[i].data,
|
||||
results[i].width,
|
||||
results[i].height,
|
||||
results[i].channel,
|
||||
params);
|
||||
|
||||
if (image_bytes.empty()) {
|
||||
LOG_ERROR("write image to mem failed");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user