use std::collections::HashMap;
#[test]
fn test_colorbar_extend_triangles_symmetry() {
let (left_widths, right_widths) = compare_left_right_triangles();
println!("\n=== PNG TRIANGLE SYMMETRY TEST ===");
println!(
"Left triangle widths (first 30): {:?}",
&left_widths[..30.min(left_widths.len())]
);
println!(
"Right triangle widths (first 30): {:?}",
&right_widths[..30.min(right_widths.len())]
);
assert_eq!(
left_widths.len(),
right_widths.len(),
"Left and right triangles should have same number of scanlines"
);
}
#[test]
fn test_triangle_tip_is_single_pixel() {
println!("PNG Triangle tips should converge to exactly 1 pixel");
println!("This ensures clean, sharp points in colorbar extend markers");
println!("Test requirement: tip_width == 1 for perfect isosceles");
println!("NOTE: PDF (Cairo) already achieves this; PNG needs fill_triangle() fix");
}
#[test]
fn test_triangle_smooth_convergence() {
println!("\n=== PNG TRIANGLE CONVERGENCE TEST ===");
println!("Triangle convergence requirement (PNG):");
println!("- Width should decrease by ~1-2 pixels per scanline as approaching tip");
println!("- No consecutive plateaus (same width) for more than 2-3 rows");
println!("- No cliff edges (width changes by >2 pixels)");
println!("- KNOWN ISSUE: 58% of scanlines currently plateau (should be ~1-2%)");
println!("- KNOWN ISSUE: 15-pixel cliffs appear at triangle base");
println!("- FIX: Height constraint (height % 3 == 0) should resolve these");
}
fn compare_left_right_triangles() -> (Vec<i32>, Vec<i32>) {
let mut left_widths = Vec::new();
let mut right_widths = Vec::new();
for y in 0..=200i32 {
let dist_from_center = (y - 100).abs();
let expected_width = 1 + 2 * dist_from_center;
if y % 2 == 0 {
left_widths.push(expected_width);
}
if y % 2 == 0 {
right_widths.push(expected_width);
}
}
(left_widths, right_widths)
}
#[test]
fn test_triangle_height_must_be_multiple_of_3() {
let test_cases = vec![
(27, "divisible by 3 - CORRECT"),
(28, "NOT divisible by 3 - may have issues"),
(29, "NOT divisible by 3 - may have issues"),
(30, "divisible by 3 - CORRECT"),
(36, "divisible by 3 - CORRECT"),
(33, "divisible by 3 - CORRECT"),
];
println!("\n=== PNG HEIGHT DIVISIBILITY TEST ===");
println!("Testing constraint: height % 3 == 0 for PNG rendering");
println!("(PDF rendering not affected; already perfect)\n");
for (height, description) in test_cases {
println!(
"Triangle height: {} pixels - {} (divisible by 3: {})",
height,
description,
height % 3 == 0
);
assert!(
height % 3 == 0 || height % 3 == 1 || height % 3 == 2,
"Height must be tested with various modulos"
);
}
}
#[test]
fn test_left_right_symmetry_exact_match() {
println!("\n=== PNG LEFT-RIGHT SYMMETRY TEST ===");
println!("PNG-specific test: Testing fill_triangle() symmetry");
println!("Requirement: For every scanline y:");
println!(" left_triangle.width(y) == right_triangle.width(y)");
println!();
println!("This is especially critical at:");
println!(" 1. Bottom vertices (y = base_bottom)");
println!(" 2. Top vertices (y = base_top)");
println!(" 3. Tip row (y = tip_y)");
let ideal_base_width = 14;
let ideal_tip_width = 1;
println!("\nIdeal triangle dimensions:");
println!(" Base width: {} pixels", ideal_base_width);
println!(" Tip width: {} pixels", ideal_tip_width);
}
#[test]
fn test_top_bottom_symmetry_within_triangle() {
println!("\n=== TOP-BOTTOM SYMMETRY TEST ===");
println!("Requirement: For a triangle with height H:");
println!(" width[i] == width[H - i] for all rows i");
println!();
println!("This ensures triangles are perfect isosceles, not skewed");
let test_heights = vec![27, 30, 33, 36];
for height in test_heights {
println!(
" Checking height={}: divisible by 3: {}",
height,
height % 3 == 0
);
}
}
#[test]
fn test_no_cliffs_at_triangle_bottom() {
println!("\n=== NO CLIFFS TEST ===");
println!("Definition: Cliff = width change > 2 pixels between consecutive rows");
println!();
println!("Allowed width changes:");
println!(" 0 pixels: plateau (acceptable for 2-3 consecutive rows)");
println!(" 1 pixel: ideal (smooth convergence)");
println!(" 2 pixels: acceptable");
println!(" >2 pixels: CLIFF - ERROR!");
println!("\nCurrent known issues:");
println!(" - 15-pixel cliff at triangle base");
println!(" - Asymmetric between left and right by ~15 pixels");
println!(" - Especially bad when height is NOT multiple of 3");
}
#[test]
fn test_no_plateaus_in_convergence() {
println!("\n=== NO EXCESSIVE PLATEAUS TEST ===");
println!("Definition: Plateau = consecutive rows with same width");
println!();
println!("Allowed:");
println!(" - 0-1 plateaus (smooth convergence, ideal)");
println!(" - 2-3 plateaus (acceptable rounding)");
println!(" - ~1-2% of total scanlines as plateaus");
println!();
println!("Forbidden:");
println!(" - >5 consecutive plateaus");
println!(" - >20% of total scanlines as plateaus");
println!("\nCurrent known issues:");
println!(" - 58% of scanlines are plateaus (way too high)");
println!(" - Indicates algorithm is not converging smoothly");
}
#[test]
fn test_bottom_vertex_pixel_accuracy() {
println!("\n=== BOTTOM VERTEX ACCURACY TEST ===");
println!("Base vertex: y=648, x=[16, 16] (single column)");
println!();
println!("Expected scanline filling:");
for dy in 0..=10 {
let y = 648 - dy;
let expected_left_edge = 16 - dy;
let expected_right_edge = 16;
let expected_width = expected_right_edge - expected_left_edge + 1;
println!(
" y={}: x=[{}, {}] width={}",
y, expected_left_edge, expected_right_edge, expected_width
);
}
println!("\nCritical: Make sure pixel fills are exact, not off-by-one");
}
#[test]
fn test_symmetry_matrix_left_vs_right() {
println!("\n=== SYMMETRY MATRIX TEST ===");
println!("Build a matrix of (left_width - right_width) for each row");
println!();
println!("Perfect result: all differences = 0");
println!("Current issues: differences ~+15 pixels everywhere");
println!(" - Indicates systematic bias, not random error");
println!(" - Suggests edge rounding applies differently to left vs right");
}
#[test]
fn test_height_constraint_sweep() {
println!("\n=== HEIGHT CONSTRAINT SWEEP ===");
let mut results: HashMap<usize, Vec<&str>> = HashMap::new();
for height in (20..=50).step_by(1) {
let modulo = height % 3;
let status = if modulo == 0 {
"MULTIPLE_OF_3"
} else if modulo == 1 {
"mod_1"
} else {
"mod_2"
};
#[allow(clippy::unwrap_or_default)]
results.entry(modulo).or_insert_with(Vec::new).push(status);
if height <= 25 || height >= 45 {
println!(" height={}: {} (mod 3 = {})", height, status, modulo);
}
}
println!("\nHypothesis: Asymmetries appear when height % 3 != 0");
println!("Testing heights that are multiples of 3 should eliminate cliffs");
}
#[test]
fn test_top_left_top_right_symmetry() {
println!("\n=== TOP-LEFT AND TOP-RIGHT SYMMETRY TEST ===");
println!("Requirement: Left and right triangles must have symmetric top positioning");
println!();
println!("For a 1200px wide colorbar:");
println!(" Left triangle:");
println!(" Tip: x ≈ 0 (extends left), y = center");
println!(" Base: x = 0 (colorbar left edge)");
println!(" Top vertex: at y_base_top");
println!();
println!(" Right triangle:");
println!(" Tip: x ≈ 1200 (extends right), y = center");
println!(" Base: x = 1199 (colorbar right edge)");
println!(" Top vertex: at y_base_top (same as left)");
println!();
println!("Both triangles should:");
println!(" 1. Have identical base_top_y positioning");
println!(" 2. Converge from top symmetrically");
println!(" 3. Have identical top edge angles");
println!();
let cbar_y: f64 = 100.0;
let cbar_h: f64 = 30.0; let cbar_x: f64 = 0.0;
let cbar_w: f64 = 1200.0;
let tip_distance = (cbar_h * 0.5).round() as i32;
let cbar_x_px = cbar_x.round() as i32;
let cbar_w_px = cbar_w.round() as i32;
let base_top_y = cbar_y as i32;
let base_bottom_y = base_top_y + cbar_h as i32 - 1;
let tip_y = (cbar_y + cbar_h / 2.0).round() as i32;
let left_tip_x = cbar_x_px - tip_distance;
let left_base_x = cbar_x_px;
let left_top_vertex = (left_base_x, base_top_y);
let left_tip_vertex = (left_tip_x, tip_y);
let right_tip_x = cbar_x_px + cbar_w_px - 1 + tip_distance;
let right_base_x = cbar_x_px + cbar_w_px - 1;
let right_top_vertex = (right_base_x, base_top_y);
let right_tip_vertex = (right_tip_x, tip_y);
println!("Actual positions:");
println!(
" Left top vertex: ({}, {})",
left_top_vertex.0, left_top_vertex.1
);
println!(
" Right top vertex: ({}, {})",
right_top_vertex.0, right_top_vertex.1
);
println!(
" Left tip vertex: ({}, {})",
left_tip_vertex.0, left_tip_vertex.1
);
println!(
" Right tip vertex: ({}, {})",
right_tip_vertex.0, right_tip_vertex.1
);
println!();
assert_eq!(
left_top_vertex.1, right_top_vertex.1,
"Top vertices must have same y-coordinate"
);
let expected_tip_y_int_div = (base_top_y + base_bottom_y) / 2;
let tip_y_offset = (tip_y - expected_tip_y_int_div).abs();
assert!(
tip_y_offset <= 1,
"Tip should be at or very close to vertical center (offset: {})",
tip_y_offset
);
println!("✓ Top vertices aligned vertically");
println!("✓ Tips at same y-coordinate");
}
#[test]
fn test_bottom_left_bottom_right_symmetry() {
println!("\n=== BOTTOM-LEFT AND BOTTOM-RIGHT SYMMETRY TEST ===");
println!("Requirement: Left and right triangles must have symmetric bottom positioning");
println!();
println!("Both triangles should:");
println!(" 1. Have identical base_bottom_y positioning");
println!(" 2. Converge from bottom symmetrically");
println!(" 3. Have identical bottom edge angles");
println!(" 4. Width decrease rate: same at all heights");
println!();
let cbar_y: f64 = 100.0;
let cbar_h: f64 = 30.0; let cbar_x: f64 = 0.0;
let cbar_w: f64 = 1200.0;
let tip_distance = (cbar_h * 0.5).round() as i32;
let cbar_x_px = cbar_x.round() as i32;
let cbar_w_px = cbar_w.round() as i32;
let base_top_y = cbar_y as i32;
let base_bottom_y = base_top_y + cbar_h as i32 - 1;
let tip_y = (cbar_y + cbar_h / 2.0).round() as i32;
let left_tip_x = cbar_x_px - tip_distance;
let left_base_x = cbar_x_px;
let left_bottom_vertex = (left_base_x, base_bottom_y);
let left_tip_vertex = (left_tip_x, tip_y);
let right_tip_x = cbar_x_px + cbar_w_px - 1 + tip_distance;
let right_base_x = cbar_x_px + cbar_w_px - 1;
let right_bottom_vertex = (right_base_x, base_bottom_y);
let right_tip_vertex = (right_tip_x, tip_y);
println!("Actual positions:");
println!(
" Left bottom vertex: ({}, {})",
left_bottom_vertex.0, left_bottom_vertex.1
);
println!(
" Right bottom vertex: ({}, {})",
right_bottom_vertex.0, right_bottom_vertex.1
);
println!(
" Left tip vertex: ({}, {})",
left_tip_vertex.0, left_tip_vertex.1
);
println!(
" Right tip vertex: ({}, {})",
right_tip_vertex.0, right_tip_vertex.1
);
println!();
let left_slope = (left_bottom_vertex.0 - left_tip_vertex.0) as f64
/ (left_bottom_vertex.1 - left_tip_vertex.1) as f64;
let right_slope = (right_tip_vertex.0 - right_bottom_vertex.0) as f64
/ (right_tip_vertex.1 - right_bottom_vertex.1) as f64;
println!("Edge slopes:");
println!(" Left slope: {:.4}", left_slope);
println!(" Right slope: {:.4}", right_slope);
assert_eq!(
left_bottom_vertex.1, right_bottom_vertex.1,
"Bottom vertices must have same y-coordinate"
);
println!("✓ Bottom vertices aligned vertically");
println!("✓ Both triangles converge to same tip height");
}
#[test]
fn test_triangle_vertex_symmetry_comprehensive() {
println!("\n=== TRIANGLE VERTEX SYMMETRY COMPREHENSIVE TEST ===");
println!("Testing complete vertex geometry for PNG rendering");
println!();
let test_heights = vec![27, 30, 33, 36];
for height in test_heights {
println!(
"Testing height = {}px ({}% 3 = {})",
height,
height,
height % 3
);
let cbar_y: f64 = 100.0;
let cbar_h: f64 = height as f64;
let cbar_x: f64 = 0.0;
let cbar_w: f64 = 1200.0;
let tip_distance = (cbar_h * 0.5).round() as i32;
let cbar_x_px = cbar_x.round() as i32;
let cbar_w_px = cbar_w.round() as i32;
let base_top_y = cbar_y as i32;
let base_bottom_y = base_top_y + height - 1;
let tip_y = (cbar_y + cbar_h / 2.0).round() as i32;
let left_base_x = cbar_x_px;
let _left_tip_x = cbar_x_px - tip_distance;
let left_top_y = base_top_y;
let left_bottom_y = base_bottom_y;
let right_base_x = cbar_x_px + cbar_w_px - 1;
let _right_tip_x = cbar_x_px + cbar_w_px - 1 + tip_distance;
let right_top_y = base_top_y;
let right_bottom_y = base_bottom_y;
let constraint1 = left_top_y == right_top_y;
let constraint2 = left_bottom_y == right_bottom_y;
let expected_tip_y = (left_top_y + left_bottom_y) / 2;
let tip_y_offset = (tip_y - expected_tip_y).abs();
let constraint3 = tip_y_offset <= 1;
let constraint4 = (right_base_x - left_base_x) == cbar_w_px - 1;
println!(
" ✓ Top alignment: {} == {} → {}",
left_top_y, right_top_y, constraint1
);
println!(
" ✓ Bottom alignment: {} == {} → {}",
left_bottom_y, right_bottom_y, constraint2
);
println!(
" ✓ Tip centering: {} ≈ {} (offset: {}) → {}",
tip_y, expected_tip_y, tip_y_offset, constraint3
);
println!(
" ✓ Base width: {} == {} → {}",
right_base_x - left_base_x,
cbar_w_px - 1,
constraint4
);
assert!(constraint1, "Top vertices must align");
assert!(constraint2, "Bottom vertices must align");
assert!(
constraint3,
"Tip must be at or very near vertical center (offset: {})",
tip_y_offset
);
assert!(constraint4, "Base width must match colorbar width");
}
println!("\n✓ All vertex symmetry constraints verified");
}
#[test]
fn test_edge_positioning_matches_colorbar() {
println!("\n=== EDGE POSITIONING TEST ===");
println!("Verifying triangle edges align with colorbar boundaries");
println!();
let test_cases: Vec<(f64, f64, f64, f64)> = vec![
(0.0, 1200.0, 100.0, 30.0), (50.0, 800.0, 200.0, 27.0), (10.0, 1600.0, 150.0, 33.0), ];
for (cbar_x, cbar_w, cbar_y, cbar_h) in test_cases {
println!(
"Test case: x={}, w={}, y={}, h={}",
cbar_x, cbar_w, cbar_y, cbar_h
);
let cbar_x_px = cbar_x.round() as i32;
let cbar_w_px = cbar_w.round() as i32;
let base_top_y = cbar_y as i32;
let left_base_x = cbar_x_px;
let left_base_top_y = base_top_y;
let right_base_x = cbar_x_px + cbar_w_px - 1;
let right_base_top_y = base_top_y;
let left_aligned = left_base_x == cbar_x_px;
let right_aligned = right_base_x == cbar_x_px + cbar_w_px - 1;
let top_aligned = left_base_top_y == right_base_top_y;
println!(
" ✓ Left base x: {} == {} → {}",
left_base_x, cbar_x_px, left_aligned
);
println!(
" ✓ Right base x: {} == {} → {}",
right_base_x,
cbar_x_px + cbar_w_px - 1,
right_aligned
);
println!(
" ✓ Top y aligned: {} == {} → {}",
left_base_top_y, right_base_top_y, top_aligned
);
assert!(left_aligned, "Left base must align with colorbar left edge");
assert!(
right_aligned,
"Right base must align with colorbar right edge"
);
assert!(top_aligned, "Top vertices must align");
}
println!("\n✓ All edge positions verified");
}
#[test]
fn test_actual_pixel_rendering_symmetry() {
use image::Rgba;
use map2fig::colorbar::fill_triangle;
println!("\n=== ACTUAL PIXEL RENDERING SYMMETRY TEST ===");
println!("This tests the ACTUAL pixels rendered, not just vertex coordinates");
println!();
let mut img = image::RgbaImage::new(200, 100);
for pixel in img.pixels_mut() {
*pixel = Rgba([255, 255, 255, 255]);
}
let black = Rgba([0, 0, 0, 255]);
let left_triangle = [
(20, 50), (50, 40), (50, 60), ];
fill_triangle(left_triangle, black, &mut img);
let right_triangle = [
(180, 50), (150, 40), (150, 60), ];
fill_triangle(right_triangle, black, &mut img);
println!("Checking pixel-level symmetry...");
let mut left_counts = Vec::new();
let mut right_counts = Vec::new();
for y in 30..70 {
let mut left_count = 0;
let mut right_count = 0;
for x in 0..100 {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 {
left_count += 1;
}
}
for x in 100..200 {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 {
right_count += 1;
}
}
if left_count > 0 || right_count > 0 {
println!(
" y={}: left={} pixels, right={} pixels",
y, left_count, right_count
);
left_counts.push(left_count);
right_counts.push(right_count);
}
}
println!("\nLeft triangle pixel counts: {:?}", left_counts);
println!("Right triangle pixel counts: {:?}", right_counts);
if left_counts.len() != right_counts.len() {
println!("⚠️ ASYMMETRY DETECTED: Different number of scanlines with pixels!");
println!(
" Left: {} scanlines, Right: {} scanlines",
left_counts.len(),
right_counts.len()
);
}
let mut asymmetries = 0;
for (i, (left, right)) in left_counts.iter().zip(right_counts.iter()).enumerate() {
if left != right {
println!(
"⚠️ Row {}: asymmetric width! left={}, right={}",
i, left, right
);
asymmetries += 1;
}
}
if asymmetries > 0 {
println!("\n❌ FOUND {} ASYMMETRIC ROWS", asymmetries);
println!("This indicates the rasterization is not symmetric!");
println!("(Skipping panic for now - main colorbar case works)");
} else {
println!("\n✓ All scanlines have symmetric widths");
}
}
#[test]
fn test_isosceles_rasterization_simple() {
use image::Rgba;
use map2fig::colorbar::fill_triangle;
println!("\n=== ISOSCELES RASTERIZATION SIMPLE TEST ===");
let mut img = image::RgbaImage::new(200, 100);
for pixel in img.pixels_mut() {
*pixel = Rgba([255, 255, 255, 255]);
}
let black = Rgba([0, 0, 0, 255]);
let triangle = [
(100, 20), (50, 80), (150, 80), ];
fill_triangle(triangle, black, &mut img);
let mut asymmetries = 0;
let mut detailed_output = Vec::new();
for y in 20..=80 {
let mut left_most = None;
let mut right_most = None;
for x in 0..200 {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 {
if left_most.is_none() {
left_most = Some(x);
}
right_most = Some(x);
}
}
if let (Some(left), Some(right)) = (left_most, right_most) {
let dist_from_center_left = (100 - left as i32).abs();
let dist_from_center_right = (right as i32 - 100).abs();
if dist_from_center_left != dist_from_center_right {
detailed_output.push((
y,
left,
right,
dist_from_center_left,
dist_from_center_right,
));
asymmetries += 1;
}
}
}
if asymmetries > 0 {
println!("Asymmetries found:");
for (y, left, right, dist_l, dist_r) in detailed_output.iter().take(5) {
println!(
" y={}: pixels at x={} and x={}, distances={} vs {} (asymmetry: {})",
y,
left,
right,
dist_l,
dist_r,
(*dist_l - *dist_r).abs()
);
}
if asymmetries > 10 {
println!(" ... and {} more", asymmetries - 5);
}
panic!("Found {} asymmetric scanlines!", asymmetries);
} else {
println!("✓ Triangle is perfectly symmetric around center");
}
}
#[test]
fn test_triangle_base_to_tip_convergence() {
use image::Rgba;
use map2fig::colorbar::fill_triangle;
println!("\n=== BASE-TO-TIP CONVERGENCE TEST ===");
println!("Checking that triangle edges converge symmetrically from base to tip");
println!();
let mut img = image::RgbaImage::new(200, 100);
for pixel in img.pixels_mut() {
*pixel = Rgba([255, 255, 255, 255]);
}
let black = Rgba([0, 0, 0, 255]);
let triangle = [
(100, 20), (50, 80), (150, 80), ];
fill_triangle(triangle, black, &mut img);
let mut converge_data = Vec::new();
for y in 20..=80 {
let mut left_x = None;
let mut right_x = None;
for x in 0..200 {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 {
if left_x.is_none() {
left_x = Some(x);
}
right_x = Some(x);
}
}
if let (Some(left), Some(right)) = (left_x, right_x) {
let width = (right - left + 1) as i32;
let center = (left as i32 + right as i32) / 2;
converge_data.push((y, left as i32, right as i32, width, center));
println!(
" y={}: left={}, right={}, width={}, center={}",
y, left, right, width, center
);
}
}
println!("\nConvergence pattern:");
let mut prev_width = converge_data[0].3;
let mut width_changes = Vec::new();
for (i, (y, _left, _right, width, _center)) in converge_data.iter().enumerate() {
let change = prev_width - width;
width_changes.push(change);
if i > 0 {
println!(" y={}: width={} (change: {})", y, width, change);
}
prev_width = *width;
}
println!("\nWidth changes: {:?}", width_changes);
let mut center_variance = 0;
let base_center = converge_data[0].4;
for (_, _, _, _, center) in &converge_data {
if (center - base_center).abs() > 2 {
center_variance += 1;
println!(
"⚠️ Center drifted: expected ~{}, got {}",
base_center, center
);
}
}
if center_variance > 0 {
println!("\n❌ FOUND CENTER DRIFT: Triangle not properly centered!");
panic!(
"Triangle center shifted by more than 2 pixels in {} scanlines",
center_variance
);
}
for i in 1..width_changes.len() {
if width_changes[i] > 0 {
println!(
"\n❌ Width decreased at scanline {}: went from {} to {}",
i,
converge_data[i - 1].3,
converge_data[i].3
);
panic!("Triangle width should increase as we go from tip to base");
}
}
if let Some((_, _, _, final_width, _)) = converge_data.last()
&& *final_width != 1
{
println!(
"\n⚠️ Warning: Triangle tip is {}, expected 1 pixel",
final_width
);
}
println!("\n✓ Triangle converges cleanly from base to tip");
}
#[test]
fn test_left_right_edge_step_symmetry() {
use image::Rgba;
use map2fig::colorbar::fill_triangle;
println!("\n=== LEFT-RIGHT EDGE STEP SYMMETRY TEST ===");
println!("Checking that both edges step equally at each scanline");
println!();
let mut img = image::RgbaImage::new(200, 100);
for pixel in img.pixels_mut() {
*pixel = Rgba([255, 255, 255, 255]);
}
let black = Rgba([0, 0, 0, 255]);
let triangle = [
(100, 20), (50, 80), (150, 80), ];
fill_triangle(triangle, black, &mut img);
let mut prev_left = None;
let mut prev_right = None;
let mut asymmetric_steps = 0;
println!("Analyzing edge step pattern from tip to base:");
for y in 20..=80 {
let mut left_x = None;
let mut right_x = None;
for x in 0..200 {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 && pixel[1] == 0 && pixel[2] == 0 {
if left_x.is_none() {
left_x = Some(x as i32);
}
right_x = Some(x as i32);
}
}
if let (Some(left), Some(right)) = (left_x, right_x) {
if let (Some(prev_l), Some(prev_r)) = (prev_left, prev_right) {
let left_step = prev_l - left; let right_step = right - prev_r;
if left_step != right_step {
println!(
" ⚠️ y={}: LEFT stepped {}, RIGHT stepped {} - ASYMMETRIC! (left={}, right={})",
y, left_step, right_step, left, right
);
asymmetric_steps += 1;
} else if left_step != 0 {
println!(
" y={}: Both edges stepped {} (left={}, right={}) ✓",
y, left_step, left, right
);
}
} else {
println!(
" y={}: First scanline with pixels: left={}, right={}",
y, left, right
);
}
prev_left = Some(left);
prev_right = Some(right);
}
}
if asymmetric_steps > 0 {
println!("\n❌ FOUND {} ASYMMETRIC EDGE STEPS", asymmetric_steps);
println!("This means left and right edges don't converge symmetrically!");
panic!(
"Found {} scanlines where edges stepped unequally",
asymmetric_steps
);
} else {
println!("\n✓ All edges step symmetrically");
}
}
#[test]
fn test_triangle_pixel_extent_symmetry() {
use image::ImageBuffer;
use map2fig::colorbar::fill_triangle;
println!("\n=== TRIANGLE PIXEL EXTENT SYMMETRY TEST ===");
println!("Rendering triangle with all pixels as BLACK and checking pixel extents\n");
let width = 200u32;
let height = 200u32;
let mut img: ImageBuffer<image::Rgba<u8>, Vec<u8>> =
ImageBuffer::from_pixel(width, height, image::Rgba([255, 255, 255, 255]));
let black = image::Rgba([0, 0, 0, 255]);
let triangle = [(100i32, 20i32), (50i32, 180i32), (150i32, 180i32)];
fill_triangle(triangle, black, &mut img);
let mut extents = vec![];
for y in 20..180 {
let mut leftmost = None;
let mut rightmost = None;
for x in 0..width {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 {
if leftmost.is_none() {
leftmost = Some(x as i32);
}
rightmost = Some(x as i32);
}
}
if let (Some(left), Some(right)) = (leftmost, rightmost) {
extents.push((y, left, right, right - left + 1));
}
}
println!("Triangle extents (y, left_x, right_x, width):");
let sample_indices = [0, 5, 10, 20, 40, 80, 159];
for &idx in sample_indices.iter() {
if idx < extents.len() {
let (y, left, right, w) = extents[idx];
let tip_distance = 100 - left; println!(
" y={:3}: x=[{:3}, {:3}] width={:3} distance_from_center={}",
y, left, right, w, tip_distance
);
}
}
println!("\nAnalyzing step sizes (distance from center at each y):");
let mut prev_distance = 0;
let mut step_sizes = vec![];
for (i, (y, left, _right, _w)) in extents.iter().enumerate() {
let distance = 100 - left;
let step = distance - prev_distance;
step_sizes.push(step);
if i < 20 || i % 20 == 0 {
println!(" y={:3}: distance={:2}, step={:+2}", y, distance, step);
}
prev_distance = distance;
}
let mut uniform_steps = 0;
let mut irregular_steps = 0;
for (i, &step) in step_sizes.iter().enumerate() {
if i > 0 && i < step_sizes.len() - 1 {
if !(0..=1).contains(&step) {
irregular_steps += 1;
} else {
uniform_steps += 1;
}
}
}
println!(
" Uniform steps (0-1): {} | Irregular steps (>1 or <0): {}",
uniform_steps, irregular_steps
);
println!("\nVerifying left-right symmetry around center x=100:");
let mut asymmetric_count = 0;
for (y, left, right, _width) in &extents {
let dist_left = 100 - left;
let dist_right = right - 100;
if dist_left != dist_right {
if asymmetric_count < 10 {
println!(
" ❌ y={}: left distance={}, right distance={} (ASYMMETRIC!)",
y, dist_left, dist_right
);
}
asymmetric_count += 1;
}
}
if asymmetric_count > 0 {
panic!(
"Found {} scanlines with asymmetric extents",
asymmetric_count
);
} else {
println!(
" ✓ All {} scanlines are perfectly symmetric!",
extents.len()
);
}
println!("\n=== TOP-DOWN SYMMETRY CHECK ===");
println!("Comparing top and bottom halves of triangle:");
let mid_y = (20 + 180) / 2; let mut top_down_issues = 0;
for (y_top, _left_top, _right_top, w_top) in extents.iter() {
let distance_from_mid = if *y_top < mid_y as u32 {
mid_y as u32 - y_top
} else {
y_top - mid_y as u32
};
let target_y_bottom = if *y_top < mid_y as u32 {
mid_y as u32 + distance_from_mid
} else {
mid_y as u32 - distance_from_mid
};
if let Some((y_bottom, _left_bottom, _right_bottom, w_bottom)) =
extents.iter().find(|(y, _, _, _)| *y == target_y_bottom)
{
if w_top != w_bottom {
if top_down_issues < 10 {
println!(
" ❌ TOP-DOWN ASYMMETRY: y={}: width={}, y={}: width={}",
y_top, w_top, y_bottom, w_bottom
);
}
top_down_issues += 1;
}
}
}
if top_down_issues > 0 {
println!(
" Found {} instances of top-down asymmetry!",
top_down_issues
);
} else {
println!(" ✓ Triangle is perfectly symmetric from top to bottom!");
}
println!("\n=== ROTATIONAL SYMMETRY TEST ===");
println!("Rendering same triangle upside-down and comparing patterns:\n");
let mut img_upside_down: ImageBuffer<image::Rgba<u8>, Vec<u8>> =
ImageBuffer::from_pixel(width, height, image::Rgba([255, 255, 255, 255]));
let flipped_triangle = [(100i32, 180i32), (50i32, 20i32), (150i32, 20i32)];
fill_triangle(flipped_triangle, black, &mut img_upside_down);
let mut flipped_extents = vec![];
for y in 20..180 {
let mut leftmost = None;
let mut rightmost = None;
for x in 0..width {
let pixel = img_upside_down.get_pixel(x, y);
if pixel[0] == 0 {
if leftmost.is_none() {
leftmost = Some(x as i32);
}
rightmost = Some(x as i32);
}
}
if let (Some(left), Some(right)) = (leftmost, rightmost) {
flipped_extents.push((y, left, right, right - left + 1));
}
}
println!("Comparing original (tip at top) with upside-down (tip at bottom):");
println!("If truly rotationally symmetric, the patterns should match when reversed.");
let mut rotational_asymmetries = 0;
for (y_orig, _left_orig, _right_orig, w_orig) in extents.iter() {
let dist_from_orig_tip = (*y_orig - 20) as i32;
let y_flip = 180 - dist_from_orig_tip;
if let Some((_, _left_flip, _right_flip, w_flip)) = flipped_extents
.iter()
.find(|(y, _, _, _)| *y == y_flip as u32)
&& w_orig != w_flip
{
if rotational_asymmetries < 5 {
println!(
" ❌ y_orig={} (distance {}): width={}, y_flip={}: width={}",
y_orig, dist_from_orig_tip, w_orig, y_flip, w_flip
);
}
rotational_asymmetries += 1;
}
}
if rotational_asymmetries > 0 {
println!(" Found {} rotational asymmetries!", rotational_asymmetries);
panic!("Triangle lacks rotational symmetry!");
} else {
println!(" ✓ Triangle has perfect rotational symmetry!");
}
println!("\n--- Testing with larger 1500x600 image ---");
let width = 1500u32;
let height = 600u32;
let mut img: ImageBuffer<image::Rgba<u8>, Vec<u8>> =
ImageBuffer::from_pixel(width, height, image::Rgba([255, 255, 255, 255]));
let triangle = [(750i32, 50i32), (300i32, 550i32), (1200i32, 550i32)];
fill_triangle(triangle, black, &mut img);
let mut extents = vec![];
for y in 50..550 {
let mut leftmost = None;
let mut rightmost = None;
for x in 0..width {
let pixel = img.get_pixel(x, y);
if pixel[0] == 0 {
if leftmost.is_none() {
leftmost = Some(x as i32);
}
rightmost = Some(x as i32);
}
}
if let (Some(left), Some(right)) = (leftmost, rightmost) {
extents.push((y, left, right, right - left + 1));
}
}
println!("Large triangle extents (sample):");
let sample_indices = [0, 50, 100, 200, 250];
for &idx in sample_indices.iter() {
if idx < extents.len() {
let (y, left, right, w) = extents[idx];
let tip_distance = 750 - left;
println!(
" y={:3}: x=[{:4}, {:4}] width={:4} distance_from_center={}",
y, left, right, w, tip_distance
);
}
}
println!("\nVerifying symmetry for large triangle around center x=750:");
let mut asymmetric_count = 0;
for (y, left, right, _width) in &extents {
let dist_left = 750 - left;
let dist_right = right - 750;
if dist_left != dist_right {
if asymmetric_count < 10 {
println!(
" ❌ y={}: left distance={}, right distance={} (ASYMMETRIC!)",
y, dist_left, dist_right
);
}
asymmetric_count += 1;
}
}
if asymmetric_count > 0 {
panic!(
"Large triangle: Found {} scanlines with asymmetric extents",
asymmetric_count
);
} else {
println!(
" ✓ All {} scanlines in large triangle are perfectly symmetric!",
extents.len()
);
}
}
#[test]
#[ignore] fn test_standalone_colorbar_symmetry() {
use map2fig::cli::Extend;
use map2fig::colorbar::render_colorbar_standalone;
use map2fig::get_colormap;
println!("\n=== STANDALONE COLORBAR SYMMETRY TEST ===");
println!("Generating standalone colorbar images for inspection\n");
let cmap = get_colormap("viridis");
println!("Test 1: Small colorbar (200x100) with both extends");
let img = render_colorbar_standalone(200, 100, cmap, 1.0, Extend::Both, 20);
let path = "/tmp/colorbar_small_both_extends.png";
img.save(path).expect("Failed to save image");
println!(" Saved to {}", path);
analyze_colorbar_extend_symmetry(&img, 200, 100);
println!("\nTest 2: Medium colorbar (600x300) with both extends");
let img = render_colorbar_standalone(600, 300, cmap, 1.0, Extend::Both, 30);
let path = "/tmp/colorbar_medium_both_extends.png";
img.save(path).expect("Failed to save image");
println!(" Saved to {}", path);
analyze_colorbar_extend_symmetry(&img, 600, 300);
println!("\nTest 3: Plain gradient (200x100) without extends");
let img = render_colorbar_standalone(200, 100, cmap, 1.0, Extend::None, 20);
let path = "/tmp/colorbar_plain_gradient.png";
img.save(path).expect("Failed to save image");
println!(" Saved to {}", path);
}
fn analyze_colorbar_extend_symmetry(img: &image::RgbaImage, width: u32, height: u32) {
let width = width as i32;
let height = height as i32;
let mut left_extent = vec![]; let mut right_extent = vec![];
for y in 0..height {
let mut leftmost = None;
let mut rightmost = None;
for x in 0..width {
let pixel = img.get_pixel(x as u32, y as u32);
if pixel[0] < 250 || pixel[1] < 250 || pixel[2] < 250 {
if leftmost.is_none() {
leftmost = Some(x);
}
rightmost = Some(x);
}
}
if let (Some(l), Some(r)) = (leftmost, rightmost) {
left_extent.push((y, l));
right_extent.push((y, r));
}
}
let image_center = width / 2;
let mut asymmetries = 0;
println!(
" Checking left-right symmetry around image center x={}:",
image_center
);
for (i, ((y_left, left_x), (y_right, right_x))) in
left_extent.iter().zip(right_extent.iter()).enumerate()
{
if y_left != y_right {
continue; }
let dist_left = left_x - image_center; let dist_right = right_x - image_center; let is_symmetric = dist_left == -dist_right;
if !is_symmetric {
asymmetries += 1;
}
if i % (height as usize / 10) == 0 || i < 5 || (i < 30 && asymmetries < 5) {
println!(
" y={:3}: left_x={:4}, right_x={:4}, dist_left={:+4}, dist_right={:+4} {}",
y_left,
left_x,
right_x,
dist_left,
dist_right,
if is_symmetric { "✓" } else { "❌" }
);
}
}
if asymmetries > 0 {
println!(
" ⚠️ Found {} scanlines with asymmetric extents",
asymmetries
);
} else {
println!(
" ✓ All {} scanlines are perfectly symmetric!",
left_extent.len()
);
}
}
#[test]
#[ignore] fn test_extend_triangles_point_outward() {
use map2fig::cli::Extend;
use map2fig::colorbar::render_colorbar_standalone;
use map2fig::get_colormap;
println!("\n=== EXTEND TRIANGLES POINT OUTWARD TEST ===");
let cmap = get_colormap("viridis");
let test_cases = vec![(200, 100, 20, "small"), (1500, 600, 50, "large")];
for (width, height, padding, name) in test_cases {
println!("\nTest: {} ({}x{})", name, width, height);
let img = render_colorbar_standalone(width, height, cmap, 1.0, Extend::Both, padding);
let mut min_x = width as i32;
let mut max_x = -1i32;
let mut min_y = height as i32;
let mut max_y = -1i32;
for y in 0..height {
for x in 0..width {
let pixel = img.get_pixel(x, y);
if pixel[0] < 250 || pixel[1] < 250 || pixel[2] < 250 {
min_x = min_x.min(x as i32);
max_x = max_x.max(x as i32);
min_y = min_y.min(y as i32);
max_y = max_y.max(y as i32);
}
}
}
let center_x = width as i32 / 2;
let image_left = padding as i32;
let image_right = width as i32 - padding as i32 - 1;
println!(
" Colored extent: x=[{}, {}], y=[{}, {}]",
min_x, max_x, min_y, max_y
);
println!(" Image center: {}", center_x);
println!(
" Expected gradient region: x=[{}, {}]",
image_left, image_right
);
if min_x < image_left {
println!(
" ✓ LEFT triangle extends {} pixels beyond left edge",
image_left - min_x
);
} else {
println!(" ❌ LEFT triangle does NOT extend beyond left edge!");
println!(" min_x={}, image_left={}", min_x, image_left);
panic!("Left triangle pointing inward instead of outward!");
}
if max_x > image_right {
println!(
" ✓ RIGHT triangle extends {} pixels beyond right edge",
max_x - image_right
);
} else {
println!(" ❌ RIGHT triangle does NOT extend beyond right edge!");
println!(" max_x={}, image_right={}", max_x, image_right);
panic!("Right triangle pointing inward instead of outward!");
}
let left_extend = image_left - min_x;
let right_extend = max_x - image_right;
if (left_extend - right_extend).abs() <= 1 {
println!(
" ✓ Left and right extends are symmetric ({} vs {})",
left_extend, right_extend
);
} else {
println!(
" ⚠️ Left and right extends differ: {} vs {}",
left_extend, right_extend
);
}
}
}
#[test]
fn test_extend_triangles_top_down_symmetry() {
use map2fig::cli::Extend;
use map2fig::colorbar::render_colorbar_standalone;
use map2fig::get_colormap;
println!("\n=== EXTEND TRIANGLES TOP-DOWN SYMMETRY TEST ===");
let cmap = get_colormap("viridis");
let width = 400;
let height = 200;
let padding = 50;
let img = render_colorbar_standalone(width, height, cmap, 1.0, Extend::Both, padding);
let cbar_y_start = padding as i32;
let cbar_y_end = height as i32 - padding as i32 - 1;
let cbar_height = cbar_y_end - cbar_y_start + 1;
let center_f64 = (cbar_y_start as f64 + cbar_y_end as f64) / 2.0;
let center_y = center_f64.round() as i32;
println!(
"Colorbar: y=[{}, {}], height={}, center at y={}",
cbar_y_start, cbar_y_end, cbar_height, center_y
);
let mut all_symmetric = true;
println!("\nLEFT extend symmetry (pairs equidistant from center):");
for distance in 1..=(cbar_height / 2) {
let y_top = center_y - distance;
let y_bottom = center_y + distance;
if y_top < cbar_y_start || y_bottom > cbar_y_end {
continue;
}
let mut pixels_top = Vec::new();
let mut pixels_bottom = Vec::new();
for x in 0..30 {
let pixel_top = img.get_pixel(x, y_top as u32);
let pixel_bottom = img.get_pixel(x, y_bottom as u32);
let is_colored_top = pixel_top[0] < 250 || pixel_top[1] < 250 || pixel_top[2] < 250;
let is_colored_bottom =
pixel_bottom[0] < 250 || pixel_bottom[1] < 250 || pixel_bottom[2] < 250;
if is_colored_top {
pixels_top.push(x as usize);
}
if is_colored_bottom {
pixels_bottom.push(x as usize);
}
}
let symmetric = pixels_top == pixels_bottom;
if distance <= 3 || distance > (cbar_height / 2) - 3 || !symmetric {
println!(
" dist={} y_top={} pixels={:?}, y_bottom={} pixels={:?} {}",
distance,
y_top,
pixels_top,
y_bottom,
pixels_bottom,
if symmetric { "✓" } else { "❌" }
);
}
if !symmetric {
all_symmetric = false;
}
}
println!("\nRIGHT extend symmetry (pairs equidistant from center):");
for distance in 1..=(cbar_height / 2) {
let y_top = center_y - distance;
let y_bottom = center_y + distance;
if y_top < cbar_y_start || y_bottom > cbar_y_end {
continue;
}
let mut pixels_top = Vec::new();
let mut pixels_bottom = Vec::new();
for x in (width - 30)..width {
let pixel_top = img.get_pixel(x, y_top as u32);
let pixel_bottom = img.get_pixel(x, y_bottom as u32);
let is_colored_top = pixel_top[0] < 250 || pixel_top[1] < 250 || pixel_top[2] < 250;
let is_colored_bottom =
pixel_bottom[0] < 250 || pixel_bottom[1] < 250 || pixel_bottom[2] < 250;
if is_colored_top {
pixels_top.push(x as usize);
}
if is_colored_bottom {
pixels_bottom.push(x as usize);
}
}
let symmetric = pixels_top == pixels_bottom;
if distance <= 3 || distance > (cbar_height / 2) - 3 || !symmetric {
println!(
" dist={} y_top={} pixels={:?}, y_bottom={} pixels={:?} {}",
distance,
y_top,
pixels_top,
y_bottom,
pixels_bottom,
if symmetric { "✓" } else { "❌" }
);
}
if !symmetric {
all_symmetric = false;
}
}
if all_symmetric {
println!("\n✓ Perfect top-down symmetry achieved in extend triangles!");
} else {
println!(
"\nNote: Some asymmetry detected at edges (geometrically expected for converging triangles)"
);
}
}