leptonica 0.4.0

Rust port of Leptonica image processing library
Documentation
//! Binary morphology regression test 5
//!
//! C version: prog/binmorph5_reg.c
//! Tests expanded DWA composite morph comparison.
//! Compares DWA composite operations with standard morph operations
//! for larger sizes (up to 240).
//!
//! Run with:
//! ```
//! cargo test -p leptonica-morph --test binmorph5_reg
//! ```

use crate::common::{RegParams, load_test_image};
use leptonica::PixelDepth;
use leptonica::morph::{
    close_brick, close_brick_dwa, dilate_brick, dilate_brick_dwa, erode_brick, erode_brick_dwa,
    open_brick, open_brick_dwa,
};

/// C version: PixCompareDwa()
#[allow(clippy::too_many_arguments)]
fn pix_compare_dwa(
    rp: &mut RegParams,
    size: u32,
    op_type: &str,
    pix1: &leptonica::Pix,
    pix2: &leptonica::Pix,
    pix3: &leptonica::Pix,
    pix4: &leptonica::Pix,
    pix5: &leptonica::Pix,
    pix6: &leptonica::Pix,
) {
    let same = pix1.equals(pix2);
    rp.compare_values(1.0, if same { 1.0 } else { 0.0 }, 0.0);
    if !same {
        eprintln!("{} ({}, 1) not same", op_type, size);
    }
    let same = pix3.equals(pix4);
    rp.compare_values(1.0, if same { 1.0 } else { 0.0 }, 0.0);
    if !same {
        eprintln!("{} (1, {}) not same", op_type, size);
    }
    let same = pix5.equals(pix6);
    rp.compare_values(1.0, if same { 1.0 } else { 0.0 }, 0.0);
    if !same {
        eprintln!("{} ({}, {}) not same", op_type, size, size);
    }
}

/// C版: DoComparisonDwa1() -- pixDilateCompBrickExtendDwa vs pixDilateCompBrick
/// Rust: pixDilateCompBrickExtendDwa, pixDilateCompBrick -- 未実装
/// 代用: DWA non-composite vs standard brick (同じ趣旨のテスト)
fn do_comparison_dwa1(rp: &mut RegParams, pixs: &leptonica::Pix, size: u32) {
    eprintln!("..{}..", size);

    // Dilation
    let pix1 = dilate_brick_dwa(pixs, size, 1).expect("dilate_brick_dwa h");
    let pix3 = dilate_brick_dwa(pixs, 1, size).expect("dilate_brick_dwa v");
    let pix5 = dilate_brick_dwa(pixs, size, size).expect("dilate_brick_dwa sq");
    let pix2 = dilate_brick(pixs, size, 1).expect("dilate_brick h");
    let pix4 = dilate_brick(pixs, 1, size).expect("dilate_brick v");
    let pix6 = dilate_brick(pixs, size, size).expect("dilate_brick sq");
    pix_compare_dwa(rp, size, "dilate", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);

    // Erosion
    let pix1 = erode_brick_dwa(pixs, size, 1).expect("erode_brick_dwa h");
    let pix3 = erode_brick_dwa(pixs, 1, size).expect("erode_brick_dwa v");
    let pix5 = erode_brick_dwa(pixs, size, size).expect("erode_brick_dwa sq");
    let pix2 = erode_brick(pixs, size, 1).expect("erode_brick h");
    let pix4 = erode_brick(pixs, 1, size).expect("erode_brick v");
    let pix6 = erode_brick(pixs, size, size).expect("erode_brick sq");
    pix_compare_dwa(rp, size, "erode", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);

    // Opening
    let pix1 = open_brick_dwa(pixs, size, 1).expect("open_brick_dwa h");
    let pix3 = open_brick_dwa(pixs, 1, size).expect("open_brick_dwa v");
    let pix5 = open_brick_dwa(pixs, size, size).expect("open_brick_dwa sq");
    let pix2 = open_brick(pixs, size, 1).expect("open_brick h");
    let pix4 = open_brick(pixs, 1, size).expect("open_brick v");
    let pix6 = open_brick(pixs, size, size).expect("open_brick sq");
    pix_compare_dwa(rp, size, "open", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);

    // Closing
    // C版: pixCloseCompBrickExtendDwa vs pixCloseSafeCompBrick -- 未実装のためclose_brick_dwa vs close_brickで代用
    let pix1 = close_brick_dwa(pixs, size, 1).expect("close_brick_dwa h");
    let pix3 = close_brick_dwa(pixs, 1, size).expect("close_brick_dwa v");
    let pix5 = close_brick_dwa(pixs, size, size).expect("close_brick_dwa sq");
    let pix2 = close_brick(pixs, size, 1).expect("close_brick h");
    let pix4 = close_brick(pixs, 1, size).expect("close_brick v");
    let pix6 = close_brick(pixs, size, size).expect("close_brick sq");
    pix_compare_dwa(rp, size, "close", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);
}

/// C版: DoComparisonDwa2() -- pixDilateCompBrickExtendDwa vs pixDilateBrick (exactly decomposable sizes)
/// 同様にDWA vs brickで代用
fn do_comparison_dwa2(rp: &mut RegParams, pixs: &leptonica::Pix, size: u32) {
    eprintln!("..{}..", size);

    // Dilation
    let pix1 = dilate_brick_dwa(pixs, size, 1).expect("dilate_brick_dwa h");
    let pix3 = dilate_brick_dwa(pixs, 1, size).expect("dilate_brick_dwa v");
    let pix5 = dilate_brick_dwa(pixs, size, size).expect("dilate_brick_dwa sq");
    let pix2 = dilate_brick(pixs, size, 1).expect("dilate_brick h");
    let pix4 = dilate_brick(pixs, 1, size).expect("dilate_brick v");
    let pix6 = dilate_brick(pixs, size, size).expect("dilate_brick sq");
    pix_compare_dwa(rp, size, "dilate", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);

    // Erosion
    let pix1 = erode_brick_dwa(pixs, size, 1).expect("erode_brick_dwa h");
    let pix3 = erode_brick_dwa(pixs, 1, size).expect("erode_brick_dwa v");
    let pix5 = erode_brick_dwa(pixs, size, size).expect("erode_brick_dwa sq");
    let pix2 = erode_brick(pixs, size, 1).expect("erode_brick h");
    let pix4 = erode_brick(pixs, 1, size).expect("erode_brick v");
    let pix6 = erode_brick(pixs, size, size).expect("erode_brick sq");
    pix_compare_dwa(rp, size, "erode", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);

    // Opening
    let pix1 = open_brick_dwa(pixs, size, 1).expect("open_brick_dwa h");
    let pix3 = open_brick_dwa(pixs, 1, size).expect("open_brick_dwa v");
    let pix5 = open_brick_dwa(pixs, size, size).expect("open_brick_dwa sq");
    let pix2 = open_brick(pixs, size, 1).expect("open_brick h");
    let pix4 = open_brick(pixs, 1, size).expect("open_brick v");
    let pix6 = open_brick(pixs, size, size).expect("open_brick sq");
    pix_compare_dwa(rp, size, "open", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);

    // Closing
    // C版: pixCloseSafeBrick -- 未実装のためclose_brickで代用
    let pix1 = close_brick_dwa(pixs, size, 1).expect("close_brick_dwa h");
    let pix3 = close_brick_dwa(pixs, 1, size).expect("close_brick_dwa v");
    let pix5 = close_brick_dwa(pixs, size, size).expect("close_brick_dwa sq");
    let pix2 = close_brick(pixs, size, 1).expect("close_brick h");
    let pix4 = close_brick(pixs, 1, size).expect("close_brick v");
    let pix6 = close_brick(pixs, size, size).expect("close_brick sq");
    pix_compare_dwa(rp, size, "close", &pix1, &pix2, &pix3, &pix4, &pix5, &pix6);
}

/// C版: selectComposableSizes() -- Rust未実装
/// Simple approximation: returns factor pair whose product is close to input size
fn select_composable_sizes(size: u32) -> (u32, u32) {
    // Find the factor pair closest to sqrt(size)
    let sqrt = (size as f64).sqrt() as u32;
    for f1 in (2..=sqrt).rev() {
        if size.is_multiple_of(f1) {
            return (f1, size / f1);
        }
    }
    (1, size)
}

#[test]
fn binmorph5_reg() {
    let mut rp = RegParams::new("binmorph5");

    let pixs = load_test_image("feyn-fract.tif").expect("load feyn-fract.tif");
    assert_eq!(pixs.depth(), PixelDepth::Bit1);

    // C版: TestAll(rp, pixs, FALSE) -- asymmetric boundary conditions
    eprintln!("Testing with asymmetric boundary conditions");

    // C版: FASTER_TEST -- selectComposableSizes for i in 65..240
    // C版: getExtendedCompositeParameters() -- Rust未実装
    // We test a representative subset of sizes matching the C test's intent
    // (DWA extended composite vs standard morph for large sizes)
    let faster_test_sizes: Vec<u32> = (65..100)
        .filter(|&i| {
            let (f1, f2) = select_composable_sizes(i);
            // Exclude the (1, size) fallback for primes, which are not
            // truly composable and would inflate runtime unnecessarily
            f1 > 1 && f1 * f2 == i
        })
        .collect();

    for &size in &faster_test_sizes {
        do_comparison_dwa1(&mut rp, &pixs, size);
    }

    // C版: SLOWER_TEST -- getExtendedCompositeParameters for i in 65..199
    // Test a subset of larger sizes from the slower test
    let slower_test_sizes = [65, 70, 77, 84, 91, 98, 100, 105, 110, 120];
    for &size in &slower_test_sizes {
        do_comparison_dwa2(&mut rp, &pixs, size);
    }

    // C版: TestAll(rp, pixs, TRUE) -- symmetric boundary conditions
    // C版: resetMorphBoundaryCondition(SYMMETRIC_MORPH_BC) -- Rust未実装のためスキップ
    // C版: pixAddBorder(pixs, 128, 0) -- Rust未実装のためスキップ
    // C版: pixTransferAllData() -- Rust未実装のためスキップ

    eprintln!();
    assert!(rp.cleanup(), "binmorph5 regression test failed");
}