I use a cr (from Gtk's widget draw event) and I want to create a rectangle (or a line) using an even number of pixels (2,4,6 etc.) without any «context transformations». According to this the line will be "around the path". And according to this "the diameter of a pen that is circular".
But in a rectangle will it be less outside and more inside or the opposite? And in a line will be up,down, left or right?
I understand that in an odd line width, "around the path" means 1 in the center and the rest are equally around. But in an even line width, as when the line width is 2, will be 1 pixel inside the path or outside?
Is there a stable way to determine the pixels affected or it is random?
The walk around of creating two times every stroke'ing, first with line width 1 and then by using the remainder (an odd) number is pain killing and time consuming.
But in a rectangle will it be less outside and more inside or the opposite? And in a line will be up,down, left or right?
The line will have the same width on either side of the path. If this does not align with the pixel grid, you get some anti-aliased result.
I understand that in an odd line width, "around the path" means 1 in the center and the rest are equally around. But in an even line width, as when the line width is 2, will be 1 pixel inside the path or outside?
With a line width of 3, 1.5 pixels will be drawn on either side of the path.
With a line width of 4, 2 pixels are drawn on either side of the path.
Perhaps the following example makes this clearer. This is written in Lua and uses LGI as cairo bindings for Lua, but this maps directly to the C API:
local cairo = require("lgi").cairo
s = cairo.ImageSurface(cairo.Format.RGB24, 100, 30)
cr = cairo.Context(s)
cr:set_source_rgb(1, 1, 1)
cr:paint()
cr:set_source_rgb(0, 0, 0)
cr:set_line_width(2)
cr:rectangle(5, 10, 5, 5)
cr:stroke()
cr:set_line_width(6)
cr:rectangle(15, 10, 14, 14)
cr:stroke()
cr:set_line_width(7)
cr:rectangle(40.5, 10.5, 14, 14)
cr:stroke()
cr:set_line_width(7)
cr:rectangle(70, 10, 14, 14)
cr:stroke()
s:write_to_png("out.png")
The first rectangle has a line width of 2. It is drawn with integer coordinates, so that there is e.g. a line from (5, 10) to (10, 10) (the top line). Half the line width is drawn on either side of the line, so this line corresponds to a "filled rectangle" from (4, 9) to (6, 11).
The last rectangle has a line width of 7 and is also drawn with integer coordinates. Its top line goes from (70, 10) to (70, 24). Since half the line width is on either side of the line, the "filled rectangle" goes from (66.5, 6.5) to (73.5, 27.5). These numbers are not integers and you can see in the result that some anti-aliasing was applied.
In contrast, the second to last rectangle has its position shifted by 0.5. This causes the "filled rectangle" for its "top line" to end up on the pixel grid again.
See also this FAQ entry: https://www.cairographics.org/FAQ/#sharp_lines