From c88ce4fe8501cac228a2d7e925ee16124618751f Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Fri, 22 Sep 2023 12:18:03 +0200 Subject: [PATCH 1/8] Allow grdimage to process byte images without indexed colors, via CPT See forum discussion for background. This PR is almost there but has a 1/3 fraction issue...Perhaps @joa-quim knows what to improve. --- src/grdimage.c | 59 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/grdimage.c b/src/grdimage.c index 8b098200708..5105c158d3a 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -1005,6 +1005,30 @@ GMT_LOCAL void grdimage_img_gray_no_intensity (struct GMT_CTRL *GMT, struct GRDI } } +GMT_LOCAL void grdimage_img_byte_index (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) no colormap, 3) external CPT */ + int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ + uint64_t byte, kk_s, node_s; + int index, k; + struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active data header */ + gmt_M_unused (GMT); + gmt_M_unused (Ctrl); + +#ifdef _OPENMP +#pragma omp parallel for private(srow,byte,kk_s,scol,node_s,k) shared(GMT,Conf,Ctrl,H_s,image) +#endif + for (srow = 0; srow < Conf->n_rows; srow++) { /* March along scanlines in the output bitimage */ + byte = (uint64_t)srow * Conf->n_columns; + kk_s = gmt_M_ijpgi (H_s, Conf->actual_row[srow], 0); /* Start pixel of this image 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]; /* Start of current pixel node */ + index = (int)Conf->Image->data[node_s]; + for (k = 0; k < 3; k++) + image[byte++] = (unsigned char)gmt_M_s255 (Conf->P->data[index].rgb_low[k]); + } + } +} + 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); @@ -1209,7 +1233,7 @@ EXTERN_MSC int gmtlib_ind2rgb (struct GMT_CTRL *GMT, struct GMT_IMAGE **I_in); (h->wesn[YHI] > 90.0 || h->wesn[XHI] > 720.0)) 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; + 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; unsigned int grid_registration = GMT_GRID_NODE_REG, try, row, col, mixed = 0, pad_mode = 0; @@ -1328,6 +1352,11 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { } } } + byte_image_no_cmap = (I && I->type == GMT_UCHAR && I->n_indexed_colors == 0); + if (byte_image_no_cmap && !Ctrl->C.active) { + GMT_Report (API, GMT_MSG_INFORMATION, "Byte image without indexed color requires a CPT via -C\n"); + Return (GMT_RUNTIME_ERROR); + } gmt_detect_oblique_region (GMT, Ctrl->In.file); /* Ensure a proper and smaller -R for oblique projections */ @@ -1628,11 +1657,27 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { /* Get or calculate a color palette file */ has_content = (got_z_grid) ? false : true; /* Images always have content but grids may be all NaN */ - if (got_z_grid) { /* Got a single grid so need to convert z to color via a CPT */ + if (byte_image_no_cmap) { /* Need external CPT for byte indices */ if (Ctrl->C.active) { /* Read a palette file */ - double zmin = Grid_orig->header->z_min, zmax = Grid_orig->header->z_max; - char *cpt = gmt_cpt_default (API, Ctrl->C.file, Ctrl->In.file, Grid_orig->header); - grdimage_reset_grd_minmax (GMT, Grid_orig, &zmin, &zmax); + if ((P = gmt_get_palette (GMT, Ctrl->C.file, GMT_CPT_OPTIONAL, 0.0, 255.0, Ctrl->C.dz)) == NULL) { + GMT_Report (API, GMT_MSG_ERROR, "Failed to read CPT %s.\n", Ctrl->C.file); + gmt_free_header (API->GMT, &header_I); + Return (API->error); /* Well, that did not go so well... */ + } + 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"); + } + } + else if (got_z_grid ) { /* Got a single grid so need to convert z to color via a CPT */ + if (Ctrl->C.active) { /* Read a palette file */ + double zmin = 0.0, zmax = 0.0; + char *cpt = (byte_image_no_cmap) ? strdup (Ctrl->C.file) : gmt_cpt_default (API, Ctrl->C.file, Ctrl->In.file, Grid_orig->header); + if (!byte_image_no_cmap) { + zmin = Grid_orig->header->z_min; + zmax = Grid_orig->header->z_max; + grdimage_reset_grd_minmax (GMT, Grid_orig, &zmin, &zmax); + } if ((P = gmt_get_palette (GMT, cpt, GMT_CPT_OPTIONAL, zmin, zmax, Ctrl->C.dz)) == NULL) { GMT_Report (API, GMT_MSG_ERROR, "Failed to read CPT %s.\n", Ctrl->C.file); gmt_free_header (API->GMT, &header_G); @@ -1870,7 +1915,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { 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) /* Use the CPT NaN color */ + else if (P && !byte_image_no_cmap) /* 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 we default to 0 0 0 of course */ } @@ -1927,6 +1972,8 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { grdimage_img_gray_no_intensity (GMT, Ctrl, Conf, bitimage_8); else if (Ctrl->M.active) /* Image, color converted to gray, with intensity */ grdimage_img_c2s_no_intensity (GMT, Ctrl, Conf, bitimage_8); + else if (byte_image_no_cmap) + grdimage_img_byte_index (GMT, Ctrl, Conf, bitimage_24); else /* Image, color, no intensity */ grdimage_img_color_no_intensity (GMT, Ctrl, Conf, bitimage_24); } From ed347c8aecc3b328bacaf0b1b11112db6806b4f3 Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Fri, 22 Sep 2023 12:34:29 +0200 Subject: [PATCH 2/8] Update grdimage.c --- src/grdimage.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/grdimage.c b/src/grdimage.c index 5105c158d3a..b401dbf0aa8 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -1006,7 +1006,7 @@ GMT_LOCAL void grdimage_img_gray_no_intensity (struct GMT_CTRL *GMT, struct GRDI } GMT_LOCAL void grdimage_img_byte_index (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) no colormap, 3) external CPT */ + /* Function that fills out the image in the special case of 1) 1-byte image, 2) no colormap, 3) external CPT given */ int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ uint64_t byte, kk_s, node_s; int index, k; @@ -1018,7 +1018,7 @@ GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CT #pragma omp parallel for private(srow,byte,kk_s,scol,node_s,k) shared(GMT,Conf,Ctrl,H_s,image) #endif for (srow = 0; srow < Conf->n_rows; srow++) { /* March along scanlines in the output bitimage */ - byte = (uint64_t)srow * Conf->n_columns; + byte = (uint64_t)(3 * srow * Conf->n_columns); kk_s = gmt_M_ijpgi (H_s, Conf->actual_row[srow], 0); /* Start pixel of this image 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]; /* Start of current pixel node */ From 11151edcccda83af796db3cccb30f33f8b1f75c6 Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Fri, 22 Sep 2023 12:38:53 +0200 Subject: [PATCH 3/8] Update grdimage.rst --- doc/rst/source/grdimage.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/rst/source/grdimage.rst b/doc/rst/source/grdimage.rst index 5d287a8b417..5b640d4b402 100644 --- a/doc/rst/source/grdimage.rst +++ b/doc/rst/source/grdimage.rst @@ -255,6 +255,14 @@ have chosen (perhaps implicitly) with **-nn+a** that turns *on* nearest neighbor gridding and turns *off* anti-aliasing. Alternatively, use |-T| instead to plot individual polygons centered on each node. +Imaging Categorical Images +-------------------------- + +If a 1-byte single layer image is given and the file has no color map then we will +interpret the byte values as categories and a categorical CPT is required via |-C|. +If no |-C| is given then we assume the image is a grayscale image with values in the +0-255 range. + Image formats recognized ------------------------ From 75d23e85266957748e887ed37c5eef8fbd9dfb56 Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Fri, 22 Sep 2023 12:42:23 +0200 Subject: [PATCH 4/8] Update grdimage.c --- src/grdimage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grdimage.c b/src/grdimage.c index b401dbf0aa8..a0200afbd36 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -1354,7 +1354,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { } byte_image_no_cmap = (I && I->type == GMT_UCHAR && I->n_indexed_colors == 0); if (byte_image_no_cmap && !Ctrl->C.active) { - GMT_Report (API, GMT_MSG_INFORMATION, "Byte image without indexed color requires a CPT via -C\n"); + GMT_Report (API, GMT_MSG_ERROR, "Byte image without indexed color requires a CPT via -C\n"); Return (GMT_RUNTIME_ERROR); } From 81f1ea64d6ba616c93f32acc5bedce82641d6e5d Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Sat, 23 Sep 2023 12:37:20 +0200 Subject: [PATCH 5/8] Update psscale.c --- src/psscale.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/psscale.c b/src/psscale.c index 33e1cbcbff7..2fe9cc5a308 100644 --- a/src/psscale.c +++ b/src/psscale.c @@ -829,7 +829,6 @@ GMT_LOCAL void psscale_draw_colorbar (struct GMT_CTRL *GMT, struct PSSCALE_CTRL if (P->categorical && !Ctrl->L.active) { /* For categorical CPTs the default is -L with sum of all gaps = 15% of bar length */ Ctrl->L.active = true; Ctrl->L.spacing = 0.01 * PSSCALE_GAP_PERCENT * Ctrl->D.dim[GMT_X] / (P->n_colors - 1); - fprintf (stderr, "Gap set to %lg\n", Ctrl->L.spacing); } max_intens[0] = Ctrl->I.min; max_intens[1] = Ctrl->I.max; From 8363666f885d78b450370e7e22b7b9246e73ef7e Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Sat, 23 Sep 2023 13:30:52 +0200 Subject: [PATCH 6/8] Handle categories starting at 1 --- src/grdimage.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/grdimage.c b/src/grdimage.c index a0200afbd36..48f2f43ee75 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -1005,15 +1005,16 @@ GMT_LOCAL void grdimage_img_gray_no_intensity (struct GMT_CTRL *GMT, struct GRDI } } -GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image) { +GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CTRL *Ctrl, struct GRDIMAGE_CONF *Conf, unsigned char *image, unsigned char *rgb_used) { /* Function that fills out the image in the special case of 1) 1-byte image, 2) no colormap, 3) external CPT given */ int64_t srow, scol; /* Due to OPENMP on Windows requiring signed int loop variables */ - uint64_t byte, kk_s, node_s; + uint64_t byte, kk_s, node_s, start; int index, k; struct GMT_GRID_HEADER *H_s = Conf->Image->header; /* Pointer to the active data header */ gmt_M_unused (GMT); gmt_M_unused (Ctrl); + start = (Conf->P->data[0].z_low == 1.0) ? 1 : 0; #ifdef _OPENMP #pragma omp parallel for private(srow,byte,kk_s,scol,node_s,k) shared(GMT,Conf,Ctrl,H_s,image) #endif @@ -1023,8 +1024,15 @@ GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CT for (scol = 0; scol < Conf->n_columns; scol++) { /* Compute rgb for each pixel along this scanline */ node_s = kk_s + Conf->actual_col[scol]; /* Start of current pixel node */ index = (int)Conf->Image->data[node_s]; - for (k = 0; k < 3; k++) - image[byte++] = (unsigned char)gmt_M_s255 (Conf->P->data[index].rgb_low[k]); + if (index < start) { /* E.g., data is 0 for Nodata */ + for (k = 0; k < 3; k++) + image[byte++] = (unsigned char)gmt_M_s255 (Conf->P->bfn[GMT_NAN].rgb[k]); + } + else { + for (k = 0; k < 3; k++) + image[byte++] = (unsigned char)gmt_M_s255 (Conf->P->data[index-start].rgb_low[k]); + rgb_used[index] = true; + } } } } @@ -1833,8 +1841,13 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { Conf->int_mode = use_intensity_grid; Conf->nm = header_work->nm; + if (byte_image_no_cmap && Conf->P->data[0].z_low == 1.0) { /* Means 0 is No data */ + Ctrl->Q.active = true; + rgb_cube_scan = true; + } + NaN_rgb = (P) ? P->bfn[GMT_NAN].rgb : GMT->current.setting.color_patch[GMT_NAN]; /* Determine which color represents a NaN grid node */ - if (got_z_grid && Ctrl->Q.active) { /* Want colormasking via the grid's NaN entries */ + if ((got_z_grid || byte_image_no_cmap) && Ctrl->Q.active) { /* Want colormasking via the grid's NaN entries */ if (gray_only) { GMT_Report (API, GMT_MSG_INFORMATION, "Your image is gray scale only but -Q requires building a 24-bit image; your image will be expanded to 24-bit.\n"); gray_only = false; /* Since we cannot do 8-bit and colormasking */ @@ -1915,7 +1928,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { 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 && !byte_image_no_cmap) /* Use the CPT NaN color */ + 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 we default to 0 0 0 of course */ } @@ -1973,7 +1986,7 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { else if (Ctrl->M.active) /* Image, color converted to gray, with intensity */ grdimage_img_c2s_no_intensity (GMT, Ctrl, Conf, bitimage_8); else if (byte_image_no_cmap) - grdimage_img_byte_index (GMT, Ctrl, Conf, bitimage_24); + grdimage_img_byte_index (GMT, Ctrl, Conf, bitimage_24, rgb_used); else /* Image, color, no intensity */ grdimage_img_color_no_intensity (GMT, Ctrl, Conf, bitimage_24); } From 46e37bb3b5c8345fd93af2d67d10c1e1449ec4f5 Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Sat, 23 Sep 2023 16:43:21 +0200 Subject: [PATCH 7/8] Update grdimage.c --- src/grdimage.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/grdimage.c b/src/grdimage.c index 48f2f43ee75..dce792a586d 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -1014,7 +1014,11 @@ GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CT gmt_M_unused (GMT); gmt_M_unused (Ctrl); - start = (Conf->P->data[0].z_low == 1.0) ? 1 : 0; + if (gmt_M_is_dnan (Conf->Image->header->nan_value)) /* Nodata not set, offset to 1 if CPT indicates first key is 1 */ + start = (irint (Conf->P->data[0].z_low) == 1) ? 1 : 0; /* No "0" key in CPT so we skip it */ + else /* Check if the nan_value is 0 or 255 */ + start = (irint (Conf->Image->header->nan_value) == 0) ? 1 : 0; /* No "0" key in CPT so we skip it */ + #ifdef _OPENMP #pragma omp parallel for private(srow,byte,kk_s,scol,node_s,k) shared(GMT,Conf,Ctrl,H_s,image) #endif @@ -1841,9 +1845,11 @@ EXTERN_MSC int GMT_grdimage (void *V_API, int mode, void *args) { Conf->int_mode = use_intensity_grid; Conf->nm = header_work->nm; - if (byte_image_no_cmap && Conf->P->data[0].z_low == 1.0) { /* Means 0 is No data */ - Ctrl->Q.active = true; - rgb_cube_scan = 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; + rgb_cube_scan = true; + } } NaN_rgb = (P) ? P->bfn[GMT_NAN].rgb : GMT->current.setting.color_patch[GMT_NAN]; /* Determine which color represents a NaN grid node */ From 002c871e35d38917a3f2540509f5a4b0472982d3 Mon Sep 17 00:00:00 2001 From: Paul Wessel Date: Sat, 23 Sep 2023 17:04:49 +0200 Subject: [PATCH 8/8] Update grdimage.c --- src/grdimage.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grdimage.c b/src/grdimage.c index dce792a586d..d13730e0aab 100644 --- a/src/grdimage.c +++ b/src/grdimage.c @@ -1017,7 +1017,7 @@ GMT_LOCAL void grdimage_img_byte_index (struct GMT_CTRL *GMT, struct GRDIMAGE_CT if (gmt_M_is_dnan (Conf->Image->header->nan_value)) /* Nodata not set, offset to 1 if CPT indicates first key is 1 */ start = (irint (Conf->P->data[0].z_low) == 1) ? 1 : 0; /* No "0" key in CPT so we skip it */ else /* Check if the nan_value is 0 or 255 */ - start = (irint (Conf->Image->header->nan_value) == 0) ? 1 : 0; /* No "0" key in CPT so we skip it */ + start = (irint (Conf->Image->header->nan_value) == 0 || irint (Conf->P->data[0].z_low) == 1) ? 1 : 0; /* No "0" key in CPT so we skip it */ #ifdef _OPENMP #pragma omp parallel for private(srow,byte,kk_s,scol,node_s,k) shared(GMT,Conf,Ctrl,H_s,image)