home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 2002 April / pcpro0402.iso / essentials / graphics / Gimp / gimp-src-20001226.exe / src / gimp / unofficial-plug-ins / ccanalyze / ccanalyze.c
Encoding:
C/C++ Source or Header  |  1999-10-20  |  16.6 KB  |  636 lines

  1. /*
  2.  * This is a plug-in for the GIMP.
  3.  *
  4.  * This program is free software; you can redistribute it and/or modify
  5.  * it under the terms of the GNU General Public License as published by
  6.  * the Free Software Foundation; either version 2 of the License, or
  7.  * (at your option) any later version.
  8.  *
  9.  * This program is distributed in the hope that it will be useful,
  10.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  * GNU General Public License for more details.
  13.  *
  14.  * You should have received a copy of the GNU General Public License
  15.  * along with this program; if not, write to the Free Software
  16.  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17.  *
  18.  *
  19.  */
  20.  
  21. /*
  22.  * Analyze colorcube.
  23.  * 
  24.  * Author: robert@experimental.net
  25.  */
  26.  
  27. #include <glib.h>
  28.  
  29. #include <stdio.h>
  30. #include <stdlib.h>
  31. #include <string.h>
  32. #include <sys/stat.h>
  33. #include "libgimp/gimp.h"
  34. #include "gtk/gtk.h"
  35.  
  36. #ifndef HAVE_RINT
  37. #include <math.h>
  38. #define rint(x) floor((x) + 0.5)
  39. #endif
  40. #ifndef bzero
  41. #define bzero(p,size) memset(p,0,size)
  42. #endif
  43.  
  44. /* 0 = not verbose, rest = verbose */
  45. #define    VERBOSE        0
  46.  
  47. /* calloc new colornode, and sanitycheck */
  48. #define CALLOC_COLOR(x)    { \
  49.                 if ((x = (ColorNode *) calloc(1, sizeof(ColorNode))) == NULL) \
  50.                 { \
  51.                     printf("Ick, couldn't calloc!()\n"); \
  52.                     return; \
  53.                 } \
  54.             }
  55.  
  56. /*
  57.  * I found the following implementation of storing a sparse color matrix
  58.  * in Dr. Dobb's Journal #232 (July 1995).
  59.  * 
  60.  * The matrix is build as three linked lists, each representing a color-
  61.  * cube axis. Each node in the matrix contains two pointers: one to its 
  62.  * neighbour and one to the next color-axis. 
  63.  *
  64.  * Each red node contains a pointer to the next red node, and a pointer to
  65.  * the green nodes. Green nodes, in turn, each contain a pointer to the next
  66.  * green node, and a pointer to the blue axis.
  67.  *
  68.  * If we want to find an RGB triplet, we first walk down the red axis, match
  69.  * the red values, from where we start walking down the green axis, etc.
  70.  * If we haven't found our color at the end of the blue axis, it's a new color
  71.  * and we store it in the matrix.
  72.  *
  73.  * For the textual-impaired (number in parentheses are color values):
  74.  *
  75.  *      start of table
  76.  *      |
  77.  *      v
  78.  *     RED(91)  ->  RED(212)  ->  ...
  79.  *      |            |             
  80.  *      |            v 
  81.  *      |            GREEN(81)  ->  GREEN(128)  ->  ...
  82.  *      |            |              |
  83.  *      |            |              v
  84.  *      |            |              BLUE(93)
  85.  *      |            v
  86.  *      |            BLUE(206)  ->  BLUE(93)  ->  ...
  87.  *      v
  88.  *      GREEN(1)  ->  ...
  89.  *      |
  90.  *      v
  91.  *      BLUE(206)  ->  BLUE(12)  ->  ...
  92.  *
  93.  * So, some colors stored are (in RGB triplets): (91, 1, 206), (91, 1, 12),
  94.  * (212, 128, 93), ...
  95.  *
  96.  */
  97. typedef enum { RED, GREEN, BLUE } ColorType;
  98.  
  99. typedef struct ColorNode
  100. {
  101.     struct ColorNode    *next_neighbour;
  102.     struct ColorNode    *next_axis;
  103.     ColorType        color;
  104.     unsigned int        r;
  105.     unsigned int        g;
  106.     unsigned int        b;
  107.     int            count;
  108. }    ColorNode;
  109.  
  110. typedef    struct
  111. {
  112.     unsigned char    r;
  113.     unsigned char    g;
  114.     unsigned char    b;
  115. }    Color;
  116.  
  117. /* lets prototype */
  118. static void    query();
  119. static void    run(char *, int, GParam *, int *, GParam **);
  120. static int    doDialog();
  121. static void    analyze(GDrawable *);
  122.  
  123. static void    histogram(guchar, guchar, guchar);
  124. static void    fillPreview(GtkWidget *);
  125. static void    insertcolor(guchar, guchar, guchar);
  126.  
  127. static void    doLabel(GtkWidget *, char *);
  128.  
  129. static void    ok_callback(GtkWidget *, gpointer);
  130. static void    close_callback(GtkWidget *, gpointer);
  131.  
  132. /* some global variables */
  133. char        *filename = NULL;
  134. int        running = 0, width, height, bpp;
  135. const    int    verbose = VERBOSE;
  136. ColorNode    *table = NULL;
  137. char        *inputname;
  138. gint32        hist_red[256], hist_green[256], hist_blue[256];
  139. int        maxred = 0, maxgreen = 0, maxblue = 0;
  140. int        uniques = 0;
  141. gint32      imageID;
  142.  
  143. /* size of histogram image */
  144. static const int PREWIDTH = 256;
  145. static const int PREWIDTH3 = 768;
  146. static const int PREHEIGHT = 150;
  147.  
  148. /* lets declare what we want to do */
  149. GPlugInInfo PLUG_IN_INFO =
  150. {
  151.     NULL,                   /* init_proc */
  152.     NULL,                   /* quit_proc */
  153.     query,                   /* query_proc */
  154.     run,                   /* run_proc */
  155. };
  156.  
  157. /* run program */
  158. MAIN();
  159.  
  160. /* tell GIMP who we are */
  161. static
  162. void    query()
  163. {
  164.     static GParamDef args[] =
  165.     {
  166.         { PARAM_INT32, "run_mode", "Interactive" },
  167.         { PARAM_IMAGE, "image", "Input image" },
  168.         { PARAM_DRAWABLE, "drawable", "Input drawable" },
  169.     };
  170.     static GParamDef *return_vals = NULL;
  171.     static int nargs = sizeof(args) / sizeof(args[0]);
  172.     static int nreturn_vals = 0;
  173.  
  174.     gimp_install_procedure("plug_in_ccanalyze",
  175.                    "Colorcube analysis",
  176.                    "Analyze colorcube and print some information about the current image (also displays a color-histogram)",
  177.                    "robert@experimental.net",
  178.                    "robert@experimental.net",
  179.                    "June 20th, 1997",
  180.                    "<Image>/Image/Colors/Colorcube analysis",
  181.                    "RGB*, INDEXED*, GRAY*",
  182.                    PROC_PLUG_IN,
  183.                    nargs, nreturn_vals,
  184.                    args, return_vals);
  185. }
  186.  
  187. /* main function */
  188. static
  189. void    run(char *name, int nparams, GParam *param, int *nreturn_vals, GParam **return_vals)
  190. {
  191.     static GParam    values[1];
  192.     GDrawable     *drawable;
  193.     GStatusType     status = STATUS_SUCCESS;
  194.  
  195.     *nreturn_vals = 1;
  196.     *return_vals = values;
  197.  
  198.     values[0].type = PARAM_STATUS;
  199.     values[0].data.d_status = status;
  200.  
  201.     drawable = gimp_drawable_get(param[2].data.d_drawable);
  202.     imageID = param[1].data.d_image;
  203.  
  204.     /* not much use of running this in non-interactive mode */
  205.     if (param[0].data.d_int32 == RUN_NONINTERACTIVE)
  206.     {
  207.         status = STATUS_EXECUTION_ERROR;
  208.         return;
  209.     }
  210.  
  211.     if (status == STATUS_SUCCESS)
  212.     {
  213.         if (gimp_drawable_color(drawable->id) || gimp_drawable_is_indexed(drawable->id))
  214.         {
  215.             bzero(hist_red, sizeof(hist_red));
  216.             bzero(hist_green, sizeof(hist_green));
  217.             bzero(hist_blue, sizeof(hist_blue));
  218.             filename = gimp_image_get_filename(imageID);
  219.             gimp_progress_init("Colorcube analysis...");
  220.             gimp_tile_cache_ntiles(2 * (drawable->width / gimp_tile_width() + 1));
  221.             analyze(drawable);
  222.             /* show dialog after we analyzed image */
  223.             doDialog();
  224.         }
  225.         else
  226.             status = STATUS_EXECUTION_ERROR;
  227.     }
  228.     values[0].data.d_status = status;
  229.     gimp_drawable_detach(drawable);
  230. }
  231.  
  232. /* do the analyzing */
  233. static
  234. void    analyze(GDrawable *drawable)
  235. {
  236.     GPixelRgn    srcPR;
  237.     guchar        *src_row, *cmap;
  238.     gint        x, y, numcol;
  239.     gint        x1, y1, x2, y2;
  240.  
  241.     /* 
  242.      * Get the input area. This is the bounding box of the selection in
  243.      * the image (or the entire image if there is no selection). Only
  244.      * operating on the input area is simply an optimization. It doesn't
  245.      * need to be done for correct operation. (It simply makes it go
  246.      * faster, since fewer pixels need to be operated on).
  247.      */
  248.     gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2);
  249.  
  250.         /* 
  251.          * Get the size of the input image (this will/must be the same
  252.          * as the size of the output image).
  253.          */
  254.     width = drawable->width;
  255.     height = drawable->height;
  256.     bpp = drawable->bpp;
  257.  
  258.     /* allocate row buffer */
  259.     src_row = (guchar *) malloc((x2 - x1) * bpp);
  260.  
  261.     /* initialize the pixel region */
  262.     gimp_pixel_rgn_init(&srcPR, drawable, 0, 0, width, height, FALSE, FALSE);
  263.  
  264.     cmap = gimp_image_get_cmap(imageID, &numcol);
  265.     for (y = y1; y < y2; y++)
  266.     {
  267.         gimp_pixel_rgn_get_row(&srcPR, src_row, x1, y, (x2 - x1));
  268.         for (x = x1; x < x2; x++)
  269.         {
  270.             guchar    red, green, blue;
  271.  
  272.             /* 
  273.              * If the image is indexed, fetch RGB values
  274.              * from colormap.
  275.              */
  276.             if (cmap)
  277.             {
  278.                 guchar    idx = src_row[x];
  279.  
  280.                 red = cmap[idx * 3];
  281.                 green = cmap[idx * 3 + 1];
  282.                 blue = cmap[idx * 3 + 2];
  283.             }
  284.             else
  285.             {
  286.                 red = src_row[x * bpp];
  287.                 green =    src_row[x * bpp + 1];
  288.                 blue = src_row[x * bpp + 2];
  289.             }
  290.             insertcolor(red, green, blue);
  291.         }
  292.         /* tell the user what we're doing */
  293.         if ((y % 10) == 0)
  294.             gimp_progress_update((double) y / (double) (y2 - y1));
  295.     }
  296.     /* clean up */
  297.     free(src_row);
  298. }
  299.  
  300. /* here's were we actually store our color-table */
  301. static
  302. void    insertcolor(guchar r, guchar g, guchar b)
  303. {
  304.     ColorNode    *node, *next = NULL, *prev = NULL,
  305.             *newred, *newgreen = NULL, *newblue;
  306.     ColorType    type = RED;
  307.  
  308.     histogram(r, g, b);
  309.     /* lets walk the tree, and see if it already contains this color */
  310.     for (node = table; node != NULL; prev = node, node = next)
  311.     {
  312.         if (node->color == RED)
  313.         {
  314.             if (node->r == r)
  315.             {
  316.                 type = GREEN;
  317.                 next = node->next_axis;
  318.             }
  319.             else
  320.             {
  321.                 type = RED;
  322.                 next = node->next_neighbour;
  323.             }
  324.             continue;
  325.         }
  326.         if (node->color == GREEN)
  327.         {
  328.             if (node->g == g)
  329.             {
  330.                 type = BLUE;
  331.                 next = node->next_axis;
  332.             }
  333.             else
  334.             {
  335.                 type = GREEN;
  336.                 next = node->next_neighbour;
  337.             }
  338.             continue;
  339.         }
  340.         if (node->color == BLUE)
  341.         {
  342.             /* found it! */
  343.             if (node->b == b)
  344.                 break;
  345.             else
  346.             {
  347.                 type = BLUE;
  348.                 next = node->next_neighbour;
  349.             }
  350.         }
  351.     }
  352.     /* this color was already stored -> update it's count */
  353.     if (node)
  354.     {
  355.         node->count++;
  356.         return;
  357.     }
  358.     /* New color! */
  359.     /* first, create blue node */
  360.     CALLOC_COLOR(newblue);
  361.     newblue->color = BLUE;
  362.     /* no neighbours or links to another axis */
  363.     newblue->next_neighbour = newblue->next_axis = NULL;
  364.     /* 
  365.      * At the end of the list, we store the entire triplet.
  366.      * For now, there is no reason whatsoever to do this, but perhaps
  367.      * it might prove useful someday :)
  368.      */
  369.     newblue->r = r;
  370.     newblue->g = g;
  371.     newblue->b = b;
  372.     newblue->count = 1;
  373.     /* previous was green: create link to axis */
  374.     if (prev && prev->color == GREEN && type == BLUE)
  375.         prev->next_axis = newblue;
  376.     /* previous was blue: create link to neighbour */
  377.     if (prev && prev->color == BLUE && type == BLUE)
  378.         prev->next_neighbour = newblue;
  379.  
  380.     /* green node */
  381.     if (type == GREEN || type == RED)
  382.     {
  383.         CALLOC_COLOR(newgreen);
  384.         newgreen->color = GREEN;
  385.         newgreen->next_neighbour =  NULL;
  386.         newgreen->next_axis = newblue;
  387.         newgreen->g = g;
  388.         /* count doesn't matter here */
  389.         newgreen->count = -1;
  390.         /* previous was red: create link to axis */
  391.         if (prev && prev->color == RED && type == GREEN)
  392.             prev->next_axis = newgreen;
  393.         /* previous was green: create link to neighbour */
  394.         if (prev && prev->color == GREEN && type == GREEN)
  395.             prev->next_neighbour = newgreen;
  396.     }
  397.  
  398.     /* red node */
  399.     if (type == RED)
  400.     {
  401.         CALLOC_COLOR(newred);
  402.         newred->color = RED;
  403.         newred->next_neighbour =  NULL;
  404.         newred->next_axis = newgreen;
  405.         newred->r = r;
  406.         /* count doesn't matter here */
  407.         newred->count = -1;
  408.         /* previous was red, update its neighbour link */
  409.         if (prev)
  410.             prev->next_neighbour = newred;
  411.         else
  412.             table = newred;
  413.     }
  414.  
  415.     /* increase the number of unique colors */
  416.     uniques++;
  417. }
  418.  
  419. /* 
  420.  * Update RGB count, and keep track of maximum values (which aren't used 
  421.  * anywhere as of yet, but they might be useful sometime).
  422.  */
  423. void    histogram(guchar r, guchar g, guchar b)
  424. {
  425.     if (++hist_red[r] > maxred)
  426.         maxred = hist_red[r];
  427.     if (++hist_green[g] > maxgreen)
  428.         maxgreen = hist_green[g];
  429.     if (++hist_blue[b] > maxblue)
  430.         maxblue = hist_blue[b];
  431. }
  432.  
  433. /* show our results */
  434. static
  435. int    doDialog()
  436. {
  437.     struct stat    st;
  438.     GtkWidget    *dialog;
  439.     GtkWidget    *button;
  440.     GtkWidget    *frame;
  441.     GtkWidget    *xframe;
  442.     GtkWidget    *table;
  443.     GtkWidget    *preview;
  444.     guchar        *color_cube;
  445.     gchar        **argv;
  446.     gint        argc;
  447.     int        filesize = 0;
  448.     char        buf[512], *c;
  449.  
  450.     argc = 1;
  451.     argv = g_new(gchar *, 1);
  452.     argv[0] = g_strdup("ccanalyze");
  453.  
  454.     gtk_init(&argc, &argv);
  455.  
  456.     gtk_preview_set_gamma(gimp_gamma());
  457.     gtk_preview_set_install_cmap(gimp_install_cmap());
  458.     color_cube = gimp_color_cube();
  459.     gtk_preview_set_color_cube(color_cube[0], color_cube[1], color_cube[2], 
  460.                    color_cube[3]);
  461.     gtk_widget_set_default_visual(gtk_preview_get_visual());
  462.     gtk_widget_set_default_colormap(gtk_preview_get_cmap());
  463.  
  464.  
  465.     /* set up the dialog */
  466.     dialog = gtk_dialog_new();
  467.     gtk_window_set_title(GTK_WINDOW(dialog), "Colorcube analysis");
  468.     gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
  469.     gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
  470.                (GtkSignalFunc) close_callback,
  471.                NULL);
  472.  
  473.     /* lets create some buttons */
  474.     button = gtk_button_new_with_label("Ok");
  475.     GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  476.     gtk_signal_connect(GTK_OBJECT(button), "clicked",
  477.                (GtkSignalFunc) ok_callback,
  478.                dialog);
  479.     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
  480.                        button, TRUE, TRUE, 0);
  481.     gtk_widget_grab_default(button);
  482.     gtk_widget_show(button); 
  483.  
  484.     /* set up frame */
  485.     frame = gtk_frame_new("Results");
  486.     gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  487.     gtk_container_border_width(GTK_CONTAINER(frame), 10);
  488.     gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
  489.                frame, TRUE, TRUE, 0);
  490.     table = gtk_table_new(12, 1, FALSE);
  491.     gtk_container_border_width(GTK_CONTAINER(table), 10);
  492.     gtk_container_add(GTK_CONTAINER(frame), table);
  493.  
  494.     /* use preview for histogram window */
  495.     preview = gtk_preview_new(GTK_PREVIEW_COLOR);
  496.     gtk_preview_size(GTK_PREVIEW(preview), PREWIDTH3, PREHEIGHT);
  497.     xframe = gtk_frame_new(NULL);
  498.     gtk_frame_set_shadow_type(GTK_FRAME(xframe), GTK_SHADOW_IN);
  499.     gtk_container_add(GTK_CONTAINER(xframe), preview);
  500.     fillPreview(preview);
  501.     gtk_widget_show(preview);
  502.     gtk_widget_show(xframe);
  503.     gtk_table_attach(GTK_TABLE(table), xframe, 0, 1, 0, 1, GTK_EXPAND, GTK_EXPAND, 0, 0);
  504.     gtk_widget_show(frame);
  505.  
  506.     /* output results */
  507.     sprintf(buf, "Name: %s", filename);
  508.     doLabel(table, buf);
  509.     sprintf(buf, "Image dimensions: %ux%u", width, height);
  510.     doLabel(table, buf);
  511.     sprintf(buf, "Uncompressed size in bytes: %u", width * height * bpp);
  512.     doLabel(table, buf);
  513.     if (filename && !stat(filename, &st))
  514.     {
  515.         filesize = st.st_size;
  516.         sprintf(buf, "Compressed size in bytes: %u", filesize);
  517.         doLabel(table, buf);
  518.         sprintf(buf, "Compression ratio (approx.): %u to 1", (int) rint((double) (width * height * bpp) / filesize));
  519.         doLabel(table, buf);
  520.     }
  521.     if (!uniques)
  522.         strcpy(buf, "No colors (??)");
  523.     else
  524.     if (uniques == 1)
  525.         strcpy(buf, "Only one unique color");
  526.     else
  527.         sprintf(buf, "Number of unique colors: %u", uniques);
  528.     doLabel(table, buf);
  529.     
  530.     /* show stuff */
  531.     gtk_widget_show(table);
  532.     gtk_widget_show(dialog);
  533.     gtk_main();
  534.     gdk_flush();
  535.  
  536.     return running;
  537. }
  538.  
  539. /* shortcut */
  540. static
  541. void    doLabel(GtkWidget *table, char *text)
  542. {
  543.     static        int    idx = 1;
  544.     GtkWidget    *label;
  545.  
  546.     label = gtk_label_new(text);
  547.     gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
  548.     gtk_table_attach(GTK_TABLE(table), label, 0, 1, idx, idx + 1, GTK_FILL, 0, 5, 0);
  549.     gtk_widget_show(label);
  550.     idx += 2;
  551. }
  552.  
  553. /* fill our preview image with the color-histogram */
  554. static
  555. void    fillPreview(GtkWidget *preview)
  556. {
  557.     guchar    *image = g_malloc(PREWIDTH3 * PREHEIGHT * 3 * sizeof(guchar)),
  558.         *pimage;
  559.     int    x, y, rowstride;
  560.  
  561.     if (!image)
  562.     {
  563.         g_warning("Ick, couldn't malloc() for preview!\n");
  564.         return;
  565.     }
  566.     bzero(image, PREWIDTH3 * PREHEIGHT * 3 * sizeof(guchar));
  567.     rowstride = PREWIDTH3 * 3;
  568.     for (x = 0; x < PREWIDTH; x++)
  569.     {
  570.         int    histcount, val;
  571.  
  572.         /*
  573.          * For every channel, calculate a logarithmic value, scale it,
  574.          * and build a one-pixel bar.
  575.          */
  576.         histcount = hist_red[x] ? hist_red[x] : 1;
  577.         val = (int) (log((double) histcount) * (PREHEIGHT / 12));
  578.         if (val > PREHEIGHT)
  579.             val = PREHEIGHT;
  580.         for (y = PREHEIGHT - 1; y > (PREHEIGHT - val); y--)
  581.         {
  582.             guchar    *pixel = image + (x * 3) + (y * rowstride);
  583.  
  584.             *pixel = 255;
  585.             *(pixel + 1) = 0;
  586.             *(pixel + 2) = 0;
  587.         }
  588.  
  589.         histcount = hist_green[x] ? hist_green[x] : 1;
  590.         val = (int) (log((double) histcount) * (PREHEIGHT / 12));
  591.         if (val > PREHEIGHT)
  592.             val = PREHEIGHT;
  593.         for (y = PREHEIGHT - 1; y > (PREHEIGHT - val); y--)
  594.         {
  595.             guchar    *pixel = image + ((x + PREWIDTH) * 3) + (y * rowstride);
  596.  
  597.             *pixel = 0;
  598.             *(pixel + 1) = 255;
  599.             *(pixel + 2) = 0;
  600.         }
  601.  
  602.         histcount = hist_blue[x] ? hist_blue[x] : 1;
  603.         val = (int) (log((double) histcount) * (PREHEIGHT / 12));
  604.         if (val > PREHEIGHT)
  605.             val = PREHEIGHT;
  606.         for (y = PREHEIGHT - 1; y > (PREHEIGHT - val); y--)
  607.         {
  608.             guchar    *pixel = image + ((x + 2 * PREWIDTH) * 3) + (y * rowstride);
  609.  
  610.             *pixel = 0;
  611.             *(pixel + 1) = 0;
  612.             *(pixel + 2) = 255;
  613.         }
  614.     }
  615.     /* move our data into the preview image */
  616.     for (pimage = image, y = 0; y < PREHEIGHT; y++)
  617.     {
  618.         gtk_preview_draw_row(GTK_PREVIEW(preview), pimage, 0, y, PREWIDTH3);
  619.         pimage += 3 * PREWIDTH3;
  620.     }
  621.     free(image);
  622. }
  623.  
  624. static
  625. void    ok_callback(GtkWidget *widget, gpointer data)
  626. {
  627.     running = 1;
  628.     gtk_widget_destroy(GTK_WIDGET(data));
  629. }
  630.  
  631. static
  632. void    close_callback(GtkWidget *widget, gpointer data)
  633. {
  634.     gtk_main_quit();
  635. }
  636.