Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drawing circle with subpixel accuracy #304

Open
paumaq opened this issue Dec 5, 2022 · 1 comment
Open

Drawing circle with subpixel accuracy #304

paumaq opened this issue Dec 5, 2022 · 1 comment

Comments

@paumaq
Copy link

paumaq commented Dec 5, 2022

Hi!

I've been trying to draw a circle with subpixel accuracy.
I am using red paint as the source and a circle as the path.
E.g. Red filled circle of radius=3 and center at (10, 10):

    surface = cairo.ImageSurface (cairo.FORMAT_RGB24, 20, 20)
    ctx = cairo.Context (surface)
    cx = 10
    cy = 10
    radius = 3
    ctx.arc(cx, cy, radius, 0, 2 * math.pi)
    ctx.set_source_rgb(1, 0, 0)
    ctx.fill()
    surface.write_to_png(imagename + '.png')

arc_center_10x10_radius_3

Even though the image looks good, the pixel values are not symmetrical.

arc_center_10x10_radius_3_px_values

Shouldn't it be symmetrical?

At first I was not setting antialias mode. By setting it to NONE, I get the expected result:

arc_center_10x10_radius_3_antialias_NONE

However, when I use floating point coordinates, I don't get what I expect either.
E.g. Red filled circle of radius=3 and center at (9.5, 9.5):

arc_center_9 5x9 5_radius_3_vs_NONE

I've tried to apply a radial gradient to simulate a smooth decrease of intensity:

    surface = cairo.ImageSurface (cairo.FORMAT_RGB24, 20, 20)
    ctx = cairo.Context (surface)
    cx = 10
    cy = 10
    radius = 3
    grad = cairo.RadialGradient(cx, cy, 0, cx, cy, radius)
    grad.add_color_stop_rgb(0, 1, 0, 0) # First stop: max red intensity in the center of the circle
    grad.add_color_stop_rgb(1, 0, 0, 0) # Last stop: no red / black in the circle outline
    ctx.arc(cx, cy, radius, 0, 2 * math.pi)
    ctx.set_source_rgb(grad)
    ctx.fill()
    surface.write_to_png(imagename + '.png')

radial_gradient_masked_by_arc_center_10x10_radius_3

Still not radially symmetrical if I look at the pixel values.

Am I doing something wrong or is this the expected behavior?

@paumaq
Copy link
Author

paumaq commented Dec 5, 2022

I've represented the source, path and output in different images:

    # ------- Save source as image --------
    # Set source as radial gradient
    ctx.set_source(grad) # The colour will decrease smoothly from 100% to 0% red intensity
    # Set path as the entire image so that the source (radial gradient) is
    # transferred to the destination (surface).
    ctx.rectangle(0, 0, 20, 20)
    ctx.fill()
    surface.write_to_png(sourcename + '.png')

    # ------- Save path/mask as image --------
    # Set the background to black
    ctx.rectangle(0, 0, 20, 20)
    ctx.set_source_rgb(0, 0, 0)
    ctx.fill()

    # Set source as white paint, so that the path is seen as a white circle (mask) over the black background
    ctx.set_source_rgb(1, 1, 1)
    ctx.arc(cx, cy, radius, 0, 2 * math.pi)

    # With the "fill" method, paint from the source (white paint) is transferred to the
    # destination (surface) wherever the mask (path) allows it.
    # After this, the current path will be cleared from the context.
    ctx.fill()
    surface.write_to_png(maskname + '.png')

    # ------- Save output as image --------

    # By default, the background is black. We want a white background in this case to see the masked source clearly:
    ctx.rectangle(0, 0, 20, 20)
    ctx.set_source_rgb(1, 1, 1)
    ctx.fill()

    # Set source as radial gradient
    ctx.set_source(grad) # The colour will decrease smoothly from 100% to 0% red intensity
    ctx.arc(cx, cy, radius, 0, 2 * math.pi)

    # With the "fill" method, paint from the source (radial gradient) is transferred to the
    # destination (surface) wherever the mask (path) allows it.
    # After this, the current path will be cleared from the context.
    ctx.fill()

    surface.write_to_png(outputname + '.png')

source_path_masked_source_10x10_radius_3

Same for antialias mode NONE:
source_path_masked_source_10x10_radius_3_antialias_NONE

Same for antialias mode NONE circle center at (9.5, 9.5):

source_path_masked_source_9 5x9 5_radius_3_antialias_NONE

The path is not radially symmetrical. It has two extra pixels. However, the source does not have those extra pixels. The value of the red component is those pixels is zero. The source is radially symmetrical.

source_radial_gradient_center_9 5x9 5_radius_3_antialias_NONE_px_values

Am I supposed to use a bigger circle for the path?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant