]> granicus.if.org Git - postgis/commitdiff
Addition of ST_ColorMap(raster). Ticket #2290
authorBborie Park <bkpark at ucdavis.edu>
Fri, 10 May 2013 00:09:11 +0000 (00:09 +0000)
committerBborie Park <bkpark at ucdavis.edu>
Fri, 10 May 2013 00:09:11 +0000 (00:09 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@11392 b70326c6-7e19-0410-871a-916f4a2858ee

13 files changed:
NEWS
doc/reference_raster.xml
raster/rt_core/rt_api.c
raster/rt_core/rt_api.h
raster/rt_pg/rt_pg.c
raster/rt_pg/rtpostgis.sql.in
raster/test/cunit/Makefile.in
raster/test/cunit/cu_mapalgebra.c
raster/test/cunit/cu_misc.c [new file with mode: 0644]
raster/test/cunit/cu_tester.c
raster/test/regress/Makefile.in
raster/test/regress/rt_colormap.sql [new file with mode: 0644]
raster/test/regress/rt_colormap_expected [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 6971e8fbfabf94f18f9fa5527c087a35e61f8743..4086c6ec76c043f3965db6a8aeb601ae904a04b8 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -86,6 +86,7 @@ PostGIS 2.1.0
   - #2163, ST_TPI for raster (Nathaniel Clay)
   - #2164, ST_TRI for raster (Nathaniel Clay)
   - #2302, ST_Roughness for raster (Nathaniel Clay)
+  - #2290, ST_ColorMap(raster) to generate RGBA bands
 
 * Enhancements *
 
index d5179842caaa86275c40bad5c98195d13282fed4..f4ba7c8d3b80d61fc6a05ffccf22f7b498bf2c6f 100644 (file)
@@ -10763,10 +10763,136 @@ UPDATE wind
 
                        <refsection>
                                <title>See Also</title>
-                               <para><xref linkend="RT_ST_AddBand" />, <xref linkend="RT_ST_Band" />, <xref linkend="RT_ST_BandPixelType" />, <xref linkend="RT_ST_MakeEmptyRaster" />,  <xref linkend="reclassarg" />, <xref linkend="RT_ST_Value" /></para>
+                               <para>
+                                       <xref linkend="RT_ST_AddBand" />, 
+                                       <xref linkend="RT_ST_Band" />, 
+                                       <xref linkend="RT_ST_BandPixelType" />, 
+                                       <xref linkend="RT_ST_MakeEmptyRaster" />,  
+                                       <xref linkend="reclassarg" />, 
+                                       <xref linkend="RT_ST_Value" />
+                               </para>
                        </refsection>
                </refentry>
+
+               <refentry id="RT_ST_ColorMap">
+                       <refnamediv>
+                               <refname>ST_ColorMap</refname>
+                               <refpurpose>Creates a new raster of up to four 8BUI bands (grayscale, RGB, RGBA) from the source raster and specified band.</refpurpose>
+                       </refnamediv>
                
+                       <refsynopsisdiv>
+                               <funcsynopsis>
+                                 <funcprototype>
+                                               <funcdef>raster <function>ST_ColorMap</function></funcdef>
+                                               <paramdef><type>raster </type> <parameter>rast</parameter></paramdef>
+                                               <paramdef choice="opt"><type>integer </type> <parameter>nband=1</parameter></paramdef>
+                                               <paramdef choice="opt"><type>text </type> <parameter>colormap=grayscale</parameter></paramdef>
+                                               <paramdef choice="opt"><type>text </type> <parameter>method=INTERPOLATE</parameter></paramdef>
+                                 </funcprototype>
+                               </funcsynopsis>
+
+                               <funcsynopsis>
+                                 <funcprototype>
+                                               <funcdef>raster <function>ST_ColorMap</function></funcdef>
+                                               <paramdef><type>raster </type> <parameter>rast</parameter></paramdef>
+                                               <paramdef><type>text </type> <parameter>colormap</parameter></paramdef>
+                                               <paramdef choice="opt"><type>text </type> <parameter>method=INTERPOLATE</parameter></paramdef>
+                                 </funcprototype>
+                               </funcsynopsis>
+                       </refsynopsisdiv>
+
+                       <refsection>
+                               <title>Description</title>
+
+                               <para>
+                                       Apply a <varname>colormap</varname> to the band at <varname>nband</varname> of <varname>rast</varname> resulting a new raster comprised of up to four 8BUI bands. The number of 8BUI bands in the new raster is determined by the number of color components defined in <varname>colormap</varname>.
+                               </para>
+
+                               <para>
+                                       <varname>colormap</varname> can be a keyword of a pre-defined colormap or a set of lines defining the value and the color components.
+                               </para>
+
+                               <para>
+                                       Valid pre-defined <varname>colormap</varname> keyword:
+                               </para>
+                                       
+                               <itemizedlist>
+                                       <listitem>
+                                               <para>
+                                                       <varname>grayscale</varname>, <varname>greyscale</varname> for a one 8BUI band raster
+                                               </para>
+                                       </listitem>
+                                       <listitem>
+                                               <para>
+                                                       <varname>pseudocolor</varname> for a four 8BUI (RGBA) band raster
+                                               </para>
+                                       </listitem>
+                               </itemizedlist>
+
+                               <para>
+                                       Users can pass a set of entries (one per line) to <varname>colormap</varname> to specify custom colormaps. Each entry generally consists of five values: the pixel value and corresponding Red, Green, Blue, Alpha components (color components between 0 and 255). Percent values can be used instead of pixel values where 0% and 100% are the minimum and maximum values found in the raster band. Values can be separated with commas (','), tabs, colons (':') and/or spaces. The pixel value can be set to <emphasis>nv</emphasis>, <emphasis>null</emphasis> or <emphasis>nodata</emphasis> for the NODATA value. An example is provided below.
+                               </para>
+
+                               <programlisting>
+5 0 0 0 255
+4 100:50 55 255
+1 150,100 150 255
+0% 255 255 255 255
+nv 0 0 0 0
+                               </programlisting>
+
+                               <para>
+                                       The syntax of <varname>colormap</varname> is similar to that of the color-relief mode of GDAL <ulink url="http://www.gdal.org/gdaldem.html#gdaldem_color_relief">gdaldem</ulink>.
+                               </para>
+
+                               <para>
+                                       Valid keywords for <varname>method</varname>:
+                               </para>
+
+                               <itemizedlist>
+                                       <listitem>
+                                               <para>
+                                                       <varname>INTERPOLATE</varname> to use linear interpolation to smoothly blend the colors between the given pixel values
+                                               </para>
+                                       </listitem>
+                                       <listitem>
+                                               <para>
+                                                       <varname>EXACT</varname> to strictly match only those pixels values found in the colormap. Pixels whose value does not match a colormap entry will be set to 0 0 0 0 (RGBA)
+                                               </para>
+                                       </listitem>
+                                       <listitem>
+                                               <para>
+                                                       <varname>NEAREST</varname> to use the colormap entry whose value is closest to the pixel value
+                                               </para>
+                                       </listitem>
+                               </itemizedlist>
+
+                               <note>
+                                       <para>
+                                               A great reference for colormaps is <ulink url="http://www.colorbrewer2.org">ColorBrewer</ulink>.
+                                       </para>
+                               </note>
+
+                               <para>Availability: 2.1.0 </para>
+                       </refsection>
+
+                       <refsection>
+                               <title>Examples</title>
+                               <para></para>
+                               <programlisting>
+-- needs examples
+                               </programlisting>                       
+                       </refsection>
+
+                       <refsection>
+                               <title>See Also</title>
+                               <para>
+                                       <xref linkend="RT_ST_MapAlgebra" />, 
+                                       <xref linkend="RT_ST_Reclass" />
+                               </para>
+                       </refsection>
+               </refentry>
+
                <refentry id="RT_ST_Union">
                        <refnamediv>
                                <refname>ST_Union</refname>
index b8f09496a68a53e133ac3a1687a687dc32a70010..3f81176928431658d565ab1aca0a9ea8d8cfda41 100644 (file)
@@ -576,6 +576,129 @@ rt_util_same_geotransform_matrix(double *gt1, double *gt2) {
        return TRUE;
 }
 
+/* coordinates in RGB and HSV are floating point values between 0 and 1 */
+rt_errorstate
+rt_util_rgb_to_hsv(double rgb[3], double hsv[3]) {
+       int i;
+
+       double minc;
+       double maxc;
+
+       double h = 0.;
+       double s = 0.;
+       double v = 0.;
+
+       minc = rgb[0];
+       maxc = rgb[0];
+
+       /* get min and max values from RGB */
+       for (i = 1; i < 3; i++) {
+               if (rgb[i] > maxc)
+                       maxc = rgb[i];
+               if (rgb[i] < minc)
+                       minc = rgb[i];
+       }
+       v = maxc;
+
+       if (maxc != minc) {
+               double diff = 0.;
+               double rc = 0.;
+               double gc = 0.;
+               double bc = 0.;
+               double junk = 0.;
+
+               diff = maxc - minc;
+               s = diff / maxc;
+               rc = (maxc - rgb[0]) / diff;
+               gc = (maxc - rgb[1]) / diff;
+               bc = (maxc - rgb[2]) / diff;
+
+               if (DBL_EQ(rgb[0], maxc))
+                       h = bc - gc;
+               else if (DBL_EQ(rgb[1], maxc))
+                       h = 2.0 + rc - bc;
+               else
+                       h = 4.0 + gc - rc;
+
+               h = modf((h / 6.0), &junk);
+       }
+
+       hsv[0] = h;
+       hsv[1] = s;
+       hsv[2] = v;
+
+       return ES_NONE;
+}
+
+/* coordinates in RGB and HSV are floating point values between 0 and 1 */
+rt_errorstate
+rt_util_hsv_to_rgb(double hsv[3], double rgb[3]) {
+       double r = 0;
+       double g = 0;
+       double b = 0;
+       double v = hsv[2];
+
+       if (DBL_EQ(hsv[1], 0.))
+               r = g = b = v;
+       else {
+               double i;
+               double f;
+               double p;
+               double q;
+               double t;
+
+               int a;
+
+               i = floor(hsv[0] * 6.);
+               f = (hsv[0] * 6.0) - i;
+               p = v * (1. - hsv[1]);
+               q = v * (1. - hsv[1] * f);
+               t = v * (1. - hsv[1] * (1. - f));
+
+               a = (int) i;
+               switch (a) {
+                       case 1:
+                               r = q;
+                               g = v;
+                               b = p;
+                               break;
+                       case 2:
+                               r = p;
+                               g = v;
+                               b = t;
+                               break;
+                       case 3:
+                               r = p;
+                               g = q;
+                               b = v;
+                               break;
+                       case 4:
+                               r = t;
+                               g = p;
+                               b = v;
+                               break;
+                       case 5:
+                               r = v;
+                               g = p;
+                               b = q;
+                               break;
+                       case 0:
+                       case 6:
+                       default:
+                               r = v;
+                               g = t;
+                               b = p;
+                               break;
+               }
+       }
+
+       rgb[0] = r;
+       rgb[1] = g;
+       rgb[2] = b;
+
+       return ES_NONE;
+}
+
 /*- rt_context -------------------------------------------------------*/
 
 /* Functions definitions */
@@ -4942,6 +5065,8 @@ rt_band_reclass(
 
        assert(NULL != srcband);
        assert(NULL != exprset && exprcount > 0);
+       RASTER_DEBUGF(4, "exprcount = %d", exprcount);
+       RASTER_DEBUGF(4, "exprset @ %p", exprset);
 
        /* source nodata */
        src_hasnodata = rt_band_get_hasnodata_flag(srcband);
@@ -5161,6 +5286,7 @@ rt_band_reclass(
 
                        /* no expression matched, do not continue */
                        if (!do_nv) continue;
+                       RASTER_DEBUGF(3, "Using exprset[%d] unless NODATA", i);
 
                        /* converting a value from one range to another range
                        OldRange = (OldMax - OldMin)
@@ -5168,7 +5294,7 @@ rt_band_reclass(
                        NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
                        */
 
-                       /* nodata */
+                       /* NODATA */
                        if (hasnodata && isnodata) {
                                nv = nodataval;
                        }
@@ -5217,7 +5343,7 @@ rt_band_reclass(
                                        break;
                        }
 
-                       RASTER_DEBUGF(3, "(%d, %d) ov: %f or: %f - %f nr: %f - %f nv: %f"
+                       RASTER_DEBUGF(4, "(%d, %d) ov: %f or: %f - %f nr: %f - %f nv: %f"
                                , x
                                , y
                                , ov
@@ -14697,3 +14823,585 @@ rt_errorstate rt_raster_get_perimeter(
 
        return ES_NONE;
 }
+
+/******************************************************************************
+* rt_raster_colormap()
+******************************************************************************/
+
+typedef struct _rti_colormap_rgbhsv_t* _rti_colormap_rgbhsv;
+typedef struct _rti_colormap_arg_t* _rti_colormap_arg;
+struct _rti_colormap_rgbhsv_t {
+       double rgb[3];
+       double hsv[3];
+};
+
+struct _rti_colormap_arg_t {
+       rt_raster raster;
+       rt_band band;
+
+       rt_colormap_entry nodataentry;
+       int hasnodata;
+       double nodataval;
+
+       int nexpr;
+       rt_reclassexpr *expr;
+
+       int npos;
+       uint16_t *pos;
+
+       int nrgbhsv;
+       _rti_colormap_rgbhsv rgbhsv;
+
+};
+
+static _rti_colormap_arg
+_rti_colormap_arg_init(rt_raster raster) {
+       _rti_colormap_arg arg = NULL;
+
+       arg = rtalloc(sizeof(struct _rti_colormap_arg_t));
+       if (arg == NULL) {
+               rterror("_rti_colormap_arg_init: Unable to allocate memory for _rti_color_arg");
+               return NULL;
+       }
+
+       arg->band = NULL;
+       arg->nodataentry = NULL;
+       arg->hasnodata = 0;
+       arg->nodataval = 0;
+
+       if (raster == NULL)
+               arg->raster = NULL;
+       /* raster provided */
+       else {
+               arg->raster = rt_raster_clone(raster, 0);
+               if (arg->raster == NULL) {
+                       rterror("_rti_colormap_arg_init: Unable to create output raster");
+                       return NULL;
+               }
+       }
+
+       arg->nexpr = 0;
+       arg->expr = NULL;
+
+       arg->npos = 0;
+       arg->pos = NULL;
+
+       arg->nrgbhsv = 0;
+       arg->rgbhsv = NULL;
+
+       return arg;
+}
+
+static void
+_rti_colormap_arg_destroy(_rti_colormap_arg arg) {
+       int i = 0;
+
+       if (arg->raster != NULL) {
+               rt_band band = NULL;
+
+               for (i = rt_raster_get_num_bands(arg->raster) - 1; i >= 0; i--) {
+                       band = rt_raster_get_band(arg->raster, i);
+                       if (band != NULL)
+                               rt_band_destroy(band);
+               }
+
+               rt_raster_destroy(arg->raster);
+       }
+
+       if (arg->nexpr) {
+               for (i = 0; i < arg->nexpr; i++) {
+                       if (arg->expr[i] != NULL)
+                               rtdealloc(arg->expr[i]);
+               }
+               rtdealloc(arg->expr);
+       }
+
+       if (arg->npos)
+               rtdealloc(arg->pos);
+
+       if (arg->rgbhsv != NULL)
+               rtdealloc(arg->rgbhsv);
+
+       rtdealloc(arg);
+       arg = NULL;
+}
+
+/**
+ * Returns a new raster with up to four 8BUI bands (RGBA) from
+ * applying a colormap to the user-specified band of the
+ * input raster.
+ *
+ * @param raster: input raster
+ * @param nband: 0-based index of the band to process with colormap
+ * @param colormap: rt_colormap object of colormap to apply to band
+ *
+ * @return new raster or NULL on error
+ */
+rt_raster rt_raster_colormap(
+       rt_raster raster, int nband,
+       rt_colormap colormap
+) {
+       _rti_colormap_arg arg = NULL;
+       rt_raster rtnraster = NULL;
+       rt_band band = NULL;
+       int i = 0;
+       int j = 0;
+       int k = 0;
+
+       assert(colormap != NULL);
+
+       /* empty raster */
+       if (rt_raster_is_empty(raster))
+               return NULL;
+
+       /* no colormap entries */
+       if (colormap->nentry < 1) {
+               rterror("rt_raster_colormap: colormap must have at least one entry");
+               return NULL;
+       }
+
+       /* nband is valid */
+       if (!rt_raster_has_band(raster, nband)) {
+               rterror("rt_raster_colormap: raster has no band at index %d", nband);
+               return NULL;
+       }
+
+       band = rt_raster_get_band(raster, nband);
+       if (band == NULL) {
+               rterror("rt_raster_colormap: Unable to get band at index %d", nband);
+               return NULL;
+       }
+
+       /* init internal variables */
+       arg = _rti_colormap_arg_init(raster);
+       if (arg == NULL) {
+               rterror("rt_raster_colormap: Unable to initialize internal variables");
+               return NULL;
+       }
+
+       /* handle NODATA */
+       if (rt_band_get_hasnodata_flag(band)) {
+               arg->hasnodata = 1;
+               rt_band_get_nodata(band, &(arg->nodataval));
+       }
+
+       /* # of colors */
+       if (colormap->ncolor < 1) {
+               rterror("rt_raster_colormap: At least one color must be provided");
+               _rti_colormap_arg_destroy(arg);
+               return NULL;
+       }
+       else if (colormap->ncolor > 4) {
+               rtinfo("More than four colors indicated. Using only the first four colors");
+               colormap->ncolor = 4;
+       }
+
+       /* find non-NODATA entries */
+       arg->npos = 0;
+       arg->pos = rtalloc(sizeof(uint16_t) * colormap->nentry);
+       if (arg->pos == NULL) {
+               rterror("rt_raster_colormap: Unable to allocate memory for valid entries");
+               _rti_colormap_arg_destroy(arg);
+               return NULL;
+       }
+       for (i = 0, j = 0; i < colormap->nentry; i++) {
+               /* special handling for NODATA entries */
+               if (colormap->entry[i].isnodata) {
+                       /* first NODATA entry found, use it */
+                       if (arg->nodataentry == NULL)
+                               arg->nodataentry = &(colormap->entry[i]);
+                       else
+                               rtwarn("More than one colormap entry found for NODATA value. Only using first NOTDATA entry");
+
+                       continue;
+               }
+
+               (arg->npos)++;
+               arg->pos[j++] = i;
+       }
+
+       /* INTERPOLATE and only one non-NODATA entry */
+       if (colormap->method == CM_INTERPOLATE && arg->npos < 2) {
+               rtinfo("Method INTERPOLATE requires at least two non-NODATA colormap entries. Using NEAREST instead");
+               colormap->method = CM_NEAREST;
+       }
+
+       /* NODATA entry but band has no NODATA value */
+       if (!arg->hasnodata && arg->nodataentry != NULL) {
+               rtinfo("Band at index %d has no NODATA value. Ignoring NODATA entry", nband);
+               arg->nodataentry = NULL;
+       }
+
+       /* allocate expr */
+       arg->nexpr = arg->npos;
+
+       /* INTERPOLATE needs one less than the number of entries */
+       if (colormap->method == CM_INTERPOLATE)
+               arg->nexpr -= 1;
+       /* EXACT requires a no matching expression */
+       else if (colormap->method == CM_EXACT)
+               arg->nexpr += 1;
+
+       /* NODATA entry exists, add expression */
+       if (arg->nodataentry != NULL)
+               arg->nexpr += 1;
+       arg->expr = rtalloc(sizeof(rt_reclassexpr) * arg->nexpr);
+       if (arg->expr == NULL) {
+               rterror("rt_raster_colormap: Unable to allocate memory for reclass expressions");
+               _rti_colormap_arg_destroy(arg);
+               return NULL;
+       }
+       RASTER_DEBUGF(4, "nexpr = %d", arg->nexpr);
+       RASTER_DEBUGF(4, "expr @ %p", arg->expr);
+
+       for (i = 0; i < arg->nexpr; i++) {
+               arg->expr[i] = rtalloc(sizeof(struct rt_reclassexpr_t));
+               if (arg->expr[i] == NULL) {
+                       rterror("rt_raster_colormap: Unable to allocate memory for reclass expression");
+                       _rti_colormap_arg_destroy(arg);
+                       return NULL;
+               }
+       }
+
+       /* rgb to hsv */
+       if (colormap->method == CM_INTERPOLATE && colormap->ncolor > 2) {
+               arg->nrgbhsv = arg->npos;
+               if (arg->nodataentry != NULL)
+                       arg->nrgbhsv += 1;
+
+               /* allocate space */
+               arg->rgbhsv = rtalloc(sizeof(struct _rti_colormap_rgbhsv_t) * arg->nrgbhsv);
+               if (arg->rgbhsv == NULL) {
+                       rterror("rt_raster_colormap: Unable to allocate memory for RGB to HSV conversion");
+                       _rti_colormap_arg_destroy(arg);
+                       return NULL;
+               }
+               memset(arg->rgbhsv, 0, sizeof(struct _rti_colormap_rgbhsv_t) * arg->nrgbhsv);
+
+               for (i = 0; i < arg->nrgbhsv; i++) {
+                       /* convert colormap's 0 - 255 to 0 - 1 */
+                       for (j = 0; j < 3; j++) {
+                               if (i < arg->npos)
+                                       arg->rgbhsv[i].rgb[j] = ((double) colormap->entry[arg->pos[i]].color[j]) / 255.;
+                               else
+                                       arg->rgbhsv[i].rgb[j] = ((double) arg->nodataentry->color[j]) / 255.;
+                       }
+                       
+                       /* convert to hsv */
+                       rt_util_rgb_to_hsv(arg->rgbhsv[i].rgb, arg->rgbhsv[i].hsv);
+               }
+       }
+
+       /* reclassify bands */
+       /* by # of colors */
+       for (i = 0; i < colormap->ncolor; i++) {
+               k = 0;
+
+               /* handle NODATA entry first */
+               if (arg->nodataentry != NULL) {
+                       arg->expr[k]->src.min = arg->nodataentry->value;
+                       arg->expr[k]->src.max = arg->nodataentry->value;
+                       arg->expr[k]->src.inc_min = 1;
+                       arg->expr[k]->src.inc_max = 1;
+                       arg->expr[k]->src.exc_min = 0;
+                       arg->expr[k]->src.exc_max = 0;
+
+                       /* use HSV */
+                       if (arg->nrgbhsv && i < 3) {
+                               arg->expr[k]->dst.min = arg->rgbhsv[arg->nrgbhsv - 1].hsv[i];
+                               arg->expr[k]->dst.max = arg->rgbhsv[arg->nrgbhsv - 1].hsv[i];
+                       }
+                       /* use RGB */
+                       else {
+                               arg->expr[k]->dst.min = arg->nodataentry->color[i];
+                               arg->expr[k]->dst.max = arg->nodataentry->color[i];
+                       }
+
+                       arg->expr[k]->dst.inc_min = 1;
+                       arg->expr[k]->dst.inc_max = 1;
+                       arg->expr[k]->dst.exc_min = 0;
+                       arg->expr[k]->dst.exc_max = 0;
+
+                       RASTER_DEBUGF(4, "NODATA expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)",
+                               k,
+                               arg->expr[k]->src.min,
+                               arg->expr[k]->src.max,
+                               arg->expr[k]->src.inc_min,
+                               arg->expr[k]->src.inc_max,
+                               arg->expr[k]->src.exc_min,
+                               arg->expr[k]->src.exc_max
+                       );
+                       RASTER_DEBUGF(4, "NODATA expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)",
+                               k,
+                               arg->expr[k]->dst.min,
+                               arg->expr[k]->dst.max,
+                               arg->expr[k]->dst.inc_min,
+                               arg->expr[k]->dst.inc_max,
+                               arg->expr[k]->dst.exc_min,
+                               arg->expr[k]->dst.exc_max
+                       );
+
+                       k++;
+               }
+
+               /* by non-NODATA entry */
+               for (j = 0; j < arg->npos; j++) {
+                       if (colormap->method == CM_INTERPOLATE) {
+                               if (j == arg->npos - 1)
+                                       continue;
+
+                               arg->expr[k]->src.min = colormap->entry[arg->pos[j + 1]].value;
+                               arg->expr[k]->src.inc_min = 1;
+                               arg->expr[k]->src.exc_min = 0;
+
+                               arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value;
+                               arg->expr[k]->src.inc_max = 1;
+                               arg->expr[k]->src.exc_max = 0;
+
+                               /* use HSV */
+                               if (arg->nrgbhsv && i < 3) {
+                                       arg->expr[k]->dst.min = arg->rgbhsv[j + 1].hsv[i];
+                                       arg->expr[k]->dst.max = arg->rgbhsv[j].hsv[i];
+                               }
+                               /* use RGB */
+                               else {
+                                       arg->expr[k]->dst.min = colormap->entry[arg->pos[j + 1]].color[i];
+                                       arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i];
+                               }
+
+                               arg->expr[k]->dst.inc_min = 1;
+                               arg->expr[k]->dst.exc_min = 0;
+
+                               arg->expr[k]->dst.inc_max = 1;
+                               arg->expr[k]->dst.exc_max = 0;
+                       }
+                       else if (colormap->method == CM_NEAREST) {
+
+                               /* NOT last entry */
+                               if (j != arg->npos - 1) {
+                                       arg->expr[k]->src.min = ((colormap->entry[arg->pos[j]].value - colormap->entry[arg->pos[j + 1]].value) / 2.) + colormap->entry[arg->pos[j + 1]].value;
+                                       arg->expr[k]->src.inc_min = 0;
+                                       arg->expr[k]->src.exc_min = 0;
+                               }
+                               /* last entry */
+                               else {
+                                       arg->expr[k]->src.min = colormap->entry[arg->pos[j]].value;
+                                       arg->expr[k]->src.inc_min = 1;
+                                       arg->expr[k]->src.exc_min = 1;
+                               }
+
+                               /* NOT first entry */
+                               if (j > 0) {
+                                       arg->expr[k]->src.max = arg->expr[k - 1]->src.min;
+                                       arg->expr[k]->src.inc_max = 1;
+                                       arg->expr[k]->src.exc_max = 0;
+                               }
+                               /* first entry */
+                               else {
+                                       arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value;
+                                       arg->expr[k]->src.inc_max = 1;
+                                       arg->expr[k]->src.exc_max = 1;
+                               }
+
+                               arg->expr[k]->dst.min = colormap->entry[arg->pos[j]].color[i];
+                               arg->expr[k]->dst.inc_min = 1;
+                               arg->expr[k]->dst.exc_min = 0;
+
+                               arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i];
+                               arg->expr[k]->dst.inc_max = 1;
+                               arg->expr[k]->dst.exc_max = 0;
+                       }
+                       else if (colormap->method == CM_EXACT) {
+                               arg->expr[k]->src.min = colormap->entry[arg->pos[j]].value;
+                               arg->expr[k]->src.inc_min = 1;
+                               arg->expr[k]->src.exc_min = 0;
+
+                               arg->expr[k]->src.max = colormap->entry[arg->pos[j]].value;
+                               arg->expr[k]->src.inc_max = 1;
+                               arg->expr[k]->src.exc_max = 0;
+
+                               arg->expr[k]->dst.min = colormap->entry[arg->pos[j]].color[i];
+                               arg->expr[k]->dst.inc_min = 1;
+                               arg->expr[k]->dst.exc_min = 0;
+
+                               arg->expr[k]->dst.max = colormap->entry[arg->pos[j]].color[i];
+                               arg->expr[k]->dst.inc_max = 1;
+                               arg->expr[k]->dst.exc_max = 0;
+                       }
+
+                       RASTER_DEBUGF(4, "expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)",
+                               k,
+                               arg->expr[k]->src.min,
+                               arg->expr[k]->src.max,
+                               arg->expr[k]->src.inc_min,
+                               arg->expr[k]->src.inc_max,
+                               arg->expr[k]->src.exc_min,
+                               arg->expr[k]->src.exc_max
+                       );
+
+                       RASTER_DEBUGF(4, "expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)",
+                               k,
+                               arg->expr[k]->dst.min,
+                               arg->expr[k]->dst.max,
+                               arg->expr[k]->dst.inc_min,
+                               arg->expr[k]->dst.inc_max,
+                               arg->expr[k]->dst.exc_min,
+                               arg->expr[k]->dst.exc_max
+                       );
+
+                       k++;
+               }
+
+               /* EXACT has one last expression for catching all uncaught values */
+               if (colormap->method == CM_EXACT) {
+                       arg->expr[k]->src.min = 0;
+                       arg->expr[k]->src.inc_min = 1;
+                       arg->expr[k]->src.exc_min = 1;
+
+                       arg->expr[k]->src.max = 0;
+                       arg->expr[k]->src.inc_max = 1;
+                       arg->expr[k]->src.exc_max = 1;
+
+                       arg->expr[k]->dst.min = 0;
+                       arg->expr[k]->dst.inc_min = 1;
+                       arg->expr[k]->dst.exc_min = 0;
+
+                       arg->expr[k]->dst.max = 0;
+                       arg->expr[k]->dst.inc_max = 1;
+                       arg->expr[k]->dst.exc_max = 0;
+
+                       RASTER_DEBUGF(4, "expr[%d]->src (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)",
+                               k,
+                               arg->expr[k]->src.min,
+                               arg->expr[k]->src.max,
+                               arg->expr[k]->src.inc_min,
+                               arg->expr[k]->src.inc_max,
+                               arg->expr[k]->src.exc_min,
+                               arg->expr[k]->src.exc_max
+                       );
+
+                       RASTER_DEBUGF(4, "expr[%d]->dst (min, max, in, ix, en, ex) = (%f, %f, %d, %d, %d, %d)",
+                               k,
+                               arg->expr[k]->dst.min,
+                               arg->expr[k]->dst.max,
+                               arg->expr[k]->dst.inc_min,
+                               arg->expr[k]->dst.inc_max,
+                               arg->expr[k]->dst.exc_min,
+                               arg->expr[k]->dst.exc_max
+                       );
+
+                       k++;
+               }
+
+               /* call rt_band_reclass */
+               if (arg->nrgbhsv)
+                       arg->band = rt_band_reclass(band, PT_32BF, 0, 0, arg->expr, arg->nexpr);
+               else
+                       arg->band = rt_band_reclass(band, PT_8BUI, 0, 0, arg->expr, arg->nexpr);
+               if (arg->band == NULL) {
+                       rterror("rt_raster_colormap: Unable to reclassify band");
+                       _rti_colormap_arg_destroy(arg);
+                       return NULL;
+               }
+
+               /* add reclassified band to raster */
+               if (rt_raster_add_band(arg->raster, arg->band, rt_raster_get_num_bands(arg->raster)) < 0) {
+                       rterror("rt_raster_colormap: Unable to add reclassified band to output raster");
+                       _rti_colormap_arg_destroy(arg);
+                       return NULL;
+               }
+       }
+
+       /* convert HSV back to RGB 0 - 255 */
+       if (arg->nrgbhsv) {
+               int width = rt_raster_get_width(arg->raster);
+               int height = rt_raster_get_height(arg->raster);
+               rt_band _band = NULL;
+               rt_band band[6];
+               struct _rti_colormap_rgbhsv_t hsvrgb;
+
+               /* get band objects */
+               for (i = 0; i < 6; i++) {
+                       /* existing band */
+                       if (i < 3)
+                               band[i] = rt_raster_get_band(arg->raster, i);
+                       /* new band */
+                       else {
+                               void *mem =rtalloc(rt_pixtype_size(PT_8BUI) * width * height);
+                               if (mem == NULL) {
+                                       rterror("rt_raster_colormap: Unable to allocate memory for new band");
+                                       for (j = 3; j < i; j++)
+                                               rt_band_destroy(band[j]);
+                                       _rti_colormap_arg_destroy(arg);
+                                       return NULL;
+                               }
+                               memset(mem, 0, rt_pixtype_size(PT_8BUI) * width * height);
+
+                               band[i] = rt_band_new_inline(
+                                       width, height,
+                                       PT_8BUI,
+                                       0, 0,
+                                       mem
+                               );
+                               if (band[i] == NULL) {
+                                       rterror("rt_raster_colormap: Unable to create new band");
+                                       rtdealloc(mem);
+                                       for (j = 3; j < i; j++)
+                                               rt_band_destroy(band[j]);
+                                       _rti_colormap_arg_destroy(arg);
+                                       return NULL;
+                               }
+                               rt_band_set_ownsdata_flag(band[i], 1);
+                       }
+               }
+
+               /* process each pixel */
+               for (i = 0; i < height; i++) {
+                       for (j = 0; j < width; j++) {
+                               /* get HSV components */
+                               for (k = 0; k < 3; k++) {
+                                       if (rt_band_get_pixel(band[k], j, i, &(hsvrgb.hsv[k]), NULL) != ES_NONE) {
+                                               rterror("rt_raster_colormap: Unable to process HSV values from bands");
+                                               for (i = 3; i < 6; i++)
+                                                       rt_band_destroy(band[j]);
+                                               _rti_colormap_arg_destroy(arg);
+                                               return NULL;
+                                       }
+                               }
+
+                               /* convert HSV to RGB 0 - 1 */
+                               rt_util_hsv_to_rgb(hsvrgb.hsv, hsvrgb.rgb);
+
+                               /* convert RGB 0 - 1 to 0 - 255 and burn */
+                               for (k = 0; k < 3; k++) {
+                                       if (rt_band_set_pixel(band[k + 3], j, i, ROUND(hsvrgb.rgb[k] * 255., 0), NULL) != ES_NONE) {
+                                               rterror("rt_raster_colormap: Unable to set RGB values to bands");
+                                               for (i = 3; i < 6; i++)
+                                                       rt_band_destroy(band[j]);
+                                               _rti_colormap_arg_destroy(arg);
+                                               return NULL;
+                                       }
+                               }
+                       }
+               }
+
+               /* replace bands */
+               for (i = 0; i < 3; i++) {
+                       _band = rt_raster_replace_band(arg->raster, band[i + 3], i);
+                       if (_band == NULL) {
+                               rterror("rt_raster_colormap: Unable to replace HSV band with RGB band");
+                               for (j = i + 3; j < 6; j++)
+                                       rt_band_destroy(band[j]);
+                               _rti_colormap_arg_destroy(arg);
+                               return NULL;
+                       }
+                       rt_band_destroy(_band);
+               }
+       }
+
+       rtnraster = arg->raster;
+       arg->raster = NULL;
+       _rti_colormap_arg_destroy(arg);
+
+       return rtnraster;
+}
index 59b729517e60903c947518e3377fb3af05df2063..1e937e18f06995dfad56324b8d2a16b49feb6bb1 100644 (file)
@@ -145,6 +145,9 @@ typedef struct rt_reclassexpr_t* rt_reclassexpr;
 typedef struct rt_iterator_t* rt_iterator;
 typedef struct rt_iterator_arg_t* rt_iterator_arg;
 
+typedef struct rt_colormap_entry_t* rt_colormap_entry;
+typedef struct rt_colormap_t* rt_colormap;
+
 /* envelope information */
 typedef struct {
        double MinX;
@@ -1955,6 +1958,22 @@ rt_raster_iterator(
        rt_raster *rtnraster
 );
 
+/**
+ * Returns a new raster with up to four 8BUI bands (RGBA) from
+ * applying a colormap to the user-specified band of the
+ * input raster.
+ *
+ * @param raster: input raster
+ * @param nband: 0-based index of the band to process with colormap
+ * @param colormap: rt_colormap object of colormap to apply to band
+ *
+ * @return new raster or NULL on error
+ */
+rt_raster rt_raster_colormap(
+       rt_raster raster, int nband,
+       rt_colormap colormap
+);
+
 /*- utilities -------------------------------------------------------*/
 
 /*
@@ -2122,6 +2141,20 @@ rt_util_same_geotransform_matrix(
        double *gt2
 );
 
+/* coordinates in RGB and HSV are floating point values between 0 and 1 */
+rt_errorstate
+rt_util_rgb_to_hsv(
+       double rgb[3],
+       double hsv[3]
+);
+
+/* coordinates in RGB and HSV are floating point values between 0 and 1 */
+rt_errorstate
+rt_util_hsv_to_rgb(
+       double hsv[3],
+       double rgb[3]
+);
+
 /*
        helper macros for consistent floating point equality checks
 */
@@ -2362,4 +2395,23 @@ struct rt_gdaldriver_t {
        char *create_options;
 };
 
+/* raster colormap entry */
+struct rt_colormap_entry_t {
+       int isnodata;
+       double value;
+       uint8_t color[4]; /* RGBA */
+};
+
+struct rt_colormap_t {
+       enum {
+               CM_INTERPOLATE,
+               CM_EXACT,
+               CM_NEAREST
+       } method;
+
+       int ncolor;
+       uint16_t nentry;
+       rt_colormap_entry entry;
+};
+
 #endif /* RT_API_H_INCLUDED */
index ed353a66fe242705ce2f24ee72cd91e9d277e098..fea5ee9282fcbebc283d6fcd6dd4dff55a048980 100644 (file)
@@ -4,7 +4,7 @@
  * WKTRaster - Raster Types for PostGIS
  * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage
  *
- * Copyright (C) 2011-2012 Regents of the University of California
+ * Copyright (C) 2011-2013 Regents of the University of California
  *   <bkpark@ucdavis.edu>
  * Copyright (C) 2010-2011 Jorge Arevalo <jorge.arevalo@deimos-space.com>
  * Copyright (C) 2010-2011 David Zwarg <dzwarg@azavea.com>
@@ -311,6 +311,9 @@ Datum RASTER_valueCountCoverage(PG_FUNCTION_ARGS);
 /* reclassify specified bands of a raster */
 Datum RASTER_reclass(PG_FUNCTION_ARGS);
 
+/* apply colormap to specified band of a raster */
+Datum RASTER_colorMap(PG_FUNCTION_ARGS);
+
 /* convert GDAL raster to raster */
 Datum RASTER_fromGDALRaster(PG_FUNCTION_ARGS);
 
@@ -388,9 +391,6 @@ Datum RASTER_union_finalfn(PG_FUNCTION_ARGS);
 /* raster clip */
 Datum RASTER_clip(PG_FUNCTION_ARGS);
 
-/* raster perimeter */
-Datum RASTER_perimeter(PG_FUNCTION_ARGS);
-
 /* string replacement function taken from
  * http://ubuntuforums.org/showthread.php?s=aa6f015109fd7e4c7e30d2fd8b717497&t=141670&page=3
  */
@@ -585,6 +585,7 @@ rtpg_trim(const char *input) {
        char *rtn;
        char *ptr;
        uint32_t offset = 0;
+       int inputlen = 0;
 
        if (!input)
                return NULL;
@@ -592,21 +593,24 @@ rtpg_trim(const char *input) {
                return (char *) input;
 
        /* trim left */
-       while (isspace(*input))
+       while (isspace(*input) && *input != '\0')
                input++;
 
        /* trim right */
-       ptr = ((char *) input) + strlen(input);
-       while (isspace(*--ptr))
-               offset++;
+       inputlen = strlen(input);
+       if (inputlen) {
+               ptr = ((char *) input) + inputlen;
+               while (isspace(*--ptr))
+                       offset++;
+       }
 
-       rtn = palloc(sizeof(char) * (strlen(input) - offset + 1));
+       rtn = palloc(sizeof(char) * (inputlen - offset + 1));
        if (rtn == NULL) {
                fprintf(stderr, "Not enough memory\n");
                return NULL;
        }
-       strncpy(rtn, input, strlen(input) - offset);
-       rtn[strlen(input) - offset] = '\0';
+       strncpy(rtn, input, inputlen - offset);
+       rtn[inputlen - offset] = '\0';
 
        return rtn;
 }
@@ -10741,9 +10745,444 @@ Datum RASTER_reclass(PG_FUNCTION_ARGS) {
        PG_RETURN_POINTER(pgrtn);
 }
 
-/**
- * Returns raster from GDAL raster;
- */
+/* ---------------------------------------------------------------- */
+/* apply colormap to specified band of a raster                     */
+/* ---------------------------------------------------------------- */
+
+typedef struct rtpg_colormap_arg_t *rtpg_colormap_arg;
+struct rtpg_colormap_arg_t {
+       rt_raster raster;
+       int nband; /* 1-based */
+       rt_band band;
+       rt_bandstats bandstats;
+
+       rt_colormap colormap;
+       int nodataentry;
+
+       char **entry;
+       int nentry;
+       char **element;
+       int nelement;
+};
+
+static rtpg_colormap_arg
+rtpg_colormap_arg_init() {
+       rtpg_colormap_arg arg = NULL;
+
+       arg = palloc(sizeof(struct rtpg_colormap_arg_t));
+       if (arg == NULL) {
+               elog(ERROR, "rtpg_colormap_arg: Unable to allocate memory for function arguments");
+               return NULL;
+       }
+
+       arg->raster = NULL;
+       arg->nband = 1;
+       arg->band = NULL;
+       arg->bandstats = NULL;
+
+       arg->colormap = palloc(sizeof(struct rt_colormap_t));
+       if (arg->colormap == NULL) {
+               elog(ERROR, "rtpg_colormap_arg: Unable to allocate memory for function arguments");
+               return NULL;
+       }
+       arg->colormap->nentry = 0;
+       arg->colormap->entry = NULL;
+       arg->colormap->ncolor = 4; /* assume RGBA */
+       arg->colormap->method = CM_INTERPOLATE;
+       arg->nodataentry = -1;
+
+       arg->entry = NULL;
+       arg->nentry = 0;
+       arg->element = NULL;
+       arg->nelement = 0;
+
+       return arg;
+}
+
+static void
+rtpg_colormap_arg_destroy(rtpg_colormap_arg arg) {
+       int i = 0;
+       if (arg->raster != NULL)
+               rt_raster_destroy(arg->raster);
+
+       if (arg->bandstats != NULL)
+               pfree(arg->bandstats);
+
+       if (arg->colormap != NULL) {
+               if (arg->colormap->entry != NULL)
+                       pfree(arg->colormap->entry);
+               pfree(arg->colormap);
+       }
+
+       if (arg->nentry) {
+               for (i = 0; i < arg->nentry; i++) {
+                       if (arg->entry[i] != NULL)
+                               pfree(arg->entry[i]);
+               }
+               pfree(arg->entry);
+       }
+
+       if (arg->nelement) {
+               for (i = 0; i < arg->nelement; i++)
+                       pfree(arg->element[i]);
+               pfree(arg->element);
+       }
+
+       pfree(arg);
+       arg = NULL;
+}
+
+PG_FUNCTION_INFO_V1(RASTER_colorMap);
+Datum RASTER_colorMap(PG_FUNCTION_ARGS)
+{
+       rt_pgraster *pgraster = NULL;
+       rtpg_colormap_arg arg = NULL;
+       char *junk = NULL;
+       rt_raster raster = NULL;
+
+       POSTGIS_RT_DEBUG(3, "RASTER_colorMap: Starting");
+
+       /* pgraster is NULL, return NULL */
+       if (PG_ARGISNULL(0))
+               PG_RETURN_NULL();
+
+       /* init arg */
+       arg = rtpg_colormap_arg_init();
+       if (arg == NULL) {
+               elog(ERROR, "RASTER_colorMap: Unable to initialize argument structure");
+               PG_RETURN_NULL();
+       }
+
+       /* raster (0) */
+       pgraster = (rt_pgraster *) PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+
+       /* raster object */
+       arg->raster = rt_raster_deserialize(pgraster, FALSE);
+       if (!arg->raster) {
+               elog(ERROR, "RASTER_colorMap: Could not deserialize raster");
+               rtpg_colormap_arg_destroy(arg);
+               PG_FREE_IF_COPY(pgraster, 0);
+               PG_RETURN_NULL();
+       }
+
+       /* nband (1) */
+       if (!PG_ARGISNULL(1))
+               arg->nband = PG_GETARG_INT32(1);
+       POSTGIS_RT_DEBUGF(4, "nband = %d", arg->nband);
+
+       /* check that band works */
+       if (!rt_raster_has_band(arg->raster, arg->nband - 1)) {
+               elog(NOTICE, "Raster does not have band at index %d. Returning empty raster", arg->nband);
+
+               raster = rt_raster_clone(arg->raster, 0);
+               if (raster == NULL) {
+                       elog(ERROR, "RASTER_colorMap: Unable to create empty raster");
+                       rtpg_colormap_arg_destroy(arg);
+                       PG_FREE_IF_COPY(pgraster, 0);
+                       PG_RETURN_NULL();
+               }
+
+               rtpg_colormap_arg_destroy(arg);
+               PG_FREE_IF_COPY(pgraster, 0);
+
+               pgraster = rt_raster_serialize(raster);
+               rt_raster_destroy(raster);
+               if (pgraster == NULL)
+                       PG_RETURN_NULL();
+
+               SET_VARSIZE(pgraster, ((rt_pgraster*) pgraster)->size);
+               PG_RETURN_POINTER(pgraster);
+       }
+
+       /* get band */
+       arg->band = rt_raster_get_band(arg->raster, arg->nband - 1);
+       if (arg->band == NULL) {
+               elog(ERROR, "RASTER_colorMap: Unable to get band at index %d", arg->nband);
+               rtpg_colormap_arg_destroy(arg);
+               PG_FREE_IF_COPY(pgraster, 0);
+               PG_RETURN_NULL();
+       }
+
+       /* method (3) */
+       if (!PG_ARGISNULL(3)) {
+               char *method = NULL;
+               char *tmp = text_to_cstring(PG_GETARG_TEXT_P(3));
+               POSTGIS_RT_DEBUGF(4, "raw method = %s", tmp);
+
+               method = rtpg_trim(tmp);
+               pfree(tmp);
+               method = rtpg_strtoupper(method);
+
+               if (strcmp(method, "INTERPOLATE") == 0)
+                       arg->colormap->method = CM_INTERPOLATE;
+               else if (strcmp(method, "EXACT") == 0)
+                       arg->colormap->method = CM_EXACT;
+               else if (strcmp(method, "NEAREST") == 0)
+                       arg->colormap->method = CM_NEAREST;
+               else {
+                       elog(NOTICE, "Unknown value provided for method. Defaulting to INTERPOLATE");
+                       arg->colormap->method = CM_INTERPOLATE;
+               }
+       }
+       /* default to INTERPOLATE */
+       else
+               arg->colormap->method = CM_INTERPOLATE;
+       POSTGIS_RT_DEBUGF(4, "method = %d", arg->colormap->method);
+
+       /* colormap (2) */
+       if (PG_ARGISNULL(2)) {
+               elog(ERROR, "RASTER_colorMap: Value must be provided for colormap");
+               rtpg_colormap_arg_destroy(arg);
+               PG_FREE_IF_COPY(pgraster, 0);
+               PG_RETURN_NULL();
+       }
+       else {
+               char *tmp = NULL;
+               char *colormap = text_to_cstring(PG_GETARG_TEXT_P(2));
+               char *_entry;
+               char *_element;
+               int i = 0;
+               int j = 0;
+
+               POSTGIS_RT_DEBUGF(4, "colormap = %s", colormap);
+
+               /* empty string */
+               if (!strlen(colormap)) {
+                       elog(ERROR, "RASTER_colorMap: Value must be provided for colormap");
+                       rtpg_colormap_arg_destroy(arg);
+                       PG_FREE_IF_COPY(pgraster, 0);
+                       PG_RETURN_NULL();
+               }
+
+               arg->entry = rtpg_strsplit(colormap, "\n", &(arg->nentry));
+               pfree(colormap);
+               if (arg->nentry < 1) {
+                       elog(ERROR, "RASTER_colorMap: Unable to process the value provided for colormap");
+                       rtpg_colormap_arg_destroy(arg);
+                       PG_FREE_IF_COPY(pgraster, 0);
+                       PG_RETURN_NULL();
+               }
+
+               /* allocate the max # of colormap entries */
+               arg->colormap->entry = palloc(sizeof(struct rt_colormap_entry_t) * arg->nentry);
+               if (arg->colormap->entry == NULL) {
+                       elog(ERROR, "RASTER_colorMap: Unable to allocate memory for colormap entries");
+                       rtpg_colormap_arg_destroy(arg);
+                       PG_FREE_IF_COPY(pgraster, 0);
+                       PG_RETURN_NULL();
+               }
+               memset(arg->colormap->entry, 0, sizeof(struct rt_colormap_entry_t) * arg->nentry);
+
+               /* each entry */
+               for (i = 0; i < arg->nentry; i++) {
+                       /* substitute space for other delimiters */
+                       tmp = rtpg_strreplace(arg->entry[i], ":", " ", NULL);
+                       _entry = rtpg_strreplace(tmp, ",", " ", NULL);
+                       pfree(tmp);
+                       tmp = rtpg_strreplace(_entry, "\t", " ", NULL);
+                       pfree(_entry);
+                       _entry = rtpg_trim(tmp);
+                       pfree(tmp);
+
+                       POSTGIS_RT_DEBUGF(4, "Processing entry[%d] = %s", i, arg->entry[i]);
+                       POSTGIS_RT_DEBUGF(4, "Cleaned entry[%d] = %s", i, _entry);
+
+                       /* empty entry, continue */
+                       if (!strlen(_entry)) {
+                               pfree(_entry);
+                               continue;
+                       }
+
+                       arg->element = rtpg_strsplit(_entry, " ", &(arg->nelement));
+                       pfree(_entry);
+                       if (arg->nelement < 2) {
+                               elog(ERROR, "RASTER_colorMap: Unable to process colormap entry %d", i + 1);
+                               rtpg_colormap_arg_destroy(arg);
+                               PG_FREE_IF_COPY(pgraster, 0);
+                               PG_RETURN_NULL();
+                       }
+                       else if (arg->nelement > 5) {
+                               elog(NOTICE, "More than five elements in colormap entry %d. Using at most five elements", i + 1);
+                               arg->nelement = 5;
+                       }
+
+                       /* smallest # of colors */
+                       if ((arg->nelement - 1) < arg->colormap->ncolor)
+                               arg->colormap->ncolor = arg->nelement - 1;
+
+                       /* each element of entry */
+                       for (j = 0; j < arg->nelement; j++) {
+
+                               _element = rtpg_trim(arg->element[j]);
+                               _element = rtpg_strtoupper(_element);
+                               POSTGIS_RT_DEBUGF(4, "Processing entry[%d][%d] = %s", i, j, arg->element[j]);
+                               POSTGIS_RT_DEBUGF(4, "Cleaned entry[%d][%d] = %s", i, j, _element);
+
+                               /* first element is ALWAYS a band value, percentage OR "nv" string */
+                               if (j == 0) {
+                                       char *percent = NULL;
+
+                                       /* NODATA */
+                                       if (
+                                               strcmp(_element, "NV") == 0 ||
+                                               strcmp(_element, "NULL") == 0 ||
+                                               strcmp(_element, "NODATA") == 0
+                                       ) {
+                                               POSTGIS_RT_DEBUG(4, "Processing NODATA string");
+
+                                               if (arg->nodataentry > -1) {
+                                                       elog(NOTICE, "More than one NODATA entry found. Using only the first one");
+                                               }
+                                               else {
+                                                       arg->colormap->entry[arg->colormap->nentry].isnodata = 1;
+                                                       /* no need to set value as value comes from band's NODATA */
+                                                       arg->colormap->entry[arg->colormap->nentry].value = 0;
+                                               }
+                                       }
+                                       /* percent value */
+                                       else if ((percent = strchr(_element, '%')) != NULL) {
+                                               double value;
+                                               POSTGIS_RT_DEBUG(4, "Processing percent string");
+
+                                               /* get the band stats */
+                                               if (arg->bandstats == NULL) {
+                                                       POSTGIS_RT_DEBUG(4, "Getting band stats");
+                                                       
+                                                       arg->bandstats = rt_band_get_summary_stats(arg->band, 1, 1, 0, NULL, NULL, NULL);
+                                                       if (arg->bandstats == NULL) {
+                                                               elog(ERROR, "RASTER_colorMap: Unable to get band's summary stats to process percentages");
+                                                               pfree(_element);
+                                                               rtpg_colormap_arg_destroy(arg);
+                                                               PG_FREE_IF_COPY(pgraster, 0);
+                                                               PG_RETURN_NULL();
+                                                       }
+                                               }
+
+                                               /* get the string before the percent char */
+                                               tmp = palloc(sizeof(char) * (percent - _element + 1));
+                                               if (tmp == NULL) {
+                                                       elog(ERROR, "RASTER_colorMap: Unable to allocate memory for value of percentage");
+                                                       pfree(_element);
+                                                       rtpg_colormap_arg_destroy(arg);
+                                                       PG_FREE_IF_COPY(pgraster, 0);
+                                                       PG_RETURN_NULL();
+                                               }
+
+                                               memcpy(tmp, _element, percent - _element);
+                                               tmp[percent - _element] = '\0';
+                                               POSTGIS_RT_DEBUGF(4, "Percent value = %s", tmp);
+
+                                               /* get percentage value */
+                                               errno = 0;
+                                               value = strtod(tmp, NULL);
+                                               pfree(tmp);
+                                               if (errno != 0 || _element == junk) {
+                                                       elog(ERROR, "RASTER_colorMap: Unable to process percent string to value");
+                                                       pfree(_element);
+                                                       rtpg_colormap_arg_destroy(arg);
+                                                       PG_FREE_IF_COPY(pgraster, 0);
+                                                       PG_RETURN_NULL();
+                                               }
+
+                                               /* check percentage */
+                                               if (value < 0.) {
+                                                       elog(NOTICE, "Percentage values cannot be less than zero. Defaulting to zero");
+                                                       value = 0.;
+                                               }
+                                               else if (value > 100.) {
+                                                       elog(NOTICE, "Percentage values cannot be greater than 100. Defaulting to 100");
+                                                       value = 100.;
+                                               }
+
+                                               /* get the true pixel value */
+                                               /* TODO: should the percentage be quantile based? */
+                                               arg->colormap->entry[arg->colormap->nentry].value = ((value / 100.) * (arg->bandstats->max - arg->bandstats->min)) + arg->bandstats->min;
+                                       }
+                                       /* straight value */
+                                       else {
+                                               errno = 0;
+                                               arg->colormap->entry[arg->colormap->nentry].value = strtod(_element, &junk);
+                                               if (errno != 0 || _element == junk) {
+                                                       elog(ERROR, "RASTER_colorMap: Unable to process string to value");
+                                                       pfree(_element);
+                                                       rtpg_colormap_arg_destroy(arg);
+                                                       PG_FREE_IF_COPY(pgraster, 0);
+                                                       PG_RETURN_NULL();
+                                               }
+                                       }
+
+                               }
+                               /* RGB values (0 - 255) */
+                               else {
+                                       int value = 0;
+
+                                       errno = 0;
+                                       value = (int) strtod(_element, &junk);
+                                       if (errno != 0 || _element == junk) {
+                                               elog(ERROR, "RASTER_colorMap: Unable to process string to value");
+                                               pfree(_element);
+                                               rtpg_colormap_arg_destroy(arg);
+                                               PG_FREE_IF_COPY(pgraster, 0);
+                                               PG_RETURN_NULL();
+                                       }
+
+                                       if (value > 255) {
+                                               elog(NOTICE, "RGBA value cannot be greater than 255. Defaulting to 255");
+                                               value = 255;
+                                       }
+                                       else if (value < 0) {
+                                               elog(NOTICE, "RGBA value cannot be less than zero. Defaulting to zero");
+                                               value = 0;
+                                       }
+                                       arg->colormap->entry[arg->colormap->nentry].color[j - 1] = value;
+                               }
+
+                               pfree(_element);
+                       }
+
+                       POSTGIS_RT_DEBUGF(4, "colormap->entry[%d] (isnodata, value, R, G, B, A) = (%d, %f, %d, %d, %d, %d)",
+                               arg->colormap->nentry,
+                               arg->colormap->entry[arg->colormap->nentry].isnodata,
+                               arg->colormap->entry[arg->colormap->nentry].value,
+                               arg->colormap->entry[arg->colormap->nentry].color[0],
+                               arg->colormap->entry[arg->colormap->nentry].color[1],
+                               arg->colormap->entry[arg->colormap->nentry].color[2],
+                               arg->colormap->entry[arg->colormap->nentry].color[3]
+                       );
+
+                       arg->colormap->nentry++;
+               }
+
+               POSTGIS_RT_DEBUGF(4, "colormap->nentry = %d", arg->colormap->nentry);
+               POSTGIS_RT_DEBUGF(4, "colormap->ncolor = %d", arg->colormap->ncolor);
+       }
+
+       /* call colormap */
+       raster = rt_raster_colormap(arg->raster, arg->nband - 1, arg->colormap);
+       if (raster == NULL) {
+               elog(ERROR, "RASTER_colorMap: Unable to create new raster with applied colormap");
+               rtpg_colormap_arg_destroy(arg);
+               PG_FREE_IF_COPY(pgraster, 0);
+               PG_RETURN_NULL();
+       }
+
+       rtpg_colormap_arg_destroy(arg);
+       PG_FREE_IF_COPY(pgraster, 0);
+       pgraster = rt_raster_serialize(raster);
+       rt_raster_destroy(raster);
+
+       POSTGIS_RT_DEBUG(3, "RASTER_colorMap: Done");
+
+       if (pgraster == NULL)
+               PG_RETURN_NULL();
+
+       SET_VARSIZE(pgraster, ((rt_pgraster*) pgraster)->size);
+       PG_RETURN_POINTER(pgraster);
+}
+
+/* ---------------------------------------------------------------- */
+/* Returns raster from GDAL raster                                  */
+/* ---------------------------------------------------------------- */
 PG_FUNCTION_INFO_V1(RASTER_fromGDALRaster);
 Datum RASTER_fromGDALRaster(PG_FUNCTION_ARGS)
 {
@@ -18692,16 +19131,6 @@ Datum RASTER_clip(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(pgrtn);
 }
 
-/* ---------------------------------------------------------------- */
-/* Find perimeter of raster                                         */
-/* ---------------------------------------------------------------- */
-
-PG_FUNCTION_INFO_V1(RASTER_perimeter);
-Datum RASTER_perimeter(PG_FUNCTION_ARGS)
-{
-       PG_RETURN_NULL();
-}
-
 /* ---------------------------------------------------------------- */
 /*  Memory allocation / error reporting hooks                       */
 /*  TODO: reuse the ones in libpgcommon ?                           */
index ba55bfd3b9c9ead20104d8981cc68d3cac87152b..c53a656e96ff751d4baed827cb5beea9e57c20ee 100644 (file)
@@ -1583,6 +1583,79 @@ CREATE OR REPLACE FUNCTION st_reclass(rast raster, reclassexpr text, pixeltype t
        AS $$ SELECT st_reclass($1, ROW(1, $2, $3, NULL)) $$
        LANGUAGE 'sql' IMMUTABLE STRICT;
 
+-----------------------------------------------------------------------
+-- ST_ColorMap
+-----------------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION _st_colormap(
+       rast raster, nband int,
+       colormap text,
+       method text DEFAULT 'INTERPOLATE'
+)
+       RETURNS raster
+       AS 'MODULE_PATHNAME', 'RASTER_colorMap'
+       LANGUAGE 'c' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION st_colormap(
+       rast raster, nband int DEFAULT 1,
+       colormap text DEFAULT 'grayscale',
+       method text DEFAULT 'INTERPOLATE'
+)
+       RETURNS raster
+       AS $$
+       DECLARE
+               _ismap boolean;
+               _colormap text;
+               _element text[];
+       BEGIN
+               _ismap := TRUE;
+
+               -- clean colormap to see what it is
+               _colormap := split_part(colormap, E'\n', 1);
+               _colormap := regexp_replace(_colormap, E':+', ' ', 'g');
+               _colormap := regexp_replace(_colormap, E',+', ' ', 'g');
+               _colormap := regexp_replace(_colormap, E'\\t+', ' ', 'g');
+               _colormap := regexp_replace(_colormap, E' +', ' ', 'g');
+               _element := regexp_split_to_array(_colormap, ' ');
+
+               -- treat as colormap
+               IF (array_length(_element, 1) > 1) THEN
+                       _colormap := colormap;
+               -- treat as keyword
+               ELSE
+                       method := 'INTERPOLATE';
+                       CASE lower(trim(both from _colormap))
+                               WHEN 'grayscale', 'greyscale' THEN
+                                       _colormap = '
+                                       100%   0
+                                         0% 254
+                                         nv 255 
+                                       ';
+                               WHEN 'pseudocolor' THEN
+                                       _colormap = '
+                                       100% 255   0   0 255
+                                        50%   0 255   0 255
+                                         0%   0   0 255 255
+                                               nv   0   0   0   0
+                                       ';
+                               ELSE
+                                       RAISE EXCEPTION 'Unknown colormap keyword: %', colormap;
+                       END CASE;
+               END IF;
+
+               RETURN _st_colormap($1, $2, _colormap, $4);
+       END;
+       $$ LANGUAGE 'plpgsql' IMMUTABLE STRICT;
+
+CREATE OR REPLACE FUNCTION st_colormap(
+       rast raster,
+       colormap text,
+       method text DEFAULT 'INTERPOLATE'
+)
+       RETURNS RASTER
+       AS $$ SELECT ST_ColorMap($1, 1, $2, $3) $$
+       LANGUAGE 'sql' IMMUTABLE STRICT;
+
 -----------------------------------------------------------------------
 -- ST_FromGDALRaster
 -----------------------------------------------------------------------
index 01905a04d832a19f3a2e62d5809181c21c681b4b..9770199f6352062e3348275a6509c54795806cd2 100644 (file)
@@ -60,6 +60,7 @@ OBJS= \
        cu_gdal.o \
        cu_spatial_relationship.o \
        cu_mapalgebra.o \
+       cu_misc.o \
        cu_tester.o 
 
 # If we couldn't find the cunit library then display a helpful message
index 2e9d9954e6b5811f1f180b14666810622a8e5ce8..0cb2f06d4171d4a4d69a020a475e8d89efecf915 100644 (file)
@@ -871,10 +871,315 @@ static void test_band_reclass() {
        rt_band_destroy(newband);
 }
 
+static void test_raster_colormap() {
+       rt_raster raster;
+       rt_raster rtn;
+       rt_band band;
+       int x;
+       int y;
+       rt_colormap colormap = NULL;
+       double value;
+       int nodata;
+
+       raster = rt_raster_new(9, 9);
+       CU_ASSERT(raster != NULL); /* or we're out of virtual memory */
+       band = cu_add_band(raster, PT_8BUI, 0, 0);
+       CU_ASSERT(band != NULL);
+       rt_band_set_nodata(band, 0, NULL);
+
+       for (y = 0; y < 9; y++) {
+               for (x = 0; x < 9; x++) {
+                       rt_band_set_pixel(band, x, y, x, NULL);
+               }
+       }
+
+       colormap = (rt_colormap) rtalloc(sizeof(struct rt_colormap_t));
+       CU_ASSERT(colormap != NULL);
+       colormap->nentry = 3;
+       colormap->entry = (rt_colormap_entry) rtalloc(sizeof(struct rt_colormap_entry_t) * colormap->nentry);
+       CU_ASSERT(colormap->entry != NULL);
+
+       colormap->entry[0].isnodata = 0;
+       colormap->entry[0].value = 8;
+       colormap->entry[0].color[0] = 255;
+       colormap->entry[0].color[1] = 255;
+       colormap->entry[0].color[2] = 255;
+       colormap->entry[0].color[3] = 255;
+
+       colormap->entry[1].isnodata = 0;
+       colormap->entry[1].value = 3;
+       colormap->entry[1].color[0] = 127;
+       colormap->entry[1].color[1] = 127;
+       colormap->entry[1].color[2] = 127;
+       colormap->entry[1].color[3] = 255;
+
+       colormap->entry[2].isnodata = 0;
+       colormap->entry[2].value = 0;
+       colormap->entry[2].color[0] = 0;
+       colormap->entry[2].color[1] = 0;
+       colormap->entry[2].color[2] = 0;
+       colormap->entry[2].color[3] = 255;
+
+       /* 2 colors, 3 entries, INTERPOLATE */
+       colormap->ncolor = 2;
+       colormap->method = CM_INTERPOLATE;
+
+       rtn = rt_raster_colormap(
+               raster, 0, 
+               colormap
+       );
+       CU_ASSERT(rtn != NULL);
+       CU_ASSERT_EQUAL(rt_raster_get_num_bands(rtn), colormap->ncolor);
+
+       band = rt_raster_get_band(rtn, 0);
+       CU_ASSERT(band != NULL);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 0, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 3, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 8, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 255, DBL_EPSILON);
+
+       cu_free_raster(rtn);
+
+       /* 4 colors, 3 entries, INTERPOLATE */
+       colormap->ncolor = 4;
+
+       rtn = rt_raster_colormap(
+               raster, 0, 
+               colormap
+       );
+       CU_ASSERT(rtn != NULL);
+       CU_ASSERT_EQUAL(rt_raster_get_num_bands(rtn), colormap->ncolor);
+       cu_free_raster(rtn);
+
+       /* 4 colors, 3 entries, EXACT */
+       colormap->method = CM_EXACT;
+
+       rtn = rt_raster_colormap(
+               raster, 0, 
+               colormap
+       );
+       CU_ASSERT(rtn != NULL);
+       CU_ASSERT_EQUAL(rt_raster_get_num_bands(rtn), colormap->ncolor);
+
+       band = rt_raster_get_band(rtn, 0);
+       CU_ASSERT(band != NULL);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 0, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 3, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 8, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 255, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 1, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 7, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+
+       cu_free_raster(rtn);
+
+       /* 4 colors, 3 entries, NEAREST */
+       colormap->method = CM_NEAREST;
+
+       rtn = rt_raster_colormap(
+               raster, 0, 
+               colormap
+       );
+       CU_ASSERT(rtn != NULL);
+       CU_ASSERT_EQUAL(rt_raster_get_num_bands(rtn), colormap->ncolor);
+
+       band = rt_raster_get_band(rtn, 0);
+       CU_ASSERT(band != NULL);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 0, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 3, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 8, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 255, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 1, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 2, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 4, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 7, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 255, DBL_EPSILON);
+
+       cu_free_raster(rtn);
+
+       /* 4 colors, 2 entries, NEAREST */
+       colormap->nentry = 2;
+       colormap->method = CM_NEAREST;
+
+       rtn = rt_raster_colormap(
+               raster, 0, 
+               colormap
+       );
+       CU_ASSERT(rtn != NULL);
+       CU_ASSERT_EQUAL(rt_raster_get_num_bands(rtn), colormap->ncolor);
+
+       band = rt_raster_get_band(rtn, 0);
+       CU_ASSERT(band != NULL);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 0, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 3, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 8, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 255, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 1, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 2, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 4, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 127, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 7, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 255, DBL_EPSILON);
+
+       cu_free_raster(rtn);
+
+       rtdealloc(colormap->entry);
+       rtdealloc(colormap);
+
+       cu_free_raster(raster);
+
+       /* new set of tests */
+       raster = rt_raster_new(10, 10);
+       CU_ASSERT(raster != NULL); /* or we're out of virtual memory */
+       band = cu_add_band(raster, PT_8BUI, 0, 0);
+       CU_ASSERT(band != NULL);
+       rt_band_set_nodata(band, 0, NULL);
+
+       for (y = 0; y < 10; y++) {
+               for (x = 0; x < 10; x++) {
+                       rt_band_set_pixel(band, x, y, (x * y) + x, NULL);
+               }
+       }
+
+       colormap = (rt_colormap) rtalloc(sizeof(struct rt_colormap_t));
+       CU_ASSERT(colormap != NULL);
+       colormap->nentry = 10;
+       colormap->entry = (rt_colormap_entry) rtalloc(sizeof(struct rt_colormap_entry_t) * colormap->nentry);
+       CU_ASSERT(colormap->entry != NULL);
+
+       colormap->entry[0].isnodata = 0;
+       colormap->entry[0].value = 90;
+       colormap->entry[0].color[0] = 255;
+       colormap->entry[0].color[1] = 255;
+       colormap->entry[0].color[2] = 255;
+       colormap->entry[0].color[3] = 255;
+
+       colormap->entry[1].isnodata = 0;
+       colormap->entry[1].value = 80;
+       colormap->entry[1].color[0] = 255;
+       colormap->entry[1].color[1] = 227;
+       colormap->entry[1].color[2] = 227;
+       colormap->entry[1].color[3] = 255;
+
+       colormap->entry[2].isnodata = 0;
+       colormap->entry[2].value = 70;
+       colormap->entry[2].color[0] = 255;
+       colormap->entry[2].color[1] = 198;
+       colormap->entry[2].color[2] = 198;
+       colormap->entry[2].color[3] = 255;
+
+       colormap->entry[3].isnodata = 0;
+       colormap->entry[3].value = 60;
+       colormap->entry[3].color[0] = 255;
+       colormap->entry[3].color[1] = 170;
+       colormap->entry[3].color[2] = 170;
+       colormap->entry[3].color[3] = 255;
+
+       colormap->entry[4].isnodata = 0;
+       colormap->entry[4].value = 50;
+       colormap->entry[4].color[0] = 255;
+       colormap->entry[4].color[1] = 142;
+       colormap->entry[4].color[2] = 142;
+       colormap->entry[4].color[3] = 255;
+
+       colormap->entry[5].isnodata = 0;
+       colormap->entry[5].value = 40;
+       colormap->entry[5].color[0] = 255;
+       colormap->entry[5].color[1] = 113;
+       colormap->entry[5].color[2] = 113;
+       colormap->entry[5].color[3] = 255;
+
+       colormap->entry[6].isnodata = 0;
+       colormap->entry[6].value = 30;
+       colormap->entry[6].color[0] = 255;
+       colormap->entry[6].color[1] = 85;
+       colormap->entry[6].color[2] = 85;
+       colormap->entry[6].color[3] = 255;
+
+       colormap->entry[7].isnodata = 0;
+       colormap->entry[7].value = 20;
+       colormap->entry[7].color[0] = 255;
+       colormap->entry[7].color[1] = 57;
+       colormap->entry[7].color[2] = 57;
+       colormap->entry[7].color[3] = 255;
+
+       colormap->entry[8].isnodata = 0;
+       colormap->entry[8].value = 10;
+       colormap->entry[8].color[0] = 255;
+       colormap->entry[8].color[1] = 28;
+       colormap->entry[8].color[2] = 28;
+       colormap->entry[8].color[3] = 255;
+
+       colormap->entry[9].isnodata = 0;
+       colormap->entry[9].value = 0;
+       colormap->entry[9].color[0] = 255;
+       colormap->entry[9].color[1] = 0;
+       colormap->entry[9].color[2] = 0;
+       colormap->entry[9].color[3] = 255;
+
+       /* 2 colors, 3 entries, INTERPOLATE */
+       colormap->ncolor = 4;
+       colormap->method = CM_INTERPOLATE;
+
+       rtn = rt_raster_colormap(
+               raster, 0, 
+               colormap
+       );
+       CU_ASSERT(rtn != NULL);
+       CU_ASSERT_EQUAL(rt_raster_get_num_bands(rtn), colormap->ncolor);
+
+       band = rt_raster_get_band(rtn, 2);
+       CU_ASSERT(band != NULL);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 0, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 0, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 5, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 14, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 6, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 17, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 9, 0, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 25, DBL_EPSILON);
+
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 2, 4, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 28, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 3, 4, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 43, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 4, 4, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 57, DBL_EPSILON);
+
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 6, 9, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 170, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 7, 9, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 198, DBL_EPSILON);
+       CU_ASSERT_EQUAL(rt_band_get_pixel(band, 8, 9, &value, &nodata), ES_NONE);
+       CU_ASSERT_DOUBLE_EQUAL(value, 227, DBL_EPSILON);
+
+       cu_free_raster(rtn);
+
+       rtdealloc(colormap->entry);
+       rtdealloc(colormap);
+
+       cu_free_raster(raster);
+}
+
 /* register tests */
 CU_TestInfo mapalgebra_tests[] = {
        PG_TEST(test_raster_iterator),
        PG_TEST(test_band_reclass),
+       PG_TEST(test_raster_colormap),
        CU_TEST_INFO_NULL
 };
 CU_SuiteInfo mapalgebra_suite = {"mapalgebra",  NULL,  NULL, mapalgebra_tests};
diff --git a/raster/test/cunit/cu_misc.c b/raster/test/cunit/cu_misc.c
new file mode 100644 (file)
index 0000000..c38f7b0
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * PostGIS Raster - Raster Types for PostGIS
+ * http://www.postgis.org/support/wiki/index.php?WKTRasterHomePage
+ *
+ * Copyright (C) 2013 Regents of the University of California
+ *   <bkpark@ucdavis.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "CUnit/Basic.h"
+#include "cu_tester.h"
+
+static void test_rgb_to_hsv() {
+       double rgb[3] = {0, 0, 0};
+       double hsv[3] = {0, 0, 0};
+
+       rt_util_rgb_to_hsv(rgb, hsv);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[0], 0, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[1], 0, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[2], 0, DBL_EPSILON);
+
+       rgb[0] = 0;
+       rgb[1] = 0;
+       rgb[2] = 1;
+       rt_util_rgb_to_hsv(rgb, hsv);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[0], 2/3., DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[1], 1, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[2], 1, DBL_EPSILON);
+
+       rgb[0] = 0;
+       rgb[1] = 0.25;
+       rgb[2] = 0.5;
+       rt_util_rgb_to_hsv(rgb, hsv);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[0], 7/12., DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[1], 1, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[2], 0.5, DBL_EPSILON);
+
+       rgb[0] = 0.5;
+       rgb[1] = 1;
+       rgb[2] = 0.5;
+       rt_util_rgb_to_hsv(rgb, hsv);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[0], 1/3., DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[1], 0.5, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[2], 1, DBL_EPSILON);
+
+       rgb[0] = 0.2;
+       rgb[1] = 0.4;
+       rgb[2] = 0.4;
+       rt_util_rgb_to_hsv(rgb, hsv);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[0], 0.5, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[1], 0.5, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(hsv[2], 0.4, DBL_EPSILON);
+}
+
+static void test_hsv_to_rgb() {
+       double hsv[3] = {0, 0, 0};
+       double rgb[3] = {0, 0, 0};
+
+       rt_util_hsv_to_rgb(hsv, rgb);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[0], 0, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[1], 0, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[2], 0, DBL_EPSILON);
+
+       hsv[0] = 2/3.;
+       hsv[1] = 1;
+       hsv[2] = 1;
+       rt_util_hsv_to_rgb(hsv, rgb);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[0], 0., DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[1], 0, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[2], 1, DBL_EPSILON);
+
+       hsv[0] = 7/12.;
+       hsv[1] = 1;
+       hsv[2] = 0.5;
+       rt_util_hsv_to_rgb(hsv, rgb);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[0], 0, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[1], 0.25, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[2], 0.5, DBL_EPSILON);
+
+       hsv[0] = 1/3.;
+       hsv[1] = 0.5;
+       hsv[2] = 1;
+       rt_util_hsv_to_rgb(hsv, rgb);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[0], 0.5, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[1], 1, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[2], 0.5, DBL_EPSILON);
+
+       hsv[0] = 0.5;
+       hsv[1] = 0.5;
+       hsv[2] = 0.4;
+       rt_util_hsv_to_rgb(hsv, rgb);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[0], 0.2, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[1], 0.4, DBL_EPSILON);
+       CU_ASSERT_DOUBLE_EQUAL(rgb[2], 0.4, DBL_EPSILON);
+}
+
+/* register tests */
+CU_TestInfo misc_tests[] = {
+       PG_TEST(test_rgb_to_hsv),
+       PG_TEST(test_hsv_to_rgb),
+       CU_TEST_INFO_NULL
+};
+CU_SuiteInfo misc_suite = {"misc",  NULL,  NULL, misc_tests};
index ab11902620ddd58b102fca660c75a8026a774f6d..31f8eb615a7d49f9ce9f2b9beed9e0cb004c8c76 100644 (file)
@@ -31,6 +31,7 @@ extern CU_SuiteInfo band_stats_suite;
 extern CU_SuiteInfo band_misc_suite;
 extern CU_SuiteInfo mapalgebra_suite;
 extern CU_SuiteInfo spatial_relationship_suite;
+extern CU_SuiteInfo misc_suite;
 
 /*
 ** The main() function for setting up and running the tests.
@@ -53,6 +54,7 @@ int main(int argc, char *argv[])
                band_misc_suite,
                spatial_relationship_suite,
                mapalgebra_suite,
+               misc_suite,
                CU_SUITE_INFO_NULL
        };
 
index e6513b7a0f47dec7b91a8bb340df30fc70203093..c2314099ada9d3b482b402db7d25900c3ad7e0e2 100644 (file)
@@ -108,7 +108,8 @@ TEST_MAPALGEBRA = \
        rt_invdistweight4ma \
        rt_4ma \
        rt_setvalues_geomval \
-       rt_elevation_functions
+       rt_elevation_functions \
+       rt_colormap
 
 TEST_SREL = \
        rt_gist_relationships \
diff --git a/raster/test/regress/rt_colormap.sql b/raster/test/regress/rt_colormap.sql
new file mode 100644 (file)
index 0000000..2a5841e
--- /dev/null
@@ -0,0 +1,133 @@
+DROP TABLE IF EXISTS raster_colormap_out;
+CREATE TABLE raster_colormap_out (
+       testid integer,
+       rid integer,
+       rast raster
+);
+DROP TABLE IF EXISTS raster_colormap_in;
+CREATE TABLE raster_colormap_in (
+       rid integer,
+       rast raster
+);
+
+INSERT INTO raster_colormap_in
+SELECT
+       1 AS rid,
+       ST_SetValues(
+               ST_AddBand(
+                       ST_MakeEmptyRaster(10, 10, 0, 0, 1, -1, 0, 0, 0),
+                       1, '8BUI', 0, 0
+               ),
+               1, 1, 1, ARRAY[
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
+                       [20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
+                       [30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
+                       [40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
+                       [50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
+                       [60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
+                       [70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
+                       [80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
+                       [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
+               ]::double precision[]
+       ) AS rast
+UNION ALL
+SELECT
+       2 AS rid,
+       ST_SetValues(
+               ST_AddBand(
+                       ST_MakeEmptyRaster(10, 10, 0, 0, 1, -1, 0, 0, 0),
+                       1, '8BUI', 0, 0
+               ),
+               1, 1, 1, ARRAY[
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
+                       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9]
+               ]::double precision[]
+       ) AS rast
+;
+
+INSERT INTO raster_colormap_out
+SELECT
+       1 AS testid,
+       rid,
+       _ST_ColorMap(
+               rast, 1,
+               '
+100% 255:255 255 256
+
+  0%   0   0   0 255
+  nv   0   0   0   0
+'
+       ) AS rast
+FROM raster_colormap_in
+UNION ALL
+SELECT
+       2 AS testid,
+       rid,
+       _ST_ColorMap(
+               rast, 1,
+               '
+100% 255:255 255 256
+ 75% 127 255 255 255
+ 50% 127,  0,127 255
+ 25%   0 127   0 255
+  0%   0   0   0 255
+  nv   0   0   0   0
+'
+       ) AS rast
+FROM raster_colormap_in
+UNION ALL
+SELECT
+       3 AS testid,
+       rid,
+       _ST_ColorMap(
+               rast, 1,
+               '
+  0%   1   127   0 255
+'
+       ) AS rast
+FROM raster_colormap_in
+UNION ALL
+SELECT
+       4 AS testid,
+       rid,
+       _ST_ColorMap(
+               rast, 1,
+               '
+9   0   0 255
+5   0 255   0
+4   0 255   0
+0 255   0   0
+'
+       ) AS rast
+FROM raster_colormap_in
+WHERE rid = 2
+UNION ALL
+SELECT
+       5 AS testid,
+       rid,
+       ST_ColorMap(
+               rast, 1,
+               'grayscale'
+       ) AS rast
+FROM raster_colormap_in
+WHERE rid = 2
+;
+
+SELECT
+       testid
+       rid,
+       (ST_DumpValues(rast)).*
+FROM raster_colormap_out
+ORDER BY 1, 2;
+
+DROP TABLE IF EXISTS raster_colormap_in;
+DROP TABLE IF EXISTS raster_colormap_out;
diff --git a/raster/test/regress/rt_colormap_expected b/raster/test/regress/rt_colormap_expected
new file mode 100644 (file)
index 0000000..447c1f9
--- /dev/null
@@ -0,0 +1,36 @@
+NOTICE:  table "raster_colormap_out" does not exist, skipping
+NOTICE:  table "raster_colormap_in" does not exist, skipping
+NOTICE:  RGBA value cannot be greater than 255. Defaulting to 255
+NOTICE:  RGBA value cannot be greater than 255. Defaulting to 255
+NOTICE:  RGBA value cannot be greater than 255. Defaulting to 255
+NOTICE:  RGBA value cannot be greater than 255. Defaulting to 255
+NOTICE:  Method INTERPOLATE requires at least two non-NODATA colormap entries. Using NEAREST instead
+NOTICE:  Method INTERPOLATE requires at least two non-NODATA colormap entries. Using NEAREST instead
+1|1|{{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255}}
+1|1|{{0,0,3,5,8,10,13,16,18,21},{23,26,29,31,34,36,39,42,44,47},{49,52,55,57,60,62,65,68,70,73},{75,78,81,83,86,88,91,94,96,99},{101,104,107,109,112,114,117,120,122,125},{128,130,133,135,138,141,143,146,148,151},{154,156,159,161,164,167,169,172,174,177},{180,182,185,187,190,193,195,198,200,203},{206,208,211,213,216,219,221,224,226,229},{232,234,237,239,242,245,247,250,252,255}}
+1|2|{{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255}}
+1|2|{{0,0,3,5,8,10,13,16,18,21},{23,26,29,31,34,36,39,42,44,47},{49,52,55,57,60,62,65,68,70,73},{75,78,81,83,86,88,91,94,96,99},{101,104,107,109,112,114,117,120,122,125},{128,130,133,135,138,141,143,146,148,151},{154,156,159,161,164,167,169,172,174,177},{180,182,185,187,190,193,195,198,200,203},{206,208,211,213,216,219,221,224,226,229},{232,234,237,239,242,245,247,250,252,255}}
+1|3|{{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255},{0,0,32,64,96,128,159,191,223,255}}
+1|3|{{0,0,3,5,8,10,13,16,18,21},{23,26,29,31,34,36,39,42,44,47},{49,52,55,57,60,62,65,68,70,73},{75,78,81,83,86,88,91,94,96,99},{101,104,107,109,112,114,117,120,122,125},{128,130,133,135,138,141,143,146,148,151},{154,156,159,161,164,167,169,172,174,177},{180,182,185,187,190,193,195,198,200,203},{206,208,211,213,216,219,221,224,226,229},{232,234,237,239,242,245,247,250,252,255}}
+1|4|{{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255}}
+1|4|{{0,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255}}
+2|1|{{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255},{0,0,63,0,127,127,191,127,223,255}}
+2|1|{{0,0,5,10,16,21,26,31,36,41},{47,52,57,62,65,67,67,66,64,61},{56,50,42,33,21,8,8,23,39,54},{70,86,101,117,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,132,137,143,148,153,158,164,169,174},{179,184,190,177,159,141,124,106,89,87},{94,101,108,116,123,130,135,140,145,151},{156,161,166,175,189,203,215,226,235,243},{250,255,255,255,255,255,255,255,255,255}}
+2|2|{{0,0,5,10,14,18,23,27,32,37},{42,48,54,62,67,73,78,83,88,93},{98,104,109,114,119,124,127,127,127,127},{127,127,127,127,122,106,91,75,60,44},{29,13,124,109,93,78,62,47,31,16},{127,24,49,74,101,128,156,43,71,99},{127,155,184,195,200,205,211,216,221,226},{231,237,242,247,252,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,254,250,247,245,244,245,247,250,255}}
+2|2|{{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255},{0,0,64,127,64,127,191,255,255,255}}
+2|3|{{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255},{0,0,32,0,0,0,48,255,191,255}}
+2|3|{{0,0,5,10,14,17,21,23,26,28},{30,31,31,32,32,31,30,29,27,25},{22,19,16,12,7,3,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,3,6,9,12,16,19,23,27,32},{36,41,46,52,57,63,68,75,81,102},{131,159,187,215,242,247,233,220,208,197},{188,180,173,171,177,182,187,192,198,203},{208,213,218,224,229,234,239,245,250,255}}
+2|4|{{0,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255}}
+2|4|{{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255},{0,255,255,255,255,255,255,255,255,255}}
+3|1|{{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1}}
+3|1|{{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1},{1,1,1,1,1,1,1,1,1,1}}
+3|2|{{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127}}
+3|2|{{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127},{127,127,127,127,127,127,127,127,127,127}}
+3|3|{{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0}}
+3|3|{{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0}}
+3|4|{{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255}}
+3|4|{{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255},{255,255,255,255,255,255,255,255,255,255}}
+4|1|{{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0},{255,255,255,128,0,0,0,0,0,0}}
+4|2|{{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0},{0,128,255,255,255,255,255,255,128,0}}
+4|3|{{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255},{0,0,0,0,0,0,127,255,255,255}}
+5|1|{{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0},{255,254,222,191,159,127,95,64,32,0}}