Skip to content

Commit 953b03a

Browse files
authored
Add color gradients to polygons in psxy (#8846)
* Initial steps to have color gradients in polygons * Add more input data formats (set colors after points coords) * Commit what we have so far. * Commit the working state where all but -N is working. * Add test and warn about -N * Expand the color gradients to psxyz. Fix issues with consequences in the BoundingBox determination and hence to the raster conversions. * Add reference to H-S-V and CMYK color formats in the -G+g option.
1 parent 65f2d39 commit 953b03a

File tree

8 files changed

+670
-25
lines changed

8 files changed

+670
-25
lines changed

doc/rst/source/plot.rst

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Synopsis
2020
[ |-D|\ *dx*\ [/*dy*] ]
2121
[ |-E|\ [**x**\|\ **y**\|\ **X**\|\ **Y**][**+a**\|\ **A**][**+cl**\|\ **f**][**+n**][**+w**\ *width*\ [/*cap*]][**+p**\ *pen*] ]
2222
[ |-F|\ [**c**\|\ **n**\|\ **p**][**a**\|\ **s**\|\ **t**\|\ **r**\|\ *refpoint*] ]
23-
[ |-G|\ *fill*\|\ **+z** ]
23+
[ |-G|\ *fill*\|\ **+g**\|\ **+z** ]
2424
[ |-H|\ [*scale*] ]
2525
[ |-I|\ [*intens*] ]
2626
[ |-L|\ [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*][**+yb**\|\ **t**\|\ *y0*][**+p**\ *pen*] ]
@@ -196,7 +196,7 @@ Optional Arguments
196196

197197
.. _-G:
198198

199-
**-G**\ *fill*\|\ **+z** :ref:`(more ...) <-Gfill_attrib>`
199+
**-G**\ *fill*\|\ **+g**\|\ **+z** :ref:`(more ...) <-Gfill_attrib>`
200200
Select color or pattern for filling of symbols or polygons [Default is no fill].
201201
Note that this module will search for |-G| and |-W| strings in all the
202202
segment headers and let any values thus found over-ride the command line settings.
@@ -206,6 +206,19 @@ Optional Arguments
206206
we will cycle through the fill colors implied by :term:`COLOR_SET` and change on a per-segment
207207
or per-table basis. Any *transparency* setting is unchanged.
208208

209+
**+g**
210+
Enable vertex-based color gradients for polygons using Gouraud shading.
211+
This modifier automatically detects the input data format and supports three formats:
212+
213+
1. **RGB format**: *x y r g b* where RGB values are in the range 0-255.
214+
2. **Color name format**: *x y colorname* where *colorname* can be a named color (e.g., "red", "blue"),
215+
hex color (e.g., "#FF0000"), slash-separated RGB (e.g., "255/0/0"), H-S-V or C/M/Y/K formats.
216+
3. **CPT format**: *x y z* where *z* values are mapped to colors via the CPT specified with |-C|.
217+
218+
Polygons with any number of vertices are supported and automatically triangulated using a fan triangulation
219+
from the first vertex. Each vertex gets its own color, and Gouraud shading creates smooth color gradients
220+
across the polygon faces. Use |-W| to add an outline to the gradient polygons.
221+
209222
.. _-H:
210223

211224
**-H**\ [*scale*]
@@ -455,6 +468,35 @@ to cm given the scale 3.60::
455468

456469
gmt plot -R20/40/-20/0 -JM6i -Sv0.15i+e+z3.6c -Gred -W0.25p -Baf data.txt -pdf map
457470

471+
To plot a triangle with smooth color gradients using RGB values directly::
472+
473+
cat << EOF > triangle.txt
474+
0 0 255 0 0
475+
3 0 0 255 0
476+
1.5 2.6 0 0 255
477+
EOF
478+
gmt plot triangle.txt -R-1/4/-1/3 -JX10c -G+g -W0.5p,black -Baf -png gradient
479+
480+
Or using color names for a quadrilateral::
481+
482+
cat << EOF > square.txt
483+
0 0 red
484+
2 0 yellow
485+
2 2 green
486+
0 2 blue
487+
EOF
488+
gmt plot square.txt -R-0.5/2.5/-0.5/2.5 -JX10c -G+g -W1p -Baf -png gradient
489+
490+
Or using a CPT file with z-values::
491+
492+
gmt makecpt -Chot -T0/100 > temp.cpt
493+
cat << EOF > triangle_z.txt
494+
0 0 0
495+
3 0 50
496+
1.5 2.6 100
497+
EOF
498+
gmt plot triangle_z.txt -R-1/4/-1/3 -JX10c -G+g -Ctemp.cpt -W0.5p -Baf -png gradient
499+
458500
.. include:: plot_notes.rst_
459501

460502
.. include:: auto_legend_info.rst_

doc/rst/source/plot3d.rst

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Synopsis
1919
[ |SYN_OPT-B| ]
2020
[ |-C|\ *cpt* ]
2121
[ |-D|\ *dx*/*dy*\ [/*dz*] ]
22-
[ |-G|\ *fill*\|\ **+z** ]
22+
[ |-G|\ *fill*\|\ **+g**\|\ **+z** ]
2323
[ |-H|\ [*scale*] ]
2424
[ |-I|\ [*intens*] ]
2525
[ |-L|\ [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*][**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*] ]
@@ -129,7 +129,7 @@ Optional Arguments
129129

130130
.. _-G:
131131

132-
**-G**\ *fill* :ref:`(more ...) <-Gfill_attrib>`
132+
**-G**\ *fill*\|\ **+g**\|\ **+z** :ref:`(more ...) <-Gfill_attrib>`
133133
Select color or pattern for filling of symbols or polygons [Default is no fill].
134134
Note that the module will search for |-G| and |-W| strings in all the
135135
segment headers and let any values thus found over-ride the command line settings.
@@ -138,6 +138,19 @@ Optional Arguments
138138
we will cycle through the fill colors implied by :term:`COLOR_SET` and change on a per-segment
139139
or per-table basis. Any *transparency* setting is unchanged.
140140

141+
**+g**
142+
Enable vertex-based color gradients for polygons using Gouraud shading.
143+
This modifier automatically detects the input data format and supports three formats:
144+
145+
1. **RGB format**: *x y z r g b* where RGB values are in the range 0-255.
146+
2. **Color name format**: *x y z colorname* where *colorname* can be a named color (e.g., "red", "blue"),
147+
hex color (e.g., "#FF0000"), slash-separated RGB (e.g., "255/0/0"), H-S-V or C/M/Y/K formats.
148+
3. **CPT format**: *x y z value* where *value* is mapped to colors via the CPT specified with |-C|.
149+
150+
Polygons with any number of vertices are supported and automatically triangulated using a fan triangulation
151+
from the first vertex. Each vertex gets its own color, and Gouraud shading creates smooth color gradients
152+
across the polygon faces. Use |-W| to add an outline to the gradient polygons.
153+
141154
.. _-H:
142155

143156
**-H**\ [*scale*]

src/postscriptlight.c

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4853,6 +4853,198 @@ int PSL_plotpolygon (struct PSL_CTRL *PSL, double *x, double *y, int n) {
48534853
return (PSL_NO_ERROR);
48544854
}
48554855

4856+
int PSL_plotgradienttriangle(struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps) {
4857+
/* Draw triangle with smooth vertex color gradient using barycentric interpolation
4858+
* x, y: arrays of 3 coordinates (in inches, will be converted to points)
4859+
* rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0
4860+
* steps: number of subdivisions (higher = smoother, slower)
4861+
*
4862+
* Writen by Claude.ai
4863+
*/
4864+
4865+
int i, j;
4866+
double s, t;
4867+
double s1, t1, s2, t2, s3, t3, s4, t4;
4868+
double u1, v1, w1, u2, v2, w2, u3, v3, w3, u4, v4, w4;
4869+
double px1, py1, px2, py2, px3, py3, px4, py4;
4870+
double pr1, pg1, pb1, pr2, pg2, pb2, pr3, pg3, pb3, pr4, pg4, pb4;
4871+
double step_inv;
4872+
double x_pts[3], y_pts[3];
4873+
4874+
if (steps <= 0) steps = 50; /* Default quality */
4875+
if (steps > 200) steps = 200; /* Limit maximum */
4876+
4877+
/* Convert inches to points */
4878+
for (i = 0; i < 3; i++) {
4879+
x_pts[i] = x[i] * PSL->internal.dpu;
4880+
y_pts[i] = y[i] * PSL->internal.dpu;
4881+
}
4882+
4883+
step_inv = 1.0 / steps;
4884+
4885+
PSL_comment(PSL, "Begin gradient triangle\n");
4886+
PSL_command(PSL, "gsave\n");
4887+
4888+
/* Subdivide triangle into micro-triangles using barycentric coordinates */
4889+
for (i = 0; i < steps; i++) {
4890+
t = i * step_inv;
4891+
for (j = 0; j < steps; j++) {
4892+
s = j * step_inv;
4893+
4894+
/* Barycentric coordinates for 4 corners of sub-quad */
4895+
s1 = s; t1 = t;
4896+
s2 = s + step_inv; t2 = t;
4897+
s3 = s + step_inv; t3 = t + step_inv;
4898+
s4 = s; t4 = t + step_inv;
4899+
4900+
/* Check if first corner is inside triangle */
4901+
if (s1 + t1 <= 1.0) {
4902+
u1 = 1.0 - s1 - t1;
4903+
v1 = s1;
4904+
w1 = t1;
4905+
4906+
/* Interpolate position */
4907+
px1 = u1 * x_pts[0] + v1 * x_pts[1] + w1 * x_pts[2];
4908+
py1 = u1 * y_pts[0] + v1 * y_pts[1] + w1 * y_pts[2];
4909+
4910+
/* Interpolate color */
4911+
pr1 = u1 * rgb[0] + v1 * rgb[3] + w1 * rgb[6];
4912+
pg1 = u1 * rgb[1] + v1 * rgb[4] + w1 * rgb[7];
4913+
pb1 = u1 * rgb[2] + v1 * rgb[5] + w1 * rgb[8];
4914+
4915+
/* Check second corner */
4916+
if (s2 + t2 <= 1.0) {
4917+
u2 = 1.0 - s2 - t2;
4918+
v2 = s2;
4919+
w2 = t2;
4920+
4921+
px2 = u2 * x_pts[0] + v2 * x_pts[1] + w2 * x_pts[2];
4922+
py2 = u2 * y_pts[0] + v2 * y_pts[1] + w2 * y_pts[2];
4923+
pr2 = u2 * rgb[0] + v2 * rgb[3] + w2 * rgb[6];
4924+
pg2 = u2 * rgb[1] + v2 * rgb[4] + w2 * rgb[7];
4925+
pb2 = u2 * rgb[2] + v2 * rgb[5] + w2 * rgb[8];
4926+
4927+
/* Check fourth corner */
4928+
if (s4 + t4 <= 1.0) {
4929+
u4 = 1.0 - s4 - t4;
4930+
v4 = s4;
4931+
w4 = t4;
4932+
4933+
px4 = u4 * x_pts[0] + v4 * x_pts[1] + w4 * x_pts[2];
4934+
py4 = u4 * y_pts[0] + v4 * y_pts[1] + w4 * y_pts[2];
4935+
pr4 = u4 * rgb[0] + v4 * rgb[3] + w4 * rgb[6];
4936+
pg4 = u4 * rgb[1] + v4 * rgb[4] + w4 * rgb[7];
4937+
pb4 = u4 * rgb[2] + v4 * rgb[5] + w4 * rgb[8];
4938+
4939+
/* Draw first micro-triangle: p1-p2-p4 */
4940+
PSL_command(PSL, "%.6f %.6f %.6f C\n",
4941+
(pr1 + pr2 + pr4) / 3.0,
4942+
(pg1 + pg2 + pg4) / 3.0,
4943+
(pb1 + pb2 + pb4) / 3.0);
4944+
PSL_command(PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", px1, py1, px2, py2, px4, py4);
4945+
}
4946+
4947+
/* Check third corner for second micro-triangle */
4948+
if (s3 + t3 <= 1.0) {
4949+
u3 = 1.0 - s3 - t3;
4950+
v3 = s3;
4951+
w3 = t3;
4952+
4953+
px3 = u3 * x_pts[0] + v3 * x_pts[1] + w3 * x_pts[2];
4954+
py3 = u3 * y_pts[0] + v3 * y_pts[1] + w3 * y_pts[2];
4955+
pr3 = u3 * rgb[0] + v3 * rgb[3] + w3 * rgb[6];
4956+
pg3 = u3 * rgb[1] + v3 * rgb[4] + w3 * rgb[7];
4957+
pb3 = u3 * rgb[2] + v3 * rgb[5] + w3 * rgb[8];
4958+
4959+
if (s4 + t4 <= 1.0) { /* Already computed p4 */
4960+
/* Draw second micro-triangle: p2-p3-p4 */
4961+
PSL_command(PSL, "%.6f %.6f %.6f C\n",
4962+
(pr2 + pr3 + pr4) / 3.0,
4963+
(pg2 + pg3 + pg4) / 3.0,
4964+
(pb2 + pb3 + pb4) / 3.0);
4965+
PSL_command(PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", px2, py2, px3, py3, px4, py4);
4966+
}
4967+
}
4968+
}
4969+
}
4970+
}
4971+
}
4972+
4973+
PSL_command(PSL, "grestore\n");
4974+
PSL_comment(PSL, "End gradient triangle\n");
4975+
4976+
return (PSL_NO_ERROR);
4977+
}
4978+
4979+
int PSL_plotgradienttriangle_gouraud(struct PSL_CTRL *PSL, double *x, double *y, double *rgb) {
4980+
/* Draw triangle with smooth Gouraud shading using PostScript Type 4 shading
4981+
* x, y: arrays of 3 coordinates (in inches, will be converted to points)
4982+
* rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0
4983+
* This uses PostScript Level 2/3 shading for perfectly smooth gradients
4984+
*
4985+
* Writen by Claude.ai
4986+
*/
4987+
4988+
int i;
4989+
double x_pts[3], y_pts[3];
4990+
4991+
/* Convert inches to points */
4992+
for (i = 0; i < 3; i++) {
4993+
x_pts[i] = x[i] * PSL->internal.dpu;
4994+
y_pts[i] = y[i] * PSL->internal.dpu;
4995+
}
4996+
4997+
PSL_comment(PSL, "Begin Gouraud shaded triangle\n");
4998+
PSL_command(PSL, "gsave\n");
4999+
/* Set clipping path to triangle to constrain shfill and establish bounding box */
5000+
PSL_command(PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L closepath clip",
5001+
x_pts[0], y_pts[0], x_pts[1], y_pts[1], x_pts[2], y_pts[2]);
5002+
5003+
5004+
/* Create Type 4 (Free-Form Gouraud) shading dictionary */
5005+
PSL_command(PSL, "<<\n");
5006+
PSL_command(PSL, " /ShadingType 4\n");
5007+
PSL_command(PSL, " /ColorSpace /DeviceRGB\n");
5008+
PSL_command(PSL, " /BitsPerCoordinate 16\n");
5009+
PSL_command(PSL, " /BitsPerComponent 8\n");
5010+
PSL_command(PSL, " /BitsPerFlag 8\n");
5011+
5012+
/* Decode arrays map from integer coordinates to actual coordinate ranges */
5013+
/* Find bounding box */
5014+
double xmin = x_pts[0], xmax = x_pts[0], ymin = y_pts[0], ymax = y_pts[0];
5015+
for (i = 1; i < 3; i++) {
5016+
if (x_pts[i] < xmin) xmin = x_pts[i];
5017+
if (x_pts[i] > xmax) xmax = x_pts[i];
5018+
if (y_pts[i] < ymin) ymin = y_pts[i];
5019+
if (y_pts[i] > ymax) ymax = y_pts[i];
5020+
}
5021+
5022+
PSL_command(PSL, " /Decode [%.4f %.4f %.4f %.4f 0 1 0 1 0 1]\n", xmin, xmax, ymin, ymax);
5023+
PSL_command(PSL, " /DataSource <\n");
5024+
5025+
/* Write triangle data as hex string */
5026+
/* Flag 0 = start new triangle, then coordinates (x,y) scaled to 0-65535, then RGB scaled to 0-255 */
5027+
for (i = 0; i < 3; i++) {
5028+
unsigned int flag = 0; /* Start new triangle with vertex 0 */
5029+
unsigned int x_scaled = (unsigned int)((x_pts[i] - xmin) / (xmax - xmin) * 65535.0 + 0.5);
5030+
unsigned int y_scaled = (unsigned int)((y_pts[i] - ymin) / (ymax - ymin) * 65535.0 + 0.5);
5031+
unsigned int r = (unsigned int)(rgb[i*3 + 0] * 255.0 + 0.5);
5032+
unsigned int g = (unsigned int)(rgb[i*3 + 1] * 255.0 + 0.5);
5033+
unsigned int b = (unsigned int)(rgb[i*3 + 2] * 255.0 + 0.5);
5034+
5035+
PSL_command(PSL, " %02X %04X %04X %02X%02X%02X\n", flag, x_scaled, y_scaled, r, g, b);
5036+
}
5037+
5038+
PSL_command(PSL, " >\n");
5039+
PSL_command(PSL, ">>\n");
5040+
PSL_command(PSL, "shfill\n");
5041+
5042+
PSL_command(PSL, "grestore\n");
5043+
PSL_comment(PSL, "End Gouraud shaded triangle\n");
5044+
5045+
return (PSL_NO_ERROR);
5046+
}
5047+
48565048
int PSL_setexec (struct PSL_CTRL *PSL, int action) {
48575049
/* Enables of disables the execution of a PSL_plot_completion function at start of a PSL_plotinit overlay */
48585050
PSL->current.complete = (action) ? 1 : 0;

src/postscriptlight.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,8 @@ EXTERN_MSC int PSL_plotparagraphbox (struct PSL_CTRL *PSL, double x, double y, d
477477
EXTERN_MSC int PSL_plotpoint (struct PSL_CTRL *PSL, double x, double y, int pen);
478478
EXTERN_MSC int PSL_plotbox (struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1);
479479
EXTERN_MSC int PSL_plotpolygon (struct PSL_CTRL *PSL, double *x, double *y, int n);
480+
EXTERN_MSC int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps);
481+
EXTERN_MSC int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y, double *rgb);
480482
EXTERN_MSC int PSL_plotsegment (struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1);
481483
EXTERN_MSC int PSL_plotsymbol (struct PSL_CTRL *PSL, double x, double y, double param[], int symbol);
482484
EXTERN_MSC int PSL_plottext (struct PSL_CTRL *PSL, double x, double y, double fontsize, char *text, double angle, int justify, int mode);

0 commit comments

Comments
 (0)