@@ -615,6 +615,44 @@ def _extract_lines_loop(geometries, values, bounds, height, width):
615615 np .array (all_vals , np .float64 ))
616616
617617
618+ # ---------------------------------------------------------------------------
619+ # Polygon boundary segments (for all_touched mode)
620+ # ---------------------------------------------------------------------------
621+
622+ def _extract_polygon_boundary_segments (geometries , values , bounds ,
623+ height , width ):
624+ """Extract polygon ring edges as line segments for Bresenham drawing.
625+
626+ Used by all_touched mode: drawing the boundary ensures every pixel
627+ the polygon touches is burned, without expanding scanline edge
628+ y-ranges (which breaks edge pairing).
629+ """
630+ from shapely .geometry import LineString as _LS
631+
632+ boundary_lines = []
633+ boundary_vals = []
634+ for geom , val in zip (geometries , values ):
635+ if geom is None or geom .is_empty :
636+ continue
637+ if geom .geom_type == 'Polygon' :
638+ parts = [geom ]
639+ elif geom .geom_type == 'MultiPolygon' :
640+ parts = list (geom .geoms )
641+ else :
642+ continue
643+ for poly in parts :
644+ boundary_lines .append (_LS (poly .exterior .coords ))
645+ boundary_vals .append (val )
646+ for interior in poly .interiors :
647+ boundary_lines .append (_LS (interior .coords ))
648+ boundary_vals .append (val )
649+
650+ if not boundary_lines :
651+ return _EMPTY_LINES
652+ return _extract_line_segments (boundary_lines , boundary_vals ,
653+ bounds , height , width )
654+
655+
618656# ---------------------------------------------------------------------------
619657# CPU burn kernels (numba)
620658# ---------------------------------------------------------------------------
@@ -752,7 +790,7 @@ def _scanline_fill_cpu(out, written, edge_y_min, edge_y_max, edge_x_at_ymin,
752790 x_start = xs [k ]
753791 x_end = xs [k + 1 ]
754792 col_start = max (int (np .ceil (x_start )), 0 )
755- col_end = min (int (np .floor (x_end )), width - 1 )
793+ col_end = min (int (np .ceil (x_end )) - 1 , width - 1 )
756794 for c in range (col_start , col_end + 1 ):
757795 _merge_pixel (out , written , row , c , val , mode )
758796 k += 2
@@ -775,14 +813,27 @@ def _run_numpy(geometries, values, bounds, height, width, fill, dtype,
775813 (poly_geoms , poly_vals , poly_ids ), (line_geoms , line_vals ), \
776814 (point_geoms , point_vals ) = _classify_geometries (geometries , values )
777815
778- # 1. Polygons
816+ # 1. Polygons -- always use normal edge ranges for scanline fill
817+ # (all_touched y-expansion breaks edge pairing, so we handle
818+ # all_touched by drawing polygon boundaries separately below).
779819 edge_arrays = _extract_edges (
780- poly_geoms , poly_vals , poly_ids , bounds , height , width , all_touched )
820+ poly_geoms , poly_vals , poly_ids , bounds , height , width ,
821+ all_touched = False )
781822 edge_arrays = _sort_edges (edge_arrays )
782823 if len (edge_arrays [0 ]) > 0 :
783824 _scanline_fill_cpu (out , written , * edge_arrays , height , width ,
784825 merge_mode )
785826
827+ # 1b. all_touched: draw polygon boundaries via Bresenham so every
828+ # pixel the boundary passes through is burned. This guarantees
829+ # all_touched is a superset of the normal fill.
830+ if all_touched and poly_geoms :
831+ br0 , bc0 , br1 , bc1 , bvals = _extract_polygon_boundary_segments (
832+ poly_geoms , poly_vals , bounds , height , width )
833+ if len (br0 ) > 0 :
834+ _burn_lines_cpu (out , written , br0 , bc0 , br1 , bc1 , bvals ,
835+ height , width , merge_mode )
836+
786837 # 2. Lines
787838 r0 , c0 , r1 , c1 , lvals = _extract_line_segments (
788839 line_geoms , line_vals , bounds , height , width )
@@ -911,10 +962,17 @@ def _scanline_fill_gpu(out, written, edge_y_min, edge_x_at_ymin,
911962 while k + 1 < j :
912963 x_start = xs [k ]
913964 x_end = xs [k + 1 ]
914- col_start = int (x_start + 0.999999 )
965+ # Proper ceil: int() truncates toward zero, so for
966+ # positive fractions we add 1; for exact ints and
967+ # negative fractions int() already rounds up.
968+ ix = int (x_start )
969+ col_start = ix + 1 if x_start > ix else ix
915970 if col_start < 0 :
916971 col_start = 0
917- col_end = int (x_end )
972+ # Half-open interval: ceil(x_end) - 1 excludes
973+ # boundary pixels whose centers are outside.
974+ ix_end = int (x_end )
975+ col_end = ix_end if x_end > ix_end else ix_end - 1
918976 if col_end >= width :
919977 col_end = width - 1
920978 for c in range (col_start , col_end + 1 ):
@@ -1038,9 +1096,10 @@ def _run_cupy(geometries, values, bounds, height, width, fill, dtype,
10381096 (poly_geoms , poly_vals , poly_ids ), (line_geoms , line_vals ), \
10391097 (point_geoms , point_vals ) = _classify_geometries (geometries , values )
10401098
1041- # 1. Polygons
1099+ # 1. Polygons -- always use normal edge ranges (see _run_numpy comment)
10421100 edge_arrays = _extract_edges (
1043- poly_geoms , poly_vals , poly_ids , bounds , height , width , all_touched )
1101+ poly_geoms , poly_vals , poly_ids , bounds , height , width ,
1102+ all_touched = False )
10441103 edge_arrays = _sort_edges (edge_arrays )
10451104 edge_y_min , edge_y_max , edge_x_at_ymin , edge_inv_slope , \
10461105 edge_value , edge_geom_id = edge_arrays
@@ -1064,6 +1123,19 @@ def _run_cupy(geometries, values, bounds, height, width, fill, dtype,
10641123 out , written , d_y_min , d_x_at_ymin , d_inv_slope ,
10651124 d_value , d_geom_id , d_row_ptr , d_col_idx , width , merge_mode )
10661125
1126+ # 1b. all_touched: draw polygon boundaries via Bresenham (GPU)
1127+ if all_touched and poly_geoms :
1128+ br0 , bc0 , br1 , bc1 , bvals = _extract_polygon_boundary_segments (
1129+ poly_geoms , poly_vals , bounds , height , width )
1130+ if len (br0 ) > 0 :
1131+ n_bsegs = len (br0 )
1132+ tpb = 256
1133+ bpg = (n_bsegs + tpb - 1 ) // tpb
1134+ kernels ['burn_lines' ][bpg , tpb ](
1135+ out , written , cupy .asarray (br0 ), cupy .asarray (bc0 ),
1136+ cupy .asarray (br1 ), cupy .asarray (bc1 ),
1137+ cupy .asarray (bvals ), n_bsegs , height , width , merge_mode )
1138+
10671139 # 2. Lines
10681140 r0 , c0 , r1 , c1 , lvals = _extract_line_segments (
10691141 line_geoms , line_vals , bounds , height , width )
0 commit comments