from PIL import Image, ImageDraw def draw_smooth_ellipse(base_img, bbox, fill=None, outline=None, width=1, scale=4): """Draw an anti-aliased ellipse on top of `base_img`. This function creates a larger temporary overlay at `scale` times the base size, draws the ellipse there, downsamples with LANCZOS and composites it onto the base image. This produces much smoother edges. Args: base_img (PIL.Image): RGBA image to draw onto. Returned image is a new Image object (alpha-composited). bbox (sequence): [x0, y0, x1, y1] bounding box in base image coordinates. fill (tuple|str): Fill color (RGBA tuple or PIL color). outline (tuple|str): Outline color. width (int): Outline width in base-image pixels. scale (int): Supersampling factor. 3-6 is usually good. Default 4. Returns: PIL.Image: New RGBA image with the ellipse composited. """ if base_img.mode != 'RGBA': base_img = base_img.convert('RGBA') w, h = base_img.size # Create large transparent overlay overlay_large = Image.new('RGBA', (w * scale, h * scale), (0, 0, 0, 0)) draw_large = ImageDraw.Draw(overlay_large) x0, y0, x1, y1 = bbox bbox_large = [int(x0 * scale), int(y0 * scale), int(x1 * scale), int(y1 * scale)] if fill: draw_large.ellipse(bbox_large, fill=fill) if outline and width and width > 0: draw_large.ellipse(bbox_large, outline=outline, width=max(1, int(width * scale))) # Downsample overlay to original size with LANCZOS (antialiasing) overlay_small = overlay_large.resize((w, h), Image.Resampling.LANCZOS) # Composite overlay onto base image result = Image.alpha_composite(base_img, overlay_small) return result