Skip to content

Commit 4fe7a35

Browse files
authored
feat(server): add generation metadata to png images (#1217)
1 parent 4d52320 commit 4fe7a35

5 files changed

Lines changed: 110 additions & 70 deletions

File tree

examples/cli/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ Generation Options:
125125
--vace-strength <float> wan vace strength
126126
--increase-ref-index automatically increase the indices of references images based on the order they are listed (starting with 1).
127127
--disable-auto-resize-ref-image disable auto resize of ref images
128+
--disable-image-metadata do not embed generation metadata on image files
128129
-s, --seed RNG seed (default: 42, use random seed for < 0)
129130
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing,
130131
tcd, res_multistep, res_2s] (default: euler for Flux/SD3/Wan, euler_a

examples/cli/main.cpp

Lines changed: 5 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -225,63 +225,6 @@ void parse_args(int argc, const char** argv, SDCliParams& cli_params, SDContextP
225225
}
226226
}
227227

228-
std::string get_image_params(const SDCliParams& cli_params, const SDContextParams& ctx_params, const SDGenerationParams& gen_params, int64_t seed) {
229-
std::string parameter_string = gen_params.prompt_with_lora + "\n";
230-
if (gen_params.negative_prompt.size() != 0) {
231-
parameter_string += "Negative prompt: " + gen_params.negative_prompt + "\n";
232-
}
233-
parameter_string += "Steps: " + std::to_string(gen_params.sample_params.sample_steps) + ", ";
234-
parameter_string += "CFG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
235-
if (gen_params.sample_params.guidance.slg.scale != 0 && gen_params.skip_layers.size() != 0) {
236-
parameter_string += "SLG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
237-
parameter_string += "Skip layers: [";
238-
for (const auto& layer : gen_params.skip_layers) {
239-
parameter_string += std::to_string(layer) + ", ";
240-
}
241-
parameter_string += "], ";
242-
parameter_string += "Skip layer start: " + std::to_string(gen_params.sample_params.guidance.slg.layer_start) + ", ";
243-
parameter_string += "Skip layer end: " + std::to_string(gen_params.sample_params.guidance.slg.layer_end) + ", ";
244-
}
245-
parameter_string += "Guidance: " + std::to_string(gen_params.sample_params.guidance.distilled_guidance) + ", ";
246-
parameter_string += "Eta: " + std::to_string(gen_params.sample_params.eta) + ", ";
247-
parameter_string += "Seed: " + std::to_string(seed) + ", ";
248-
parameter_string += "Size: " + std::to_string(gen_params.get_resolved_width()) + "x" + std::to_string(gen_params.get_resolved_height()) + ", ";
249-
parameter_string += "Model: " + sd_basename(ctx_params.model_path) + ", ";
250-
parameter_string += "RNG: " + std::string(sd_rng_type_name(ctx_params.rng_type)) + ", ";
251-
if (ctx_params.sampler_rng_type != RNG_TYPE_COUNT) {
252-
parameter_string += "Sampler RNG: " + std::string(sd_rng_type_name(ctx_params.sampler_rng_type)) + ", ";
253-
}
254-
parameter_string += "Sampler: " + std::string(sd_sample_method_name(gen_params.sample_params.sample_method));
255-
if (!gen_params.custom_sigmas.empty()) {
256-
parameter_string += ", Custom Sigmas: [";
257-
for (size_t i = 0; i < gen_params.custom_sigmas.size(); ++i) {
258-
std::ostringstream oss;
259-
oss << std::fixed << std::setprecision(4) << gen_params.custom_sigmas[i];
260-
parameter_string += oss.str() + (i == gen_params.custom_sigmas.size() - 1 ? "" : ", ");
261-
}
262-
parameter_string += "]";
263-
} else if (gen_params.sample_params.scheduler != SCHEDULER_COUNT) { // Only show schedule if not using custom sigmas
264-
parameter_string += " " + std::string(sd_scheduler_name(gen_params.sample_params.scheduler));
265-
}
266-
parameter_string += ", ";
267-
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}) {
268-
if (!te.empty()) {
269-
parameter_string += "TE: " + sd_basename(te) + ", ";
270-
}
271-
}
272-
if (!ctx_params.diffusion_model_path.empty()) {
273-
parameter_string += "Unet: " + sd_basename(ctx_params.diffusion_model_path) + ", ";
274-
}
275-
if (!ctx_params.vae_path.empty()) {
276-
parameter_string += "VAE: " + sd_basename(ctx_params.vae_path) + ", ";
277-
}
278-
if (gen_params.clip_skip != -1) {
279-
parameter_string += "Clip skip: " + std::to_string(gen_params.clip_skip) + ", ";
280-
}
281-
parameter_string += "Version: stable-diffusion.cpp";
282-
return parameter_string;
283-
}
284-
285228
void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) {
286229
SDCliParams* cli_params = (SDCliParams*)data;
287230
log_print(level, log, cli_params->verbose, cli_params->color);
@@ -414,12 +357,14 @@ bool save_results(const SDCliParams& cli_params,
414357
if (!img.data)
415358
return false;
416359

417-
std::string params = get_image_params(cli_params, ctx_params, gen_params, gen_params.seed + idx);
360+
std::string params = gen_params.embed_image_metadata
361+
? get_image_params(ctx_params, gen_params, gen_params.seed + idx)
362+
: "";
418363
int ok = 0;
419364
if (is_jpg) {
420-
ok = stbi_write_jpg(path.string().c_str(), img.width, img.height, img.channel, img.data, 90, params.c_str());
365+
ok = stbi_write_jpg(path.string().c_str(), img.width, img.height, img.channel, img.data, 90, params.size() > 0 ? params.c_str() : nullptr);
421366
} else {
422-
ok = stbi_write_png(path.string().c_str(), img.width, img.height, img.channel, img.data, 0, params.c_str());
367+
ok = stbi_write_png(path.string().c_str(), img.width, img.height, img.channel, img.data, 0, params.size() > 0 ? params.c_str() : nullptr);
423368
}
424369
LOG_INFO("save result image %d to '%s' (%s)", idx, path.string().c_str(), ok ? "success" : "failure");
425370
return ok != 0;

examples/common/common.hpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,7 @@ struct SDGenerationParams {
965965
std::string control_video_path;
966966
bool auto_resize_ref_image = true;
967967
bool increase_ref_index = false;
968+
bool embed_image_metadata = true;
968969

969970
std::vector<int> skip_layers = {7, 8, 9};
970971
sd_sample_params_t sample_params;
@@ -1199,10 +1200,16 @@ struct SDGenerationParams {
11991200
"disable auto resize of ref images",
12001201
false,
12011202
&auto_resize_ref_image},
1203+
{"",
1204+
"--disable-image-metadata",
1205+
"do not embed generation metadata on image files",
1206+
false,
1207+
&embed_image_metadata},
12021208
{"",
12031209
"--vae-tiling",
12041210
"process vae in tiles to reduce memory usage",
1205-
true, &vae_tiling_params.enabled},
1211+
true,
1212+
&vae_tiling_params.enabled},
12061213
};
12071214

12081215
auto on_seed_arg = [&](int argc, const char** argv, int index) {
@@ -1567,6 +1574,7 @@ struct SDGenerationParams {
15671574

15681575
load_if_exists("auto_resize_ref_image", auto_resize_ref_image);
15691576
load_if_exists("increase_ref_index", increase_ref_index);
1577+
load_if_exists("embed_image_metadata", embed_image_metadata);
15701578

15711579
load_if_exists("skip_layers", skip_layers);
15721580
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,
20942102
int expected_channel = 3) {
20952103
return load_image_common(true, image_bytes, len, width, height, expected_width, expected_height, expected_channel);
20962104
}
2105+
2106+
std::string get_image_params(const SDContextParams& ctx_params, const SDGenerationParams& gen_params, int64_t seed) {
2107+
std::string parameter_string;
2108+
if (gen_params.prompt_with_lora.size() != 0) {
2109+
parameter_string += gen_params.prompt_with_lora + "\n";
2110+
} else {
2111+
parameter_string += gen_params.prompt + "\n";
2112+
}
2113+
if (gen_params.negative_prompt.size() != 0) {
2114+
parameter_string += "Negative prompt: " + gen_params.negative_prompt + "\n";
2115+
}
2116+
parameter_string += "Steps: " + std::to_string(gen_params.sample_params.sample_steps) + ", ";
2117+
parameter_string += "CFG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
2118+
if (gen_params.sample_params.guidance.slg.scale != 0 && gen_params.skip_layers.size() != 0) {
2119+
parameter_string += "SLG scale: " + std::to_string(gen_params.sample_params.guidance.txt_cfg) + ", ";
2120+
parameter_string += "Skip layers: [";
2121+
for (const auto& layer : gen_params.skip_layers) {
2122+
parameter_string += std::to_string(layer) + ", ";
2123+
}
2124+
parameter_string += "], ";
2125+
parameter_string += "Skip layer start: " + std::to_string(gen_params.sample_params.guidance.slg.layer_start) + ", ";
2126+
parameter_string += "Skip layer end: " + std::to_string(gen_params.sample_params.guidance.slg.layer_end) + ", ";
2127+
}
2128+
parameter_string += "Guidance: " + std::to_string(gen_params.sample_params.guidance.distilled_guidance) + ", ";
2129+
parameter_string += "Eta: " + std::to_string(gen_params.sample_params.eta) + ", ";
2130+
parameter_string += "Seed: " + std::to_string(seed) + ", ";
2131+
parameter_string += "Size: " + std::to_string(gen_params.width) + "x" + std::to_string(gen_params.height) + ", ";
2132+
parameter_string += "Model: " + sd_basename(ctx_params.model_path) + ", ";
2133+
parameter_string += "RNG: " + std::string(sd_rng_type_name(ctx_params.rng_type)) + ", ";
2134+
if (ctx_params.sampler_rng_type != RNG_TYPE_COUNT) {
2135+
parameter_string += "Sampler RNG: " + std::string(sd_rng_type_name(ctx_params.sampler_rng_type)) + ", ";
2136+
}
2137+
parameter_string += "Sampler: " + std::string(sd_sample_method_name(gen_params.sample_params.sample_method));
2138+
if (!gen_params.custom_sigmas.empty()) {
2139+
parameter_string += ", Custom Sigmas: [";
2140+
for (size_t i = 0; i < gen_params.custom_sigmas.size(); ++i) {
2141+
std::ostringstream oss;
2142+
oss << std::fixed << std::setprecision(4) << gen_params.custom_sigmas[i];
2143+
parameter_string += oss.str() + (i == gen_params.custom_sigmas.size() - 1 ? "" : ", ");
2144+
}
2145+
parameter_string += "]";
2146+
} else if (gen_params.sample_params.scheduler != SCHEDULER_COUNT) { // Only show schedule if not using custom sigmas
2147+
parameter_string += " " + std::string(sd_scheduler_name(gen_params.sample_params.scheduler));
2148+
}
2149+
parameter_string += ", ";
2150+
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}) {
2151+
if (!te.empty()) {
2152+
parameter_string += "TE: " + sd_basename(te) + ", ";
2153+
}
2154+
}
2155+
if (!ctx_params.diffusion_model_path.empty()) {
2156+
parameter_string += "Unet: " + sd_basename(ctx_params.diffusion_model_path) + ", ";
2157+
}
2158+
if (!ctx_params.vae_path.empty()) {
2159+
parameter_string += "VAE: " + sd_basename(ctx_params.vae_path) + ", ";
2160+
}
2161+
if (gen_params.clip_skip != -1) {
2162+
parameter_string += "Clip skip: " + std::to_string(gen_params.clip_skip) + ", ";
2163+
}
2164+
parameter_string += "Version: stable-diffusion.cpp";
2165+
return parameter_string;
2166+
}

examples/server/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ Default Generation Options:
205205
--vace-strength <float> wan vace strength
206206
--increase-ref-index automatically increase the indices of references images based on the order they are listed (starting with 1).
207207
--disable-auto-resize-ref-image disable auto resize of ref images
208+
--disable-image-metadata do not embed generation metadata on image files
208209
-s, --seed RNG seed (default: 42, use random seed for < 0)
209210
--sampling-method sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing,
210211
tcd, res_multistep, res_2s] (default: euler for Flux/SD3/Wan, euler_a

examples/server/main.cpp

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,24 @@ std::string extract_and_remove_sd_cpp_extra_args(std::string& text) {
220220
enum class ImageFormat { JPEG,
221221
PNG };
222222

223+
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) {
224+
int len;
225+
unsigned char* png = stbi_write_png_to_mem((const unsigned char*)data, stride_bytes, x, y, comp, &len, parameters);
226+
if (png == NULL)
227+
return 0;
228+
func(context, png, len);
229+
STBIW_FREE(png);
230+
return 1;
231+
}
232+
223233
std::vector<uint8_t> write_image_to_vector(
224234
ImageFormat format,
225235
const uint8_t* image,
226236
int width,
227237
int height,
228238
int channels,
229-
int quality = 90) {
239+
std::string params = "",
240+
int quality = 90) {
230241
std::vector<uint8_t> buffer;
231242

232243
auto write_func = [&buffer](void* context, void* data, int size) {
@@ -249,7 +260,7 @@ std::vector<uint8_t> write_image_to_vector(
249260
result = stbi_write_jpg_to_func(c_func, &ctx, width, height, channels, image, quality);
250261
break;
251262
case ImageFormat::PNG:
252-
result = stbi_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels);
263+
result = stbi_ext_write_png_to_func(c_func, &ctx, width, height, channels, image, width * channels, params.size() > 0 ? params.c_str() : nullptr);
253264
break;
254265
default:
255266
throw std::runtime_error("invalid image format");
@@ -497,11 +508,15 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
497508
if (results[i].data == nullptr) {
498509
continue;
499510
}
500-
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
511+
std::string params = gen_params.embed_image_metadata
512+
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
513+
: "";
514+
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
501515
results[i].data,
502516
results[i].width,
503517
results[i].height,
504518
results[i].channel,
519+
params,
505520
output_compression);
506521
if (image_bytes.empty()) {
507522
LOG_ERROR("write image to mem failed");
@@ -747,11 +762,15 @@ void register_openai_api_endpoints(httplib::Server& svr, ServerRuntime& rt) {
747762
for (int i = 0; i < num_results; i++) {
748763
if (results[i].data == nullptr)
749764
continue;
750-
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
765+
std::string params = gen_params.embed_image_metadata
766+
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
767+
: "";
768+
auto image_bytes = write_image_to_vector(output_format == "jpeg" ? ImageFormat::JPEG : ImageFormat::PNG,
751769
results[i].data,
752770
results[i].width,
753771
results[i].height,
754772
results[i].channel,
773+
params,
755774
output_compression);
756775
std::string b64 = base64_encode(image_bytes);
757776
json item;
@@ -1062,11 +1081,15 @@ void register_sdapi_endpoints(httplib::Server& svr, ServerRuntime& rt) {
10621081
continue;
10631082
}
10641083

1065-
auto image_bytes = write_image_to_vector(ImageFormat::PNG,
1066-
results[i].data,
1067-
results[i].width,
1068-
results[i].height,
1069-
results[i].channel);
1084+
std::string params = gen_params.embed_image_metadata
1085+
? get_image_params(*runtime->ctx_params, gen_params, gen_params.seed + i)
1086+
: "";
1087+
auto image_bytes = write_image_to_vector(ImageFormat::PNG,
1088+
results[i].data,
1089+
results[i].width,
1090+
results[i].height,
1091+
results[i].channel,
1092+
params);
10701093

10711094
if (image_bytes.empty()) {
10721095
LOG_ERROR("write image to mem failed");

0 commit comments

Comments
 (0)