mtmd: add lanczos resize method [no release] (#26341)

This commit is contained in:
Xuan-Son Nguyen
2026-07-30 21:59:49 +02:00
committed by GitHub
parent b4ca032ae3
commit 5f55650a78
2 changed files with 51 additions and 11 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ enum resize_algo {
RESIZE_ALGO_BILINEAR, // stretch to target resolution RESIZE_ALGO_BILINEAR, // stretch to target resolution
RESIZE_ALGO_BICUBIC, // center-crop when aspect ratio doesn't match RESIZE_ALGO_BICUBIC, // center-crop when aspect ratio doesn't match
RESIZE_ALGO_BICUBIC_PILLOW, RESIZE_ALGO_BICUBIC_PILLOW,
// RESIZE_ALGO_LANCZOS, // TODO RESIZE_ALGO_LANCZOS,
}; };
// Padding style for img_tool::resize // Padding style for img_tool::resize
+50 -10
View File
@@ -68,6 +68,9 @@ struct img_tool {
case RESIZE_ALGO_BICUBIC_PILLOW: case RESIZE_ALGO_BICUBIC_PILLOW:
resize_bicubic_pillow(src, dst, target_resolution.width, target_resolution.height); resize_bicubic_pillow(src, dst, target_resolution.width, target_resolution.height);
break; break;
case RESIZE_ALGO_LANCZOS:
resize_lanczos_pillow(src, dst, target_resolution.width, target_resolution.height);
break;
default: default:
throw std::runtime_error("Unsupported resize algorithm"); throw std::runtime_error("Unsupported resize algorithm");
} }
@@ -97,6 +100,9 @@ struct img_tool {
case RESIZE_ALGO_BICUBIC_PILLOW: case RESIZE_ALGO_BICUBIC_PILLOW:
resize_bicubic_pillow(src, resized_image, new_width, new_height); resize_bicubic_pillow(src, resized_image, new_width, new_height);
break; break;
case RESIZE_ALGO_LANCZOS:
resize_lanczos_pillow(src, resized_image, new_width, new_height);
break;
default: default:
throw std::runtime_error("Unsupported resize algorithm"); throw std::runtime_error("Unsupported resize algorithm");
} }
@@ -337,22 +343,50 @@ private:
} }
} }
// Bicubic resize function using Pillow's ImagingResample algorithm // Pillow-compatible separable resampling (Bicubic and Lanczos)
// Adapted from https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Resample.c // Adapted from https://github.com/python-pillow/Pillow/blob/main/src/libImaging/Resample.c
// //
// Key Difference with resize_bicubic: // Key properties:
// 1. Uses separable filtering: horizontal pass followed by vertical pass // 1. Separable filtering: horizontal pass followed by vertical pass
// 2. Pre-computes normalized filter coefficients for each output pixel // 2. Pre-computes normalized filter coefficients for each output pixel
// 3. Applies convolution using fixed-point integer arithmetic for performance // 3. Fixed-point integer arithmetic (22 fractional bits) for speed and determinism
static bool resize_bicubic_pillow(const clip_image_u8 & img, clip_image_u8 & dst, int target_width, int target_height) { static bool resize_bicubic_pillow(const clip_image_u8 & img, clip_image_u8 & dst, int target_width, int target_height) {
return resize_pillow(img, dst, target_width, target_height, /*use_lanczos=*/false);
}
// Lanczos-3 (support radius 3), matches Pillow's Image.LANCZOS
static bool resize_lanczos_pillow(const clip_image_u8 & img, clip_image_u8 & dst, int target_width, int target_height) {
return resize_pillow(img, dst, target_width, target_height, /*use_lanczos=*/true);
}
static bool resize_pillow(
const clip_image_u8 & img,
clip_image_u8 & dst,
int target_width,
int target_height,
bool use_lanczos) {
// Fixed-point precision: 22 bits = 32 (int32_t) - 8 (uint8_t pixels) - 2 (headroom for accumulation) // Fixed-point precision: 22 bits = 32 (int32_t) - 8 (uint8_t pixels) - 2 (headroom for accumulation)
// This allows encoding fractional weights as integers: weight * 2^22 // This allows encoding fractional weights as integers: weight * 2^22
const int PRECISION_BITS = 32 - 8 - 2; const int PRECISION_BITS = 32 - 8 - 2;
// Bicubic filter function with a = -0.5 (Note that GGML/PyTorch takes a = -0.75) // Resample filter: Lanczos-3 (support [-3, 3]) or bicubic with a = -0.5 (support [-2, 2])
// Note: GGML/PyTorch bicubic uses a = -0.75, Pillow uses a = -0.5
// Returns filter weight for distance x from pixel center // Returns filter weight for distance x from pixel center
// Support: [-2, 2], meaning the filter influences pixels within 2 units of distance auto resample_filter = [use_lanczos](double x) -> double {
auto bicubic_filter = [](double x) -> double { if (use_lanczos) {
if (-3.0 <= x && x < 3.0) {
auto sinc = [](double v) {
if (v == 0.0) {
return 1.0;
}
const double pi_v = v * 3.141592653589793238462643383279502884;
return std::sin(pi_v) / pi_v;
};
return sinc(x) * sinc(x / 3.0);
}
return 0.0;
}
constexpr double a = -0.5; constexpr double a = -0.5;
if (x < 0.0) { if (x < 0.0) {
x = -x; x = -x;
@@ -366,8 +400,8 @@ private:
return 0.0; // Zero outside [-2, 2] return 0.0; // Zero outside [-2, 2]
}; };
// Filter support radius: bicubic extends 2 pixels in each direction // Filter support radius: 2 for bicubic, 3 for lanczos
constexpr double filter_support = 2.0; const double filter_support = use_lanczos ? 3.0 : 2.0;
// Clipping function for 8-bit values // Clipping function for 8-bit values
auto clip8 = [](int val) -> uint8_t { auto clip8 = [](int val) -> uint8_t {
@@ -434,7 +468,7 @@ private:
// Compute filter weights for each contributing input pixel // Compute filter weights for each contributing input pixel
for (x = 0; x < xmax; x++) { for (x = 0; x < xmax; x++) {
// Distance from input pixel center to output pixel center in input space // Distance from input pixel center to output pixel center in input space
double w = bicubic_filter((x + xmin - center + 0.5) * ss); double w = resample_filter((x + xmin - center + 0.5) * ss);
pre_weights[xx * ksize + x] = w; pre_weights[xx * ksize + x] = w;
ww += w; // Accumulate for normalization ww += w; // Accumulate for normalization
} }
@@ -463,6 +497,12 @@ private:
const double fxp_scale = std::ldexp(1.0, PRECISION_BITS); // 1.0 * 2^PRECISION_BITS const double fxp_scale = std::ldexp(1.0, PRECISION_BITS); // 1.0 * 2^PRECISION_BITS
for (int i = 0; i < outSize * ksize; i++) { for (int i = 0; i < outSize * ksize; i++) {
if (use_lanczos) {
// Pillow adds +/- 0.5 then truncates toward zero; std::round would round twice
const double rounded = pre_weights[i] * fxp_scale + (pre_weights[i] < 0 ? -0.5 : 0.5);
weights[i] = static_cast<int32_t>(rounded);
continue;
}
double tmp_val = pre_weights[i] * fxp_scale; double tmp_val = pre_weights[i] * fxp_scale;
if (pre_weights[i] < 0) { if (pre_weights[i] < 0) {
tmp_val -= 0.5; tmp_val -= 0.5;