Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/fibo2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
48 changes: 48 additions & 0 deletions src/pipeline/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <limits>
#include <optional>

#include "core/rng.hpp"
Expand Down Expand Up @@ -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<uint8_t>& 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<int>::max() || image.height > std::numeric_limits<int>::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<size_t>::max() / 3) {
LOG_ERROR("Fibo masked reference is too large");
return false;
}
pixels.resize(static_cast<size_t>(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<float> encode_z_image_control_latent(StableDiffusionGGML* sd, const sd::Tensor<float>& control_rgb) {
sd::Tensor<float> vae_output = sd->first_stage_model->encode(sd->n_threads,
control_rgb,
Expand Down Expand Up @@ -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<uint8_t> 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;
Expand Down
Loading