Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

r.colors.out: Add JSON support #4555

Merged
merged 14 commits into from
Nov 5, 2024
Merged
1 change: 1 addition & 0 deletions general/g.parser/standard_option.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ static char *STD_OPT_STRINGS[] = {"G_OPT_UNDEFINED",
"G_OPT_F_SEP",
"G_OPT_C",
"G_OPT_CN",
"G_OPT_C_FORMAT",
"G_OPT_M_UNITS",
"G_OPT_M_DATATYPE",
"G_OPT_M_MAPSET",
Expand Down
5 changes: 3 additions & 2 deletions include/grass/gis.h
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,9 @@ typedef enum {
G_OPT_F_OUTPUT, /*!< new output file */
G_OPT_F_SEP, /*!< data field separator */

G_OPT_C, /*!< color */
G_OPT_CN, /*!< color or none */
G_OPT_C, /*!< color */
G_OPT_CN, /*!< color or none */
G_OPT_C_FORMAT, /*!< set color format to rgb,hex,hsv or triplet */

G_OPT_M_UNITS, /*!< units */
G_OPT_M_DATATYPE, /*!< datatype */
Expand Down
17 changes: 17 additions & 0 deletions lib/gis/parser_standard_options.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
- colors
- G_OPT_C
- G_OPT_CN
- G_OPT_C_FORMAT

- misc
- G_OPT_M_DIR
Expand Down Expand Up @@ -652,6 +653,22 @@ struct Option *G_define_standard_option(int opt)
Opt->description =
_("Either a standard color name, R:G:B triplet, or \"none\"");
break;
case G_OPT_C_FORMAT:
Opt->key = "color_format";
Opt->type = TYPE_STRING;
Opt->key_desc = "name";
Opt->required = YES;
Opt->multiple = NO;
Opt->answer = "hex";
Opt->options = "rgb,hex,hsv,triplet";
Opt->label = _("Color format");
Opt->description = _("Color format for output values.");
G_asprintf(
(char **)&(Opt->descriptions), "rgb;%s;hex;%s;hsv;%s;triplet;%s",
_("output color in RGB format"), _("output color in HEX format"),
_("output color in HSV format (experimental)"),
_("output color in colon-separated RGB format"));
break;

/* misc */

Expand Down
8 changes: 4 additions & 4 deletions raster/r.colors.out/Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
MODULE_TOPDIR = ../..

LIBES2 = $(RASTERLIB) $(GISLIB)
LIBES3 = $(RASTER3DLIB) $(RASTERLIB) $(GISLIB)
LIBES2 = $(RASTERLIB) $(GISLIB) $(PARSONLIB)
LIBES3 = $(RASTER3DLIB) $(RASTERLIB) $(GISLIB) $(PARSONLIB)
DEPENDENCIES = $(RASTER3DDEP) $(GISDEP) $(RASTERDEP)

PROGRAMS = r.colors.out r3.colors.out

r_colors_out_OBJS = raster_main.o
r3_colors_out_OBJS = raster3d_main.o
r_colors_out_OBJS = raster_main.o prt_json.o
r3_colors_out_OBJS = raster3d_main.o prt_json.o

include $(MODULE_TOPDIR)/include/Make/Multi.make

Expand Down
7 changes: 7 additions & 0 deletions raster/r.colors.out/local_proto.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include <grass/gis.h>
#include <grass/parson.h>

enum ColorFormat { RGB, HEX, HSV, TRIPLET };

void print_json_colors(struct Colors *colors, DCELL min, DCELL max, FILE *fp,
int perc, enum ColorFormat clr_frmt);
265 changes: 265 additions & 0 deletions raster/r.colors.out/prt_json.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <grass/gis.h>
#include <grass/glocale.h>
#include <grass/parson.h>
#include <grass/raster.h>

#include "local_proto.h"

#define COLOR_STRING_LENGTH 30

NishantBansal2003 marked this conversation as resolved.
Show resolved Hide resolved
/*!
\brief Closes the file if it is not stdout.

\param fp file where to print color table rules
*/
static void close_file(FILE *fp)
{
if (fp != stdout)
fclose(fp);
}

/*!
\brief Converts RGB color values to HSV format.

\note This implementation is experimental and may be subject to change.

\param r red component of the RGB color
\param g green component of the RGB color
\param b blue component of the RGB color
\param[out] h pointer to store the calculated hue
\param[out] s pointer to store the calculated saturation
\param[out] v pointer to store the calculated value
*/
static void rgb_to_hsv(int r, int g, int b, float *h, float *s, float *v)
wenzeslaus marked this conversation as resolved.
Show resolved Hide resolved
{
float r_norm = (float)r / 255.0f;
float g_norm = (float)g / 255.0f;
float b_norm = (float)b / 255.0f;

float cmax = MAX(r_norm, MAX(g_norm, b_norm));
float cmin = MIN(r_norm, MIN(g_norm, b_norm));
float diff = cmax - cmin;

if (cmax == cmin) {
*h = 0;
}
else if (cmax == r_norm) {
*h = fmodf((60.0f * ((g_norm - b_norm) / diff) + 360.0f), 360.0f);
}
else if (cmax == g_norm) {
*h = fmodf((60.0f * ((b_norm - r_norm) / diff) + 120.0f), 360.0f);
}
else {
*h = fmodf((60.0f * ((r_norm - g_norm) / diff) + 240.0f), 360.0f);
}

if (cmax == 0) {
*s = 0;
}
else {
*s = (diff / cmax) * 100.0f;
}

*v = cmax * 100.0f;
}

/*!
\brief Writes color entry in JSON in specified clr_frmt.

\param r red component of RGB color
\param g green component of RGB color
\param b blue component of RGB color
\param clr_frmt color format to be used (RGB, HEX, HSV, TRIPLET).
\param color_object pointer to the JSON object
*/
static void set_color(int r, int g, int b, enum ColorFormat clr_frmt,
JSON_Object *color_object)
{
char color_string[COLOR_STRING_LENGTH];
float h, s, v;

switch (clr_frmt) {
case RGB:
snprintf(color_string, sizeof(color_string), "rgb(%d, %d, %d)", r, g,
b);
json_object_set_string(color_object, "rgb", color_string);
break;

case HEX:
snprintf(color_string, sizeof(color_string), "#%02X%02X%02X", r, g, b);
json_object_set_string(color_object, "hex", color_string);
break;

case HSV:
rgb_to_hsv(r, g, b, &h, &s, &v);
snprintf(color_string, sizeof(color_string), "hsv(%d, %d, %d)", (int)h,
(int)s, (int)v);
json_object_set_string(color_object, "hsv", color_string);
break;

case TRIPLET:
snprintf(color_string, sizeof(color_string), "%d:%d:%d", r, g, b);
json_object_set_string(color_object, "triplet", color_string);
break;
}
}

/*!
\brief Writes a JSON rule for a specific color entry.

\param val pointer to the DCELL value
\param min,max minimum and maximum value for percentage output (used only
when \p perc is non-zero)
\param r red component of RGB color
\param g green component of RGB color
\param b blue component of RGB color
\param root_array pointer to the JSON array
\param perc TRUE for percentage output
\param clr_frmt color format to be used (RBG, HEX, HSV, TRIPLET).
\param fp file where to print color table rules
\param root_value pointer to json value
*/
static void write_json_rule(DCELL *val, DCELL *min, DCELL *max, int r, int g,
int b, JSON_Array *root_array, int perc,
enum ColorFormat clr_frmt, FILE *fp,
JSON_Value *root_value)
{
static DCELL v0;
static int r0 = -1, g0 = -1, b0 = -1;

// Skip writing if the current color is the same as the last one
if (v0 == *val && r0 == r && g0 == g && b0 == b)
return;
// Update last processed values
v0 = *val, r0 = r, g0 = g, b0 = b;
NishantBansal2003 marked this conversation as resolved.
Show resolved Hide resolved

JSON_Value *color_value = json_value_init_object();
NishantBansal2003 marked this conversation as resolved.
Show resolved Hide resolved
if (color_value == NULL) {
json_value_free(root_value);
close_file(fp);
G_fatal_error(_("Failed to initialize JSON object. Out of memory?"));
}
JSON_Object *color_object = json_object(color_value);

// Set the value as a percentage if requested, otherwise set it as-is
if (perc)
json_object_set_number(color_object, "value",
100 * (*val - *min) / (*max - *min));
else
json_object_set_number(color_object, "value", *val);

set_color(r, g, b, clr_frmt, color_object);

json_array_append_value(root_array, color_value);
}

/*!
\brief Print color table in JSON format

\param colors pointer to Colors structure
\param min,max minimum and maximum value for percentage output (used only
when \p perc is non-zero)
\param fp file where to print color table rules
\param perc TRUE for percentage output
\param clr_frmt color format to be used (RBG, HEX, HSV, TRIPLET).
*/
void print_json_colors(struct Colors *colors, DCELL min, DCELL max, FILE *fp,
int perc, enum ColorFormat clr_frmt)
{
JSON_Value *root_value = json_value_init_array();
if (root_value == NULL) {
close_file(fp);
G_fatal_error(_("Failed to initialize JSON array. Out of memory?"));
}
JSON_Array *root_array = json_array(root_value);

if (colors->version < 0) {
/* 3.0 format */
CELL lo, hi;

// Retrieve the integer color range
Rast_get_c_color_range(&lo, &hi, colors);

for (int i = lo; i <= hi; i++) {
unsigned char r, g, b, set;
DCELL val = (DCELL)i;

// Look up the color for the current value and write JSON rule
Rast_lookup_c_colors(&i, &r, &g, &b, &set, 1, colors);
write_json_rule(&val, &min, &max, r, g, b, root_array, perc,
clr_frmt, fp, root_value);
}
}
else {
// Get the count of floating-point color rules
int count = Rast_colors_count(colors);

for (int i = 0; i < count; i++) {
DCELL val1, val2;
unsigned char r1, g1, b1, r2, g2, b2;

// Retrieve the color rule values and their respective RGB colors
Rast_get_fp_color_rule(&val1, &r1, &g1, &b1, &val2, &r2, &g2, &b2,
petrasovaa marked this conversation as resolved.
Show resolved Hide resolved
colors, count - 1 - i);

// write JSON rule
write_json_rule(&val1, &min, &max, r1, g1, b1, root_array, perc,
clr_frmt, fp, root_value);
write_json_rule(&val2, &min, &max, r2, g2, b2, root_array, perc,
clr_frmt, fp, root_value);
}
}

// Add special color entries for "null" and "default" values
{
int r, g, b;

// Get RGB color for null values and create JSON entry
Rast_get_null_value_color(&r, &g, &b, colors);
JSON_Value *nv_value = json_value_init_object();
if (nv_value == NULL) {
json_value_free(root_value);
close_file(fp);
G_fatal_error(
nilason marked this conversation as resolved.
Show resolved Hide resolved
_("Failed to initialize JSON object. Out of memory?"));
}
JSON_Object *nv_object = json_object(nv_value);
json_object_set_string(nv_object, "value", "nv");
set_color(r, g, b, clr_frmt, nv_object);
json_array_append_value(root_array, nv_value);

// Get RGB color for default values and create JSON entry
Rast_get_default_color(&r, &g, &b, colors);
JSON_Value *default_value = json_value_init_object();
if (default_value == NULL) {
json_value_free(root_value);
close_file(fp);
G_fatal_error(
_("Failed to initialize JSON object. Out of memory?"));
}
JSON_Object *default_object = json_object(default_value);
json_object_set_string(default_object, "value", "default");
set_color(r, g, b, clr_frmt, default_object);
json_array_append_value(root_array, default_value);
}

// Serialize JSON array to a string and print to the file
char *json_string = json_serialize_to_string_pretty(root_value);
if (!json_string) {
json_value_free(root_value);
close_file(fp);
G_fatal_error(_("Failed to serialize JSON to pretty format."));
}

fputs(json_string, fp);

json_free_serialized_string(json_string);
json_value_free(root_value);

close_file(fp);
}
1 change: 1 addition & 0 deletions raster/r.colors.out/r.colors.out.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ <h2>EXAMPLES</h2>
<div class="code"><pre>
r.colors.out map=el_D782_6m rules=rules.txt
r.colors map=el_D783_6m rules=rules.txt
r.colors.out map=el_D782_6m rules=rules.json format=json
</pre></div>

<h2>SEE ALSO</h2>
Expand Down
1 change: 1 addition & 0 deletions raster/r.colors.out/r3.colors.out.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ <h2>EXAMPLES</h2>
<div class="code"><pre>
r3.colors.out map=volume_1 rules=rules.txt
r3.colors map=volume_2 rules=rules.txt
r3.colors.out map=volume_1 rules=rules.json format=json
NishantBansal2003 marked this conversation as resolved.
Show resolved Hide resolved
</pre></div>

<h2>SEE ALSO</h2>
Expand Down
Loading
Loading