For the developers who often deal with planar vector graphics, how to describe arbitrary shapes easily becomes a problem they have to face.
One way to do this is to use d attributes defined in SVG format. This is one of the more common solutions today, and you can see it in common use in vector tools like Adobe Illustrator
Yes, this property is just one letter, lowercase D.
D is the first letter of the English word “draw” and, as its name suggests, describes the path of drawing. Strokes that can be interpreted as human drawings.
Essentially, the D attribute is a string composed of a series of regular formats.
Rules are called commands (or commands). Since it is a stroke describing a drawing, it has an inherent property, which is sequential.
What that means is that every rule and every point in this string is sorted. It is these explicit positions and rules, together with implicit sequencing, that determine the unique path of the graph.
1. D
The first time you’re faced with the D property, you might be confused by a long string of unexplained “garbled” characters, but it’s actually composed in a simple way: [instruction (parameter)]+
Each number is the parameter of the most recent instruction in the preceding order, and several complete instruction equations constitute the instruction set, which is the content of D itself.
The instructions themselves are not arbitrary and infinite. Regardless of alphabetic case classification, a total of 4 categories, 10 kinds, a total of 20 items.
The four categories are: ① [move] X1 ② [straight line] x3 ③ [curve] X5 – [cubic Bezier curve] X2 – [quadratic Bezier curve] x2 – [elliptic arc curve] X1 ④ [path closure] X1
There are 10 letters in total.
The 10 types are: ① M/ M: move to ② L/ L: line to ③ V/ V: vertical line to ④ H/ H: horizontal line to ⑤ C/ C: horizontal line to ③ V/ V: vertical line to ④ H/ H: horizontal line to ⑤ C/ C: Cubic Bezier curve to draw a smooth cubic Bezier curve ⑦ Q/ Q: Quadratic Bezier curve ⑧ T/ T: smooth cubic Bezier curve ⑨ A/ A: arc curve ⑩ Z/ Z: ClosePath Close graph
Each type has two terms, uppercase for absolute location and lowercase for relative location (that is, offset). In order to facilitate understanding and reduce intracranial computing costs, the example in this paper preferentially selects capital absolute positioning. So there are 20 instructions, each with its own argument list.
At the same time, the parameter list under an instruction supports plural pairs of parameters, that is, if two or more adjacent instructions are the same, they can be abbreviated as a letter, and the parameter list can be amplified according to the number of instructions.
In this section, the “+” character in the regular expression is used to indicate that the previous expression is matched one or more times.
Such as:
d = "M 10,10 L 10,50, 20,20,30"
d = "M 10,10 L 10,50 L 20,20 L 30 30"
Copy the code
These two are equivalent.
Of course, commas (,) can also be separated by Spaces without affecting parsing.
In practical development, the first requirement we face is usually to reverse solve the specific location coordinates of each anchor point on the canvas from the attribute string, so the next step is to describe one by one from here.
2. D attribute instruction set
① M/ M: move to
instruction | parameter | The formula |
---|---|---|
M | (x, y)+ | P = {x, y} |
m | (dx, dy)+ | P = {x_old + dx, y_old + dy} |
# M x,y
# P={x, y}
def __move_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
return point
# m dx,dy
# P={x_old + dx, y_old + dy}
def __move_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
return point
Copy the code
In general, M exists as a location starting with a path.
So it tends to appear in the first character of any continuous path.
So you can simply say that the function of M is to move a point to a specific location without any explicit drawing of the path.
d = "50, 50 M"
Copy the code
Here, for the sake of presentation, the starting point is marked in the graph. In fact, there is no display here, and the appearance of the point does not exist, full screen pure blank.
If you need to display something, you need to follow the instructions.
② L/ L: Line to
instruction | parameter | The formula |
---|---|---|
L | (x, y)+ | P = {x, y} |
l | (dx, dy)+ | P = {x_old + dx, y_old + dy} |
# L x,y
# P={x, y}
def __line_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
return point
# l dx,dy
# P={x_old + dx, y_old + dy}
def __line_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
return point
Copy the code
The line command is the same as the move command. The only difference is that line draws the move path as a straight line segment.
d = "M 25, 50, 75, 50 L"
Copy the code
③ V/ V: vertical line to
instruction | parameter | The formula |
---|---|---|
V | (y)+ | P = {x_old, y} |
v | (dy)+ | P = {x_old, y_old + dy} |
# V y
# P={x_old, y}
def __vertical_to(*args) - >dict:
point = {'x': args['x_old'].'y': args['y']}
return point
# v dy
# P={x_old, y_old + dy}
def __vertical_to_d(*args) - >dict:
point = {'x': args['x_old'].'y': args['y_old'] + args['dy']}
return point
Copy the code
As the name suggests, it is the same as line, but draws a vertical line segment.
d = "M 50, 25 V 75"
Copy the code
④ H/ H: Horizontal line to
instruction | parameter | The formula |
---|---|---|
H | (x)+ | P = {x, y_old} |
h | (dx)+ | P = {x_old + dx, y_old} |
# H x
# P={x, y_old}
def __horizontal_to(*args) - >dict:
point = {'x': args['x'].'y': args['y_old']}
return point
# h dx
# P={x_old + dx, y_old}
def __horizontal_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old']}
return point
Copy the code
It’s the same thing as V over V, except I’m drawing a horizontal line here.
d = "H M 25, 50, 75"
Copy the code
⑤ C/ C: Cubic Bezier curve to
instruction | parameter | The formula |
---|---|---|
C | (x1, y1, x2, y2, x, y)+ | P = {x, y} P_cs = {x1, y1} P_ce = {x2, y2} |
c | (dx1, dy1, dx2, dy2, dx, dy)+ | P = {x_old + dx, y_old + dy} P_cs = {x1_old + dx1, y1_old + dy1 } P_ce = {x2_old + dx2, y2_old + dy2} |
# C x1,y1,x2,y2,x,y
# P={x, y} P_cs={x1, y1} P_ce={x2, y2}
def __cubic_curve_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
# point_cs = {'x1': args['x1'], 'y1': args['y1']}
# point_ce = {'x2': args['x2'], 'y2': args['y2']}
return point
# c dx1,dy1,dx2,dy2,dx,dy
# P={x_old + dx, y_old + dy} P_cs={x1_old + dx1, y1_old + dy1} P_ce={x2_old + dx2, y2_old + dy2}
def __cubic_curve_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
# point_cs = {'x1': args['x1_old'] + args['dx1'], 'y1': args['y1_old'] + args['dy1']}
# point_ce = {'x2': args['x2_old'] + args['dx2'], 'y2': args['y2_old'] + args['dy2']}
return point
Copy the code
The plot here is the Cubic Bezier curve, from the cubic initials, which can also be understood as the cubic Bezier curve.
For an introduction to Bezier curves, see the previous article: Graphics: Bezier Curves
The cubic Bezier curve has 4 control points (this paper starts with subscript 1 to facilitate understanding of the parameter table) : P1: the default is the coordinate of the current point P2 (x1, y1) P3 (x2, y2 p4 (x, Y)
The parameter list is p2, P3, and p4.
Such as:
d = "M 10,10 C 10,90 90,90 90,10"
Copy the code
⑥ S/ S: Smooth Cubic Bezier curve to
instruction | parameter | The formula |
---|---|---|
S | (x2, y2, x, y)+ | P = {x, y} P_ce = {x2, y2} |
s | (dx2, dy2, dx, dy)+ | P = {x_old + dx, y_old + dy} P_ce = {x2_old + dx2, y2_old + dy2} |
# S x2,y2,x,y
# P={x, y} P_ce={x2, y2}
def __short_cc_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
# point_ce = {'x2': args['x2'], 'y2': args['y2']}
return point
# s dx2,dy2,dx,dy
# P={x_old + dx, y_old + dy} P_ce={x2_old + dx2, y2_old + dy2}
def __short_cc_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
# point_ce = {'x2': args['x2_old'] + args['dx2'], 'y2': args['y2_old'] + args['dy2']}
return point
Copy the code
S/ S is a variant of C/ C with a special instruction, taken from the first letter of “smooth”, which, as the name suggests, means smooth.
The mathematical definition of smoothness can be simply understood as “continuous and differentiable”.
Obviously, the only places where the bezier curve is not smooth are the two endpoints. If the end point is a path endpoint, it must be a breakpoint, so the effect of “smooth” is limited to the start endpoint.
Meanwhile, according to the SVG writing style, the starting coordinate of a path is not in the command parameter list of the path, so the only parameters left for the smooth transition of Bezier curve are the control point P_control_start (P_cs) of the starting endpoint.
How does “Smooth” work:
P_cs is determined by the immediate preceding instruction of the path (or current instruction).
P_cs is a reflection of P_ce if the preceding path, or the preceding instruction, is a cubic Bezier curve. Can be understood as centrosymmetric/mirror/reflection. ★ If not, P_cs is the starting endpoint of the path.
In simple terms, the implicit P_cs is derived from P_ce when the preceding instruction is C/ C or S/ S (S/ S is obvious, because the argument list is a complex pair, SVG supports this syntax above) and evaluates to P_ce and the end point P.
Otherwise, the default is consistent/coincident/identical with the starting point coordinates.
If the display indicates smoothing the cubic Bezier curve, then the 4 control points are: P1: the default is the current point coordinate P2: (pre_P3)’ or pre_P4 P3: (x2, y2) p4: (x, y)
Implicitly, there are only p3 and p4, which are the argument lists for S.
Of course, this shorthand is reasonable, saving nearly half of the parameter list, the number of characters will also be reduced in half, for production environment and life applications, can save a lot of repeated content, reduce bandwidth, reduce redundancy, improve performance.
Except for a little bit of trouble for humans to understand, for machines, all kinds of angles are basically good.
Take the following four paths for example:
d = "M 10,90 C 30,90 30,10 50,10 S 70,90 90,90"
d = "M 10,90 S 30,10 50,10 S 70,90 90,90"
d = "M 10,90 L 50,10 S 70,90 90,90"
d = "M 10,90 L 50,10 C 70,10 70,90 90,90"
Copy the code
The following four images can cross-verify the above rules. The core area is 100 square wide, the dark area on the left is the forward path, and the light area on the right is the observation object.
Figure 1: C + S
From left to right, it is C + S, and the green dot is the junction of the two paths.
It can be seen that the control point shown in black on the right is the symmetric point of C on the left, and the curve transitions smoothly at the intermediate junction point.
If C is used to display S instead, see figure 4 below.
Figure 2: S + S
d="M 10,90 S 30,10 50,10 S 70,90 90,90"
d="M 10,90 S 30,10 50,10 70,90 90,90"
Copy the code
These two are equivalent.
Since it can be abbreviated to an instruction, the unique endpoints of both paths are at the beginning and end.
Also, S is a form of C, so the rule is the same as C.
Figure 3: L + S
What is listed here is a counter example. It can be seen that although the junction is continuous, the curvature of the two paths before and after is different, so it cannot be differentiated here, so it is not smooth.
Then the implicit control point of S defaults to the starting endpoint coordinate.
Figure 4: L + C
If you still want to represent the right side of the curve as it started, then you can only use C to describe it, and you need to explicitly write the coordinate of the control point, so S will not play its role.
⑦ Q/ Q: Quadratic Bezier curve to
instruction | parameter | The formula |
---|---|---|
Q | (x1, y1, x, y)+ | P = {x, y}P_c = {x1, y1} |
q | (dx1, dy1, dx, dy)+ | P = {x_old + dx, y_old + dy} P_c = {x1_old + dx1, y1_old + dy1} |
# Q x1,y1,x,y
# P={x, y} P_c={x1, y1}
def __quadratic_curve_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
# point_cs = {'x1': args['x1'], 'y1': args['y1']}
return point
# q dx1,dy1,dx,dy
# P={x_old + dx, y_old + dy} P_c={x1_old + dx1, y1_old + dy1}
def __quadratic_curve_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
# point_ce = {'x1': args['x1_old'] + args['dx1'], 'y1': args['y1_old'] + args['dy1']}
return point
Copy the code
Q is the first letter of the quadratic Bezier curve, similar to C, but with a quadratic Bezier curve.
It’s also called a second order/square Bezier curve.
The quadratic Bezier curve has three control points: P1: the default is the coordinate of the current point P2: (x1, y1) P3: (x, y)
Such as:
d = "M 10,90 Q 50,10 90,90"
Copy the code
It should be noted that quadratic and cubic Bezier curves differ not only in the number of control points, but also in the following example:
d = "M 10,90 Q 50,10 90,90"
d = "M 10,90 C 50,10 50,10 90,90"
Copy the code
The orange curve above is the cubic (cubic) Bezier curve drawn under the C command.
Although the two points in C overlap and are consistent with Q, it can be seen that the curvature of tertiary is larger and the radius of curvature is smaller (that is, the curvature degree is larger and sharper).
⑧ T/ T: Smooth quadratic Bezier curve to
instruction | parameter | The formula |
---|---|---|
T | (x, y)+ | P = {x, y} |
t | (dx, dy)+ | P = {x_old + dx, y_old + dy} |
# T x,y
# P={x, y}
def __t_qc_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
return point
# t dx,dy
# P={x_old + dx, y_old + dy}
def __t_qc_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
return point
Copy the code
T/ T is short for “smooth quadratic Bezier curve to”. Although I don’t know why the letter “T/ T “was chosen (maybe S follows), it works in the same way as S/ S, so I won’t repeat it here. You just replace all the cubic Bezier curves in S/ S with quadratic Bezier curves, and then you subtract one of the parameters.
Here’s an example:
d = "M 10,90 Q 25,10 50,50 T 90,10"
Copy the code
⑨ A/ A: Arc curve to
instruction | parameter | The formula |
---|---|---|
A | (rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y)+ |
P = {x, y} |
a | (rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, dx, dy)+ |
P = {x_old + dx, y_old + dy} |
# A rx,ry,x-axis-rotation,large-arc-flag,sweep-flag,x,y
# P={x, y}
def __arcs_to(*args) - >dict:
point = {'x': args['x'].'y': args['y']}
return point
# a rx,ry,x-axis-rotation,large-arc-flag,sweep-flag,dx,dy
# P={x_old + dx, y_old + dy}
def __arcs_to_d(*args) - >dict:
point = {'x': args['x_old'] + args['dx'].'y': args['y_old'] + args['dy']}
return point
Copy the code
A/ A is short for “Slabs Arc Curves,” derived from the initials “Arc.” Obviously, the Bezier curve born from the interpolation algorithm cannot draw any curve, so A/ A stands for drawing an elliptic arc curve.
A circle, or an oval, is also a very common curve in real life. It’s a little bit complicated. First of all, if you want to have an arc, you have to have a circle.
So the A/ A instruction inevitably computes the ellipse first.
· Parameters: Rx, RY
These two represent the shortest radius of the ellipse along the x and y axes in Cartesian coordinates.
Rx and RY represent the shortest distance. Due to the existence of terminal coordinate parameters (i.e., the final x and y), in actual drawing, instruction A will first calculate the minimum arc according to Rx and RY. If the terminal parameter is not on this ellipse, a new larger ellipse is drawn, at which point Rx and RY are given new values and the original parameters are overwritten.
Such as:
d = "M 50,50 A 15,5 0 1,1 50.1,50"
d = "M 50,50 A 15,5 0 1,1 501,50"
Copy the code
Rx is 15, ry is 5. That makes sense.
The difference in the figure above is the positioning of the end point, as you can see that the x in the parameter list has been expanded by a factor of 10.
In order for the ellipse to match that point, the half-axis length of the ellipse will inevitably get larger.
· Parameter: X-axis-rotation
There’s also a place to write Angle here, but it means the same thing.
Represents the rotation Angle of the graph in the X-axis direction, in degrees (°) – degree.
Here are two examples:
d = "M 50,50 A 15,5 0 1,1 50.1,50"
d = "M 50,50 A 15,5 90 1,1 50.1,50"
Copy the code
· Parameters: large-Arc-Flag, sweep-Flag
These are the control parameters, each with a value of 0 or 1, so there are four combinations.
Large-arc-flag (arc size) |
||||
0 |
1 |
|||
Sweep-flag (Direction of rotation) |
0 |
Take a small arc and go counterclockwise |
Take a large arc and go counterclockwise |
|
1 |
Take a small arc, clockwise |
Take a large arc, clockwise |
Here are four examples:
<path fill="none" stroke="red" d = "M 50,50 A 15,5 90 0,0 50,25" stroke-width="2"/>
<path fill="none" stroke="orange" d = "M 50,50 A 15,5 90 1,0 50,25" stroke-width="2"/>
<path fill="none" stroke="yellow" d = "M 50,50 A 15,5 90 0,1 50,25" stroke-width="2"/>
<path fill="none" stroke="green" d = "M 50,50 A 15,5 90 1,1 50,25" stroke-width="2"/>
Copy the code
As shown in the figure above: (0, 0) Red: short arc, counterclockwise (1, 0) Orange: long arc, counterclockwise (0, 1) Yellow: short arc, clockwise (1, 1) Green: long arc: clockwise
· Parameters: X, y
This is the endpoint parameter, which is easy to understand.
Attending Z/Z: ClosePath
instruction | parameter | The formula |
---|---|---|
Z, z |
def __close_path(*args) :
return
Copy the code
I didn’t omit it, but Z over Z means the curve is closed.
If this command is added to the end of a path, the curve is closed in a straight line. If not, it is an open curve.
It’s easy to understand.
As shown in the following example:
<path fill="none" stroke="#ffffff" stroke-width="3" d = "(21.3 c0, 0-6.4, 2.5-6.1, 21.8 c0.3, 19.2-10.2, 23.1, 10,26.2 S63.1, 70,64.9 c1.8 79.7, 9.7, 23.1, 8.8, 23.1, 8.8 s15.3-41.7, 4.3 to 43.4 C81.2, 43.5, 65.8, 32,65.8, 32"/>
<path fill="none" stroke="green" stroke-width="1" d = "(21.3 c0, 0-6.4, 2.5-6.1, 21.8 c0.3, 19.2-10.2, 23.1, 10,26.2 S63.1, 70,64.9 c1.8 79.7, 9.7, 23.1, 8.8, 23.1, 8.8 s15.3-41.7, 4.3 to 43.4 C81.2, 43.5, 65.8, 32,65.8, 32 z"/>
Copy the code
Just watch for the instruction character “Z/ Z “at the end.
Green is the closed graph after adding the closed instruction, and white is the open curve.
3. Write at the end
At this point, the content of the SVG-D attribute is complete.
This is a very complex property, and developers often get their hands on a designer’s design document and see unusually dense and large D strings, especially if a photo has been traced.
For example, many people still get a tingling feeling when they look at a path collection of tens of thousands of lines.
When you understand what each character represents, the vast array of characters doesn’t bother you so much.
It can be said that the existence of d attribute, SVG vector graphics format has a soul, is also the existence of the essence of most vector graphics.
Basically, most of the planar shapes that we encounter in our lives can be described in terms of D, and in fact, people do.
Master this attribute, can say the real sense of vector graphics have an understanding.
Have fun.