diff --git a/examples/cli/README.md b/examples/cli/README.md index 84dd5c716..98c85a683 100644 --- a/examples/cli/README.md +++ b/examples/cli/README.md @@ -122,6 +122,7 @@ Generation Options: --vace-strength 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] (default: euler for Flux/SD3/Wan, euler_a otherwise) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index ab58ab5f0..07d8edcff 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -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); @@ -411,12 +354,14 @@ bool save_results(const SDCliParams& cli_params, if (!img.data) return; - 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"); }; diff --git a/examples/common/common.hpp b/examples/common/common.hpp index ba1b0d8d9..285d9c653 100644 --- a/examples/common/common.hpp +++ b/examples/common/common.hpp @@ -1035,6 +1035,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 skip_layers = {7, 8, 9}; sd_sample_params_t sample_params; @@ -1260,6 +1261,11 @@ 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}, }; auto on_seed_arg = [&](int argc, const char** argv, int index) { @@ -1590,6 +1596,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); @@ -2115,3 +2122,66 @@ 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; +} + diff --git a/examples/server/README.md b/examples/server/README.md index 7e6681570..8c1eb11d2 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -114,6 +114,7 @@ Default Generation Options: --vace-strength 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] (default: euler for Flux/SD3/Wan, euler_a otherwise) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 76199ac69..7b61068ef 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -216,12 +216,23 @@ 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 write_image_to_vector( ImageFormat format, const uint8_t* image, int width, int height, int channels, + std::string params = "", int quality = 90) { std::vector buffer; @@ -245,7 +256,7 @@ std::vector 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"); @@ -464,11 +475,15 @@ int main(int argc, const char** argv) { if (results[i].data == nullptr) { continue; } + std::string params = gen_params.embed_image_metadata + ? get_image_params(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"); @@ -684,11 +699,15 @@ int main(int argc, const char** argv) { for (int i = 0; i < num_results; i++) { if (results[i].data == nullptr) continue; + std::string params = gen_params.embed_image_metadata + ? get_image_params(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; @@ -938,11 +957,15 @@ int main(int argc, const char** argv) { continue; } + std::string params = gen_params.embed_image_metadata + ? get_image_params(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); + results[i].channel, + params); if (image_bytes.empty()) { LOG_ERROR("write image to mem failed");