diff --git a/docs/fibo2.md b/docs/fibo2.md index 064d800892..5ce549ad27 100644 --- a/docs/fibo2.md +++ b/docs/fibo2.md @@ -50,4 +50,6 @@ The automatic `fibo2` reference preset combines `resize_single_ref_to_target=tru The vision encoder defaults to `vlm_resize_mode=longest_side,vlm_max_size=768`. Override these with `--ref-image-args`, for example `vlm_max_size=512`, or use `vlm_resize_mode=none` to skip size-based scaling. Vision inputs are still aligned to the encoder's patch grid. -Plain prompts and structured JSON captions are accepted. This integration uses the caption-only text-rendering path (`render_text_images=False`); it does not generate auxiliary glyph images or expand prompts with Bria VGL. Native Fibo mask preprocessing is not connected to `--mask`. +Plain prompts and structured JSON captions are accepted. This integration uses the caption-only text-rendering path (`render_text_images=False`); it does not generate auxiliary glyph images or expand prompts with Bria VGL. + +For native masked editing, pass one reference image with `-r` and a mask of the same source dimensions with `--mask`. White pixels select the region to edit. The mask is made binary at 128 and the selected source pixels are filled with RGB 128 before the reference is cropped or resized for the VAE and vision encoder. This does not use a latent denoising mask. Ordinary `--init-img` image-to-image masking without reference images retains its existing behavior. When both an initial image and a reference are supplied, the mask applies to the reference; the initial image is still used for image-to-image initialization, without a latent mask. diff --git a/src/pipeline/image.cpp b/src/pipeline/image.cpp index 7d264e22cd..f946afe4c6 100644 --- a/src/pipeline/image.cpp +++ b/src/pipeline/image.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "core/rng.hpp" @@ -118,6 +119,49 @@ namespace sd::pipeline { channels); } + static bool prepare_fibo2_mask(sd_img_gen_params_t& params, + sd_image_t& masked_reference, + std::vector& pixels) { + if (params.mask_image.data == nullptr || + (params.ref_images_count == 0 && params.init_image.data != nullptr)) { + return true; + } + if (params.ref_images_count != 1 || params.ref_images == nullptr || params.ref_images[0].data == nullptr) { + LOG_ERROR("Fibo masked editing requires exactly one reference image"); + return false; + } + const auto& image = params.ref_images[0]; + const auto& mask = params.mask_image; + if (image.width == 0 || image.height == 0 || + image.width > std::numeric_limits::max() || image.height > std::numeric_limits::max() || + image.channel < 1 || image.channel > 4 || mask.channel < 1 || mask.channel > 4 || + image.width != mask.width || image.height != mask.height) { + LOG_ERROR("Fibo mask and reference must be valid images with matching source dimensions"); + return false; + } + const uint64_t count = uint64_t(image.width) * image.height; + if (count > std::numeric_limits::max() / 3) { + LOG_ERROR("Fibo masked reference is too large"); + return false; + } + pixels.resize(static_cast(count) * 3); + for (size_t i = 0; i < count; ++i) { + const auto* source = image.data + i * image.channel; + const auto* value = mask.data + i * mask.channel; + const int gray = mask.channel < 3 + ? value[0] + : (19595 * value[0] + 38470 * value[1] + 7471 * value[2] + 32768) >> 16; + for (int channel = 0; channel < 3; ++channel) { + pixels[i * 3 + channel] = gray >= 128 ? 128 : source[image.channel < 3 ? 0 : channel]; + } + } + masked_reference = {image.width, image.height, 3, pixels.data()}; + params.ref_images = &masked_reference; + // Fibo masks condition the reference before geometry processing, not the denoising latent. + params.mask_image = {}; + return true; + } + static sd::Tensor encode_z_image_control_latent(StableDiffusionGGML* sd, const sd::Tensor& control_rgb) { sd::Tensor vae_output = sd->first_stage_model->encode(sd->n_threads, control_rgb, @@ -862,6 +906,10 @@ namespace sd::pipeline { const auto info = sd::generation_info(request, *sd_img_gen_params); sd::ImagePreprocessor preprocessing(sd_img_gen_params->image_preprocess.rules); sd_img_gen_params_t processed_params = *sd_img_gen_params; + sd_image_t masked_reference{}; + std::vector masked_reference_pixels; + if (sd->version == VERSION_FIBO2 && !prepare_fibo2_mask(processed_params, masked_reference, masked_reference_pixels)) + return false; if (!preprocessing.prepare_inputs(processed_params, request.width, request.height)) return false; sd_img_gen_params = &processed_params;