#include #include #include #include #include "cairo-helpers.hpp" namespace cdt{ /* * auxiliar functions and types * ------------------ */ enum class shape_partition : uchar { INTERIOR, BOUNDARY, EXTERIOR }; using boundary = std::vector; static bool is_boundary(uchar pixel) { return pixel == static_cast(shape_partition::BOUNDARY); } static bool is_interior(uchar pixel) { return pixel == static_cast(shape_partition::INTERIOR); } static bool is_exterior(uchar pixel) { return pixel == static_cast(shape_partition::EXTERIOR); } static bool is_in_domain(cv::Mat const& img, cv::Point2i const& p) { return p.x >= 0 && p.y >= 0 && p.x < img.cols && p.y < img.rows; } static void compute_partitions( cv::Mat const& binary, cv::Mat& dst, int32_t boundary_size) { auto kernel = cv::getStructuringElement( cv::MORPH_RECT, cv::Size(boundary_size + 1, boundary_size + 1) ); cv::Mat morphological_gradient; cv::morphologyEx( binary, morphological_gradient, cv::MORPH_GRADIENT, kernel ); dst.create(binary.size(), CV_8UC1); auto b = binary.begin(); auto g = morphological_gradient.begin(); auto d = dst.begin(); while(b != binary.end()){ if(*g == 255) *d = static_cast(shape_partition::BOUNDARY); else if(*b == 0) *d = static_cast(shape_partition::EXTERIOR); else *d = static_cast(shape_partition::INTERIOR); ++b; ++g; ++d; } } static cv::Mat build_neighbourhood_distance_map(int32_t size) { if(size%2 != 1){ throw std::runtime_error( "to build the neighbourhood distance map, the size " "must be odd" ); } cv::Mat dm(cv::Size(size, size), CV_8UC1); auto max_distance = size/2.; auto max_uchar = std::numeric_limits::max()/2; cv::Point2i center(size/2, size/2); for(auto [ pixel, point ] : pixels_at(dm)){ auto norm = cv::norm(point - center)/max_distance; if(norm > 1.){ pixel = static_cast(max_uchar); }else{ pixel = static_cast(norm*max_uchar); } } return dm; } static void relax( cv::Mat& cdt2d, cv::Point2i const& relax_at, cv::Mat const& partitions, cv::Mat const& dm) { static const uchar boundary_value = std::numeric_limits::max()/2; for(auto [ pixel, point ] : pixels_at(cdt2d)){ auto partition_point = relax_at + point; auto distance = dm.at(point); if(is_in_domain(partitions, partition_point)){ auto partition_pixel = partitions.at( partition_point ); if(is_interior(partition_pixel)){ auto signed_dist = static_cast( boundary_value - distance ); pixel = std::max(pixel, signed_dist); }else if(is_exterior(partition_pixel)){ auto dist = static_cast( boundary_value + distance ); pixel = std::min(pixel, dist); } }else{ auto dist = static_cast( boundary_value + distance ); pixel = std::min(pixel, dist); } } } /* * binary->cdt2d algorithms implementation * --------------------------------------- */ static void convert_binary_to_cdt2d_boundary_relax( cv::Mat const& partitions, cv::Mat& cdt2d, uint32_t delta_in_pixel, margin_type margin) { uchar boundary_value = std::numeric_limits::max()/2; auto d = static_cast(delta_in_pixel+margin); cv::Point2i displacement{ d , d }; /* * the cdt inicialization is, for an unsigned char in 0-255, * 0 the interior, 255 for the exterior and 127 for the boundary * * TODO: does not need to scan the whole image to set the pixels * that are not in ini_roi */ cdt2d = cv::Scalar::all(std::numeric_limits::max()); auto ini_roi = cv::Rect{ displacement, cv::Size{ partitions.cols, partitions.rows } }; cdt2d(ini_roi) = partitions*boundary_value; auto dm_size = static_cast(delta_in_pixel*2 + 1); auto dm = build_neighbourhood_distance_map(dm_size); cv::Size relax_size{ dm_size, dm_size }; cv::Point2i relax_displacement{ dm_size/2, dm_size/2 }; for(auto [partition_pixel , partition_point] : pixels_at(partitions)){ if(is_boundary(partition_pixel)){ auto partition_begin = partition_point - relax_displacement; auto cdt_point = partition_point + displacement; auto cdt_begin = cdt_point - relax_displacement; auto cdt_rect = cv::Rect2i{ cdt_begin, relax_size }; auto cdt_roi = cdt2d(cdt_rect); relax(cdt_roi, partition_begin, partitions, dm); } } } /* * cdt2d implementations * --------------------- */ void polygon_to_binary( polygon2d const& polygon, cv::Mat& binary, ruler::ppmm resolution, margin_type margin) { // calculate the resolution auto r = polygon.bbox(); auto ppmm = resolution.count(); auto fartherest_from_origin_x = std::max( std::abs(r.xmax()), std::abs(r.xmin()) ); auto fartherest_from_origin_y = std::max( std::abs(r.ymax()), std::abs(r.ymin()) ); auto md = static_cast(margin); auto width = static_cast( std::ceil((fartherest_from_origin_x + md)*2) ); auto height = static_cast( std::ceil((fartherest_from_origin_y + md)*2) ); auto surface_width = static_cast(width*ppmm); auto surface_height = static_cast(height*ppmm); // create cairo context and surface cairo_surface_ptr surface{ cairo_image_surface_create( CAIRO_FORMAT_A1, surface_width, surface_height ), &cairo_surface_destroy }; cairo_context_ptr cr{ cairo_create(surface.get()), &cairo_destroy }; // translate to make the origin the center of the surface auto surface_half_width = surface_width/2.0; auto surface_half_height = surface_height/2.0; cairo_translate(cr.get(), surface_half_width, surface_half_height); // scale to fit the polygon in the correct size auto scale_x = static_cast(surface_width)/width; auto scale_y = static_cast(surface_height)/height; cairo_scale(cr.get(), scale_x, scale_y); // transform to reflect in the x axis (middle of surface) cairo_matrix_t mat; cairo_matrix_init(&mat, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0 ); cairo_transform(cr.get(), &mat); // draw the polygon draw(cr.get(), polygon); cairo_set_fill_rule(cr.get(), CAIRO_FILL_RULE_EVEN_ODD); cairo_fill(cr.get()); surface_to_mat(surface.get(), binary); } void binary_to_cdt2d( cv::Mat const& binary, cv::Mat& cdt2d, ruler::ppmm resolution, k_type k, delta_type delta, margin_type margin) { auto delta_in_pixel = static_cast( std::ceil(delta*resolution.count()) ); auto scale = 1./k; auto increment = static_cast(2*(margin + delta_in_pixel)); auto width = static_cast(binary.cols*scale + increment); auto height = static_cast(binary.rows*scale + increment); cdt2d.create(height, width, CV_8UC1); cv::Mat partitions, partitions_resized; compute_partitions( binary, partitions, static_cast(std::ceil(k)) ); cv::resize( partitions, partitions_resized, cv::Size(0, 0), scale, scale, cv::INTER_NEAREST ); convert_binary_to_cdt2d_boundary_relax( partitions_resized, cdt2d, delta_in_pixel, margin ); } }