From 44f0e1b62a4855c14426aa3057eb78aa877694d8 Mon Sep 17 00:00:00 2001 From: Li Fengmin <2080291162@qq.com> Date: Fri, 31 Jul 2026 19:16:45 +0800 Subject: [PATCH] feat: bicubic resize (Catmull-Rom) --- bicubic.mbt | 79 ++++++++++++++++++++++++++++++++++++++++++++++ bicubic_test.mbt | 78 +++++++++++++++++++++++++++++++++++++++++++++ pkg.generated.mbti | 1 + 3 files changed, 158 insertions(+) create mode 100644 bicubic.mbt create mode 100644 bicubic_test.mbt diff --git a/bicubic.mbt b/bicubic.mbt new file mode 100644 index 0000000..2c8c054 --- /dev/null +++ b/bicubic.mbt @@ -0,0 +1,79 @@ +///| +/// Bicubic resize using the Catmull-Rom spline: each output pixel blends a +/// 4x4 source neighborhood with cubic weights, giving noticeably smoother +/// results than bilinear on photographic content while staying local. Uses +/// the same center-aligned sampling and clamp-to-edge policy as the other +/// resizers, so a same-size resize stays an identity up to rounding. + +///| +/// Catmull-Rom kernel weight for the four taps around fractional offset +/// `t` in [0, 1): returns the weights of taps at offsets -1, 0, +1, +2. +fn catmull_weights(t : Double) -> (Double, Double, Double, Double) { + let t2 = t * t + let t3 = t2 * t + // Standard Catmull-Rom (a = -0.5) expanded per tap. + let w0 = -0.5 * t3 + t2 - 0.5 * t + let w1 = 1.5 * t3 - 2.5 * t2 + 1.0 + let w2 = -1.5 * t3 + 2.0 * t2 + 0.5 * t + let w3 = 0.5 * t3 - 0.5 * t2 + (w0, w1, w2, w3) +} + +///| +/// Bicubic (Catmull-Rom) resize to `new_width` x `new_height` (both clamped +/// to at least 1). All four channels including alpha are interpolated; the +/// cubic can overshoot near edges, so results are clamped back to 0..255. +pub fn Image::resize_bicubic( + self : Image, + new_width : Int, + new_height : Int, +) -> Image { + let w = if new_width < 1 { 1 } else { new_width } + let h = if new_height < 1 { 1 } else { new_height } + let out = Image::new(w, h) + for y in 0.. 10 && v < 245 { + has_mid = true + } + } + assert_true(has_mid) + // Endpoints stay at the extremes. + assert_eq(cubic.get_pixel(0, 0).0.to_int(), 0) + assert_eq(cubic.get_pixel(15, 0).0.to_int(), 255) +} + +///| +test "bicubic clamps degenerate target sizes to 1x1" { + let img = solid(4, 4, 50, 60, 70, 255) + let out = img.resize_bicubic(0, -3) + assert_eq(out.width, 1) + assert_eq(out.height, 1) + assert_eq(out.get_pixel(0, 0).0.to_int(), 50) +} diff --git a/pkg.generated.mbti b/pkg.generated.mbti index 0972e41..a598a79 100644 --- a/pkg.generated.mbti +++ b/pkg.generated.mbti @@ -115,6 +115,7 @@ pub fn Image::pad(Self, Int, Int, Int, Int, Byte, Byte, Byte, Byte) -> Self pub fn Image::pixel_count(Self) -> Int pub fn Image::pixelate(Self, Int) -> Self pub fn Image::posterize(Self, Int) -> Self +pub fn Image::resize_bicubic(Self, Int, Int) -> Self pub fn Image::resize_bilinear(Self, Int, Int) -> Self pub fn Image::resize_nearest(Self, Int, Int) -> Self pub fn Image::rotate(Self, Double) -> Self