Skip to content
Merged
9 changes: 9 additions & 0 deletions doc/rst/source/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
Changelog
=========

New Features in GMT 6.7
=======================

* Add quadratic (**Q**) and cubic (**B**) Bezier curve commands to the custom symbol
macro language, enabling smooth curves in ``.def`` symbol files. Both commands
extend the path from the previous **M** or **D** point; **Q** takes one control point
plus endpoint, **B** takes two control points plus endpoint. See
:ref:`Custom Symbols <custom-symbols>` for details.

New Features in GMT 6.6
=======================

Expand Down
14 changes: 14 additions & 0 deletions doc/rst/source/reference/custom-symbols.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ are constants.
+===============+============+========================================+============================================+
| arc | **A** | Append circular arc to existing path | :math:`x_c, y_c, d, \alpha_1, \alpha_2` |
+---------------+------------+----------------------------------------+--------------------------------------------+
| cubicbezier | **B** | Cubic Bezier from last point | :math:`cx_1, cy_1, cx_2, cy_2, ex, ey` |
+---------------+------------+----------------------------------------+--------------------------------------------+
| drawto | **D** | Draw line from previous point | :math:`x, y` |
+---------------+------------+----------------------------------------+--------------------------------------------+
| moveto | **M** | Set a new anchor point | :math:`x_0, y_0` |
Expand Down Expand Up @@ -179,6 +181,8 @@ are constants.
+---------------+------------+----------------------------------------+--------------------------------------------+
| pentagon | **n** | Plot a pentagon | :math:`x, y, size` |
+---------------+------------+----------------------------------------+--------------------------------------------+
| quadbezier | **Q** | Quadratic Bezier from last point | :math:`cx, cy, ex, ey` |
+---------------+------------+----------------------------------------+--------------------------------------------+
| rect | **r** | Plot a rectangle | :math:`x, y, width, height` |
+---------------+------------+----------------------------------------+--------------------------------------------+
| roundrect | **R** | Plot a rounded rectangle | :math:`x, y, width, height, radius` |
Expand All @@ -200,6 +204,16 @@ are constants.
| plus | **+** | Plot a plus sign | :math:`x, y, size` |
+---------------+------------+----------------------------------------+--------------------------------------------+

Note for **B** and **Q**\: Both Bezier commands extend the current path from the
*last point set by* **M** *or* **D** (the implicit start point :math:`P_0`); you do not
supply :math:`P_0` explicitly. **B** takes two control points followed by the endpoint
(:math:`cx_1, cy_1, cx_2, cy_2, ex, ey`) and draws a cubic Bezier curve.
**Q** takes one control point followed by the endpoint (:math:`cx, cy, ex, ey`) and
draws a quadratic Bezier curve. Both commands are tessellated into line segments
at render time and integrate with the normal fill/stroke pipeline, so **-G** and **-W**
apply as usual. Use **B** when converting SVG paths (SVG ``C``/``c`` maps directly);
use **Q** for simpler arcs where one control point is sufficient.

Note for **O**\: if an **a** is appended to the angle then :math:`\alpha` is considered
to be a map azimuth; otherwise it is a Cartesian map angle. The **a** modifier
does not apply if the angle is given via a variable, in which case the type of angle
Expand Down
10 changes: 10 additions & 0 deletions share/custom/heart.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Heart shape using cubic Bezier curves (B command)
# Classic heart: two cubic bezier lobes meeting at bottom tip (0,-0.5)
# and top center notch (0, 0.15)

# Right lobe: from notch (0,0.15) → top-right → right → bottom tip
0.0 0.15 M
0.5 0.5 0.5 -0.1 0.0 -0.5 B

# Left lobe: from bottom tip (0,-0.5) → left → top-left → back to notch
-0.5 -0.1 -0.5 0.5 0.0 0.15 B
8 changes: 8 additions & 0 deletions share/custom/leaf.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Leaf shape using quadratic Bezier curves
# Tip at bottom (0,-0.5), apex at top (0,0.5)
# Right lobe: cp=(0.5,-0.2), right side curves out
# Left lobe: cp=(-0.5,-0.2), symmetric

0.0 -0.5 M
0.5 0.3 0.0 0.5 Q
-0.5 0.3 0.0 -0.5 Q
33 changes: 33 additions & 0 deletions src/gmt_plot.c
Original file line number Diff line number Diff line change
Expand Up @@ -7727,6 +7727,22 @@ int gmt_draw_custom_symbol (struct GMT_CTRL *GMT, double x0, double y0, double s
gmt_M_free (GMT, yp);
break;

case GMT_SYMBOL_QUAD_BEZIER: { /* Quadratic Bezier: cp=(x,y) endpoint=(dim[0],dim[1]) */
/* P0 = last path point, P1 = (x,y) [control], P2 = (dim[0],dim[1]) [endpoint] */
double t, mt, x0b = 0.0, y0b = 0.0;
flush = true;
if (n > 0) { x0b = xx[n-1]; y0b = yy[n-1]; }
for (i = 1; i <= GMT_BEZIER_NPTS; i++) {
t = (double)i / GMT_BEZIER_NPTS;
mt = 1.0 - t;
if (n >= n_alloc) gmt_M_malloc2 (GMT, xx, yy, n, &n_alloc, double);
xx[n] = mt*mt*x0b + 2.0*mt*t*x + t*t*dim[0];
yy[n] = mt*mt*y0b + 2.0*mt*t*y + t*t*dim[1];
n++;
}
break;
}

case GMT_SYMBOL_ROTATE: /* Rotate the symbol coordinate system by a fixed amount */
if (flush) gmtplot_flush_symbol_piece (GMT, PSL, xx, yy, &n, &p, &f, this_outline, &flush);
PSL_setorigin (PSL, 0.0, 0.0, s->p[0], PSL_FWD);
Expand Down Expand Up @@ -7756,6 +7772,23 @@ int gmt_draw_custom_symbol (struct GMT_CTRL *GMT, double x0, double y0, double s
if (s->pen) current_pen = s->pen;
break;

case GMT_SYMBOL_CUBIC_BEZIER: { /* Cubic Bezier (B): cp1=(x,y) cp2=(dim[0],dim[1]) endpoint=(dim[2], p[3]*size) */
/* B(t) = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3 */
double t, mt, mt2, t2, x0b = 0.0, y0b = 0.0;
double cx2 = dim[0], cy2 = dim[1], ex = dim[2], ey = s->p[3] * size[0];
flush = true;
if (n > 0) { x0b = xx[n-1]; y0b = yy[n-1]; }
for (i = 1; i <= GMT_BEZIER_NPTS; i++) {
t = (double)i / GMT_BEZIER_NPTS;
mt = 1.0 - t; mt2 = mt * mt; t2 = t * t;
if (n >= n_alloc) gmt_M_malloc2(GMT, xx, yy, n, &n_alloc, double);
xx[n] = mt2*mt*x0b + 3.0*mt2*t*x + 3.0*mt*t2*cx2 + t2*t*ex;
yy[n] = mt2*mt*y0b + 3.0*mt2*t*y + 3.0*mt*t2*cy2 + t2*t*ey;
n++;
}
break;
}

case (int)'C':
if (gmt_M_compat_check (GMT, 4)) { /* Warn and purposefully fall through to assign the rest of the statements */
GMT_Report (GMT->parent, GMT_MSG_COMPAT, "Circle macro symbol C is deprecated; use c instead\n");
Expand Down
4 changes: 4 additions & 0 deletions src/gmt_plot.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@
#define GMT_SYMBOL_GEOVECTOR ((int)'=')
#define GMT_SYMBOL_VARTEXT ((int)'L')
#define GMT_SYMBOL_EPS ((int)'P')
#define GMT_SYMBOL_QUAD_BEZIER ((int)'Q') /* Quadratic Bezier: cp=(x,y) endpoint=(p[0],p[1]) */
#define GMT_SYMBOL_CUBIC_BEZIER ((int)'B') /* Cubic Bezier: cp1=(x,y) cp2=(p[0],p[1]) endpoint=(p[2],p[3]) */

#define GMT_SYMBOL_LINE 0
#define GMT_SYMBOL_NONE ((int)' ')
#define GMT_SYMBOL_NOT_SET ((int)'*')

#define GMT_DOT_SIZE 0.005 /* Size of a "dot" on a GMT PS map [in inches] */

#define GMT_BEZIER_NPTS 20 /* Tessellation segments for Bezier curve macro commands */

/*! FRONT symbols */

enum GMT_enum_front {GMT_FRONT_FAULT = 0,
Expand Down
14 changes: 14 additions & 0 deletions src/gmt_support.c
Original file line number Diff line number Diff line change
Expand Up @@ -5784,6 +5784,20 @@ GMT_LOCAL int gmtsupport_init_custom_symbol (struct GMT_CTRL *GMT, char *in_name
if ((s->eps = gmtsupport_load_eps_symbol (GMT, name, path)) == NULL) return GMT_RUNTIME_ERROR;
break;

case GMT_SYMBOL_QUAD_BEZIER: /* Quadratic Bezier: cx cy ex ey Q */
if (last != 4) error++; /* Need cx cy ex ey + action = 5 cols */
s->p[0] = atof (col[2]); /* endpoint x */
s->p[1] = atof (col[3]); /* endpoint y */
break;

case GMT_SYMBOL_CUBIC_BEZIER: /* Cubic Bezier: cx1 cy1 cx2 cy2 ex ey C */
if (last != 6) error++; /* Need cx1 cy1 cx2 cy2 ex ey + action = 7 cols */
s->p[0] = atof (col[2]); /* second control point x */
s->p[1] = atof (col[3]); /* second control point y */
s->p[2] = atof (col[4]); /* endpoint x */
s->p[3] = atof (col[5]); /* endpoint y */
break;

default:
error++;
break;
Expand Down
2 changes: 1 addition & 1 deletion src/gmt_symbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ struct GMT_REFPOINT { /* Used to hold items relevant for a reference point */
char *args; /* Text representation of any additional arguments */
};

#define CUSTOM_SYMBOL_MAXVAR 3 /* So we can check in the code if we exceed this */
#define CUSTOM_SYMBOL_MAXVAR 4 /* So we can check in the code if we exceed this */

enum gmt_enum_custsymb {
GMT_CONST_STRING = -5, /* We have a constant string in a conditional test */
Expand Down
Binary file added test/baseline/grdmix/grdmix.ps
Binary file not shown.
Binary file added test/baseline/psxy/bezier_demo_cubic.ps
Binary file not shown.
Binary file added test/baseline/psxy/bezier_demo_quadratic.ps
Binary file not shown.
26 changes: 26 additions & 0 deletions test/psxy/bezier_demo_cubic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
#
# Test C (cubic Bezier) command in custom symbol macro language.
# Uses share/custom/heart.def (closed heart shape via two C curves)
# alongside share/custom/leaf.def (Q-based) for visual comparison.

ps=bezier_demo_cubic.ps

gmt psbasemap -R-5/5/-4/4 -JX14c/10c -Ba2f1 -BWeSn+t"Cubic Bezier (C) custom symbol demo" -P -K > $ps

# Hearts in a row, different sizes and colors
for lon in -4 -2 0 2 4; do
echo "$lon 2" | gmt psxy -R -J -Skheart/1.5c -Gred -W0.5p,darkred -O -K >> $ps
echo "$lon 0" | gmt psxy -R -J -Skheart/2.0c -Gtomato -W0.7p,red -O -K >> $ps
echo "$lon -2" | gmt psxy -R -J -Skheart/2.5c -Gdarkred -W1p,black -O -K >> $ps
done

# Compare: leaves (Q) vs hearts (C) side by side
for lon in -3.5 -1.5 0.5 2.5; do
echo "$lon 3.3" | gmt psxy -R -J -Skleaf/1.2c -Ggreen -W0.5p,darkgreen -O -K >> $ps
done
for lon in -2.5 -0.5 1.5 3.5; do
echo "$lon 3.3" | gmt psxy -R -J -Skheart/1.2c -Gred -W0.5p,darkred -O -K >> $ps
done

echo "0 3.8 Q (leaf) vs C (heart)" | gmt pstext -R -J -F+f10p,Helvetica-Bold+jCM -O >> $ps
31 changes: 31 additions & 0 deletions test/psxy/bezier_demo_quadratic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
#
# Test Q (quadratic Bezier) command in custom symbol macro language.
# Uses share/custom/leaf.def (closed leaf shape from two Q curves)
# and share/custom/qbezier_test.def (open parabolic arc).

ps=bezier_demo_quadratic.ps

gmt psbasemap -R-5/5/-4/4 -JX14c/10c -Ba2f1 -BWeSn+t"Quadratic Bezier (Q) custom symbol demo" -P -K > $ps

# Grid of leaves at different sizes and colors
for lon in -4 -2 0 2 4; do
echo "$lon 2" | gmt psxy -R -J -Skleaf/1.5c -Glightgreen -W0.5p,darkgreen -O -K >> $ps
echo "$lon 0" | gmt psxy -R -J -Skleaf/2.0c -Ggreen -W0.7p,darkgreen -O -K >> $ps
echo "$lon -2" | gmt psxy -R -J -Skleaf/2.5c -Gdarkgreen -W1p,black -O -K >> $ps
done

# Parabolic arc symbol (open path, just stroked)
for lon in -3 -1 1 3; do
echo "$lon 3.2" | gmt psxy -R -J -Skqbezier_test/1.5c -W1.5p,red -O -K >> $ps
done

# Labels
gmt pstext -R -J -F+f11p,Helvetica-Bold,red+jCM -O -K >> $ps << EOF
0 3.7 Parabolic arcs
EOF
gmt pstext -R -J -F+f9p+jCM -O >> $ps << EOF
0 2.7 1.5c leaves
0 0.7 2.0c leaves
0 -1.3 2.5c leaves
EOF
Loading