Texture Transfer

Texture transfer synthesises a new image by stitching patches taken from one or more source textures so that the result visually resembles a given target image. A classic use-case is turning a portrait into a stencil made entirely of rice, wood-grain, or any other tileable material.

Three public entry-points are provided.

Function

When to use

texture_transfer

Handles curation and most of the configuration details internally.

texture_transfer_advanced

When you want to curate the texture yourself or have complete control over the synthesis schedule.

texture_transfer_guided_advanced

When you want patch selection guided by a proxy texture that differs from the source. See proxy_synth.md for the full rationale and use-cases.

All three functions are interruptible from the UI and return None values if the user cancels mid-run.


Texture Curation

Curation is the preprocessing step that converts a raw image into a representation that can be used for patch similarity scoring.

curate_for_tex_transfer is called automatically by texture_transfer on both the source textures and the target. When using the _advanced variants, you can call it yourself or replace it with your own custom curation procedure.

Crucially, the curation you apply defines what “similar patches” means — it is the matching criterion. By changing how the image is converted to a single channel, you can make the algorithm match patches based on luminance, a specific colour channel, or any other scalar feature you can derive from the image.

The curated textures and curated target must always be produced with the same method and the same pre-processing, otherwise the similarity scores will be meaningless. When calling texture_transfer_advanced or texture_transfer_guided_advanced, it is your responsibility to ensure this.

Processing pipeline

curate_for_tex_transfer applies the following operations:

  1. Channel reduction: converts the input to a single-channel 2D image using cvt_code (see Choosing a matching criterion below). If the input is already single-channel, this step is skipped.

  2. Gaussian blur: 5×5 kernel, σ = 1. Suppresses high-frequency noise so that patch comparison is driven by structure rather than potential noise.

  3. UINT8 Normalisation: normalise the values to the interval [0, 255]; this is done with respect to the minimum and maximum values found in the data, not the format bounds.

  4. CLAHE (Contrast Limited Adaptive Histogram Equalisation): clip limit 2.0, tile grid size (H/10, W/10). Boosts local contrast while preventing over-amplification, making patch differences more discriminable across varying illumination conditions.

  5. Global histogram equalisation: spreads the intensity distribution evenly, normalising global brightness differences between texture and target so that their similarity scores are directly comparable.

  6. Normalisation: maps the values to the interval [0, 1].

  7. Reshape: output is always H×W×1 (ndim = 3) regardless of the input shape.

The mask, when provided, is applied after steps 2, 4, and 6 to zero out regions of no interest at each stage.

Important

Mask values must be 0 and 1, not 0 and 255. Using 255s will incorrectly scale pixel values at each masking step and produce wrong results.

Choosing a matching criterion

The cvt_code argument is the main lever for controlling the matching criterion:

  • Default (cv2.COLOR_BGR2GRAY) – Converts BGR to perceived luminance using the standard Rec. 601 formula. Best for matching the light/dark structure of the target.

  • Other grayscale conversion codes – For example, cv2.COLOR_RGB2GRAY if your input is RGB. Any code that produces a single channel is acceptable.

  • None – Computes the per‑pixel mean across channels. Similar to grayscale but without perceptual weighting.

Important

Avoid circular colour spaces (e.g. HSV hue): patch similarity is computed with matchTemplate, which measures linear distance. The hue channel in HSV is circular (0° and 360° are the same colour but numerically far apart), so matchTemplate will produce incorrect distances near the wrap-around point. Stick to colour spaces where all channels vary linearly, such as CIELAB a* and b*.

Tip

Matching by chrominance (CIELAB a*, b*): properly representing chrominance requires keeping both the a* and b* channels together — collapsing them to a single channel loses colour direction information. curate_for_tex_transfer always reduces to one channel and is therefore not suitable for this case. You will need to implement a custom curation function that normalises and enhances the a* and b* channels while preserving both, and pass the result (shape H×W×2) directly to texture_transfer_advanced or texture_transfer_guided_advanced. Those functions do accept multi-channel curated inputs.


Iteration Scheduling

All three transfer functions ultimately run the quilting algorithm as a sequence of passes, each defined by a patch geometry (CircularPatchingConfig) and a blending weight (alpha). Together these form the iteration schedule.

The alpha parameter

Alpha controls the trade-off between two patch selection criteria at each pass:

  • α = 0 : select patches purely by how well they blend with already-placed neighbours (overlap coherence). The target image is ignored.

  • α = 1 : select patches purely by how closely they resemble the target at that location. Existing neighbours are ignored.

  • 0 < α < 1 : a weighted combination of both.

How each function handles scheduling

The three functions differ in how much of the schedule is computed automatically.

texture_transfer — automatic

The schedule is derived from patching_config, alphas, and last_diameter via an internal helper:

  • alphas defaults to [0.75, 0.5, 0.25]. The number of elements determines the number of passes.

  • last_diameter defaults to round(diameter / 3.6) | 1 (roughly 28 % of the starting diameter, rounded up to the nearest odd integer).

  • Patch diameters are linearly interpolated from patching_config.diameter (the starting diameter) down to last_diameter across the passes.

  • overlap_ratio is overridden to 0.5 at every pass regardless of the value in patching_config.

  • All other config fields (tolerance, spacing, feathering, etc.) are preserved across passes.

texture_transfer_advanced — manual

You supply the full config_alpha_pairs list directly: a list of (CircularPatchingConfig, float) tuples, one per pass. There is no automatic interpolation. This gives complete control over the schedule.

texture_transfer_guided_advanced — manual, proxy-scaled

Same as texture_transfer_advanced, but with one important distinction: configs must be specified in source-resolution units, not proxy-resolution units. The function scales them down to the proxy resolution internally. Expressing configs at source resolution keeps the schedule intuitive and consistent regardless of the proxy scale factor.