From e8c88178bd00f52cfb24a8c3284e75146ebf3450 Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Mon, 1 Jan 2024 20:23:28 +0100 Subject: [PATCH] Final version of updates for 6.5: grdimage, colorbar, grdmix (#8251) * Update grdimage.c Moving parts over from old branch * More brought over and still no failures * Update grdimage.c * Update grdimage.c * More copying with no failures * More OK updates * Update grdimage.c * Add test grid_transp.sh * Handle -Q+i for image * Add transp_mix test and PS * Update grdimage.c Did not test OpenMP so missed some new variables. * Update transp_mix.sh * Update grdimage.dvc * Fix OpenMP issue * Add image_vartrans.sh test and PS to DVC * Update grdimage.c * Update image_vartrans.sh * polish * Add test grdimage_Q_effects.sh and PS to DVC * indents * Update grdimage.c * Be more careful on memory layout for iimages This PR does now write a blank space after TRB, but places a there only if the images has transparency. * Fill in missing variables in OPENMP pragmas Especially in grdmix * Add gmt_setrgb and remove OPENMP loop when PSL memory is required * Update grdimage.c * Update grdimage.c * Fix opacity issue * Update psscale.c Finalize the -Li|I positioning and frame dimensions and not break previous tests. * Add new test and in DVC * alpha_count instead of alpha, which is unclear. --------- Co-authored-by: Joaquim --- src/gmt_hidden.h | 1 + src/gmt_plot.c | 6 + src/gmt_prototypes.h | 1 + src/grdimage.c | 359 +++++++++++++++++++++++----- src/grdmix.c | 15 +- src/postscriptlight.c | 16 ++ src/postscriptlight.h | 1 + src/psscale.c | 15 +- test/baseline/grdimage.dvc | 4 +- test/baseline/grdmix.dvc | 3 +- test/baseline/psscale.dvc | 7 +- test/grdimage/grdimage_Q_effects.sh | 35 +++ test/grdimage/grid_transp.sh | 35 +++ test/grdimage/image_vartrans.sh | 31 +++ test/grdimage/transp_mix.sh | 36 +++ test/psscale/interval-panel.sh | 29 +++ 16 files changed, 511 insertions(+), 83 deletions(-) create mode 100755 test/grdimage/grdimage_Q_effects.sh create mode 100755 test/grdimage/grid_transp.sh create mode 100755 test/grdimage/image_vartrans.sh create mode 100755 test/grdimage/transp_mix.sh create mode 100755 test/psscale/interval-panel.sh diff --git a/src/gmt_hidden.h b/src/gmt_hidden.h index 35052c97547..8a2adc4bedb 100644 --- a/src/gmt_hidden.h +++ b/src/gmt_hidden.h @@ -177,6 +177,7 @@ struct GMT_GRID_HEADER_HIDDEN { char flags[4]; /* Flags used for ESRI grids */ char *pocket; /* GDAL: A working variable handy to transmit info between funcs e.g. +b to gdalread */ double bcr_threshold; /* sum of cardinals must >= threshold in bilinear; else NaN */ + unsigned int used_indexed_rgb; /* 1 if originally an indexed RGB, 0 otherwise */ unsigned int has_NaN_rgb; /* Is 1 if an indexed RGB image had a specific NaN color for transparency */ unsigned int has_NaNs; /* Is 2 if the grid contains any NaNs, 1 if it does not, and 0 if no check has yet happened */ unsigned int bcr_interpolant; /* Interpolation function used (0, 1, 2, 3) */ diff --git a/src/gmt_plot.c b/src/gmt_plot.c index 9df13172d12..e0124f60db8 100644 --- a/src/gmt_plot.c +++ b/src/gmt_plot.c @@ -6914,6 +6914,12 @@ void gmt_BB_clip_on (struct GMT_CTRL *GMT, double rgb[], unsigned int flag) { PSL_beginclipping (PSL, work_x, work_y, 5, rgb, flag); } +void gmt_setrgb (struct GMT_CTRL *GMT, double *rgb) { + struct PSL_CTRL *PSL= GMT->PSL; + /* Fill with a color */ + PSL_setrgb (PSL, rgb); +} + void gmt_setfill (struct GMT_CTRL *GMT, struct GMT_FILL *fill, int outline) { struct PSL_CTRL *PSL= GMT->PSL; if (!fill) /* NO fill pointer = no fill */ diff --git a/src/gmt_prototypes.h b/src/gmt_prototypes.h index 9cb0c0ab17a..a7502cf7f98 100644 --- a/src/gmt_prototypes.h +++ b/src/gmt_prototypes.h @@ -304,6 +304,7 @@ EXTERN_MSC void gmt_BB_clip_on (struct GMT_CTRL *GMT, double rgb[], unsigned int EXTERN_MSC void gmt_plot_line (struct GMT_CTRL *GMT, double *x, double *y, unsigned int *pen, uint64_t n, unsigned int mode); EXTERN_MSC void gmt_setpen (struct GMT_CTRL *GMT, struct GMT_PEN *pen); EXTERN_MSC void gmt_setfill (struct GMT_CTRL *GMT, struct GMT_FILL *fill, int outline); +EXTERN_MSC void gmt_setrgb (struct GMT_CTRL *GMT, double *rgb); EXTERN_MSC void gmt_vertical_axis (struct GMT_CTRL *GMT, unsigned int mode); EXTERN_MSC void gmt_xy_axis (struct GMT_CTRL *GMT, double x0, double y0, double length, double val0, double val1, struct GMT_PLOT_AXIS *A, bool below, unsigned int side); EXTERN_MSC void gmt_xy_axis2 (struct GMT_CTRL *GMT, double x0, double y0, double length, double val0, double val1, struct GMT_PLOT_AXIS *A, bool below, bool annotate, unsigned side); diff --git a/src/grdimage.c b/src/grdimage.c index 45695e35a34..0c5a579dc72 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -29,12 +29,12 @@ #include "longopt/grdimage_inc.h" #define THIS_MODULE_CLASSIC_NAME "grdimage" -#define THIS_MODULE_MODERN_NAME "grdimage" -#define THIS_MODULE_LIB "core" -#define THIS_MODULE_PURPOSE "Project and plot grids or images" -#define THIS_MODULE_KEYS "X},>IA,BJKOPRUVXYfnptxy" GMT_OPT("Sc") GMT_ADD_x_OPT +#define THIS_MODULE_MODERN_NAME "grdimage" +#define THIS_MODULE_LIB "core" +#define THIS_MODULE_PURPOSE "Project and plot grids or images" +#define THIS_MODULE_KEYS "X},>IA,BJKOPRUVXYfnptxy" GMT_OPT("Sc") GMT_ADD_x_OPT /* These are images that GDAL knows how to read for us. */ #define N_IMG_EXTENSIONS 7 @@ -99,6 +99,7 @@ struct GRDIMAGE_CTRL { bool transp_color; /* true if a color was given */ bool z_given; /* true if a z-value was given */ bool mask_color; /* true if NaN color for image -Qcolor was set */ + bool got_color; /* true if -Qcolor was set on an image */ bool invert; /* If true we turn transparency = 1 - transparency [i.e., opacity] */ double rgb[4]; /* Pixel value for transparency in images */ double value; /* If +z is used this z-value will give us the r/g/b via CPT */ @@ -115,7 +116,7 @@ struct GRDIMAGE_CTRL { }; struct GRDIMAGE_TRANSP { - unsigned int alpha[256]; /* Transparency distribution for RGBA image */ + unsigned int alpha_count[256]; /* Transparency distribution for RGBA image */ unsigned int mode; /* value is transparency (mode == 0) or count (mode == 1) */ unsigned int value; /* Either chosen transparency (mode == 0) */ uint64_t n_transp; /* Number of different transparencies found */ @@ -432,25 +433,33 @@ static int parse (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GMT_O break; case 'Q': /* PS3 colormasking -Q[][+i][+z] */ n_errors += gmt_M_repeated_module_option (API, Ctrl->Q.active); - if ((c = strstr (opt->arg, "+z"))) { /* Gave a z-value */ - if (c[2]) { - Ctrl->Q.value = atof (&c[2]); - Ctrl->Q.z_given = true; - } - else { - GMT_Report (API, GMT_MSG_ERROR, "Option -Q: The +z modifier requires a valid z-value\n"); - n_errors++; + if (gmt_validate_modifiers (GMT, opt->arg, 'Q', "iz", GMT_MSG_ERROR)) n_errors++; + if ((c = gmt_first_modifier (GMT, opt->arg, "iz"))) { /* Got at least one modifier */ + char txt[GMT_LEN256] = {""}; + if (gmt_get_modifier (c, 'i', txt)) + Ctrl->Q.invert = true; + if (gmt_get_modifier (c, 'z', txt)) { + if (txt[0]) { + Ctrl->Q.value = atof (txt); + Ctrl->Q.z_given = true; + } + else { + GMT_Report (API, GMT_MSG_ERROR, "Option -Q: The +z modifier requires a valid z-value\n"); + n_errors++; + } } c[0] = '\0'; /* Chop off modifiers */ } - if (opt->arg[0]) { /* Change input image transparency pixel color */ + if (opt->arg[0]) { /* Change input image transparency pixel color from default white */ if (gmt_getrgb (GMT, opt->arg, Ctrl->Q.rgb)) { /* Change input image transparency pixel color */ gmt_rgb_syntax (GMT, 'Q', " "); n_errors++; } else - Ctrl->Q.transp_color = true; + Ctrl->Q.transp_color = Ctrl->Q.got_color = Ctrl->Q.mask_color = true; } + else /* Default white transparency color */ + Ctrl->Q.mask_color = true; if (c) c[0] = '+'; /* Restore the modifier */ break; case 'T': /* Tile plot -T[+s][+o] */ @@ -960,14 +969,29 @@ GMT_LOCAL void grdimage_grd_color_with_intensity_CM (struct GMT_CTRL *GMT, struc } } -GMT_LOCAL void grdimage_img_set_transparency (struct GMT_CTRL *GMT, unsigned char pix4, double *rgb) { - /* JL: Here we assume background color is white, hence t * 1. - But what would it take to have a user selected background color? */ +GMT_LOCAL void grdimage_img_set_transparency (struct GMT_CTRL *GMT, struct GRDIMAGE_CONF *Conf, unsigned char pix4, double *rgb) { + /* JL: Here we assume Conf->tr_rgb background color is white, hence t * 1. + If user selected background color with -Q that that is what Conf->tr_rgb contains */ + + /* PW Dec 2023: There are several situations we must handle: + * 1. There is no transparency, in which case this function is not called. + * 2. There are only two levels of transparency 0 or 255. In that case + * we let t = 1 and effectively select the -Q color for transparency + * By default that is white. + * 3. If more than 2 transparencies we expect we have full variable transparency + * and most of the time we will get a blended color output. + */ double o, t; /* o - opacity, t = transparency */ o = pix4 / 255.0; t = 1 - o; - rgb[0] = o * rgb[0] + t * GMT->current.map.frame.fill[GMT_Z].rgb[0]; - rgb[1] = o * rgb[1] + t * GMT->current.map.frame.fill[GMT_Z].rgb[1]; - rgb[2] = o * rgb[2] + t * GMT->current.map.frame.fill[GMT_Z].rgb[2]; + if (Conf->Transp->n_transp == 2) + gmt_M_rgb_only_copy (rgb, Conf->tr_rgb); + else { /* Blend */ + if (Conf->invert) t = o, o = 1 - t; + rgb[0] = o * rgb[0] + t * Conf->tr_rgb[0]; + rgb[1] = o * rgb[1] + t * Conf->tr_rgb[1]; + rgb[2] = o * rgb[2] + t * Conf->tr_rgb[2]; + } + rgb[3] = 0; } GMT_LOCAL void grdimage_img_gray_with_intensity (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image) { @@ -1055,6 +1079,76 @@ GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CT } } +GMT_LOCAL bool grdimage_transparencies (struct GMT_CTRL *GMT, struct GMT_IMAGE *I, bool opacity, struct GRDIMAGE_TRANSP *T) { + /* Determine if we have a transparency channel and if a fixed or variable (per-pixel) situation. + * We return false if no alpha channel present, T is left untouched, else return true. + * Return values via T are: T->n_transp is the number of different transparencies found. + * For modes 3 & 4, T->n_dominant is how many in the majority + * + * T->mode == 1: T->value is the constant transparency k + * T->mode == 2: T->value is dominant transparency k when >2 are found + * T->mode == 3: T->value is dominant transparency 0 + * T->mode == 4: T->value is dominant transparency 255 + */ + + struct GMT_GRID_HEADER *H = I->header; /* Pointer to the active image header */ + unsigned char *transparency; /* Pointer to memory where transparency resides */ + int64_t row, col, node, n_0 = 0, n_255 = 0; + unsigned int k, tr, tr_max = 0, tr_min = 255, tr_band; /* tr_band is 0 if image has alpha channel, else 1 for gray and 3 for color */ + unsigned int rgba[4], n_bands = H->n_bands; + + if (n_bands%2 == 1 && I->alpha == NULL) return false; /* No transparencies either in band 1 or 4 nor in alpha */ + + tr_band = (I->alpha) ? 0 : n_bands - 1; + transparency = (tr_band) ? I->data : I->alpha; /* Points to the data array with A */ + for (row = 0; row < H->n_rows; row++) { /* March along scanlines in the output bitimage */ + node = gmt_M_ijp (H, row, 0) + tr_band; /* Start pixel with 4 bytes of this image row */ + for (col = 0; col < H->n_columns; col++, node += n_bands) { /* March along this scanline in steps of 2 (gA) or 4 (RGBA) */ + tr = (unsigned int)transparency[node]; /* Get transparency values */ + if (opacity) tr = 255 - tr; /* Must flip from opacity to transparency */ + T->alpha_count[tr]++; /* Count frequency of transparency values */ + } + } + for (k = 0; k < GMT_LEN256; k++) { /* Determine how many different transparencies */ + if (T->alpha_count[k]) { /* Used at least once */ + T->n_transp++; + if (T->alpha_count[k] < tr_min) tr_min = k; /* Keep track of smallest value */ + if (T->alpha_count[k] > tr_max) tr_max = k; /* Keep track of largest value */ + } + } + if (T->n_transp == 1) { /* Case 1: Constant transparency */ + T->mode = 1; + T->value = tr_max; + T->n_dominant = T->n_transp; + } + else if (T->n_transp > 2) { /* Case 2: Variable transparency, not just on|off */ + T->value = tr_max; + T->mode = 2; + T->n_dominant = T->alpha_count[tr_max]; + } + else if (T->alpha_count[0] > T->alpha_count[255]) { /* Case 3: 0 most used of only two transparency values */ + T->value = 0; + T->mode = 3; + T->n_dominant = T->alpha_count[0]; + fprintf (stderr, "Min A = %d [x %d] Max A = %d [x %d]\n", tr_min, T->alpha_count[tr_min], tr_max, T->alpha_count[tr_max]); + } + else { + T->value = 255; /* Case 4: Like 3 but 255 most used of two transparency values */ + T->n_dominant = T->alpha_count[255]; + T->mode = 4; + fprintf (stderr, "Min A = %d [x %d] Max A = %d [x %d]\n", tr_min, T->alpha_count[tr_min], tr_max, T->alpha_count[tr_max]); + } + return (true); +} + +GMT_LOCAL bool grdimage_is_transparent (struct GRDIMAGE_CONF *Conf, uint64_t node) { + unsigned int transp; + if (Conf->Transp && Conf->Transp->n_transp > 2) return true; + transp = Conf->Image->data[node]; + if (Conf->Transp->n_transp == 2 && transp == Conf->Transp->value) return true; + return (false); +} + GMT_LOCAL void grdimage_img_c2s_with_intensity (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image) { /* Function that fills out the image in the special case of 1) image, 2) color -> gray via YIQ, 3) with intensity */ bool transparency = (Conf->Image->header->n_bands == 4); @@ -1074,8 +1168,8 @@ GMT_LOCAL void grdimage_img_c2s_with_intensity (struct GMT_CTRL *GMT, struct GRD for (scol = 0; scol < Conf->n_columns; scol++) { /* Compute rgb for each pixel along this scanline */ node_s = kk_s + Conf->actual_col[scol] * n_bands; /* Start of current pixel node */ for (k = 0; k < 3; k++) rgb[k] = gmt_M_is255 (Conf->Image->data[node_s++]); - if (transparency && Conf->Image->data[node_s] < 255) /* Dealing with an image with transparency values less than 255 */ - grdimage_img_set_transparency (GMT, Conf->Image->data[node_s], rgb); + if (transparency && grdimage_is_transparent (Conf, node_s)) + grdimage_img_set_transparency (GMT, Conf, Conf->Image->data[node_s], rgb); if (Conf->int_mode == 2) { /* Intensity value comes from the grid, so update node */ node_i = gmt_M_ijp (H_i, Conf->actual_row[srow], Conf->actual_col[scol]); gmt_illuminate (GMT, Conf->Intens->data[node_i], rgb); /* Apply illumination to this color */ @@ -1089,13 +1183,13 @@ GMT_LOCAL void grdimage_img_c2s_with_intensity (struct GMT_CTRL *GMT, struct GRD GMT_LOCAL void grdimage_img_c2s_no_intensity (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image) { /* Function that fills out the image in the special case of 1) image, 2) color -> gray via YIQ, 3) with intensity */ - bool transparency = (Conf->Image->header->n_bands == 4); - int k; + struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active data header */ + bool transparency = (H_s->n_bands == 4); + unsigned int k; int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ - uint64_t n_bands = Conf->Image->header->n_bands; + uint64_t n_bands = H_s->n_bands; uint64_t byte, kk_s, node_s; double rgb[4] = {0.0, 0.0, 0.0, 0.0}; - struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active data header */ gmt_M_unused (Ctrl); #ifdef _OPENMP @@ -1107,8 +1201,8 @@ GMT_LOCAL void grdimage_img_c2s_no_intensity (struct GMT_CTRL *GMT, struct GRDIM for (scol = 0; scol < Conf->n_columns; scol++) { /* Compute rgb for each pixel along this scanline */ node_s = kk_s + Conf->actual_col[scol] * n_bands; /* Start of current pixel node */ for (k = 0; k < 3; k++) rgb[k] = gmt_M_is255 (Conf->Image->data[node_s++]); - if (transparency && Conf->Image->data[node_s] < 255) /* Dealing with an image with transparency values less than 255 */ - grdimage_img_set_transparency (GMT, Conf->Image->data[node_s], rgb); + if (transparency && grdimage_is_transparent (Conf, node_s)) + grdimage_img_set_transparency (GMT, Conf, Conf->Image->data[node_s], rgb); image[byte++] = gmt_M_u255 (gmt_M_yiq (rgb)); } } @@ -1116,13 +1210,13 @@ GMT_LOCAL void grdimage_img_c2s_no_intensity (struct GMT_CTRL *GMT, struct GRDIM GMT_LOCAL void grdimage_img_color_no_intensity (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image) { /* Function that fills out the image in the special case of 1) image, 2) color, 3) with intensity */ - bool transparency = (Conf->Image->header->n_bands == 4); - int k; /* Due to OPENMP on Windows requiring signed int loop variables */ + struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active data header */ + bool transparency = (H_s->n_bands == 4); + unsigned int k; /* Due to OPENMP on Windows requiring signed int loop variables */ int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ - uint64_t n_bands = Conf->Image->header->n_bands; + uint64_t n_bands = H_s->n_bands; uint64_t byte, kk_s, node_s; double rgb[4] = {0.0, 0.0, 0.0, 0.0}; - struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active data header */ gmt_M_unused (Ctrl); #ifdef _OPENMP @@ -1134,8 +1228,8 @@ GMT_LOCAL void grdimage_img_color_no_intensity (struct GMT_CTRL *GMT, struct GRD for (scol = 0; scol < Conf->n_columns; scol++) { /* Compute rgb for each pixel along this scanline */ node_s = kk_s + Conf->actual_col[scol] * n_bands; /* Start of current pixel node */ for (k = 0; k < 3; k++) rgb[k] = gmt_M_is255 (Conf->Image->data[node_s++]); - if (transparency && Conf->Image->data[node_s] < 255) /* Dealing with an image with transparency values less than 255 */ - grdimage_img_set_transparency (GMT, Conf->Image->data[node_s], rgb); + if (transparency && grdimage_is_transparent (Conf, node_s)) + grdimage_img_set_transparency (GMT, Conf, Conf->Image->data[node_s], rgb); for (k = 0; k < 3; k++) image[byte++] = gmt_M_u255 (rgb[k]); /* Scale up to integer 0-255 range */ } } @@ -1143,26 +1237,26 @@ GMT_LOCAL void grdimage_img_color_no_intensity (struct GMT_CTRL *GMT, struct GRD GMT_LOCAL void grdimage_img_color_with_intensity (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image) { /* Function that fills out the image in the special case of 1) image, 2) color, 3) with intensity */ - bool transparency = (Conf->Image->header->n_bands == 4); - int k; + struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active image header */ + struct GMT_GRID_HEADER *H_i = (Conf->int_mode == 2) ? Conf->Intens->header : NULL; /* Pointer to the active intensity header */ + bool transparency = (H_s->n_bands == 4); + unsigned int k; int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ - uint64_t n_bands = Conf->Image->header->n_bands; + uint64_t n_bands = H_s->n_bands; uint64_t byte, kk_s, node_s, node_i; double rgb[4] = {0.0, 0.0, 0.0, 0.0}; - struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active image header */ - struct GMT_GRID_HEADER *H_i = (Conf->int_mode == 2) ? Conf->Intens->header : NULL; /* Pointer to the active intensity header */ #ifdef _OPENMP #pragma omp parallel for private(srow,byte,kk_s,k,scol,node_s,node_i,rgb) shared(GMT,Conf,Ctrl,H_s,H_i,image) #endif for (srow = 0; srow < Conf->n_rows; srow++) { /* March along scanlines in the output bitimage */ byte = (uint64_t)Conf->colormask_offset + 3 * srow * Conf->n_columns; /* Start of output color image row */ - kk_s = gmt_M_ijpgi (H_s, Conf->actual_row[srow], 0); /* Start pixel of this image row */ + kk_s = gmt_M_ijpgi (H_s, Conf->actual_row[srow], 0); /* Start pixel of this row */ for (scol = 0; scol < Conf->n_columns; scol++) { /* Compute rgb for each pixel along this scanline */ node_s = kk_s + Conf->actual_col[scol] * n_bands; /* Start of current input pixel node */ for (k = 0; k < 3; k++) rgb[k] = gmt_M_is255 (Conf->Image->data[node_s++]); - if (transparency && Conf->Image->data[node_s] < 255) /* Dealing with an image with transparency values less than 255 */ - grdimage_img_set_transparency (GMT, Conf->Image->data[node_s], rgb); + if (transparency && grdimage_is_transparent (Conf, node_s)) + grdimage_img_set_transparency (GMT, Conf, Conf->Image->data[node_s], rgb); if (Conf->int_mode == 2) { /* Intensity value comes from the grid, so update node */ node_i = gmt_M_ijp (H_i, Conf->actual_row[srow], Conf->actual_col[scol]); gmt_illuminate (GMT, Conf->Intens->data[node_i], rgb); /* Apply illumination to this color */ @@ -1174,6 +1268,71 @@ GMT_LOCAL void grdimage_img_color_with_intensity (struct GMT_CTRL *GMT, struct G } } +GMT_LOCAL int grdimage_plotsquare (struct PSL_CTRL *PSL, int ix, int iy, int isize[]) { + /* Plotting a high-resolution square with coordinate in 10*PS units as integers. + * We will write x, y, dims using one decimal by dividing by 10 to get exact matches + * at square boundaries. Command is "dy dx x y SS". + */ + bool xneg = (ix < 0) ? true : false, yneg = (iy < 0) ? true : false; + ix = abs (ix); iy = abs (iy); + int xx = (int)floor (ix * 0.1), dx = ix - xx * 10; if (xneg) xx = -xx; + int yy = (int)floor (iy * 0.1), dy = iy - yy * 10; if (yneg) yy = -yy; + int sx = (int)floor (isize[0] * 0.1), sdx = isize[0] - sx * 10; + int sy = (int)floor (isize[1] * 0.1), sdy = isize[1] - sy * 10; + PSL_command (PSL, "%d.%d %d.%d %d.%d %d.%d SS\n", sy, sdy, sx, sdx, xx, dx, yy, dy); + return (PSL_NO_ERROR); +} + +GMT_LOCAL void grdimage_img_variable_transparency (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, struct GMT_IMAGE *image) { + /* Because of variable transparency we cannot use the PostScript image operator but must plot each pixel + * as a square of the right color and set transparency before rendering that square. Because we cannot + * have tiny gaps or overlaps between neighboring squares we up the resolution by a factor of 10 and + * use integer calculations to ensure of no gaps. */ + int k, *ix = NULL, *iy = NULL, idim[2] = {0, 0}; + int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ + uint64_t n_bands = Conf->Image->header->n_bands, bt = n_bands - 1; + uint64_t kk_s, node_s, node_i; + double rgb[4] = {0.0, 0.0, 0.0, 0.0}, transp[2] = {0.0, 0.0}; /* None selected */ + struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active image header */ + struct GMT_GRID_HEADER *H_i = (Conf->int_mode == 2) ? Conf->Intens->header : NULL; /* Pointer to the active intensity header */ + + PSL_command (GMT->PSL, "/SS {M dup 0 D exch 0 exch D neg 0 D P FO}!\n"); /* Special PSL macro added for 10*integer squares */ + if ((ix = gmt_M_memory (GMT, NULL, Conf->n_columns + 1, int)) == NULL) { /* Add 1 so we can get width without special test*/ + GMT_Report (GMT->parent, GMT_MSG_ERROR, "grdimage_img_variable_transparency: Allocation failure for %d integers\n", Conf->n_columns); + return; + } + if ((iy = gmt_M_memory (GMT, NULL, Conf->n_rows + 1, int)) == NULL) { /* Same for height: add 1 */ + GMT_Report (GMT->parent, GMT_MSG_ERROR, "grdimage_img_variable_transparency: Allocation failure for %d integers\n", Conf->n_rows); + return; + } + /* Precompute BL square coordinates in PostSCript units times 10, yielding 1/12000 inches precision */ + for (srow = 0; srow <= Conf->n_rows; srow++) iy[srow] = irint (10.0 * PSL_DOTS_PER_INCH * (Conf->orig[GMT_Y] + Conf->dim[GMT_Y] * (Conf->n_rows - 1 - srow))); + for (scol = 0; scol <= Conf->n_columns; scol++) ix[scol] = irint (10.0 * PSL_DOTS_PER_INCH * (Conf->orig[GMT_Y] + Conf->dim[GMT_X] * scol)); + for (srow = 0; srow < Conf->n_rows; srow++) { /* March along scanlines in the output bitimage */ + kk_s = gmt_M_ijpgi (H_s, Conf->actual_row[srow], 0); /* Start pixel of this image row */ + idim[GMT_Y] = iy[Conf->actual_row[srow]] - iy[Conf->actual_row[srow]+1]; /* Constant height of square in 1/12000 inches integer units for this row */ + for (scol = 0; scol < Conf->n_columns; scol++) { /* Compute rgb for each pixel along this scanline */ + node_s = kk_s + Conf->actual_col[scol] * n_bands; /* Start of current input pixel node */ + /* Get pixel color */ + for (k = 0; k < bt; k++) rgb[k] = gmt_M_is255 (Conf->Image->data[node_s++]); /* 0-255 normalized to 0-1 */ + grdimage_img_set_transparency (GMT, Conf, Conf->Image->data[node_s], rgb); + if (Conf->invert) rgb[bt] = 1.0 - rgb[bt]; + if (Conf->int_mode == 2) { /* Intensity value comes from the grid, so update node */ + node_i = gmt_M_ijp (H_i, Conf->actual_row[srow], Conf->actual_col[scol]); + gmt_illuminate (GMT, Conf->Intens->data[node_i], rgb); /* Apply illumination to this color */ + } + else if (Conf->int_mode == 1) /* A constant (ambient) intensity was given via -I */ + gmt_illuminate (GMT, Ctrl->I.value, rgb); /* Apply constant illumination to this color */ + gmt_setrgb (GMT, rgb); /* Set current square pixel color w/ no outline */ + idim[GMT_X] = ix[scol+1] - ix[scol]; /* Constant width of square in 1/12000 inches integer units for this column */ + grdimage_plotsquare (GMT->PSL, ix[scol], iy[srow], idim); + } + } + gmt_M_free (GMT, ix); + gmt_M_free (GMT, iy); + PSL_settransparencies (GMT->PSL, transp); +} + GMT_LOCAL bool grdimage_adjust_R_consideration (struct GMT_CTRL *GMT, struct GMT_GRID_HEADER *h) { /* As per https://github.com/GenericMappingTools/gmt/issues/4440, when the user wants * to plot a pixel-registered global grid using a lon-lat scaling with periodic boundaries @@ -1261,10 +1420,10 @@ EXTERN_MSC int gmtlib_ind2rgb (struct GMT_CTRL *GMT, struct GMT_IMAGE **I_in); EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { bool done, need_to_project, normal_x, normal_y, resampled = false, gray_only = false, byte_image_no_cmap; bool nothing_inside = false, use_intensity_grid = false, got_data_tiles = false, rgb_cube_scan; - bool has_content, mem_G = false, mem_I = false, mem_D = false, got_z_grid = true; + bool has_content, mem_G = false, mem_I = false, mem_D = false, got_z_grid = true, plot_squares = false; unsigned int grid_registration = GMT_GRID_NODE_REG, try, row, col, mixed = 0, pad_mode = 0; uint64_t node, k, kk, dim[GMT_DIM_SIZE] = {0, 0, 3, 0}; - int error = 0, ret_val = GMT_NOERROR, ftype = GMT_NOTSET; + int error = 0, ret_val = GMT_NOERROR, ftype = GMT_NOTSET, ftype2 = GMT_NOTSET; char *img_ProjectionRefPROJ4 = NULL, *way[2] = {"via GDAL", "directly"}, cmd[GMT_LEN256] = {""}, data_grd[GMT_VF_LEN] = {""}, *e = NULL; unsigned char *bitimage_8 = NULL, *bitimage_24 = NULL, *rgb_used = NULL; @@ -1289,6 +1448,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { struct GMT_IMAGE *Out = NULL; /* A GMT image datatype, if external interface is used with -A */ struct GMT_GRID *G2 = NULL; struct GRDIMAGE_CONF *Conf = NULL; + struct GRDIMAGE_TRANSP Transp; /*----------------------- Standard module initialization and parsing ----------------------*/ @@ -1311,7 +1471,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { GMT_Report (API, GMT_MSG_ERROR, "Failed to allocate Conf structure\n"); Return (GMT_MEMORY_ERROR); } - + gmt_M_memset (&Transp, 1, struct GRDIMAGE_TRANSP); /* Make sure it is initialized to nothing */ gmt_grd_set_datapadding (GMT, true); /* Turn on gridpadding when reading a subset */ use_intensity_grid = (Ctrl->I.active && !Ctrl->I.constant); /* We want to use an intensity grid */ @@ -1365,6 +1525,16 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { Ctrl->D.active = true; if (GMT->common.R.active[RSET] && !strstr (Ctrl->In.file, "earth_")) Ctrl->D.mode = true; } + if (!Ctrl->D.active && (ftype2 = gmt_raster_type (GMT, Ctrl->In.file, true)) == GMT_IS_IMAGE) { + if (Ctrl->Q.z_given) { + GMT_Report (API, GMT_MSG_ERROR, "Option Q: Cannot use +z with an image\n"); + Return (GMT_RUNTIME_ERROR); + } + } + else if (ftype == GMT_IS_GRID && Ctrl->Q.invert) { /* Cannot invert color for grid-derived images */ + GMT_Report (API, GMT_MSG_ERROR, "Option Q: Cannot use +i with a grid\n"); + Return (GMT_RUNTIME_ERROR); + } if (!Ctrl->D.active && ftype == GMT_IS_GRID) { /* See if input could be an image of a kind that could also be a grid and we don't yet know what it is. Pass GMT_GRID_IS_IMAGE mode */ if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_ONLY | GMT_GRID_IS_IMAGE|pad_mode, NULL, Ctrl->In.file, NULL)) != NULL) { @@ -1410,13 +1580,37 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { if (Ctrl->D.mode && GMT->common.R.active[RSET]) { /* Need to assign given -R as the image's -R, so cannot pass -R in when reading */ GMT->common.R.active[RSET] = false; /* Temporarily turn off -R if given */ - I_wesn = NULL; + I_wesn = NULL; } /* Read in the the entire image that is to be mapped */ GMT_Report (API, GMT_MSG_INFORMATION, "Allocate memory and read image file %s\n", Ctrl->In.file); if ((I = GMT_Read_Data (API, GMT_IS_IMAGE, GMT_IS_FILE, GMT_IS_SURFACE, GMT_CONTAINER_AND_DATA | GMT_IMAGE_NO_INDEX | pad_mode, I_wesn, Ctrl->In.file, NULL)) == NULL) { Return (API->error); } + + if (grdimage_transparencies (GMT, I, Ctrl->Q.invert, &Transp)) { /* What if any transparency situation do we have in the image? */ + double percent = 100.0 * (double)Transp.n_dominant / (double) I->header->nm; + switch (Transp.mode) { + case 1: + GMT_Report(API, GMT_MSG_INFORMATION, "Image alpha channel: Constant transparency is %d.\n", Transp.value); + break; + case 2: + GMT_Report(API, GMT_MSG_INFORMATION, "Image alpha channel: Variable, but dominant transparency (%.1lf%%) is %d.\n", percent, Transp.value); + Conf->invert = Ctrl->Q.invert; /* Flag that -Q+i was used on the image */ + break; + case 3: + GMT_Report(API, GMT_MSG_INFORMATION, "Image alpha channel: 0 or 255, mostly (%.1lf%%) were 0.\n", percent); + Ctrl->Q.active = Ctrl->Q.transp_color = Conf->invert = true; + break; + case 4: + GMT_Report(API, GMT_MSG_INFORMATION, "Image alpha channel: 0 or 255, mostly (%.1lf%%) were 255.\n", percent); + Ctrl->Q.active = Ctrl->Q.transp_color = Conf->invert = true; + break; + } + Conf->Transp = &Transp; + plot_squares = (Transp.mode == 2 && !Ctrl->Q.active); /* Plot tiny squares is selected */ + } + GMT->common.R.active[RSET] = R_save; /* Restore -R if it was set */ grid_registration = I->header->registration; /* This is presumably pixel registration since it is an image */ if (grid_registration != GMT_GRID_PIXEL_REG) @@ -1425,7 +1619,6 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { HH = gmt_get_H_hidden (I->header); if ((I->header->n_bands > 1 && strncmp (I->header->mem_layout, "BRP", 3)) || strncmp (I->header->mem_layout, "BR", 2)) GMT_Report(API, GMT_MSG_INFORMATION, "The image memory layout (%s) may be of the wrong type. It should be BRPa.\n", I->header->mem_layout); - if (!Ctrl->D.mode && !Ctrl->I.active && !GMT->common.R.active[RSET]) /* No -R or -I were set. Use image dimensions as -R */ gmt_M_memcpy (GMT->common.R.wesn, I->header->wesn, 4, double); @@ -1488,6 +1681,10 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { } if (!Ctrl->C.active) /* Set no specific CPT so we turn -C on to use current or default CPT */ Ctrl->C.active = true; /* Use default CPT (GMT->current.setting.cpt) and autostretch or under modern reuse current CPT */ + if (got_z_grid && Ctrl->Q.mask_color && use_intensity_grid) { + GMT_Report (API, GMT_MSG_ERROR, "Option -Q: Cannot specify a transparent color for grids when intensities are also used\n"); + Return (API->error); + } } if (got_z_grid) header_work = Grid_orig->header; /* OK, we are in GRID mode and this was not set previously. Do it now. */ @@ -1709,10 +1906,14 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { if (cpt) gmt_M_str_free (cpt); gray_only = (P && P->is_gray); /* Flag that we are doing a gray scale image below */ Conf->P = P; - if (P && P->has_pattern) GMT_Report (API, GMT_MSG_WARNING, "Patterns in CPTs will be ignored\n"); - if (Ctrl->Q.z_given) { /* Obtain the transparent color based on P and value */ + if (Ctrl->Q.z_given) (void)gmt_get_rgb_from_z (GMT, Conf->P, Ctrl->Q.value, Ctrl->Q.rgb); + else if (!Ctrl->Q.got_color) /* No Q-color set; default to NaN color */ + (void)gmt_get_rgb_from_z (GMT, Conf->P, GMT->session.d_NaN, Ctrl->Q.rgb); + if (P && P->has_pattern) GMT_Report (API, GMT_MSG_WARNING, "Patterns in CPTs will be ignored\n"); + if (Ctrl->Q.active) { /* Obtain the transparent color based on P and z-value or given color */ Ctrl->Q.transp_color = true; + Ctrl->Q.mask_color = true; } } if (Ctrl->W.active) { /* Check if there are just NaNs in the grid */ @@ -1723,10 +1924,14 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { if (Ctrl->T.active) { /* Plot colored graticules instead */ need_to_project = false; /* Since we are not doing reprojection of the grid */ - gmt_plot_grid_graticules (GMT, Grid_orig, Intens_orig, P, (Ctrl->T.outline) ? &Ctrl->T.pen : NULL, Ctrl->T.skip, Ctrl->I.constant ? &Ctrl->I.value : NULL, false); + if (Grid_orig) + gmt_plot_grid_graticules (GMT, Grid_orig, Intens_orig, P, (Ctrl->T.outline) ? &Ctrl->T.pen : NULL, Ctrl->T.skip, Ctrl->I.constant ? &Ctrl->I.value : NULL, false); goto basemap_and_free; /* Skip all the image projection and just overlay basemap and free memory */ } + if (Transp.mode == 2) { /* Must do variable transparency via squares and projection */ + goto tr_image; + } if (need_to_project) { /* Need to resample the grid or image [and intensity grid] using the specified map projection */ int nx_proj = 0, ny_proj = 0; double inc[2] = {0.0, 0.0}; @@ -1746,8 +1951,12 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { ny_proj = Conf->n_rows; } if (Ctrl->D.active) { /* Must project the input image instead */ - GMT_Report (API, GMT_MSG_INFORMATION, "Project the input image\n"); +tr_image: GMT_Report (API, GMT_MSG_INFORMATION, "Project the input image\n"); if ((Img_proj = GMT_Duplicate_Data (API, GMT_IS_IMAGE, GMT_DUPLICATE_NONE, I)) == NULL) Return (API->error); /* Just to get a header we can change */ + if (Transp.mode == 2) { /* Must do variable transparency via squares */ + nx_proj = Conf->n_columns; + ny_proj = Conf->n_rows; + } grid_registration = GMT_GRID_PIXEL_REG; /* Force pixel */ grdimage_set_proj_limits (GMT, Img_proj->header, I->header, need_to_project, mixed); if (gmt_M_err_fail (GMT, gmt_project_init (GMT, Img_proj->header, inc, nx_proj, ny_proj, Ctrl->E.dpi, grid_registration), @@ -1855,6 +2064,9 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { Conf->int_mode = (use_intensity_grid) ? 2 : ((Ctrl->I.constant) ? 1 : 0); /* 2 is variable grid intensity, 1 is constant intensity, 0 no intensity */ Conf->nm = header_work->nm; + /* Set the blend color for image transparency */ + gmt_M_rgb_copy (Conf->tr_rgb, GMT->current.map.frame.fill[GMT_Z].rgb); + if (!got_z_grid && Conf->Image && header_work->n_bands == 4) Ctrl->Q.mask_color = true; if (byte_image_no_cmap) { /* Check if we have a nan_value (No data pixel) */ if (gmt_M_is_dnan (Conf->Image->header->nan_value) || irint (Conf->Image->header->nan_value) == 0 || irint (Conf->P->data[0].z_low) == 1) { /* Nodata given as 0 */ Ctrl->Q.active = true; @@ -1936,16 +2148,20 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { else /* Need 3-byte array for a 24-bit image */ bitimage_24 = Out->data; } - else { /* Produce a PostScript image layer */ + else if (!plot_squares) { /* Produce a PostScript image layer */ if (Ctrl->M.active || gray_only) /* Only need a byte-array to hold this image */ bitimage_8 = gmt_M_memory (GMT, NULL, header_work->nm, unsigned char); else { /* Need 3-byte array for a 24-bit image, plus possibly 3 bytes for the NaN mask color */ - if (Ctrl->Q.active) Conf->colormask_offset = 3; + if (Ctrl->Q.mask_color || Ctrl->Q.transp_color) Conf->colormask_offset = 3; bitimage_24 = gmt_M_memory (GMT, NULL, 3 * header_work->nm + Conf->colormask_offset, unsigned char); if (Ctrl->Q.transp_color) for (k = 0; k < 3; k++) bitimage_24[k] = gmt_M_u255 (Ctrl->Q.rgb[k]); /* Scale the specific rgb up to 0-255 range */ else if (P && Ctrl->Q.active) /* Use the CPT NaN color */ for (k = 0; k < 3; k++) bitimage_24[k] = gmt_M_u255 (P->bfn[GMT_NAN].rgb[k]); /* Scale the NaN rgb up to 0-255 range */ + else if (Ctrl->Q.got_color) + for (k = 0; k < 3; k++) bitimage_24[k] = gmt_M_u255 (Ctrl->Q.rgb[k]); /* Scale the NaN rgb up to 0-255 range */ + else if (Ctrl->Q.active) /* Use the CPT NaN color */ + for (k = 0; k < 3; k++) bitimage_24[k] = gmt_M_u255 (Ctrl->Q.rgb[k]); /* Scale the NaN rgb up to 0-255 range */ /* else we default to 0 0 0 of course */ } } @@ -1961,6 +2177,8 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { Conf->actual_col = gmt_M_memory (GMT, NULL, Conf->n_columns, unsigned int); /* Deal with any reversal of the x-axis due to -J */ for (col = 0; col < (unsigned int)Conf->n_columns; col++) Conf->actual_col[col] = (normal_x) ? col : Conf->n_columns - col - 1; + if (plot_squares) goto ready; + rgb_cube_scan = (P && Ctrl->Q.active && !Ctrl->A.active); /* Need to look for unique rgb for PostScript masking */ /* Evaluate colors at least once (try = 0), but may do twice if -Q is active and we need to select another unique NaN color not used in the image */ @@ -1997,6 +2215,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { grdimage_img_color_with_intensity (GMT, Ctrl, Conf, bitimage_24); } else { /* No intensity */ + Ctrl->Q.active = Ctrl->Q.mask_color; if (gray_only) /* Image, grayscale, no intensity */ grdimage_img_gray_no_intensity (GMT, Ctrl, Conf, bitimage_8); else if (Ctrl->M.active) /* Image, color converted to gray, with intensity */ @@ -2035,15 +2254,8 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { } if (rgb_used) gmt_M_free (GMT, rgb_used); /* Done using the r/g/b cube */ - gmt_M_free (GMT, Conf->actual_row); - gmt_M_free (GMT, Conf->actual_col); - if (use_intensity_grid) { /* Also done with the intensity grid */ - if (need_to_project || !got_z_grid) { - if (GMT_Destroy_Data (API, &Intens_proj) != GMT_NOERROR) - GMT_Report (API, GMT_MSG_ERROR, "Failed to free Intens_proj\n"); - } - } +ready: /* Get actual plot size of each pixel */ dx = gmt_M_get_inc (GMT, header_work->wesn[XLO], header_work->wesn[XHI], Conf->n_columns, header_work->registration); @@ -2056,11 +2268,19 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { x0 -= 0.5 * dx; y0 -= 0.5 * dy; } + Conf->orig[GMT_X] = x0; Conf->orig[GMT_Y] = y0; /* Lower left corner in plot coordinates */ + Conf->dim[GMT_X] = dx; Conf->dim[GMT_Y] = dy; /* Pixel dimension in plot coordinates */ + Conf->invert = Ctrl->Q.invert; /* Full rectangular dimension of the projected image in inches */ x_side = dx * Conf->n_columns; y_side = dy * Conf->n_rows; + if (plot_squares) { /* Variable transparency image must be done via individual squares */ + grdimage_img_variable_transparency (GMT, Ctrl, Conf, Img_proj); + goto basemap_and_free; /* Jump to basemap since not building an image */ + } + if (P && gray_only && !Ctrl->A.active) /* Determine if the gray image in fact is just black & white since the PostScript can then simplify */ for (kk = 0, P->is_bw = true; P->is_bw && kk < header_work->nm; kk++) if (!(bitimage_8[kk] == 0 || bitimage_8[kk] == 255)) P->is_bw = false; @@ -2102,6 +2322,13 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { basemap_and_free: + if (use_intensity_grid) { /* Also done with the intensity grid */ + if (need_to_project || !got_z_grid) { + if (GMT_Destroy_Data (API, &Intens_proj) != GMT_NOERROR) + GMT_Report (API, GMT_MSG_ERROR, "Failed to free Intens_proj\n"); + } + } + if (!Ctrl->A.active) { /* Finalize PostScript plot, possibly including basemap */ if (!Ctrl->N.active) gmt_map_clip_off (GMT); @@ -2153,9 +2380,11 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { if (GMT_Destroy_Data (API, &P) != GMT_NOERROR) {Return (API->error);} } + gmt_M_free (GMT, Conf->actual_row); + gmt_M_free (GMT, Conf->actual_col); gmt_M_free (GMT, Conf); - /* May return a flag that the image/PS had no data (see -W), or just NO_ERROR */ + /* May return a flag that the image/PS had no data (see -W), or just NO_ERROR [for grd2kml] */ ret_val = (Ctrl->W.active && !has_content) ? GMT_IMAGE_NO_DATA : GMT_NOERROR; Return (ret_val); } diff --git a/src/grdmix.c b/src/grdmix.c index 56c6dcb705c..c3a024133a1 100644 --- a/src/grdmix.c +++ b/src/grdmix.c @@ -47,7 +47,8 @@ struct GRDMIX_AIW { /* For various grid, image, or constant arguments */ bool active; - bool opacity; /* true if we hvae opacity instead of transparency [Default] */ + /* Our implementation is a bit backwards, so to get expected results we turn opacity on by default and let +o reverse that */ + bool opacity; /* true if we have opacity instead of transparency [Default] */ unsigned int mode; /* 0 a file, 1 a constant */ char *file; double value; @@ -580,7 +581,7 @@ EXTERN_MSC int GMT_grdmix (void *V_API, int mode, void *args) { } else { #ifdef _OPENMP -#pragma omp parallel for private(row,col,node) shared(GMT,G,off,scale,I_in) +#pragma omp parallel for private(row,col,node) shared(GMT,G,off,I_in) #endif gmt_M_grd_loop (GMT, G, row, col, node) G->data[node] = I_in[0]->data[node+off]; @@ -627,9 +628,9 @@ EXTERN_MSC int GMT_grdmix (void *V_API, int mode, void *args) { GMT_Report (API, GMT_MSG_WARNING, "Component grid values in %s exceed 0-1 range, probably need to specify -Ni\n", Ctrl->In.file[band]); } if (Ctrl->I.active && Ctrl->In.n_in == 3) { /* Make the most work-intensive version under OpenMP */ - #ifdef _OPENMP - #pragma omp parallel for private(row,col,node,band,rgb,pix) shared(GMT,I,G_in,H,intens) - #endif +#ifdef _OPENMP +#pragma omp parallel for private(row,col,node,band,rgb,pix) shared(GMT,I,P,G_in,H,intens) +#endif gmt_M_grd_loop (GMT, I, row, col, node) { /* The node is one per pixel in a band, so stride into additional bands */ if (P) /* Get r/g/b from grid z-value via CPT lookup */ (void)gmt_get_rgb_from_z (GMT, P, G_in[0]->data[node], rgb); @@ -769,11 +770,11 @@ EXTERN_MSC int GMT_grdmix (void *V_API, int mode, void *args) { Return (GMT_RUNTIME_ERROR); } #ifdef _OPENMP -#pragma omp parallel for private(node) shared(H,I,alpha) +#pragma omp parallel for private(node,transparency) shared(H,I,alpha,Ctrl) #endif for (node = 0; node < (int64_t)H->size; node++) { /* Scale to 0-255 range */ transparency = gmt_M_is_dnan (alpha[node]) ? 1.0 : alpha[node]; /* NaN means full transparency */ - if (Ctrl->A.opacity) transparency = 1.0 - alpha[node]; /* Turns out we got opacities */ + if (!Ctrl->A.opacity) transparency = 1.0 - alpha[node]; /* Turns out we got opacities */ I->alpha[node] = gmt_M_u255 (transparency); } gmt_M_free (GMT, alpha); diff --git a/src/postscriptlight.c b/src/postscriptlight.c index 49f61d2984e..ee5f678deb7 100644 --- a/src/postscriptlight.c +++ b/src/postscriptlight.c @@ -4982,6 +4982,22 @@ int PSL_setcolor (struct PSL_CTRL *PSL, double rgb[], int mode) { return (PSL_NO_ERROR); } +int PSL_setrgb (struct PSL_CTRL *PSL, double rgb[]) { + /* Set the fill (PSL_IS_FILL) r/g/b color + * rgb[0] >= 0: rgb is the color with R G B in 0-1 range. + */ + if (!rgb) return (PSL_NO_ERROR); /* NULL args to be ignored */ + if (PSL_same_rgb (rgb, PSL->current.rgb[PSL_IS_FILL])) return (PSL_NO_ERROR); /* Same color as already set */ + + /* Then, finally, set the color using psl_putcolor */ + PSL_command (PSL, "{%s} FS\n", psl_putcolor (PSL, rgb, 0)); + + /* Update the current stroke/fill color information */ + + PSL_rgb_copy (PSL->current.rgb[PSL_IS_FILL], rgb); + return (PSL_NO_ERROR); +} + char * PSL_makepen (struct PSL_CTRL *PSL, double linewidth, double rgb[], char *pattern, double offset) { /* Creates a text string with the corresponding PS command to set the pen */ static char buffer[PSL_BUFSIZ]; diff --git a/src/postscriptlight.h b/src/postscriptlight.h index 9f94763a6b4..d9afe271620 100644 --- a/src/postscriptlight.h +++ b/src/postscriptlight.h @@ -490,6 +490,7 @@ EXTERN_MSC int PSL_setdefaults (struct PSL_CTRL *PSL, double xyscales[], double EXTERN_MSC int PSL_setdash (struct PSL_CTRL *PSL, char *pattern, double offset); EXTERN_MSC int PSL_setfill (struct PSL_CTRL *PSL, double rgb[], int outline); EXTERN_MSC int PSL_setfont (struct PSL_CTRL *PSL, int font_no); +EXTERN_MSC int PSL_setrgb (struct PSL_CTRL *PSL, double rgb[]); EXTERN_MSC int PSL_setfontdims (struct PSL_CTRL *PSL, double supsub, double scaps, double sup_lc, double sup_uc, double sdown); EXTERN_MSC int PSL_setformat (struct PSL_CTRL *PSL, int n_decimals); EXTERN_MSC int PSL_setimage (struct PSL_CTRL *PSL, int image_no, char *imagefile, unsigned char *image, int image_dpi, unsigned int dim[], double f_rgb[], double b_rgb[]); diff --git a/src/psscale.c b/src/psscale.c index 45b70aeb03f..a78a48f7445 100644 --- a/src/psscale.c +++ b/src/psscale.c @@ -184,7 +184,6 @@ static int usage (struct GMTAPI_CTRL *API, int level) { GMT_Usage (API, 1, "\n-D%s[+w[/]][+e[b|f][]][+h|v][+j][+m[a|c|l|u]][+n[]]%s[+r]]", GMT_XYANCHOR, GMT_OFFSET); GMT_Usage (API, -2, "Specify position and dimensions of the scale bar [JBC]. "); gmt_refpoint_syntax (API->GMT, "D", NULL, GMT_ANCHOR_COLORBAR, 3); - //gmt_refpoint_syntax (API->GMT, " ", " Specify position and dimensions of the scale bar [JBC].", GMT_ANCHOR_COLORBAR, 1); GMT_Usage (API, -2, "For -DJ|j w/TC|BC|ML|MR the values for +w (%d%% of map width), +h|v,+j,+o,+m have defaults. " "You can override any of these settings with these explicit modifiers:", PSSCALE_L_SCALE); GMT_Usage (API, 3, "+h Select a horizontal scale."); @@ -1092,7 +1091,7 @@ GMT_LOCAL void psscale_draw_colorbar (struct GMT_CTRL *GMT, struct PSSCALE_CTRL annot_off += bar_tick_len; /* Extend x clearance by annotation width */ annot_off += hor_annot_width; - if (Ctrl->L.interval) annot_off += 0.4 * hor_annot_width; + if (Ctrl->L.interval) annot_off += 0.50 * hor_annot_width; /* Increase width if there is a label */ if (label[0]) label_off = MAX (0.0, GMT->current.setting.map_label_offset[GMT_Y]) + GMT->current.setting.font_label.size / PSL_POINTS_PER_INCH; @@ -1584,6 +1583,13 @@ GMT_LOCAL void psscale_draw_colorbar (struct GMT_CTRL *GMT, struct PSSCALE_CTRL gmt_M_memset (text, 256U, char); gmt_M_memset (test, 256U, char); if (center && Ctrl->L.interval) { + sprintf (text, "%ld - %ld", lrint (floor (P->data[0].z_low)), lrint (ceil (P->data[0].z_high))); + sprintf (test, "%ld - %ld", lrint (floor (P->data[P->n_colors-1].z_low)), lrint (ceil (P->data[P->n_colors-1].z_high))); + hor_annot_width = ((MAX ((int)strlen (text), (int)strlen (test)) + 2*ndec) * GMT_DEC_WIDTH - 0.4 + + ((ndec > 0) ? 2*GMT_PER_WIDTH : 0.0)) + * GMT->current.setting.font_annot[GMT_PRIMARY].size * GMT->session.u2u[GMT_PT][GMT_INCH]; + } + else if (center && Ctrl->L.interval && Ctrl->F.active) { sprintf (format2, "%s%c%s", one_format, endash, one_format); sprintf (text, format2, P->data[0].z_low, P->data[0].z_high); sprintf (test, format2, P->data[P->n_colors-1].z_low, P->data[P->n_colors-1].z_high); @@ -1606,8 +1612,8 @@ GMT_LOCAL void psscale_draw_colorbar (struct GMT_CTRL *GMT, struct PSSCALE_CTRL y_annot = y_base + dir * (((len > 0.0) ? len : 0.0) + GMT->current.setting.map_annot_offset[GMT_PRIMARY] * cosd (Ctrl->S.angle)); justify = l_justify = (dir == -1) ? PSL_ML : PSL_MR; } - else if (Ctrl->L.interval) - y_annot = y_base + dir * annot_off * 0.65; + else if (Ctrl->L.interval && Ctrl->F.active) + y_annot = y_base + dir * annot_off * 0.9; else y_annot = y_base + dir * annot_off; if ((flip & PSSCALE_FLIP_ANNOT) == (flip & PSSCALE_FLIP_LABEL) / 2) y_label = y_base + dir * label_off; @@ -1773,6 +1779,7 @@ GMT_LOCAL void psscale_draw_colorbar (struct GMT_CTRL *GMT, struct PSSCALE_CTRL if (use_labels && (no_B_mode & PSSCALE_ANNOT_CUSTOM)) this_just = psscale_set_custom_annot (GMT, P, i, justify, l_justify, text); else if (center && Ctrl->L.interval) { + sprintf (text, format, P->data[i].z_low, P->data[i].z_high); if (Ctrl->L.interval == 2 && i == 0) { sprintf (format2, "< %s", one_format); sprintf (text, format2, P->data[i].z_high); diff --git a/test/baseline/grdimage.dvc b/test/baseline/grdimage.dvc index 6768172640c..b39b655b54c 100644 --- a/test/baseline/grdimage.dvc +++ b/test/baseline/grdimage.dvc @@ -1,5 +1,5 @@ outs: -- md5: d1c15fadf2e09ad118cc77c53bbbff92.dir - nfiles: 31 +- md5: 226f00f8bb56bfda27786069c3ecfa19.dir + nfiles: 35 path: grdimage hash: md5 diff --git a/test/baseline/grdmix.dvc b/test/baseline/grdmix.dvc index 2906dc5e5d4..fe56aefc265 100644 --- a/test/baseline/grdmix.dvc +++ b/test/baseline/grdmix.dvc @@ -1,6 +1,5 @@ outs: -- md5: 4aa0bfc2018d514b2b1e7af6231f457f.dir - size: 2345366 +- md5: 6549edda1e5c0c5ac5f3917baf574a83.dir nfiles: 2 path: grdmix hash: md5 diff --git a/test/baseline/psscale.dvc b/test/baseline/psscale.dvc index c8c6827b729..3eb46bd517e 100644 --- a/test/baseline/psscale.dvc +++ b/test/baseline/psscale.dvc @@ -1,5 +1,6 @@ outs: -- md5: 452ffd60491b33499fed19555a0bb535.dir - size: 1400714 - nfiles: 25 +- md5: 6e77da8a8cb4a2816e41162fe275360d.dir + size: 1431120 + nfiles: 26 path: psscale + hash: md5 diff --git a/test/grdimage/grdimage_Q_effects.sh b/test/grdimage/grdimage_Q_effects.sh new file mode 100755 index 00000000000..4a7435ae60f --- /dev/null +++ b/test/grdimage/grdimage_Q_effects.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# Testing a simple grid with -Q options for transparency based on +# a computed color (via grid z-value and CPT) or a specific color. + +gmt begin grdimage_Q_effects + gmt set FONT_TAG 12p PS_MEDIA letter + gmt grdmath -R0/100/0/50 -I1 -rp X 10 DIV FLOOR = stripes.nc + gmt grdmath -Rstripes.nc X 100 DIV Y 50 DIV MUL 2 MUL 1 SUB = intensity.nc + gmt makecpt -Chot -T0/10/1 + gmt subplot begin 4x1 -R0/100/0/50 -Fs15c/5c -JX15c/5c -Scb -Srl -A+gwhite+p0.25p -Bafg10 -X3c -Y3c + # 1. Just plot the stripes grid with no other effects + gmt subplot set 0 -A"PLAIN GRID and CPT [NO -Q]" + gmt basemap + echo 50 20 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage stripes.nc + # 2. Plot stripes with intensity variations + gmt subplot set 1 -A"SAME PLUS LINEAR INTENSITY FROM LL (-1) TO UR (+1) [No -Q]" + gmt basemap + echo 50 20 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage stripes.nc -Iintensity.nc + # 3. Plot stripes but set z = 5 to be transparent + gmt subplot set 2 -A"PLAIN GRID WITH TRANSPARENCY FOR Z = 5 [-Q+z5]" + gmt basemap + echo 50 20 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage stripes.nc -Q+z5 + # 4. Plot stripes but set color equal yellow (z = 7-8) to be transparent + gmt subplot set 3 -A"PLAIN GRID WITH TRANSPARENCY AT COLOR = yellow (z = 7-8) [-Qyellow]" + gmt basemap + echo 70 25 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage stripes.nc -Qyellow + gmt subplot end + # Place the colorbar beneath the subplot */ + gmt colorbar -DJBC +gmt end show diff --git a/test/grdimage/grid_transp.sh b/test/grdimage/grid_transp.sh new file mode 100755 index 00000000000..68d184d0ddb --- /dev/null +++ b/test/grdimage/grid_transp.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# +# Testing various grids and transparency in grdimage +# Top row is a topo relief grid (image via CPT) and the same grid with fake NaNs to be transparent + +gmt begin grid_transp + gmt set FONT_TAG 12p PS_MEDIA letter + # Make two grids: one a 0|1 multiply grid setting a region to zero and one making NaNs over Africa and 1 elsewhere + gmt grdmath -Rd -I01d -rp -fg 1 X -40 SUB ABS 40 LT Y -20 SUB ABS 20 LT MUL SUB = zero.nc + gmt grdmath -Rzero.nc 0 0 SDIST 4000 GT 0 NAN = nan.nc + gmt subplot begin 4x1 -Rd -Fs12c/6c -JQ12c -Scb -Srl -A+gwhite+p0.25p -Bafg10 -X5c -Y1c + # 1. Plot 01d relief grid on top of grid lines (which will be invisible) + gmt subplot set 0 -A"TOPOGRAPHY GRID WITH NO NANS" + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage @earth_relief_01d_p + # 2. Plot 01d relief grid after setting a R=4000 chunk around (0,0) to NaNs + gmt subplot set 1 -A"TOPOGRAPHY GRID WITH NANS AS MISSING DATA" + gmt grdmath nan.nc @earth_relief_01d_p MUL = hurt.nc + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage hurt.nc -Cgeo + # 3. Plot the same grid but now say NaNs should be transparent + gmt subplot set 2 -A"TOPO WITH NANS INDICATING FULL TRANSPARENCY" + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage hurt.nc -Cgeo -Q + # 4. Plot 01d relief grid after setting a big chunk to zero and say zero is transparent + gmt grdmath zero.nc @earth_relief_01d_p MUL = hurt.nc + gmt subplot set 3 -A"TOPO WITH ZEROS INDICATING FULL TRANSPARENCY" + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage hurt.nc -Cgeo -Q+z0 + gmt subplot end +gmt end show diff --git a/test/grdimage/image_vartrans.sh b/test/grdimage/image_vartrans.sh new file mode 100755 index 00000000000..b67018e94f7 --- /dev/null +++ b/test/grdimage/image_vartrans.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# For variable transparency TIF we plot pixels as squares with individual transparency +# Must add -Q to revert to original opaque blended image (color match the transparent +# colors but the pixels remain opaque). + +gmt begin image_vartrans + gmt set FONT_TAG 9p PS_MEDIA letter + gmt subplot begin 4x1 -Rd -Fs12c/6c -JQ12c -Scb -Srl -A+gwhite+p0.25p -Bafg10 -X5c -Y1c + # 1. Create and plot a continuously varying transparency grid (0-1) + gmt grdmath -Rd -I01d -r Y 180 DIV 0.5 ADD = transparency.nc + gmt subplot set 0 -A"FAKE VARIABLE TRANSPARENCIES" + gmt grdcontour transparency.nc -C0.05 -A0.1 + # 2. Plot original day image + gmt subplot set 1 -A"RGB IMAGE WITH NO TRANSPARENCY (OPAQUE)" + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage @earth_day_01d + # 3. Achieve variable transparency via transparent squares as pixels + gmt subplot set 2 -A"RGBA IMAGE WITH VARIABLE TRANSPARENCY AS SQUARES" + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + # Plot 01d day image grid after adding variable transparency + gmt grdmix @earth_day_01d -Atransparency.nc -C -Grgba.tif + gmt grdimage rgba.tif + # 4. Do variable transparency via image blending instead (opaque) + gmt subplot set 3 -A"RGBA IMAGE WITH VARIABLE TRANSPARENCY AS OPAQUE BLEND" + gmt basemap + echo 0 0 BACKGROUND | gmt text -F+f32p,1+a45 + gmt grdimage rgba.tif -Q + gmt subplot end +gmt end show diff --git a/test/grdimage/transp_mix.sh b/test/grdimage/transp_mix.sh new file mode 100755 index 00000000000..971b0aa07e0 --- /dev/null +++ b/test/grdimage/transp_mix.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Testing gmt grdmix on adding A to RGB image and plot via grdimage + +gmt begin transp_mix ps + gmt set MAP_GRID_PEN 2p FONT_TAG 9p PS_MEDIA letter PS_PAGE_ORIENTATION portrait + gmt grdmath -Rd -I01d -rp X 360 DIV 0.5 ADD Y 180 DIV 0.5 ADD MUL = transparency.nc + gmt subplot begin 3x2 -Rd -Fs9c/4.5c -JQ9c -Scb -Srl -A+gwhite+p0.25p -Bafg30 -X2c + # 1. Plot 01d day image on top of grid lines (which will be invisible) + gmt subplot set 0 -A"JUST IMAGE (OPAQUE)" + gmt basemap + gmt grdimage @earth_day_01d_p + # 2. Plot the fake transparencies + gmt subplot set 1 -A"FAKE TRANSPARENCY (OPAQUE)" + gmt basemap + gmt grdimage transparency.nc -Chot + # 3. Ignore transparencies when making the tif and plot the image on top of grid lines (which will be invisible) + gmt grdmix @earth_day_01d_p -C -Grgba.tif -A0.7 + gmt subplot set 2 -A"IMAGE FIXED 0.7 TRANSPARENCY (TRUE TRANSPARENCY)" + gmt basemap + gmt grdimage rgba.tif + # 4. Mix transparencies into the tif and plot the image on top of grid lines (which will be invisible) + gmt grdmix @earth_day_01d_p -Atransparency.nc+o -C -Grgba.tif + gmt subplot set 3 -A"BLENDED TRANSPARENT IMAGE (OPAQUE)" + gmt basemap + gmt grdimage rgba.tif -Q + # 5. Plot image using -Q+i to invert opacity to transparency on top of grid lines (which will be invisible) + gmt subplot set 4 -A"BLENDED OPACITY IMAGE (OPAQUE)" + gmt basemap + gmt grdimage rgba.tif -Q + # 6. Plot image using defaults on top of grid lines (which will visible through the squares) + gmt subplot set 5 -A"DEFAULT IMAGE VIA SQUARES (TRUE TRANSPARENCY)" + gmt basemap + gmt grdimage rgba.tif + gmt subplot end +gmt end show diff --git a/test/psscale/interval-panel.sh b/test/psscale/interval-panel.sh new file mode 100755 index 00000000000..f4cb43cbb5a --- /dev/null +++ b/test/psscale/interval-panel.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Testing categorical colorbars with default panels and interval annotations +# E.g., https://forum.generic-mapping-tools.org/t/colorbar-box-and-annotation/4548 + +ps=interval-panel.ps + +cat << EOF > t.cpt +# COLOR_MODEL = RGB +0 247/251/255 10 247/251/255 +10 222/235/247 20 222/235/247 +20 198/219/239 50 198/219/239 +50 158/202/225 100 158/202/225 +100 107/174/214 150 107/174/214 +150 066/146/198 200 066/146/198 +200 033/113/181 300 033/113/181 +300 008/081/156 500 008/081/156 +500 008/048/107 600 008/048/107 +EOF +# Vertical colorbars +gmt psscale -Ct.cpt -Dx0c/13c+w12c+v -Li3p -F+p2p -P -K > ${ps} +gmt psscale -Ct.cpt -Dx5c/13c+w12c+v -LI3p -F+p2p -O -K >> ${ps} +gmt psscale -Ct.cpt -Dx10c/13c+w12c+v -Li3p -O -K >> ${ps} +gmt psscale -Ct.cpt -Dx15c/13c+w12c+v -LI3p -O -K >> ${ps} +# Horizontal colorbars +gmt psscale -Ct.cpt -Dx0c/1c+w18c+h -Li3p -F+p2p -O -K -X-1c >> ${ps} +gmt psscale -Ct.cpt -Dx0c/4c+w18c+h -LI3p -F+p2p -O -K >> ${ps} +gmt psscale -Ct.cpt -Dx0c/7c+w18c+h -Li3p -O -K >> ${ps} +gmt psscale -Ct.cpt -Dx0c/10c+w18c+h -LI3p -O >> ${ps}