=================
AA Line Dimension
=================
.. figure:: ../figures/aadims/dimension_2_aa_arrows.png
:width: 120
:height: 120
Dimension_aa can draw an inclined line with arrows at both or only one
end.
The first function ``dimension_aa`` can be used to draw a line with arrows
at one or both ends, alternatively if only the start coordinates and angle
are given it can attach an arrow to the end of a line or arc. Use the two
attributes ``arrow`` and ``arrowshape`` to
define the arrow(s), just as in tkinter.
AA Dimension Attributes
-----------------------
.. raw:: html
Show/Hide dimension_aa AA Line Dimension
The PIL line has 3 documented attributes
* xy
Sequence of 2 tuples like [(x, y), (x, y), ...] or numeric values like
[x, y, x, y, ...].
* fill
Line colour, as an RGB tuple
* width
Line width in pixels
dimension_aa is similar to line, with the following changes
* im
PIL image handle, linked to calling program
* dr
PIL draw handle, link to the calling program
* ptA
Start coordinates
* ptB
Finishing line coordinates, optional
* angle
Angle in degrees, optional
* arrowhead
Three integer tuple describing the shape and size of the arrow
* arrow
position of the arrow on the line, which influences the direction it
points.
* back
Background colour, as an RGB tuple
Width has been removed.
.. raw:: html
|
AA Arrow and Arrowshape Attributes
----------------------------------
**arrow** has three options "first", "last" and "both" which
positions the arrow relative to the line ends.
**arrowshape** is a tuple
of three figures, the first d1 shows the length down the line covered by the
arrow (AC), the second d2 is the length between the point and trailing tips
(AF) and lastly the perpendicular distance d3 between the shaft and the
trailing tips (EF). The default value (8, 10, 3) is not a right angled
triangle but produces a shape like that of a swift's tail.
Create dimension_aa Script
--------------------------
Since the PIL polygon only applies to normal shapes with no antialiasing we
need to make our own arrow. Use a modified antialiased line with only one
side having antialiased pixels, floodfill the inside with the required
colour. There are some new functions and the line function requires some
modification
to plot the required antialias pixels.
As before with DimLinesPIL the user interacts with dimension, but this time
``dimension_aa``.
The only external changes are that when calling we include the PIL ``image``
handle for flood fill, and we have an optional element ``back`` for
antialiasing.
dimension_aa is very similar to the original script, except that the
connecting line is
shortened so that it does not overwrite the arrows.
.. raw:: html
Show/Hide dimension_aa change connecting line
::
if angle is None and ptB:
x1, y1 = ptB
phi = atan2(y1-y0, x1-x0)
if arrow in ('first', 'both'):
cx0 = int_up(x0 + d1 * cos(phi))
cy0 = int_up(y0 + d1 * sin(phi))
else:
cx0, cy0 = ptA
if arrow in ('last', 'both'):
cx1 = int_up(x1 - d1 * cos(phi))
cy1 = int_up(y1 - d1 * sin(phi))
else:
cx1, cy1 = ptB
plotLineAA(dr, (cx0, cy0), (cx1, cy1), back=(255,255,221), fill=fill)
.. raw:: html
|
then call the antialiased polygon, as the lengths along the arrow shaft have
already been calculated these are not required again. When calculating the
arrow polygon points ensure that they are integers.
Generally ``polyAA`` is hidden from the user, but can be used if needed,
but may require support functions. If required, the list of polygon points
is first converted to a 2D list, this follows the allowable
PIL polygon options. The polygon centroid is found, this point, in conjunction
with the lines making the polygon sides, tell us the orientation of
the sides, which in turn shows on which side the antialiasing is required. A
loop is used to plot almost all of the sides, except for the last side which
is plotted separately. The order of plotting the long lines is important,
seen already when drawing widget arrows,
`Simple Arrows `_
.. raw:: html
Show/Hide AA Polygon and support functions
::
def to_matrix(l,n):
# convert list to multidimensional list
return [l[i:i+n] for i in range(0, len(l), n)]
def above_below(pta,ptb,ptc):
x1, y1 = pta
x2, y2 = ptb
xA, yA = ptc
# line [(x1,y1),(x2,y2)],point (xA,xB) is point one side or other
v1 = (x2-x1, y2-y1) # Vector 1
v2 = (x2-xA, y2-yA) # Vector 1
xp = v1[0]*v2[1] - v1[1]*v2[0] # Cross product
def centroid(points):
# assume that points is a 2D list of points polygon
x = [p[0] for p in points]
y = [p[1] for p in points]
centroid = int_up(sum(x) / len(points)), int_up(sum(y) / len(points))
return centroid
def flood(im, dr, x, y, fill, back):
xy = x,y
if im.getpixel(xy) == back:
dr.point((x,y), fill)
flood(im, dr, x+1,y, fill, back)
flood(im, dr, x,y+1, fill, back)
flood(im, dr, x-1,y, fill, back)
flood(im, dr, x,y-1, fill, back)
def polyAA(im, dr,xy,back=(255,255,221),fill=(0,0,0), outline=None):
# xy list of consecutive points
try:
lpts = len(xy[0])
except:
lpts = 0
if lpts ==0:
xy = to_matrix(xy, 2)
lxy = len(xy)
cx, cy = centroid(xy)
for ix in range(lxy):
if ix > 0:
cross = above_below(xy[ix-1],xy[ix],(cx,cy))
plotLinePartAA(dr, xy[ix-1], xy[ix], back=(255,255,221),fill=fill,cross=cross)
cross = above_below(xy[0],xy[lxy-1],(cx,cy))
plotLinePartAA(dr, xy[0], xy[lxy-1], back=(255,255,221),fill=fill,cross=cross)
if isinstance(outline,tuple) is False:
flood(im, dr, cx, cy, fill, back)
.. raw:: html
|
polyAA calls a specialised single pixel wide antialiased line ``PartLineAA``
which in conjunction with the functions **findSect** and **above_below**
determines which side of
the line should be antialiased or not. When drawing an antialiased polygon
it is best to turn off the inner antialiasing when creating the outside
border. This helps ensure that there are no light pixels on the inside before
flood filling. Even when making unfilled polygons it can help to prevent
the corners being obscured by antialiasing.
As already :ref:`explained` the Zigl algorithm is not totally
accurate, so when
one side of the antialiasing is switched off it is better to use a corrected
version, resulting in fewer stray light pixels. Essentially the line
follows the pattern of the corrected Zigl line with flipped coordinates. Let
the algorithm plot as normal then add the anialiasing just after the main
line plot. The main line plotting changes colour intensity according to the
errors/differences at that point.
.. raw:: html
Show/Hide plotLinePartAA customised line
::
def PartLineAA(draw, pta, ptb, fill=(0,0,0), back=(255,255,221), cross=0):
x0, y0 = pta
x1, y1 = ptb
sects = findSect(pta, ptb)
dx = dx0 = abs(x1 - x0)
dy = dy0 = abs(y1 - y0)
sx = 1 if x0 < x1 else -1
sy = 1 if y0 < y1 else -1
if dx0 > dy0: # gentle incline
dy = -dy
dr = dx0 + 1
else: # steep slope
dx = -dx
dr = dy0 + 1
dx, dy = dy, dx
err = dx + dy
ed = 1 if err == 0 else sqrt(dx*dx+dy*dy)
def errs(comp, size,j):
return 255 if comp == 255 else int((255-comp) * j / size) + comp
diffs = defaultdict(list)
diffs = defaultdict(lambda:back, diffs)
for i in range(int(ed)+1):
if fill == (0,0,0):
diffs[i] = tuple(int(255*i/ed) for k in range(3))
else:
diffs[i] = tuple(errs(fill[k],ed,i) for k in range(3))
for j in range (dr): # main loop
ez = err-dx-dy
out = abs(ez)
draw.point([x0, y0], fill=diffs[out])
if abs(ez+dx) < ed-1:
out = abs(ez+dx)
if dx0 > dy0:
if cross < 0 and sects[0] in (4,8) or \
cross > 0 and sects[0] in (5,1):
draw.point([x0,y0+sy], fill=diffs[out])
else:
if cross < 0 and sects[0] in (2,6) or \
cross > 0 and sects[0] in (3,7):
draw.point([x0+sx,y0], fill=diffs[out])
if abs(ez-dx) < ed-1:
out = abs(ez-dx)
if dx0 > dy0:
if cross < 0 and sects[0] in (1,5) or \
cross > 0 and sects[0] in (8,4):
draw.point([x0,y0-sy], fill=diffs[out])
else:
if cross < 0 and sects[0] in (3,7) or \
cross > 0 and sects[0] in (6,2):
draw.point([x0-sx,y0], fill=diffs[out])
e2 = err<<1
if e2 >= dy:
err += dy
if dx0 > dy0:
x0 += sx
else:
y0 += sy
if e2 <= dx:
err += dx
if dx0 > dy0:
y0 += sy
else:
x0 += sx
.. raw:: html
|
Flood has been limited to moving around along the main axes, otherwise it
might not be contained on an inclined arrow.
Use the normal antialiased line which looks correct and is
essentially the same as already developed.
.. raw:: html
Show/Hide LineAA standard line
::
def LineAA(draw, pta, ptb, fill=(0,0,0), back=(255,255,255))
# draw a dark anti-aliased line on light background
x0, y0 = pta
x1, y1 = ptb
dx = abs(x1 - x0)
dy = abs(y1 - y0)
sx = 1 if x0 < x1 else -1
sy = 1 if y0 < y1 else -1
err = dx - dy # error value e_xy
ed = dx + dy
ed = 1 if ed == 0 else sqrt(dx*dx+dy*dy)
dr = dx + 1 if dx > dy else dy + 1 # better plotting when steep
def errs(comp, size,j):
return 255 if comp == 255 else int((255-comp) * j / size) + comp
diffs = defaultdict(list)
diffs = defaultdict(lambda:back, diffs)
for i in range(int(ed)+1):
if fill == (0,0,0):
diffs[i] = tuple(int(255*i/ed) for j in range(3))
else:
diffs[i] = tuple(errs(fill[j],ed,i) for j in range(3))
for x in range (dr): # pixel loop
draw.point([x0, y0], fill=diffs[abs(err-dx+dy)])
e2 = err
x2 = x0
if e2<<1 >= -dx: # y-step
if e2+dy < ed and x < dr - 1:
draw.point([x0,y0+sy], fill=diffs[abs(e2+dy)])
err -= dy
x0 += sx
if e2<<1 <= dy and x < dr - 1: # x-step
if dx-e2 < ed:
draw.point([x2+sx,y0], diffs[abs(dx-e2)])
err += dx
y0 += sy
.. raw:: html
|
.. figure:: ../figures/aadims/aa_line_10_3.png
:width: 450
:height: 292
:align: center
Shallow antialiased line, antialiasing values are mirrored with opposite
signs.
This figure was made directly from the antialiasing difference values
without using any algorithm.
.. figure:: ../figures/aadims/aa_line_3_10.png
:width: 292
:height: 450
:align: center
Steep antialiased line, as before, antialiasing values are mirrored with
opposite signs.
When checking the antialiasing note that the Zigl algorithm
places the antialiasing on one side of the line with a change in the larger
coordinate and antialiasing on the opposite side with the smaller coordinate.
On lines close to 45° this works well. For shallower or steeper lines there
is an imbalance of antialiasing which becomes most apparent in situations
when only one side of antialiasing is used.