From 6acb3dd2073b65de830933ff9f3091cf06c1c83d Mon Sep 17 00:00:00 2001 From: Joaquim Date: Wed, 24 Dec 2025 17:05:35 +0000 Subject: [PATCH 1/7] Initial steps to have color gradients in polygons --- src/postscriptlight.c | 186 ++++++++++++++++++++++++++++++++++++++++++ src/postscriptlight.h | 2 + src/psxy.c | 161 ++++++++++++++++++++++++++++++++++-- 3 files changed, 343 insertions(+), 6 deletions(-) diff --git a/src/postscriptlight.c b/src/postscriptlight.c index 8c5eb8c5b20..94e1b20219c 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -4849,6 +4849,192 @@ int PSL_plotpolygon (struct PSL_CTRL *PSL, double *x, double *y, int n) { return (PSL_NO_ERROR); } +int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps) { + /* Draw triangle with smooth vertex color gradient using barycentric interpolation + * x, y: arrays of 3 coordinates (in inches, will be converted to points) + * rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0 + * steps: number of subdivisions (higher = smoother, slower) + */ + + int i, j; + double s, t; + double s1, t1, s2, t2, s3, t3, s4, t4; + double u1, v1, w1, u2, v2, w2, u3, v3, w3, u4, v4, w4; + double px1, py1, px2, py2, px3, py3, px4, py4; + double pr1, pg1, pb1, pr2, pg2, pb2, pr3, pg3, pb3, pr4, pg4, pb4; + double step_inv; + double x_pts[3], y_pts[3]; + + if (steps <= 0) steps = 50; /* Default quality */ + if (steps > 200) steps = 200; /* Limit maximum */ + + /* Convert inches to points */ + for (i = 0; i < 3; i++) { + x_pts[i] = x[i] * PSL->internal.dpu; + y_pts[i] = y[i] * PSL->internal.dpu; + } + + step_inv = 1.0 / steps; + + PSL_comment (PSL, "Begin gradient triangle\n"); + PSL_command (PSL, "gsave\n"); + + /* Subdivide triangle into micro-triangles using barycentric coordinates */ + for (i = 0; i < steps; i++) { + t = i * step_inv; + for (j = 0; j < steps; j++) { + s = j * step_inv; + + /* Barycentric coordinates for 4 corners of sub-quad */ + s1 = s; t1 = t; + s2 = s + step_inv; t2 = t; + s3 = s + step_inv; t3 = t + step_inv; + s4 = s; t4 = t + step_inv; + + /* Check if first corner is inside triangle */ + if (s1 + t1 <= 1.0) { + u1 = 1.0 - s1 - t1; + v1 = s1; + w1 = t1; + + /* Interpolate position */ + px1 = u1 * x_pts[0] + v1 * x_pts[1] + w1 * x_pts[2]; + py1 = u1 * y_pts[0] + v1 * y_pts[1] + w1 * y_pts[2]; + + /* Interpolate color */ + pr1 = u1 * rgb[0] + v1 * rgb[3] + w1 * rgb[6]; + pg1 = u1 * rgb[1] + v1 * rgb[4] + w1 * rgb[7]; + pb1 = u1 * rgb[2] + v1 * rgb[5] + w1 * rgb[8]; + + /* Check second corner */ + if (s2 + t2 <= 1.0) { + u2 = 1.0 - s2 - t2; + v2 = s2; + w2 = t2; + + px2 = u2 * x_pts[0] + v2 * x_pts[1] + w2 * x_pts[2]; + py2 = u2 * y_pts[0] + v2 * y_pts[1] + w2 * y_pts[2]; + pr2 = u2 * rgb[0] + v2 * rgb[3] + w2 * rgb[6]; + pg2 = u2 * rgb[1] + v2 * rgb[4] + w2 * rgb[7]; + pb2 = u2 * rgb[2] + v2 * rgb[5] + w2 * rgb[8]; + + /* Check fourth corner */ + if (s4 + t4 <= 1.0) { + u4 = 1.0 - s4 - t4; + v4 = s4; + w4 = t4; + + px4 = u4 * x_pts[0] + v4 * x_pts[1] + w4 * x_pts[2]; + py4 = u4 * y_pts[0] + v4 * y_pts[1] + w4 * y_pts[2]; + pr4 = u4 * rgb[0] + v4 * rgb[3] + w4 * rgb[6]; + pg4 = u4 * rgb[1] + v4 * rgb[4] + w4 * rgb[7]; + pb4 = u4 * rgb[2] + v4 * rgb[5] + w4 * rgb[8]; + + /* Draw first micro-triangle: p1-p2-p4 */ + PSL_command (PSL, "%.6f %.6f %.6f C\n", + (pr1 + pr2 + pr4) / 3.0, + (pg1 + pg2 + pg4) / 3.0, + (pb1 + pb2 + pb4) / 3.0); + PSL_command (PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", + px1, py1, px2, py2, px4, py4); + } + + /* Check third corner for second micro-triangle */ + if (s3 + t3 <= 1.0) { + u3 = 1.0 - s3 - t3; + v3 = s3; + w3 = t3; + + px3 = u3 * x_pts[0] + v3 * x_pts[1] + w3 * x_pts[2]; + py3 = u3 * y_pts[0] + v3 * y_pts[1] + w3 * y_pts[2]; + pr3 = u3 * rgb[0] + v3 * rgb[3] + w3 * rgb[6]; + pg3 = u3 * rgb[1] + v3 * rgb[4] + w3 * rgb[7]; + pb3 = u3 * rgb[2] + v3 * rgb[5] + w3 * rgb[8]; + + if (s4 + t4 <= 1.0) { /* Already computed p4 */ + /* Draw second micro-triangle: p2-p3-p4 */ + PSL_command (PSL, "%.6f %.6f %.6f C\n", + (pr2 + pr3 + pr4) / 3.0, + (pg2 + pg3 + pg4) / 3.0, + (pb2 + pb3 + pb4) / 3.0); + PSL_command (PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", + px2, py2, px3, py3, px4, py4); + } + } + } + } + } + } + + PSL_command (PSL, "grestore\n"); + PSL_comment (PSL, "End gradient triangle\n"); + + return (PSL_NO_ERROR); +} + +int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y, double *rgb) { + /* Draw triangle with smooth Gouraud shading using PostScript Type 4 shading + * x, y: arrays of 3 coordinates (in inches, will be converted to points) + * rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0 + * This uses PostScript Level 2/3 shading for perfectly smooth gradients + */ + + int i; + double x_pts[3], y_pts[3]; + + /* Convert inches to points */ + for (i = 0; i < 3; i++) { + x_pts[i] = x[i] * PSL->internal.dpu; + y_pts[i] = y[i] * PSL->internal.dpu; + } + + PSL_comment (PSL, "Begin Gouraud shaded triangle\n"); + PSL_command (PSL, "gsave\n"); + + /* Create Type 4 (Free-Form Gouraud) shading dictionary */ + PSL_command (PSL, "<<\n"); + PSL_command (PSL, " /ShadingType 4\n"); + PSL_command (PSL, " /ColorSpace /DeviceRGB\n"); + PSL_command (PSL, " /BitsPerCoordinate 16\n"); + PSL_command (PSL, " /BitsPerComponent 8\n"); + PSL_command (PSL, " /BitsPerFlag 8\n"); + + /* Decode arrays map from integer coordinates to actual coordinate ranges */ + /* Find bounding box */ + double xmin = x_pts[0], xmax = x_pts[0], ymin = y_pts[0], ymax = y_pts[0]; + for (i = 1; i < 3; i++) { + if (x_pts[i] < xmin) xmin = x_pts[i]; + if (x_pts[i] > xmax) xmax = x_pts[i]; + if (y_pts[i] < ymin) ymin = y_pts[i]; + if (y_pts[i] > ymax) ymax = y_pts[i]; + } + + PSL_command (PSL, " /Decode [%.4f %.4f %.4f %.4f 0 1 0 1 0 1]\n", xmin, xmax, ymin, ymax); + PSL_command (PSL, " /DataSource <\n"); + + /* Write triangle data as hex string */ + /* Flag 0 = start new triangle, then coordinates (x,y) scaled to 0-65535, then RGB scaled to 0-255 */ + for (i = 0; i < 3; i++) { + unsigned int flag = 0; /* Start new triangle with vertex 0 */ + unsigned int x_scaled = (unsigned int)((x_pts[i] - xmin) / (xmax - xmin) * 65535.0 + 0.5); + unsigned int y_scaled = (unsigned int)((y_pts[i] - ymin) / (ymax - ymin) * 65535.0 + 0.5); + unsigned int r = (unsigned int)(rgb[i*3 + 0] * 255.0 + 0.5); + unsigned int g = (unsigned int)(rgb[i*3 + 1] * 255.0 + 0.5); + unsigned int b = (unsigned int)(rgb[i*3 + 2] * 255.0 + 0.5); + + PSL_command (PSL, " %02X %04X %04X %02X%02X%02X\n", flag, x_scaled, y_scaled, r, g, b); + } + + PSL_command (PSL, " >\n"); + PSL_command (PSL, ">>\n"); + PSL_command (PSL, "shfill\n"); + + PSL_command (PSL, "grestore\n"); + PSL_comment (PSL, "End Gouraud shaded triangle\n"); + + return (PSL_NO_ERROR); +} + int PSL_setexec (struct PSL_CTRL *PSL, int action) { /* Enables of disables the execution of a PSL_plot_completion function at start of a PSL_plotinit overlay */ PSL->current.complete = (action) ? 1 : 0; diff --git a/src/postscriptlight.h b/src/postscriptlight.h index 1b5132e6f26..14f7d29e037 100644 --- a/src/postscriptlight.h +++ b/src/postscriptlight.h @@ -476,6 +476,8 @@ EXTERN_MSC int PSL_plotparagraphbox (struct PSL_CTRL *PSL, double x, double y, d EXTERN_MSC int PSL_plotpoint (struct PSL_CTRL *PSL, double x, double y, int pen); EXTERN_MSC int PSL_plotbox (struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1); EXTERN_MSC int PSL_plotpolygon (struct PSL_CTRL *PSL, double *x, double *y, int n); +EXTERN_MSC int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps); +EXTERN_MSC int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y, double *rgb); EXTERN_MSC int PSL_plotsegment (struct PSL_CTRL *PSL, double x0, double y0, double x1, double y1); EXTERN_MSC int PSL_plotsymbol (struct PSL_CTRL *PSL, double x, double y, double param[], int symbol); EXTERN_MSC int PSL_plottext (struct PSL_CTRL *PSL, double x, double y, double fontsize, char *text, double angle, int justify, int mode); diff --git a/src/psxy.c b/src/psxy.c index d15274efb01..d566bfa339f 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -72,6 +72,8 @@ struct PSXY_CTRL { struct PSXY_G { /* -G|+z */ bool active; bool set_color; + bool gradient; + bool smooth; /* Use Gouraud shading instead of subdivision */ unsigned int sequential; struct GMT_FILL fill; } G; @@ -941,6 +943,16 @@ static int parse (struct GMT_CTRL *GMT, struct PSXY_CTRL *Ctrl, struct GMT_OPTIO n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active); if (strncmp (opt->arg, "+z", 2U) == 0) Ctrl->G.set_color = true; + else if (strncmp (opt->arg, "+gs", 3U) == 0) { + Ctrl->G.gradient = true; + Ctrl->G.smooth = true; /* Use Gouraud shading */ + Ctrl->G.active = true; + } + else if (strncmp (opt->arg, "+g", 2U) == 0) { + Ctrl->G.gradient = true; + Ctrl->G.smooth = false; /* Use subdivision */ + Ctrl->G.active = true; + } else if (!opt->arg[0] || gmt_getfill (GMT, opt->arg, &Ctrl->G.fill)) { gmt_fill_syntax (GMT, 'G', NULL, " "); n_errors++; } @@ -1124,6 +1136,12 @@ static int parse (struct GMT_CTRL *GMT, struct PSXY_CTRL *Ctrl, struct GMT_OPTIO if (Ctrl->G.set_color && !Ctrl->L.polygon) { /* Otherwise -G+z -Z and open polylines would color only the outline */ Ctrl->L.active = Ctrl->L.polygon = true; } + if (Ctrl->G.gradient && !Ctrl->L.polygon) { /* Same for gradient triangles */ + Ctrl->L.active = Ctrl->L.polygon = true; + } + if (Ctrl->G.gradient) { /* Gradient triangles need x,y,z columns */ + gmt_set_cols (GMT, GMT_IN, 3); + } gmt_consider_current_cpt (API, &Ctrl->C.active, &(Ctrl->C.file)); @@ -2465,6 +2483,7 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (Ctrl->L.anchor == PSXY_POL_SYMM_DEV) n_cols = 3; else if (Ctrl->L.anchor == PSXY_POL_ASYMM_DEV || Ctrl->L.anchor == PSXY_POL_ASYMM_ENV) n_cols = 4; + if (Ctrl->G.gradient) n_cols = 3; /* Gradient triangles need x,y,z */ conf_line = (Ctrl->L.anchor >= PSXY_POL_SYMM_DEV && Ctrl->L.anchor <= PSXY_POL_ASYMM_ENV); if (GMT_Init_IO (API, GMT_IS_DATASET, geometry, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Register data input */ @@ -2560,6 +2579,7 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (Ctrl->W.cpt_effect && Ctrl->W.pen.cptmode & 2) polygon = true; if (Ctrl->G.set_color) polygon = true; + if (Ctrl->G.gradient) polygon = true; if (Ctrl->M.active && !Ctrl->M.constant) { /* Must check input matches requirements */ if (Ctrl->M.mode == GMT_CURVES_SEPARATE) { @@ -2818,8 +2838,55 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { } if (polygon && !no_line_clip) { /* Want a closed polygon (with or without fill and with or without outline) */ - gmt_setfill (GMT, ¤t_fill, outline_setting); - gmt_geo_polygons (GMT, L); + /* Check if this is a gradient polygon (triangle or quad) */ + bool gradient_done = false; + fprintf(stderr, "DEBUG: polygon check: gradient=%d n_rows=%llu n_cols=%llu P=%p\n", Ctrl->G.gradient, L->n_rows, L->n_columns, (void*)P); + if (Ctrl->G.gradient && (L->n_rows == 4 || L->n_rows == 5) && L->n_columns >= 3 && P) { + double tri_rgb[9], rgb_tmp[4]; + double plot_x[3], plot_y[3]; + + /* Project to map coordinates */ + if ((GMT->current.plot.n = gmt_geo_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) > 0 && GMT->current.plot.n >= 3) { + unsigned int n_triangles = (L->n_rows == 5) ? 2 : 1; /* 5 rows = 4 unique vertices + closing = square */ + unsigned int tri_indices[2][3] = {{0, 1, 2}, {0, 2, 3}}; /* Triangulation for quad */ + + for (unsigned int tri = 0; tri < n_triangles; tri++) { + /* Get RGB colors from z-values via CPT for this triangle */ + for (unsigned int k = 0; k < 3; k++) { + unsigned int idx = tri_indices[tri][k]; + gmt_get_rgb_from_z (GMT, P, L->data[2][idx], rgb_tmp); + tri_rgb[k * 3 + 0] = rgb_tmp[0]; + tri_rgb[k * 3 + 1] = rgb_tmp[1]; + tri_rgb[k * 3 + 2] = rgb_tmp[2]; + } + + /* Copy coordinates (PSL expects inches, not points!) */ + for (unsigned int k = 0; k < 3; k++) { + unsigned int idx = tri_indices[tri][k]; + plot_x[k] = GMT->current.plot.x[idx]; + plot_y[k] = GMT->current.plot.y[idx]; + } + + /* Draw gradient triangle */ + if (Ctrl->G.smooth) + PSL_plotgradienttriangle_gouraud (PSL, plot_x, plot_y, tri_rgb); + else + PSL_plotgradienttriangle (PSL, plot_x, plot_y, tri_rgb, 50); + } + + gradient_done = true; + + /* Optionally draw outline */ + if (outline_setting) { + gmt_setpen (GMT, ¤t_pen); + PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, L->n_rows); + } + } + } + if (!gradient_done) { + gmt_setfill (GMT, ¤t_fill, outline_setting); + gmt_geo_polygons (GMT, L); + } } else if (S.symbol == GMT_SYMBOL_QUOTED_LINE) { /* Labeled lines are dealt with by the contour machinery */ bool closed, split = false; @@ -2973,11 +3040,47 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { else GMT->current.plot.n = gmt_cart_to_xy_line (GMT, GMT->hidden.mem_coord[GMT_X], GMT->hidden.mem_coord[GMT_Y], end); if (Ctrl->L.outline) gmt_setpen (GMT, &Ctrl->L.pen); /* Select separate pen for polygon outline */ - if (Ctrl->G.active) /* Specify the fill, possibly set outline */ + if (Ctrl->G.active) { /* Specify the fill, possibly set outline */ gmt_setfill (GMT, ¤t_fill, Ctrl->L.outline); - else /* No fill, just outline */ + /* Check if gradient triangle */ + if (Ctrl->G.gradient && (GMT->current.plot.n == 3 || GMT->current.plot.n == 4) && D->table[tbl]->segment[seg]->n_columns >= 3 && L->n_rows >= 3 && P) { + double tri_rgb[9], rgb_tmp[4]; + double plot_x[3], plot_y[3]; + struct GMT_DATASEGMENT *S_orig = D->table[tbl]->segment[seg]; + + /* Get RGB colors from z-values via CPT */ + for (unsigned int k = 0; k < 3; k++) { + gmt_get_rgb_from_z (GMT, P, S_orig->data[2][k], rgb_tmp); + tri_rgb[k * 3 + 0] = gmt_M_is255(rgb_tmp[0]); + tri_rgb[k * 3 + 1] = gmt_M_is255(rgb_tmp[1]); + tri_rgb[k * 3 + 2] = gmt_M_is255(rgb_tmp[2]); + } + + /* Convert to PostScript points */ + for (unsigned int k = 0; k < 3; k++) { + plot_x[k] = GMT->current.plot.x[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; + plot_y[k] = GMT->current.plot.y[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; + } + + /* Draw gradient triangle */ + if (Ctrl->G.smooth) + PSL_plotgradienttriangle_gouraud (PSL, plot_x, plot_y, tri_rgb); + else + PSL_plotgradienttriangle (PSL, plot_x, plot_y, tri_rgb, 50); + + /* Optionally draw outline */ + if (Ctrl->L.outline) { + PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (GMT->current.plot.n == 4) ? 3 : (int)GMT->current.plot.n); + } + } + else { + PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + } + } + else { /* No fill, just outline */ gmt_setfill (GMT, NULL, Ctrl->L.outline); - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + } if (!Ctrl->W.active) draw_line = false; /* Did not want to actually draw the main line */ if (Ctrl->L.outline) gmt_setpen (GMT, ¤t_pen); /* Reset the pen to what -W indicates */ } @@ -2986,7 +3089,53 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (outline_active) gmt_setpen (GMT, ¤t_pen); /* Select separate pen for polygon outline */ if (Ctrl->G.active) { /* Specify the fill, possibly set outline */ gmt_setfill (GMT, ¤t_fill, outline_active); - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + /* DEBUG gradient */ + if (Ctrl->G.gradient) { + GMT_Report (API, GMT_MSG_WARNING, "Gradient: n=%d seg_ncols=%d table_ncols=%d nrows=%d\n", + GMT->current.plot.n, L->n_columns, D->table[tbl]->n_columns, L->n_rows); + /* Try to access z from original table/segment */ + if (D->table[tbl]->n_columns >= 3 && L->n_rows >= 3) { + struct GMT_DATASEGMENT *orig_seg = D->table[tbl]->segment[seg]; + if (orig_seg->n_columns >= 3) { + GMT_Report (API, GMT_MSG_WARNING, "Z-values from orig seg: z[0]=%g z[1]=%g z[2]=%g\n", + orig_seg->data[2][0], orig_seg->data[2][1], orig_seg->data[2][2]); + } + } + } + if (Ctrl->G.gradient && (GMT->current.plot.n == 3 || GMT->current.plot.n == 4) && D->table[tbl]->segment[seg]->n_columns >= 3 && L->n_rows >= 3 && P) { + /* Draw gradient-filled triangle */ + double tri_rgb[9], rgb_tmp[4]; + double plot_x[3], plot_y[3]; + struct GMT_DATASEGMENT *S_orig = D->table[tbl]->segment[seg]; + + /* Get RGB colors from z-values via CPT */ + for (unsigned int k = 0; k < 3; k++) { + gmt_get_rgb_from_z (GMT, P, S_orig->data[2][k], rgb_tmp); + tri_rgb[k * 3 + 0] = gmt_M_is255(rgb_tmp[0]); + tri_rgb[k * 3 + 1] = gmt_M_is255(rgb_tmp[1]); + tri_rgb[k * 3 + 2] = gmt_M_is255(rgb_tmp[2]); + } + + /* Convert to PostScript points */ + for (unsigned int k = 0; k < 3; k++) { + plot_x[k] = GMT->current.plot.x[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; + plot_y[k] = GMT->current.plot.y[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; + } + + /* Draw gradient triangle */ + if (Ctrl->G.smooth) + PSL_plotgradienttriangle_gouraud (PSL, plot_x, plot_y, tri_rgb); + else + PSL_plotgradienttriangle (PSL, plot_x, plot_y, tri_rgb, 50); + + /* Optionally draw outline */ + if (outline_active) { + PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, 3); + } + } + else { + PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + } } else { /* No fill but may still be a polygon */ gmt_setfill (GMT, NULL, outline_active); From 4dd096ccc190228ea39ebe9ce106a3d1c826a825 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Thu, 25 Dec 2025 11:51:09 +0000 Subject: [PATCH 2/7] Add more input data formats (set colors after points coords) --- src/psxy.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/src/psxy.c b/src/psxy.c index d566bfa339f..5e17a4ecc41 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -74,6 +74,7 @@ struct PSXY_CTRL { bool set_color; bool gradient; bool smooth; /* Use Gouraud shading instead of subdivision */ + bool direct; /* Direct color specification instead of CPT */ unsigned int sequential; struct GMT_FILL fill; } G; @@ -943,14 +944,28 @@ static int parse (struct GMT_CTRL *GMT, struct PSXY_CTRL *Ctrl, struct GMT_OPTIO n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active); if (strncmp (opt->arg, "+z", 2U) == 0) Ctrl->G.set_color = true; + else if (strncmp (opt->arg, "+gcs", 4U) == 0) { + Ctrl->G.gradient = true; + Ctrl->G.direct = true; /* Direct colors */ + Ctrl->G.smooth = true; /* Gouraud shading */ + Ctrl->G.active = true; + } + else if (strncmp (opt->arg, "+gc", 3U) == 0) { + Ctrl->G.gradient = true; + Ctrl->G.direct = true; /* Direct colors */ + Ctrl->G.smooth = false; /* Subdivision */ + Ctrl->G.active = true; + } else if (strncmp (opt->arg, "+gs", 3U) == 0) { Ctrl->G.gradient = true; - Ctrl->G.smooth = true; /* Use Gouraud shading */ + Ctrl->G.direct = false; /* CPT-based */ + Ctrl->G.smooth = true; /* Gouraud shading */ Ctrl->G.active = true; } else if (strncmp (opt->arg, "+g", 2U) == 0) { Ctrl->G.gradient = true; - Ctrl->G.smooth = false; /* Use subdivision */ + Ctrl->G.direct = false; /* CPT-based */ + Ctrl->G.smooth = false; /* Subdivision */ Ctrl->G.active = true; } else if (!opt->arg[0] || gmt_getfill (GMT, opt->arg, &Ctrl->G.fill)) { @@ -2483,7 +2498,8 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (Ctrl->L.anchor == PSXY_POL_SYMM_DEV) n_cols = 3; else if (Ctrl->L.anchor == PSXY_POL_ASYMM_DEV || Ctrl->L.anchor == PSXY_POL_ASYMM_ENV) n_cols = 4; - if (Ctrl->G.gradient) n_cols = 3; /* Gradient triangles need x,y,z */ + if (Ctrl->G.gradient && !Ctrl->G.direct) n_cols = 3; /* CPT-based gradients: x,y,z */ + /* For direct colors, don't force n_cols - let GMT auto-detect (can be 3 or 5 columns) */ conf_line = (Ctrl->L.anchor >= PSXY_POL_SYMM_DEV && Ctrl->L.anchor <= PSXY_POL_ASYMM_ENV); if (GMT_Init_IO (API, GMT_IS_DATASET, geometry, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Register data input */ @@ -2840,8 +2856,8 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (polygon && !no_line_clip) { /* Want a closed polygon (with or without fill and with or without outline) */ /* Check if this is a gradient polygon (triangle or quad) */ bool gradient_done = false; - fprintf(stderr, "DEBUG: polygon check: gradient=%d n_rows=%llu n_cols=%llu P=%p\n", Ctrl->G.gradient, L->n_rows, L->n_columns, (void*)P); - if (Ctrl->G.gradient && (L->n_rows == 4 || L->n_rows == 5) && L->n_columns >= 3 && P) { + fprintf(stderr, "DEBUG: polygon check: gradient=%d direct=%d n_rows=%llu n_cols=%llu P=%p\n", Ctrl->G.gradient, Ctrl->G.direct, L->n_rows, L->n_columns, (void*)P); + if (Ctrl->G.gradient && (L->n_rows == 4 || L->n_rows == 5) && L->n_columns >= 3 && (P || Ctrl->G.direct)) { double tri_rgb[9], rgb_tmp[4]; double plot_x[3], plot_y[3]; @@ -2851,13 +2867,44 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { unsigned int tri_indices[2][3] = {{0, 1, 2}, {0, 2, 3}}; /* Triangulation for quad */ for (unsigned int tri = 0; tri < n_triangles; tri++) { - /* Get RGB colors from z-values via CPT for this triangle */ + /* Get RGB colors for this triangle */ for (unsigned int k = 0; k < 3; k++) { unsigned int idx = tri_indices[tri][k]; - gmt_get_rgb_from_z (GMT, P, L->data[2][idx], rgb_tmp); - tri_rgb[k * 3 + 0] = rgb_tmp[0]; - tri_rgb[k * 3 + 1] = rgb_tmp[1]; - tri_rgb[k * 3 + 2] = rgb_tmp[2]; + if (Ctrl->G.direct) { + /* Direct color specification */ + if (L->n_columns == 5) { + /* Separate R G B columns: x y r g b */ + tri_rgb[k * 3 + 0] = L->data[2][idx] / 255.0; /* R */ + tri_rgb[k * 3 + 1] = L->data[3][idx] / 255.0; /* G */ + tri_rgb[k * 3 + 2] = L->data[4][idx] / 255.0; /* B */ + } + else if (L->text) { + /* Color string: x y colorstring (stored in L->text) */ + char *color_str = L->text[idx]; + if (color_str && gmt_getrgb (GMT, color_str, rgb_tmp) == 0) { + tri_rgb[k * 3 + 0] = gmt_M_is255(rgb_tmp[0]); + tri_rgb[k * 3 + 1] = gmt_M_is255(rgb_tmp[1]); + tri_rgb[k * 3 + 2] = gmt_M_is255(rgb_tmp[2]); + } + else { + /* Failed to parse, use black */ + tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; + GMT_Report (API, GMT_MSG_WARNING, "Failed to parse color '%s', using black\n", color_str ? color_str : "NULL"); + } + } + else { + /* No text data available, use black */ + tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; + GMT_Report (API, GMT_MSG_WARNING, "No color data found, using black\n"); + } + } + else { + /* CPT-based: look up z-value */ + gmt_get_rgb_from_z (GMT, P, L->data[2][idx], rgb_tmp); + tri_rgb[k * 3 + 0] = rgb_tmp[0]; + tri_rgb[k * 3 + 1] = rgb_tmp[1]; + tri_rgb[k * 3 + 2] = rgb_tmp[2]; + } } /* Copy coordinates (PSL expects inches, not points!) */ From 129174aec1b48ce257acffbf5bfcaddd30481a3b Mon Sep 17 00:00:00 2001 From: Joaquim Date: Fri, 26 Dec 2025 02:08:42 +0000 Subject: [PATCH 3/7] Commit what we have so far. --- src/psxy.c | 105 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/src/psxy.c b/src/psxy.c index 5e17a4ecc41..136d5eb8cc7 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -944,28 +944,14 @@ static int parse (struct GMT_CTRL *GMT, struct PSXY_CTRL *Ctrl, struct GMT_OPTIO n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active); if (strncmp (opt->arg, "+z", 2U) == 0) Ctrl->G.set_color = true; - else if (strncmp (opt->arg, "+gcs", 4U) == 0) { + else if (strncmp (opt->arg, "+gt", 3U) == 0) { Ctrl->G.gradient = true; - Ctrl->G.direct = true; /* Direct colors */ - Ctrl->G.smooth = true; /* Gouraud shading */ - Ctrl->G.active = true; - } - else if (strncmp (opt->arg, "+gc", 3U) == 0) { - Ctrl->G.gradient = true; - Ctrl->G.direct = true; /* Direct colors */ - Ctrl->G.smooth = false; /* Subdivision */ - Ctrl->G.active = true; - } - else if (strncmp (opt->arg, "+gs", 3U) == 0) { - Ctrl->G.gradient = true; - Ctrl->G.direct = false; /* CPT-based */ - Ctrl->G.smooth = true; /* Gouraud shading */ + Ctrl->G.smooth = false; /* Subdivision/triangulation, format auto-detected */ Ctrl->G.active = true; } else if (strncmp (opt->arg, "+g", 2U) == 0) { Ctrl->G.gradient = true; - Ctrl->G.direct = false; /* CPT-based */ - Ctrl->G.smooth = false; /* Subdivision */ + Ctrl->G.smooth = true; /* Gouraud shading (default), format auto-detected */ Ctrl->G.active = true; } else if (!opt->arg[0] || gmt_getfill (GMT, opt->arg, &Ctrl->G.fill)) { @@ -2498,15 +2484,64 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (Ctrl->L.anchor == PSXY_POL_SYMM_DEV) n_cols = 3; else if (Ctrl->L.anchor == PSXY_POL_ASYMM_DEV || Ctrl->L.anchor == PSXY_POL_ASYMM_ENV) n_cols = 4; - if (Ctrl->G.gradient && !Ctrl->G.direct) n_cols = 3; /* CPT-based gradients: x,y,z */ - /* For direct colors, don't force n_cols - let GMT auto-detect (can be 3 or 5 columns) */ + bool need_text = false; /* Flag for color string format requiring text reading */ + if (Ctrl->G.gradient) { + /* Auto-detect format: 'x y z' (CPT), 'x y r g b' (RGB), or 'x y colorstring' */ + /* Try reading first line to detect format */ + FILE *fp = NULL; + char line[GMT_BUFSIZ]; + bool format_detected = false; + struct GMT_OPTION *opt = NULL; + + /* Find input file in options list (look for option without leading dash) */ + for (opt = options; opt; opt = opt->next) { + if (opt->option == GMT_OPT_INFILE) { + fp = fopen (opt->arg, "r"); + break; + } + } + + if (fp) { + /* Read lines until we find a data line (skip comments) */ + while (fgets(line, GMT_BUFSIZ, fp)) { + if (line[0] == '#' || line[0] == '\n') continue; /* Skip comments and empty lines */ + /* Try to parse as 5 or 3 numeric values */ + double v[5]; + int n_read = sscanf(line, "%lf %lf %lf %lf %lf", &v[0], &v[1], &v[2], &v[3], &v[4]); + if (n_read == 5) { + n_cols = 5; /* RGB format: x y r g b */ + Ctrl->G.direct = true; + need_text = false; + } + else if (n_read == 3) { + n_cols = 3; /* CPT format: x y z */ + Ctrl->G.direct = false; + need_text = false; + } + else { + n_cols = 2; /* Color string format: x y colorstring */ + Ctrl->G.direct = true; + need_text = true; + } + format_detected = true; + break; + } + fclose (fp); + } + if (!format_detected) { + /* Couldn't detect format, default to CPT */ + n_cols = 3; + Ctrl->G.direct = false; + need_text = false; + } + } conf_line = (Ctrl->L.anchor >= PSXY_POL_SYMM_DEV && Ctrl->L.anchor <= PSXY_POL_ASYMM_ENV); - if (GMT_Init_IO (API, GMT_IS_DATASET, geometry, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Register data input */ + if (GMT_Init_IO(API, GMT_IS_DATASET, geometry, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Register data input */ Return (API->error); } - if ((error = GMT_Set_Columns (API, GMT_IN, (unsigned int)n_cols, GMT_COL_FIX_NO_TEXT)) != GMT_NOERROR) { - /* We don't want trailing text because we may need to resample lines below */ + if ((error = GMT_Set_Columns(API, GMT_IN, (unsigned int)n_cols, need_text ? GMT_COL_FIX : GMT_COL_FIX_NO_TEXT)) != GMT_NOERROR) { + /* We don't want trailing text because we may need to resample lines below, except for direct color strings */ Return (API->error); } if ((S.symbol == GMT_SYMBOL_QUOTED_LINE || S.symbol == GMT_SYMBOL_DECORATED_LINE) && S.G.segmentize) { /* Special quoted/decorated line where each point-pair should be considered a line segment */ @@ -2856,8 +2891,16 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (polygon && !no_line_clip) { /* Want a closed polygon (with or without fill and with or without outline) */ /* Check if this is a gradient polygon (triangle or quad) */ bool gradient_done = false; - fprintf(stderr, "DEBUG: polygon check: gradient=%d direct=%d n_rows=%llu n_cols=%llu P=%p\n", Ctrl->G.gradient, Ctrl->G.direct, L->n_rows, L->n_columns, (void*)P); - if (Ctrl->G.gradient && (L->n_rows == 4 || L->n_rows == 5) && L->n_columns >= 3 && (P || Ctrl->G.direct)) { + + /* Error check: CPT mode requires a color palette */ + if (Ctrl->G.gradient && !Ctrl->G.direct && P == NULL) { + GMT_Report(API, GMT_MSG_ERROR, "Gradient option -G+g detected CPT format (x y z) but no color palette provided. Use -C to specify a color palette.\n"); + Return (GMT_RUNTIME_ERROR); + } + + /* For gradients: need enough vertices AND either CPT or direct colors */ + if (Ctrl->G.gradient && (L->n_rows == 4 || L->n_rows == 5) && + ((Ctrl->G.direct && L->n_columns >= 2) || (!Ctrl->G.direct && L->n_columns >= 3 && P))) { double tri_rgb[9], rgb_tmp[4]; double plot_x[3], plot_y[3]; @@ -2881,21 +2924,23 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { else if (L->text) { /* Color string: x y colorstring (stored in L->text) */ char *color_str = L->text[idx]; - if (color_str && gmt_getrgb (GMT, color_str, rgb_tmp) == 0) { - tri_rgb[k * 3 + 0] = gmt_M_is255(rgb_tmp[0]); - tri_rgb[k * 3 + 1] = gmt_M_is255(rgb_tmp[1]); - tri_rgb[k * 3 + 2] = gmt_M_is255(rgb_tmp[2]); + int parse_result = gmt_getrgb (GMT, color_str, rgb_tmp); + if (color_str && parse_result == 0) { + /* gmt_getrgb returns 0-1 range, use directly */ + tri_rgb[k * 3 + 0] = rgb_tmp[0]; + tri_rgb[k * 3 + 1] = rgb_tmp[1]; + tri_rgb[k * 3 + 2] = rgb_tmp[2]; } else { /* Failed to parse, use black */ tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; - GMT_Report (API, GMT_MSG_WARNING, "Failed to parse color '%s', using black\n", color_str ? color_str : "NULL"); + GMT_Report(API, GMT_MSG_WARNING, "Failed to parse color '%s', using black\n", color_str ? color_str : "NULL"); } } else { /* No text data available, use black */ tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; - GMT_Report (API, GMT_MSG_WARNING, "No color data found, using black\n"); + GMT_Report(API, GMT_MSG_WARNING, "No color data found, using black\n"); } } else { From 82e86e745b01e82e2c8825c5016cb755c6442314 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Fri, 26 Dec 2025 11:40:31 +0000 Subject: [PATCH 4/7] Commit the working state where all but -N is working. --- doc/rst/source/plot.rst | 44 ++++++++- src/postscriptlight.c | 20 ++-- src/psxy.c | 201 +++++++++++++++++++--------------------- 3 files changed, 148 insertions(+), 117 deletions(-) diff --git a/doc/rst/source/plot.rst b/doc/rst/source/plot.rst index 79b2ad02fb0..243c607d4ff 100644 --- a/doc/rst/source/plot.rst +++ b/doc/rst/source/plot.rst @@ -196,7 +196,7 @@ Optional Arguments .. _-G: -**-G**\ *fill*\|\ **+z** :ref:`(more ...) <-Gfill_attrib>` +**-G**\ *fill*\|\ **+z**\|\ **+g** :ref:`(more ...) <-Gfill_attrib>` Select color or pattern for filling of symbols or polygons [Default is no fill]. Note that this module will search for |-G| and |-W| strings in all the segment headers and let any values thus found over-ride the command line settings. @@ -206,6 +206,19 @@ Optional Arguments we will cycle through the fill colors implied by :term:`COLOR_SET` and change on a per-segment or per-table basis. Any *transparency* setting is unchanged. + **+g** + Enable vertex-based color gradients for polygons using Gouraud shading. + This modifier automatically detects the input data format and supports three formats: + + 1. **RGB format**: *x y r g b* where RGB values are in the range 0-255. + 2. **Color name format**: *x y colorname* where *colorname* can be a named color (e.g., "red", "blue"), + hex color (e.g., "#FF0000"), or slash-separated RGB (e.g., "255/0/0"). + 3. **CPT format**: *x y z* where *z* values are mapped to colors via the CPT specified with |-C|. + + Polygons with any number of vertices are supported and automatically triangulated using a fan triangulation + from the first vertex. Each vertex gets its own color, and Gouraud shading creates smooth color gradients + across the polygon faces. Use |-W| to add an outline to the gradient polygons. + .. _-H: **-H**\ [*scale*] @@ -455,6 +468,35 @@ to cm given the scale 3.60:: gmt plot -R20/40/-20/0 -JM6i -Sv0.15i+e+z3.6c -Gred -W0.25p -Baf data.txt -pdf map +To plot a triangle with smooth color gradients using RGB values directly:: + + cat << EOF > triangle.txt + 0 0 255 0 0 + 3 0 0 255 0 + 1.5 2.6 0 0 255 + EOF + gmt plot triangle.txt -R-1/4/-1/3 -JX10c -G+g -W0.5p,black -Baf -png gradient + +Or using color names for a quadrilateral:: + + cat << EOF > square.txt + 0 0 red + 2 0 yellow + 2 2 green + 0 2 blue + EOF + gmt plot square.txt -R-0.5/2.5/-0.5/2.5 -JX10c -G+g -W1p -Baf -png gradient + +Or using a CPT file with z-values:: + + gmt makecpt -Chot -T0/100 > temp.cpt + cat << EOF > triangle_z.txt + 0 0 0 + 3 0 50 + 1.5 2.6 100 + EOF + gmt plot triangle_z.txt -R-1/4/-1/3 -JX10c -G+g -Ctemp.cpt -W0.5p -Baf -png gradient + .. include:: plot_notes.rst_ .. include:: auto_legend_info.rst_ diff --git a/src/postscriptlight.c b/src/postscriptlight.c index 94e1b20219c..a2a002034fe 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -4849,7 +4849,7 @@ int PSL_plotpolygon (struct PSL_CTRL *PSL, double *x, double *y, int n) { return (PSL_NO_ERROR); } -int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps) { +int PSL_plotgradienttriangle(struct PSL_CTRL *PSL, double *x, double *y, double *rgb, int steps) { /* Draw triangle with smooth vertex color gradient using barycentric interpolation * x, y: arrays of 3 coordinates (in inches, will be converted to points) * rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0 @@ -4876,8 +4876,8 @@ int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double step_inv = 1.0 / steps; - PSL_comment (PSL, "Begin gradient triangle\n"); - PSL_command (PSL, "gsave\n"); + PSL_comment(PSL, "Begin gradient triangle\n"); + PSL_command(PSL, "gsave\n"); /* Subdivide triangle into micro-triangles using barycentric coordinates */ for (i = 0; i < steps; i++) { @@ -4931,12 +4931,11 @@ int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double pb4 = u4 * rgb[2] + v4 * rgb[5] + w4 * rgb[8]; /* Draw first micro-triangle: p1-p2-p4 */ - PSL_command (PSL, "%.6f %.6f %.6f C\n", + PSL_command(PSL, "%.6f %.6f %.6f C\n", (pr1 + pr2 + pr4) / 3.0, (pg1 + pg2 + pg4) / 3.0, (pb1 + pb2 + pb4) / 3.0); - PSL_command (PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", - px1, py1, px2, py2, px4, py4); + PSL_command(PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", px1, py1, px2, py2, px4, py4); } /* Check third corner for second micro-triangle */ @@ -4953,12 +4952,11 @@ int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double if (s4 + t4 <= 1.0) { /* Already computed p4 */ /* Draw second micro-triangle: p2-p3-p4 */ - PSL_command (PSL, "%.6f %.6f %.6f C\n", + PSL_command(PSL, "%.6f %.6f %.6f C\n", (pr2 + pr3 + pr4) / 3.0, (pg2 + pg3 + pg4) / 3.0, (pb2 + pb3 + pb4) / 3.0); - PSL_command (PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", - px2, py2, px3, py3, px4, py4); + PSL_command(PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L P F\n", px2, py2, px3, py3, px4, py4); } } } @@ -4966,8 +4964,8 @@ int PSL_plotgradienttriangle (struct PSL_CTRL *PSL, double *x, double *y, double } } - PSL_command (PSL, "grestore\n"); - PSL_comment (PSL, "End gradient triangle\n"); + PSL_command(PSL, "grestore\n"); + PSL_comment(PSL, "End gradient triangle\n"); return (PSL_NO_ERROR); } diff --git a/src/psxy.c b/src/psxy.c index 136d5eb8cc7..3d0eb4ff779 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -579,6 +579,10 @@ static int usage (struct GMTAPI_CTRL *API, int level) { gmt_fill_syntax (API->GMT, 'G', NULL, "Specify color or pattern [no fill]."); GMT_Usage (API, -2, "The -G option can be present in all segment headers (not with -S). " "To assign fill color via -Z, give -G+z)."); + GMT_Usage (API, 3, "+g Enable vertex-based color gradients for polygons using Gouraud shading."); + GMT_Usage (API, -3, "Input data formats: (1) x y r g b (RGB values 0-255), (2) x y colorname, or (3) x y z with -C."); + GMT_Usage (API, -3, "Requires at least 3 vertices per polygon. Use -W to add outline."); + GMT_Usage (API, 3, "+gt Same as +g but reserved for future subdivision algorithm [currently uses Gouraud]."); GMT_Usage (API, 1, "\n-H[]"); GMT_Usage (API, -2, "Scale symbol sizes (set via -S or input column) by factors read from scale column. " "The scale column follows the symbol size column. Alternatively, append a fixed ."); @@ -2485,64 +2489,30 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (Ctrl->L.anchor == PSXY_POL_SYMM_DEV) n_cols = 3; else if (Ctrl->L.anchor == PSXY_POL_ASYMM_DEV || Ctrl->L.anchor == PSXY_POL_ASYMM_ENV) n_cols = 4; bool need_text = false; /* Flag for color string format requiring text reading */ + bool skip_set_columns = false; if (Ctrl->G.gradient) { - /* Auto-detect format: 'x y z' (CPT), 'x y r g b' (RGB), or 'x y colorstring' */ - /* Try reading first line to detect format */ - FILE *fp = NULL; - char line[GMT_BUFSIZ]; - bool format_detected = false; - struct GMT_OPTION *opt = NULL; - - /* Find input file in options list (look for option without leading dash) */ - for (opt = options; opt; opt = opt->next) { - if (opt->option == GMT_OPT_INFILE) { - fp = fopen (opt->arg, "r"); - break; - } - } - - if (fp) { - /* Read lines until we find a data line (skip comments) */ - while (fgets(line, GMT_BUFSIZ, fp)) { - if (line[0] == '#' || line[0] == '\n') continue; /* Skip comments and empty lines */ - /* Try to parse as 5 or 3 numeric values */ - double v[5]; - int n_read = sscanf(line, "%lf %lf %lf %lf %lf", &v[0], &v[1], &v[2], &v[3], &v[4]); - if (n_read == 5) { - n_cols = 5; /* RGB format: x y r g b */ - Ctrl->G.direct = true; - need_text = false; - } - else if (n_read == 3) { - n_cols = 3; /* CPT format: x y z */ - Ctrl->G.direct = false; - need_text = false; - } - else { - n_cols = 2; /* Color string format: x y colorstring */ - Ctrl->G.direct = true; - need_text = true; - } - format_detected = true; - break; - } - fclose (fp); - } - if (!format_detected) { - /* Couldn't detect format, default to CPT */ - n_cols = 3; - Ctrl->G.direct = false; - need_text = false; - } + /* For gradients, don't force column count - let GMT read all available columns + * and enable text for color name support */ + need_text = true; + skip_set_columns = true; /* Skip GMT_Set_Columns to let GMT auto-detect */ } conf_line = (Ctrl->L.anchor >= PSXY_POL_SYMM_DEV && Ctrl->L.anchor <= PSXY_POL_ASYMM_ENV); if (GMT_Init_IO(API, GMT_IS_DATASET, geometry, GMT_IN, GMT_ADD_DEFAULT, 0, options) != GMT_NOERROR) { /* Register data input */ Return (API->error); } - if ((error = GMT_Set_Columns(API, GMT_IN, (unsigned int)n_cols, need_text ? GMT_COL_FIX : GMT_COL_FIX_NO_TEXT)) != GMT_NOERROR) { - /* We don't want trailing text because we may need to resample lines below, except for direct color strings */ - Return (API->error); + if (skip_set_columns && need_text) { + /* For gradients: read 5 columns with text - GMT will read what's available and set L->n_columns correctly */ + GMT->current.io.max_cols_to_read = 0; /* Do not make any assumptions about max columns */ + if ((error = GMT_Set_Columns(API, GMT_IN, 0, GMT_COL_VAR)) != GMT_NOERROR) { + Return (API->error); + } + } + else if (!skip_set_columns) { + if ((error = GMT_Set_Columns(API, GMT_IN, (unsigned int)n_cols, need_text ? GMT_COL_FIX : GMT_COL_FIX_NO_TEXT)) != GMT_NOERROR) { + /* We don't want trailing text because we may need to resample lines below, except for gradients */ + Return (API->error); + } } if ((S.symbol == GMT_SYMBOL_QUOTED_LINE || S.symbol == GMT_SYMBOL_DECORATED_LINE) && S.G.segmentize) { /* Special quoted/decorated line where each point-pair should be considered a line segment */ struct GMT_SEGMENTIZE S; @@ -2892,92 +2862,113 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { /* Check if this is a gradient polygon (triangle or quad) */ bool gradient_done = false; + /* Auto-detect gradient format based on GMT's column count */ + if (Ctrl->G.gradient) { + if (L->n_columns == 5) { + /* RGB format: x y r g b */ + Ctrl->G.direct = true; + } + else if (L->n_columns == 2 && L->text && L->text[0]) { + /* Color string format: x y colorname (L->n_columns counts text as column 3) */ + Ctrl->G.direct = true; + } + else if (L->n_columns == 3) { + /* CPT format: x y z (3 numeric columns) */ + Ctrl->G.direct = false; + } + } + /* Error check: CPT mode requires a color palette */ if (Ctrl->G.gradient && !Ctrl->G.direct && P == NULL) { GMT_Report(API, GMT_MSG_ERROR, "Gradient option -G+g detected CPT format (x y z) but no color palette provided. Use -C to specify a color palette.\n"); Return (GMT_RUNTIME_ERROR); } - /* For gradients: need enough vertices AND either CPT or direct colors */ - if (Ctrl->G.gradient && (L->n_rows == 4 || L->n_rows == 5) && - ((Ctrl->G.direct && L->n_columns >= 2) || (!Ctrl->G.direct && L->n_columns >= 3 && P))) { + /* For gradients: need at least 3 vertices AND appropriate data format */ + if (Ctrl->G.gradient && L->n_rows >= 3 && + ((L->n_columns == 5 && Ctrl->G.direct) || (L->n_columns == 3 && !Ctrl->G.direct && P) || (L->n_columns == 2 && L->text && Ctrl->G.direct))) { double tri_rgb[9], rgb_tmp[4]; double plot_x[3], plot_y[3]; /* Project to map coordinates */ if ((GMT->current.plot.n = gmt_geo_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) > 0 && GMT->current.plot.n >= 3) { - unsigned int n_triangles = (L->n_rows == 5) ? 2 : 1; /* 5 rows = 4 unique vertices + closing = square */ - unsigned int tri_indices[2][3] = {{0, 1, 2}, {0, 2, 3}}; /* Triangulation for quad */ + /* Check if polygon is closed (last vertex == first vertex) */ + bool closed = (L->data[GMT_X][0] == L->data[GMT_X][L->n_rows-1] && + L->data[GMT_Y][0] == L->data[GMT_Y][L->n_rows-1]); + unsigned int n_unique = closed ? L->n_rows - 1 : L->n_rows; + + /* Error check: need at least 3 unique vertices */ + if (n_unique < 3) { + GMT_Report(API, GMT_MSG_ERROR, "Gradient polygons require at least 3 vertices (found %u)\n", n_unique); + Return (GMT_RUNTIME_ERROR); + } + + /* Fan triangulation from first vertex */ + unsigned int n_triangles = n_unique - 2; for (unsigned int tri = 0; tri < n_triangles; tri++) { + /* Triangle i uses vertices: 0, i+1, i+2 */ + unsigned int tri_indices[3] = {0, tri + 1, tri + 2}; /* Get RGB colors for this triangle */ for (unsigned int k = 0; k < 3; k++) { - unsigned int idx = tri_indices[tri][k]; - if (Ctrl->G.direct) { - /* Direct color specification */ - if (L->n_columns == 5) { - /* Separate R G B columns: x y r g b */ - tri_rgb[k * 3 + 0] = L->data[2][idx] / 255.0; /* R */ - tri_rgb[k * 3 + 1] = L->data[3][idx] / 255.0; /* G */ - tri_rgb[k * 3 + 2] = L->data[4][idx] / 255.0; /* B */ - } - else if (L->text) { - /* Color string: x y colorstring (stored in L->text) */ - char *color_str = L->text[idx]; - int parse_result = gmt_getrgb (GMT, color_str, rgb_tmp); - if (color_str && parse_result == 0) { - /* gmt_getrgb returns 0-1 range, use directly */ - tri_rgb[k * 3 + 0] = rgb_tmp[0]; - tri_rgb[k * 3 + 1] = rgb_tmp[1]; - tri_rgb[k * 3 + 2] = rgb_tmp[2]; - } - else { - /* Failed to parse, use black */ - tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; - GMT_Report(API, GMT_MSG_WARNING, "Failed to parse color '%s', using black\n", color_str ? color_str : "NULL"); - } + unsigned int idx = tri_indices[k]; + if (Ctrl->G.direct && L->n_columns == 5) { + /* RGB from columns: x y r g b */ + tri_rgb[k * 3 + 0] = L->data[2][idx] / 255.0; + tri_rgb[k * 3 + 1] = L->data[3][idx] / 255.0; + tri_rgb[k * 3 + 2] = L->data[4][idx] / 255.0; + } + else if (Ctrl->G.direct && L->n_columns == 2 && L->text && L->text[idx]) { + /* Color name from text (n_columns==2 for x,y + text field) */ + if (gmt_getrgb(GMT, L->text[idx], rgb_tmp) == 0) { + tri_rgb[k * 3 + 0] = rgb_tmp[0]; + tri_rgb[k * 3 + 1] = rgb_tmp[1]; + tri_rgb[k * 3 + 2] = rgb_tmp[2]; } else { - /* No text data available, use black */ tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; - GMT_Report(API, GMT_MSG_WARNING, "No color data found, using black\n"); + GMT_Report(API, GMT_MSG_WARNING, "Failed to parse color '%s', using black\n", L->text[idx]); } } - else { - /* CPT-based: look up z-value */ - gmt_get_rgb_from_z (GMT, P, L->data[2][idx], rgb_tmp); + else if (!Ctrl->G.direct && L->n_columns == 3) { + /* CPT-based: z from column 3 */ + gmt_get_rgb_from_z(GMT, P, L->data[2][idx], rgb_tmp); tri_rgb[k * 3 + 0] = rgb_tmp[0]; tri_rgb[k * 3 + 1] = rgb_tmp[1]; tri_rgb[k * 3 + 2] = rgb_tmp[2]; } + else { + /* Fallback */ + tri_rgb[k * 3 + 0] = tri_rgb[k * 3 + 1] = tri_rgb[k * 3 + 2] = 0.0; + } } /* Copy coordinates (PSL expects inches, not points!) */ for (unsigned int k = 0; k < 3; k++) { - unsigned int idx = tri_indices[tri][k]; + unsigned int idx = tri_indices[k]; plot_x[k] = GMT->current.plot.x[idx]; plot_y[k] = GMT->current.plot.y[idx]; } - /* Draw gradient triangle */ + /* Draw gradient */ if (Ctrl->G.smooth) - PSL_plotgradienttriangle_gouraud (PSL, plot_x, plot_y, tri_rgb); + PSL_plotgradienttriangle_gouraud(PSL, plot_x, plot_y, tri_rgb); else - PSL_plotgradienttriangle (PSL, plot_x, plot_y, tri_rgb, 50); + PSL_plotgradienttriangle(PSL, plot_x, plot_y, tri_rgb, 50); } gradient_done = true; /* Optionally draw outline */ if (outline_setting) { - gmt_setpen (GMT, ¤t_pen); - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, L->n_rows); + gmt_setpen(GMT, ¤t_pen); + PSL_plotpolygon(PSL, GMT->current.plot.x, GMT->current.plot.y, L->n_rows); } } } if (!gradient_done) { - gmt_setfill (GMT, ¤t_fill, outline_setting); - gmt_geo_polygons (GMT, L); + gmt_setfill(GMT, ¤t_fill, outline_setting); + gmt_geo_polygons(GMT, L); } } else if (S.symbol == GMT_SYMBOL_QUOTED_LINE) { /* Labeled lines are dealt with by the contour machinery */ @@ -3142,7 +3133,7 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { /* Get RGB colors from z-values via CPT */ for (unsigned int k = 0; k < 3; k++) { - gmt_get_rgb_from_z (GMT, P, S_orig->data[2][k], rgb_tmp); + gmt_get_rgb_from_z(GMT, P, S_orig->data[2][k], rgb_tmp); tri_rgb[k * 3 + 0] = gmt_M_is255(rgb_tmp[0]); tri_rgb[k * 3 + 1] = gmt_M_is255(rgb_tmp[1]); tri_rgb[k * 3 + 2] = gmt_M_is255(rgb_tmp[2]); @@ -3154,27 +3145,27 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { plot_y[k] = GMT->current.plot.y[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; } - /* Draw gradient triangle */ + /* Draw gradient */ if (Ctrl->G.smooth) - PSL_plotgradienttriangle_gouraud (PSL, plot_x, plot_y, tri_rgb); + PSL_plotgradienttriangle_gouraud(PSL, plot_x, plot_y, tri_rgb); else - PSL_plotgradienttriangle (PSL, plot_x, plot_y, tri_rgb, 50); + PSL_plotgradienttriangle(PSL, plot_x, plot_y, tri_rgb, 50); /* Optionally draw outline */ if (Ctrl->L.outline) { - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (GMT->current.plot.n == 4) ? 3 : (int)GMT->current.plot.n); + PSL_plotpolygon(PSL, GMT->current.plot.x, GMT->current.plot.y, (GMT->current.plot.n == 4) ? 3 : (int)GMT->current.plot.n); } } else { - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + PSL_plotpolygon(PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); } } else { /* No fill, just outline */ - gmt_setfill (GMT, NULL, Ctrl->L.outline); - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); + gmt_setfill(GMT, NULL, Ctrl->L.outline); + PSL_plotpolygon(PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); } if (!Ctrl->W.active) draw_line = false; /* Did not want to actually draw the main line */ - if (Ctrl->L.outline) gmt_setpen (GMT, ¤t_pen); /* Reset the pen to what -W indicates */ + if (Ctrl->L.outline) gmt_setpen(GMT, ¤t_pen); /* Reset the pen to what -W indicates */ } if (no_line_clip) { /* Draw line or polygon without border clipping at all */ if ((GMT->current.plot.n = gmt_cart_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) == 0) continue; @@ -3214,11 +3205,11 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { plot_y[k] = GMT->current.plot.y[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; } - /* Draw gradient triangle */ + /* Draw gradient */ if (Ctrl->G.smooth) - PSL_plotgradienttriangle_gouraud (PSL, plot_x, plot_y, tri_rgb); + PSL_plotgradienttriangle_gouraud(PSL, plot_x, plot_y, tri_rgb); else - PSL_plotgradienttriangle (PSL, plot_x, plot_y, tri_rgb, 50); + PSL_plotgradienttriangle(PSL, plot_x, plot_y, tri_rgb, 50); /* Optionally draw outline */ if (outline_active) { From 18d85025e96585d6640a68fc9b75c0208dcf5502 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Fri, 26 Dec 2025 13:42:43 +0000 Subject: [PATCH 5/7] Add test and warn about -N --- src/postscriptlight.c | 34 +++++++++--------- src/psxy.c | 76 ++++++++++++---------------------------- test/psxy/gradients.sh | 79 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 71 deletions(-) create mode 100644 test/psxy/gradients.sh diff --git a/src/postscriptlight.c b/src/postscriptlight.c index a2a002034fe..7259a39d7ab 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -4970,7 +4970,7 @@ int PSL_plotgradienttriangle(struct PSL_CTRL *PSL, double *x, double *y, double return (PSL_NO_ERROR); } -int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y, double *rgb) { +int PSL_plotgradienttriangle_gouraud(struct PSL_CTRL *PSL, double *x, double *y, double *rgb) { /* Draw triangle with smooth Gouraud shading using PostScript Type 4 shading * x, y: arrays of 3 coordinates (in inches, will be converted to points) * rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0 @@ -4986,16 +4986,16 @@ int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y y_pts[i] = y[i] * PSL->internal.dpu; } - PSL_comment (PSL, "Begin Gouraud shaded triangle\n"); - PSL_command (PSL, "gsave\n"); + PSL_comment(PSL, "Begin Gouraud shaded triangle\n"); + PSL_command(PSL, "gsave\n"); /* Create Type 4 (Free-Form Gouraud) shading dictionary */ - PSL_command (PSL, "<<\n"); - PSL_command (PSL, " /ShadingType 4\n"); - PSL_command (PSL, " /ColorSpace /DeviceRGB\n"); - PSL_command (PSL, " /BitsPerCoordinate 16\n"); - PSL_command (PSL, " /BitsPerComponent 8\n"); - PSL_command (PSL, " /BitsPerFlag 8\n"); + PSL_command(PSL, "<<\n"); + PSL_command(PSL, " /ShadingType 4\n"); + PSL_command(PSL, " /ColorSpace /DeviceRGB\n"); + PSL_command(PSL, " /BitsPerCoordinate 16\n"); + PSL_command(PSL, " /BitsPerComponent 8\n"); + PSL_command(PSL, " /BitsPerFlag 8\n"); /* Decode arrays map from integer coordinates to actual coordinate ranges */ /* Find bounding box */ @@ -5007,8 +5007,8 @@ int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y if (y_pts[i] > ymax) ymax = y_pts[i]; } - PSL_command (PSL, " /Decode [%.4f %.4f %.4f %.4f 0 1 0 1 0 1]\n", xmin, xmax, ymin, ymax); - PSL_command (PSL, " /DataSource <\n"); + PSL_command(PSL, " /Decode [%.4f %.4f %.4f %.4f 0 1 0 1 0 1]\n", xmin, xmax, ymin, ymax); + PSL_command(PSL, " /DataSource <\n"); /* Write triangle data as hex string */ /* Flag 0 = start new triangle, then coordinates (x,y) scaled to 0-65535, then RGB scaled to 0-255 */ @@ -5020,15 +5020,15 @@ int PSL_plotgradienttriangle_gouraud (struct PSL_CTRL *PSL, double *x, double *y unsigned int g = (unsigned int)(rgb[i*3 + 1] * 255.0 + 0.5); unsigned int b = (unsigned int)(rgb[i*3 + 2] * 255.0 + 0.5); - PSL_command (PSL, " %02X %04X %04X %02X%02X%02X\n", flag, x_scaled, y_scaled, r, g, b); + PSL_command(PSL, " %02X %04X %04X %02X%02X%02X\n", flag, x_scaled, y_scaled, r, g, b); } - PSL_command (PSL, " >\n"); - PSL_command (PSL, ">>\n"); - PSL_command (PSL, "shfill\n"); + PSL_command(PSL, " >\n"); + PSL_command(PSL, ">>\n"); + PSL_command(PSL, "shfill\n"); - PSL_command (PSL, "grestore\n"); - PSL_comment (PSL, "End Gouraud shaded triangle\n"); + PSL_command(PSL, "grestore\n"); + PSL_comment(PSL, "End Gouraud shaded triangle\n"); return (PSL_NO_ERROR); } diff --git a/src/psxy.c b/src/psxy.c index 3d0eb4ff779..87a464ca275 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -1155,6 +1155,13 @@ static int parse (struct GMT_CTRL *GMT, struct PSXY_CTRL *Ctrl, struct GMT_OPTIO if (Ctrl->T.active && (GMT->common.B.active[GMT_PRIMARY] == false && GMT->common.B.active[GMT_SECONDARY] == false)) Ctrl->no_RJ_needed = true; /* Not plotting any data or frame that needs -R -J */ + if (Ctrl->G.smooth && Ctrl->N.active) { + GMT_Report(API, GMT_MSG_WARNING, "Option -G+g (Gouraud shading) is (almost) incompatible with -N. The problem is that " + "apparently the 'shfill' postscript operator prevents a correct detection of the BoundingBox by Ghostscript " + "and that results in an incapacity to correctly convert to raster formats and cropping the white sapces, " + "or even fail the conversion in modern mode.\n"); + } + if (Ctrl->T.active && n_files) GMT_Report (API, GMT_MSG_WARNING, "Option -T ignores all input files\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->Z.active && Ctrl->Z.set_transp != 1 && !Ctrl->C.active, "Option -Z: No CPT given via -C\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->C.active && (Ctrl->C.file == NULL || Ctrl->C.file[0] == '\0'), "Option -C: No CPT given\n"); @@ -2858,10 +2865,9 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { gmt_set_seg_minmax (GMT, D->geometry, 2, L); /* Update min/max of x/y only */ } - if (polygon && !no_line_clip) { /* Want a closed polygon (with or without fill and with or without outline) */ - /* Check if this is a gradient polygon (triangle or quad) */ - bool gradient_done = false; - + /* Handle gradient polygons (works for both clipped and non-clipped paths) */ + bool gradient_done = false; + if (polygon && Ctrl->G.gradient) { /* Auto-detect gradient format based on GMT's column count */ if (Ctrl->G.gradient) { if (L->n_columns == 5) { @@ -2890,8 +2896,13 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { double tri_rgb[9], rgb_tmp[4]; double plot_x[3], plot_y[3]; - /* Project to map coordinates */ - if ((GMT->current.plot.n = gmt_geo_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) > 0 && GMT->current.plot.n >= 3) { + /* Project to map coordinates (use cart_to_xy if no clipping) */ + if (no_line_clip) + GMT->current.plot.n = gmt_cart_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows); + else + GMT->current.plot.n = gmt_geo_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows); + + if (GMT->current.plot.n > 0 && GMT->current.plot.n >= 3) { /* Check if polygon is closed (last vertex == first vertex) */ bool closed = (L->data[GMT_X][0] == L->data[GMT_X][L->n_rows-1] && L->data[GMT_Y][0] == L->data[GMT_Y][L->n_rows-1]); @@ -3168,55 +3179,12 @@ EXTERN_MSC int GMT_psxy (void *V_API, int mode, void *args) { if (Ctrl->L.outline) gmt_setpen(GMT, ¤t_pen); /* Reset the pen to what -W indicates */ } if (no_line_clip) { /* Draw line or polygon without border clipping at all */ - if ((GMT->current.plot.n = gmt_cart_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) == 0) continue; - if (outline_active) gmt_setpen (GMT, ¤t_pen); /* Select separate pen for polygon outline */ - if (Ctrl->G.active) { /* Specify the fill, possibly set outline */ + /* Skip projection if gradient already handled it */ + if (!gradient_done && (GMT->current.plot.n = gmt_cart_to_xy_line (GMT, L->data[GMT_X], L->data[GMT_Y], L->n_rows)) == 0) continue; + if (outline_active && !gradient_done) gmt_setpen (GMT, ¤t_pen); /* Select separate pen for polygon outline */ + if (Ctrl->G.active && !gradient_done) { /* Specify the fill (skip if gradient already drawn) */ gmt_setfill (GMT, ¤t_fill, outline_active); - /* DEBUG gradient */ - if (Ctrl->G.gradient) { - GMT_Report (API, GMT_MSG_WARNING, "Gradient: n=%d seg_ncols=%d table_ncols=%d nrows=%d\n", - GMT->current.plot.n, L->n_columns, D->table[tbl]->n_columns, L->n_rows); - /* Try to access z from original table/segment */ - if (D->table[tbl]->n_columns >= 3 && L->n_rows >= 3) { - struct GMT_DATASEGMENT *orig_seg = D->table[tbl]->segment[seg]; - if (orig_seg->n_columns >= 3) { - GMT_Report (API, GMT_MSG_WARNING, "Z-values from orig seg: z[0]=%g z[1]=%g z[2]=%g\n", - orig_seg->data[2][0], orig_seg->data[2][1], orig_seg->data[2][2]); - } - } - } - if (Ctrl->G.gradient && (GMT->current.plot.n == 3 || GMT->current.plot.n == 4) && D->table[tbl]->segment[seg]->n_columns >= 3 && L->n_rows >= 3 && P) { - /* Draw gradient-filled triangle */ - double tri_rgb[9], rgb_tmp[4]; - double plot_x[3], plot_y[3]; - struct GMT_DATASEGMENT *S_orig = D->table[tbl]->segment[seg]; - - /* Get RGB colors from z-values via CPT */ - for (unsigned int k = 0; k < 3; k++) { - gmt_get_rgb_from_z (GMT, P, S_orig->data[2][k], rgb_tmp); - tri_rgb[k * 3 + 0] = gmt_M_is255(rgb_tmp[0]); - tri_rgb[k * 3 + 1] = gmt_M_is255(rgb_tmp[1]); - tri_rgb[k * 3 + 2] = gmt_M_is255(rgb_tmp[2]); - } - - /* Convert to PostScript points */ - for (unsigned int k = 0; k < 3; k++) { - plot_x[k] = GMT->current.plot.x[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; - plot_y[k] = GMT->current.plot.y[k] * GMT->session.u2u[GMT_INCH][GMT_PT]; - } - - /* Draw gradient */ - if (Ctrl->G.smooth) - PSL_plotgradienttriangle_gouraud(PSL, plot_x, plot_y, tri_rgb); - else - PSL_plotgradienttriangle(PSL, plot_x, plot_y, tri_rgb, 50); - - /* Optionally draw outline */ - if (outline_active) { - PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, 3); - } - } - else { + { PSL_plotpolygon (PSL, GMT->current.plot.x, GMT->current.plot.y, (int)GMT->current.plot.n); } } diff --git a/test/psxy/gradients.sh b/test/psxy/gradients.sh new file mode 100644 index 00000000000..eb9474f7762 --- /dev/null +++ b/test/psxy/gradients.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# +# Test gradient-filled polygons with -G+g (Gouraud shading) +# Tests RGB format (x y r g b), color names (x y colorname), +# CPT format (x y z), and arbitrary polygon vertex counts + +ps=gradients.ps + +# Create CPT for CPT-based tests +gmt makecpt -Chot -T0/100 > t.cpt + +# Row 1: RGB format - triangle and square +gmt psxy -R-1/4/-1/3 -JX4c -Bafg -BWSne+t"RGB Triangle" -G+g -P -K -Xc -Y18c << EOF > $ps +0 0 255 0 0 +3 0 0 255 0 +1.5 2.6 0 0 255 +0 0 255 0 0 +EOF + +gmt psxy -R-0.5/2.5/-0.5/2.5 -JX4c -Bafg -BWSne+t"RGB Square" -G+g -O -K -X5c << EOF >> $ps +0 0 255 0 0 +2 0 255 255 0 +2 2 0 255 0 +0 2 0 0 255 +0 0 255 0 0 +EOF + +# Row 2: Color names - triangle and square +gmt psxy -R-1/4/-1/3 -JX4c -Bafg -BWSne+t"Color Names Triangle" -G+g -O -K -X-5c -Y-5.5c << EOF >> $ps +0 0 red +3 0 green +1.5 2.6 blue +0 0 red +EOF + +gmt psxy -R-0.5/2.5/-0.5/2.5 -JX4c -Bafg -BWSne+t"Color Names Square" -G+g -O -K -X5c << EOF >> $ps +0 0 red +2 0 yellow +2 2 green +0 2 blue +0 0 red +EOF + +# Row 3: CPT format - triangle and square +gmt psxy -R-1/4/-1/3 -JX4c -Bafg -BWSne+t"CPT Triangle" -G+g -Ct.cpt -O -K -X-5c -Y-5.5c << EOF >> $ps +0 0 0 +3 0 50 +1.5 2.6 100 +0 0 0 +EOF + +gmt psxy -R-0.5/2.5/-0.5/2.5 -JX4c -Bafg -BWSne+t"CPT Square" -G+g -Ct.cpt -O -K -X5c << EOF >> $ps +0 0 0 +2 0 33 +2 2 67 +0 2 100 +0 0 0 +EOF + +# Row 4: Pentagon (arbitrary polygon) with hex colors and slash-separated RGB +gmt psxy -R-0.5/2.5/-0.5/2 -JX4c -Bafg -BWSne+t"Pentagon Hex" -G+g -O -K -X-5c -Y-5.5c << EOF >> $ps +1 0 #FF0000 +1.951 0.588 #FFA500 +1.588 1.538 #FFFF00 +0.412 1.538 #00FF00 +0.049 0.588 #0000FF +1 0 #FF0000 +EOF + +gmt psxy -R-0.5/2.5/-0.5/2 -JX4c -Bafg -BWSne+t"Pentagon Slash RGB" -G+g -O -X5c << EOF >> $ps +1 0 255/0/0 +1.951 0.588 255/165/0 +1.588 1.538 255/255/0 +0.412 1.538 0/255/0 +0.049 0.588 0/0/255 +1 0 255/0/0 +EOF + +\rm t.cpt \ No newline at end of file From 3a305872b2048ddc32f5c240432ee51e1e600689 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Fri, 26 Dec 2025 18:46:03 +0000 Subject: [PATCH 6/7] Expand the color gradients to psxyz. Fix issues with consequences in the BoundingBox determination and hence to the raster conversions. --- doc/rst/source/plot.rst | 4 +-- doc/rst/source/plot3d.rst | 17 +++++++-- src/postscriptlight.c | 8 +++++ src/psxy.c | 7 ---- src/psxyz.c | 74 ++++++++++++++++++++++++++++++++++++--- test/psxyz/gradients3d.sh | 60 +++++++++++++++++++++++++++++++ 6 files changed, 154 insertions(+), 16 deletions(-) create mode 100644 test/psxyz/gradients3d.sh diff --git a/doc/rst/source/plot.rst b/doc/rst/source/plot.rst index 243c607d4ff..1e7393b4b8b 100644 --- a/doc/rst/source/plot.rst +++ b/doc/rst/source/plot.rst @@ -20,7 +20,7 @@ Synopsis [ |-D|\ *dx*\ [/*dy*] ] [ |-E|\ [**x**\|\ **y**\|\ **X**\|\ **Y**][**+a**\|\ **A**][**+cl**\|\ **f**][**+n**][**+w**\ *width*\ [/*cap*]][**+p**\ *pen*] ] [ |-F|\ [**c**\|\ **n**\|\ **p**][**a**\|\ **s**\|\ **t**\|\ **r**\|\ *refpoint*] ] -[ |-G|\ *fill*\|\ **+z** ] +[ |-G|\ *fill*\|\ **+g**\|\ **+z** ] [ |-H|\ [*scale*] ] [ |-I|\ [*intens*] ] [ |-L|\ [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*][**+yb**\|\ **t**\|\ *y0*][**+p**\ *pen*] ] @@ -196,7 +196,7 @@ Optional Arguments .. _-G: -**-G**\ *fill*\|\ **+z**\|\ **+g** :ref:`(more ...) <-Gfill_attrib>` +**-G**\ *fill*\|\ **+g**\|\ **+z** :ref:`(more ...) <-Gfill_attrib>` Select color or pattern for filling of symbols or polygons [Default is no fill]. Note that this module will search for |-G| and |-W| strings in all the segment headers and let any values thus found over-ride the command line settings. diff --git a/doc/rst/source/plot3d.rst b/doc/rst/source/plot3d.rst index cb4173d133a..0d6aeef3815 100644 --- a/doc/rst/source/plot3d.rst +++ b/doc/rst/source/plot3d.rst @@ -19,7 +19,7 @@ Synopsis [ |SYN_OPT-B| ] [ |-C|\ *cpt* ] [ |-D|\ *dx*/*dy*\ [/*dz*] ] -[ |-G|\ *fill*\|\ **+z** ] +[ |-G|\ *fill*\|\ **+g**\|\ **+z** ] [ |-H|\ [*scale*] ] [ |-I|\ [*intens*] ] [ |-L|\ [**+b**\|\ **d**\|\ **D**][**+xl**\|\ **r**\|\ *x0*][**+yl**\|\ **r**\|\ *y0*][**+p**\ *pen*] ] @@ -129,7 +129,7 @@ Optional Arguments .. _-G: -**-G**\ *fill* :ref:`(more ...) <-Gfill_attrib>` +**-G**\ *fill*\|\ **+g**\|\ **+z** :ref:`(more ...) <-Gfill_attrib>` Select color or pattern for filling of symbols or polygons [Default is no fill]. Note that the module will search for |-G| and |-W| strings in all the segment headers and let any values thus found over-ride the command line settings. @@ -138,6 +138,19 @@ Optional Arguments we will cycle through the fill colors implied by :term:`COLOR_SET` and change on a per-segment or per-table basis. Any *transparency* setting is unchanged. + **+g** + Enable vertex-based color gradients for polygons using Gouraud shading. + This modifier automatically detects the input data format and supports three formats: + + 1. **RGB format**: *x y z r g b* where RGB values are in the range 0-255. + 2. **Color name format**: *x y z colorname* where *colorname* can be a named color (e.g., "red", "blue"), + hex color (e.g., "#FF0000"), or slash-separated RGB (e.g., "255/0/0"). + 3. **CPT format**: *x y z value* where *value* is mapped to colors via the CPT specified with |-C|. + + Polygons with any number of vertices are supported and automatically triangulated using a fan triangulation + from the first vertex. Each vertex gets its own color, and Gouraud shading creates smooth color gradients + across the polygon faces. Use |-W| to add an outline to the gradient polygons. + .. _-H: **-H**\ [*scale*] diff --git a/src/postscriptlight.c b/src/postscriptlight.c index 0f5be063ffb..7bf16adaa42 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -4858,6 +4858,8 @@ int PSL_plotgradienttriangle(struct PSL_CTRL *PSL, double *x, double *y, double * x, y: arrays of 3 coordinates (in inches, will be converted to points) * rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0 * steps: number of subdivisions (higher = smoother, slower) + * + * Writen by Claude.ai */ int i, j; @@ -4979,6 +4981,8 @@ int PSL_plotgradienttriangle_gouraud(struct PSL_CTRL *PSL, double *x, double *y, * x, y: arrays of 3 coordinates (in inches, will be converted to points) * rgb: array of 9 values [r1 g1 b1 r2 g2 b2 r3 g3 b3], each 0.0-1.0 * This uses PostScript Level 2/3 shading for perfectly smooth gradients + * + * Writen by Claude.ai */ int i; @@ -4992,6 +4996,10 @@ int PSL_plotgradienttriangle_gouraud(struct PSL_CTRL *PSL, double *x, double *y, PSL_comment(PSL, "Begin Gouraud shaded triangle\n"); PSL_command(PSL, "gsave\n"); + /* Set clipping path to triangle to constrain shfill and establish bounding box */ + PSL_command(PSL, "N %.4f %.4f M %.4f %.4f L %.4f %.4f L closepath clip", + x_pts[0], y_pts[0], x_pts[1], y_pts[1], x_pts[2], y_pts[2]); + /* Create Type 4 (Free-Form Gouraud) shading dictionary */ PSL_command(PSL, "<<\n"); diff --git a/src/psxy.c b/src/psxy.c index fd2ca62fd7c..741a6d19cf9 100644 --- a/src/psxy.c +++ b/src/psxy.c @@ -1155,13 +1155,6 @@ static int parse (struct GMT_CTRL *GMT, struct PSXY_CTRL *Ctrl, struct GMT_OPTIO if (Ctrl->T.active && (GMT->common.B.active[GMT_PRIMARY] == false && GMT->common.B.active[GMT_SECONDARY] == false)) Ctrl->no_RJ_needed = true; /* Not plotting any data or frame that needs -R -J */ - if (Ctrl->G.smooth && Ctrl->N.active) { - GMT_Report(API, GMT_MSG_WARNING, "Option -G+g (Gouraud shading) is (almost) incompatible with -N. The problem is that " - "apparently the 'shfill' postscript operator prevents a correct detection of the BoundingBox by Ghostscript " - "and that results in an incapacity to correctly convert to raster formats and cropping the white sapces, " - "or even fail the conversion in modern mode.\n"); - } - if (Ctrl->T.active && n_files) GMT_Report (API, GMT_MSG_WARNING, "Option -T ignores all input files\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->Z.active && Ctrl->Z.set_transp != 1 && !Ctrl->C.active, "Option -Z: No CPT given via -C\n"); n_errors += gmt_M_check_condition (GMT, Ctrl->C.active && (Ctrl->C.file == NULL || Ctrl->C.file[0] == '\0'), "Option -C: No CPT given\n"); diff --git a/src/psxyz.c b/src/psxyz.c index 967b1e5ee02..3e3493dc2c4 100644 --- a/src/psxyz.c +++ b/src/psxyz.c @@ -53,9 +53,12 @@ struct PSXYZ_CTRL { bool active; double dx, dy, dz; } D; - struct PSXYZ_G { /* -G|+z */ + struct PSXYZ_G { /* -G|+z|+g */ bool active; bool set_color; + bool gradient; /* Use gradient fill with vertex colors */ + bool smooth; /* Use Gouraud shading (true) vs subdivision (false) */ + bool direct; /* Direct color specification (RGB/names) vs CPT */ unsigned int sequential; struct GMT_FILL fill; } G; @@ -206,6 +209,10 @@ static int usage (struct GMTAPI_CTRL *API, int level) { gmt_fill_syntax (API->GMT, 'G', NULL, "Specify color or pattern [Default is no fill]."); GMT_Usage (API, -2, "The -G option can be present in all segment headers (not with -S). " "To assign fill color via -Z, give -G+z)."); + GMT_Usage (API, 3, "+g Enable vertex-based color gradients for polygons using Gouraud shading."); + GMT_Usage (API, -3, "Input data formats: (1) x y z r g b (RGB values 0-255), (2) x y z colorname, or (3) x y z value with -C."); + GMT_Usage (API, -3, "Requires at least 3 vertices per polygon. Use -W to add outline."); + GMT_Usage (API, 3, "+gt Same as +g but reserved for future subdivision algorithm [currently uses Gouraud]."); GMT_Usage (API, 1, "\n-H[]"); GMT_Usage (API, -2, "Scale symbol sizes (set via -S or input column) by factors read from scale column. " "The scale column follows the symbol size column. Alternatively, append a fixed ."); @@ -460,6 +467,16 @@ static int parse (struct GMT_CTRL *GMT, struct PSXYZ_CTRL *Ctrl, struct GMT_OPTI n_errors += gmt_M_repeated_module_option (API, Ctrl->G.active); if (strncmp (opt->arg, "+z", 2U) == 0) Ctrl->G.set_color = true; + else if (strncmp (opt->arg, "+gt", 3U) == 0) { + Ctrl->G.gradient = true; + Ctrl->G.smooth = false; /* Subdivision/triangulation */ + Ctrl->G.active = true; + } + else if (strncmp (opt->arg, "+g", 2U) == 0) { + Ctrl->G.gradient = true; + Ctrl->G.smooth = true; /* Gouraud shading */ + Ctrl->G.active = true; + } else if (!opt->arg[0] || gmt_getfill (GMT, opt->arg, &Ctrl->G.fill)) { gmt_fill_syntax (GMT, 'G', NULL, " "); n_errors++; @@ -2280,7 +2297,54 @@ EXTERN_MSC int GMT_psxyz (void *V_API, int mode, void *args) { xp = gmt_M_memory (GMT, NULL, n, double); yp = gmt_M_memory (GMT, NULL, n, double); - if (polygon && !no_line_clip) { + /* Handle gradient polygons */ + bool gradient_done = false; + if (polygon && Ctrl->G.gradient && L->n_rows >= 3) { + if (L->n_columns == 6) Ctrl->G.direct = true; + else if (L->n_columns == 3 && L->text && L->text[0]) Ctrl->G.direct = true; + else if (L->n_columns == 4) Ctrl->G.direct = false; + if (!Ctrl->G.direct && P == NULL) { + GMT_Report(API, GMT_MSG_ERROR, "Gradient requires -C"); + Return (GMT_RUNTIME_ERROR); + } + if ((L->n_columns == 6 && Ctrl->G.direct) || (L->n_columns == 4 && !Ctrl->G.direct && P) || (L->n_columns == 3 && L->text && Ctrl->G.direct)) { + double tri_rgb[9], rgb_tmp[4], plot_x[3], plot_y[3]; + bool closed = (L->data[GMT_X][0] == L->data[GMT_X][L->n_rows-1] && L->data[GMT_Y][0] == L->data[GMT_Y][L->n_rows-1]); + unsigned int n_unique = closed ? L->n_rows - 1 : L->n_rows, n_triangles = n_unique - 2; + gmt_plane_perspective (GMT, -1, 0.0); + for (unsigned int tri = 0; tri < n_triangles; tri++) { + unsigned int tri_indices[3] = {0, tri + 1, tri + 2}; + for (unsigned int k = 0; k < 3; k++) { + unsigned int idx = tri_indices[k]; + if (Ctrl->G.direct && L->n_columns == 6) { + tri_rgb[k*3+0] = L->data[3][idx] / 255.0; + tri_rgb[k*3+1] = L->data[4][idx] / 255.0; + tri_rgb[k*3+2] = L->data[5][idx] / 255.0; + } else if (Ctrl->G.direct && L->n_columns == 3 && L->text && L->text[idx]) { + if (gmt_getrgb(GMT, L->text[idx], rgb_tmp) == 0) { + tri_rgb[k*3+0] = rgb_tmp[0]; tri_rgb[k*3+1] = rgb_tmp[1]; tri_rgb[k*3+2] = rgb_tmp[2]; + } else tri_rgb[k*3+0] = tri_rgb[k*3+1] = tri_rgb[k*3+2] = 0.0; + } else if (!Ctrl->G.direct && L->n_columns == 4) { + gmt_get_rgb_from_z(GMT, P, L->data[3][idx], rgb_tmp); + tri_rgb[k*3+0] = rgb_tmp[0]; tri_rgb[k*3+1] = rgb_tmp[1]; tri_rgb[k*3+2] = rgb_tmp[2]; + } else tri_rgb[k*3+0] = tri_rgb[k*3+1] = tri_rgb[k*3+2] = 0.0; + } + for (unsigned int k = 0; k < 3; k++) { + unsigned int idx = tri_indices[k]; + gmt_geoz_to_xy(GMT, L->data[GMT_X][idx], L->data[GMT_Y][idx], L->data[GMT_Z][idx], &plot_x[k], &plot_y[k]); + } + if (Ctrl->G.smooth) PSL_plotgradienttriangle_gouraud(PSL, plot_x, plot_y, tri_rgb); + else PSL_plotgradienttriangle(PSL, plot_x, plot_y, tri_rgb, 50); + } + gradient_done = true; + if (outline_setting) { + for (i = 0; i < n; i++) gmt_geoz_to_xy(GMT, L->data[GMT_X][i], L->data[GMT_Y][i], L->data[GMT_Z][i], &xp[i], &yp[i]); + gmt_setpen(GMT, ¤t_pen); PSL_plotpolygon(PSL, xp, yp, (int)L->n_rows); + } + } + } + + if (polygon && !no_line_clip && !gradient_done) { gmt_plane_perspective (GMT, -1, 0.0); for (i = 0; i < n; i++) gmt_geoz_to_xy (GMT, L->data[GMT_X][i], L->data[GMT_Y][i], L->data[GMT_Z][i], &xp[i], &yp[i]); gmt_setfill (GMT, ¤t_fill, outline_setting); @@ -2404,9 +2468,9 @@ EXTERN_MSC int GMT_psxyz (void *V_API, int mode, void *args) { gmt_geoz_to_xy (GMT, L->data[GMT_X][i], L->data[GMT_Y][i], L->data[GMT_Z][i], &xp[i], &yp[i]); } if (no_line_clip) { /* Draw line or polygon without border clipping at all */ - if ((GMT->current.plot.n = gmt_cart_to_xy_line (GMT, xp, yp, n)) == 0) continue; - if (outline_active) gmt_setpen (GMT, ¤t_pen); /* Select separate pen for polygon outline */ - if (Ctrl->G.active) { /* Specify the fill, possibly set outline */ + if (!gradient_done && (GMT->current.plot.n = gmt_cart_to_xy_line (GMT, xp, yp, n)) == 0) continue; + if (outline_active && !gradient_done) gmt_setpen (GMT, ¤t_pen); /* Select separate pen for polygon outline */ + if (Ctrl->G.active && !gradient_done) { /* Specify the fill, possibly set outline */ gmt_setfill (GMT, ¤t_fill, outline_active); PSL_plotpolygon (PSL, xp, yp, (int)n); } diff --git a/test/psxyz/gradients3d.sh b/test/psxyz/gradients3d.sh new file mode 100644 index 00000000000..c0fbd278fd5 --- /dev/null +++ b/test/psxyz/gradients3d.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# +# Test gradient-filled 3D polygons with -G+g (Gouraud shading) +# Tests RGB format (x y z r g b), color names (x y z colorname), +# and CPT format (x y z value) + +ps=gradients3d.ps + +# Create CPT for CPT-based tests +gmt makecpt -Chot -T0/100 > t.cpt + +# Row 1: RGB format - triangle and square at z=0 +gmt psxyz -R-1/4/-1/3/0/3 -JX6c -JZ4c -p135/30 -Bxafg -Byafg -Bzafg -BWSneZ+t"RGB Triangle" -G+g -P -K -X1.5 -Y19c << EOF > $ps +0 0 0 255 0 0 +3 0 0 0 255 0 +1.5 2.6 0 0 0 255 +0 0 0 255 0 0 +EOF + +gmt psxyz -R-0.5/2.5/-0.5/2.5/0/3 -JX6c -JZ4c -p135/30 -Bxafg -Byafg -Bzafg -BWSneZ+t"RGB Square" -G+g -O -K -X9.5c << EOF >> $ps +0 0 1 255 0 0 +2 0 1 255 255 0 +2 2 1 0 255 0 +0 2 1 0 0 255 +0 0 1 255 0 0 +EOF + +# Row 2: Color names - triangle and square at different z levels +gmt psxyz -R-1/4/-1/3/0/3 -JX6c -JZ4c -p135/30 -Bxafg -Byafg -Bzafg -BWSneZ+t"Color Names Triangle" -G+g -O -K -X-9.5c -Y-8.5c << EOF >> $ps +0 0 0 red +3 0 0.5 green +1.5 2.6 1 blue +0 0 0 red +EOF + +gmt psxyz -R-0.5/2.5/-0.5/2.5/0/3 -JX6c -JZ4c -p135/30 -Bxafg -Byafg -Bzafg -BWSneZ+t"Color Names Square" -G+g -O -K -X9.5c << EOF >> $ps +0 0 0.5 red +2 0 1 yellow +2 2 1.5 green +0 2 1 blue +0 0 0.5 red +EOF + +# Row 3: CPT format - triangle and square +gmt psxyz -R-1/4/-1/3/0/3 -JX6c -JZ4c -p135/30 -Bxafg -Byafg -Bzafg -BWSneZ+t"CPT Triangle" -G+g -Ct.cpt -O -K -X-9.5c -Y-8.5c << EOF >> $ps +0 0 0 0 +3 0 1 50 +1.5 2.6 2 100 +0 0 0 0 +EOF + +gmt psxyz -R-0.5/2.5/-0.5/2.5/0/3 -JX6c -JZ4c -p135/30 -Bxafg -Byafg -Bzafg -BWSneZ+t"CPT Square" -G+g -Ct.cpt -O -X9.5c << EOF >> $ps +0 0 0.5 0 +2 0 1 33 +2 2 1.5 67 +0 2 1 100 +0 0 0.5 0 +EOF + +\rm t.cpt From a30535d83f0bce6ae868c94df26f93bc770879fb Mon Sep 17 00:00:00 2001 From: Joaquim Date: Sat, 27 Dec 2025 23:48:47 +0000 Subject: [PATCH 7/7] Add reference to H-S-V and CMYK color formats in the -G+g option. --- doc/rst/source/plot.rst | 2 +- doc/rst/source/plot3d.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/rst/source/plot.rst b/doc/rst/source/plot.rst index 1e7393b4b8b..0f0ce787ff3 100644 --- a/doc/rst/source/plot.rst +++ b/doc/rst/source/plot.rst @@ -212,7 +212,7 @@ Optional Arguments 1. **RGB format**: *x y r g b* where RGB values are in the range 0-255. 2. **Color name format**: *x y colorname* where *colorname* can be a named color (e.g., "red", "blue"), - hex color (e.g., "#FF0000"), or slash-separated RGB (e.g., "255/0/0"). + hex color (e.g., "#FF0000"), slash-separated RGB (e.g., "255/0/0"), H-S-V or C/M/Y/K formats. 3. **CPT format**: *x y z* where *z* values are mapped to colors via the CPT specified with |-C|. Polygons with any number of vertices are supported and automatically triangulated using a fan triangulation diff --git a/doc/rst/source/plot3d.rst b/doc/rst/source/plot3d.rst index 0d6aeef3815..093d1b3a190 100644 --- a/doc/rst/source/plot3d.rst +++ b/doc/rst/source/plot3d.rst @@ -144,7 +144,7 @@ Optional Arguments 1. **RGB format**: *x y z r g b* where RGB values are in the range 0-255. 2. **Color name format**: *x y z colorname* where *colorname* can be a named color (e.g., "red", "blue"), - hex color (e.g., "#FF0000"), or slash-separated RGB (e.g., "255/0/0"). + hex color (e.g., "#FF0000"), slash-separated RGB (e.g., "255/0/0"), H-S-V or C/M/Y/K formats. 3. **CPT format**: *x y z value* where *value* is mapped to colors via the CPT specified with |-C|. Polygons with any number of vertices are supported and automatically triangulated using a fan triangulation