KSM edits to RA8875

Dependents:   Liz_Test_Code

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers GraphicsDisplayGIF.cpp Source File

GraphicsDisplayGIF.cpp

00001 
00002 
00003 
00004 // GIFRender.cpp : Defines the entry point for the console application.
00005 //
00006 // The code in this file was initially found online, in a tutorial.
00007 // It has been revised significantly in this derivative.
00008 // No copyright claim was found in the code.
00009 // http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art011
00010 //
00011 
00012 #include "mbed.h"
00013 
00014 #include "GraphicsDisplay.h"
00015 
00016 
00017 #define DEBUG "GIF_"
00018 // 
00019 // INFO("Stuff to show %d", var); // new-line is automatically appended
00020 //
00021 #if (defined(DEBUG) && !defined(TARGET_LPC11U24))
00022 #define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00023 #define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00024 #define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00025 static void HexDump(const char * title, const uint8_t * p, int count) {
00026     int i;
00027     char buf[100] = "0000: ";
00028 
00029     if (*title)
00030         INFO("%s", title);
00031     for (i = 0; i<count; ) {
00032         sprintf(buf + strlen(buf), "%02X ", *(p + i));
00033         if ((++i & 0x0F) == 0x00) {
00034             INFO("%s", buf);
00035             if (i < count)
00036                 sprintf(buf, "%04X: ", i);
00037             else
00038                 buf[0] = '\0';
00039         }
00040     }
00041     if (strlen(buf))
00042         INFO("%s", buf);
00043 }
00044 #else
00045 #define INFO(x, ...)
00046 #define WARN(x, ...)
00047 #define ERR(x, ...)
00048 #define HexDump(a, b, c)
00049 #endif
00050 
00051 
00052 #define EXTENSION_INTRODUCER   0x21
00053 #define IMAGE_DESCRIPTOR       0x2C
00054 #define TRAILER                0x3B
00055 
00056 #define GRAPHIC_CONTROL        0xF9
00057 #define APPLICATION_EXTENSION  0xFF
00058 #define COMMENT_EXTENSION      0xFE
00059 #define PLAINTEXT_EXTENSION    0x01
00060 
00061 
00062 typedef struct {
00063     unsigned char byte;
00064     int prev;
00065     int len;
00066 } dictionary_entry_t;
00067 
00068 typedef struct {
00069     unsigned char extension_code;
00070     unsigned char block_size;
00071 } extension_t;
00072 const uint16_t extension_size = 2;
00073 
00074 typedef struct {
00075     unsigned char fields;
00076     unsigned short delay_time;
00077     unsigned char transparent_color_index;
00078 } graphic_control_extension_t;
00079 const uint16_t graphic_control_extension_size = 4;
00080 
00081 typedef struct {
00082     unsigned char application_id[8];
00083     unsigned char version[3];
00084 } application_extension_t;
00085 const uint16_t application_extension_size = 11;
00086 
00087 typedef struct {
00088     unsigned short left;
00089     unsigned short top;
00090     unsigned short width;
00091     unsigned short height;
00092     unsigned char cell_width;
00093     unsigned char cell_height;
00094     unsigned char foreground_color;
00095     unsigned char background_color;
00096 } plaintext_extension_t;
00097 const uint16_t plaintext_extension_size = 12;
00098 
00099 
00100 size_t GraphicsDisplay::read_filesystem_bytes(void * buffer, int numBytes, FILE * fh) {
00101     size_t bytesRead;
00102 
00103     bytesRead = fread(buffer, 1, numBytes, fh);
00104     HexDump("mem", (const uint8_t *) buffer, bytesRead);
00105     return bytesRead;
00106 }
00107 
00108 
00109 /// uncompress_gif the data to memory
00110 ///
00111 /// @param[in] code_length is the size of this descriptor field
00112 /// @param[in] input is a pointer to the input data
00113 /// @param[in] input_length is the number of bytes in the input.
00114 /// @param[out] out is where the uncompress_gifed information is written.
00115 /// @return noerror on success, or failure code
00116 ///
00117 RetCode_t GraphicsDisplay::uncompress_gif(int code_length, const unsigned char *input, int input_length, unsigned char *out) {
00118     int i, bit;
00119     int code, prev = -1;
00120     dictionary_entry_t * dictionary;
00121     int dictionary_ind;
00122     unsigned int mask = 0x01;
00123     int reset_code_length;
00124     int clear_code; // This varies depending on code_length
00125     int stop_code;  // one more than clear code
00126     int match_len;
00127 
00128     clear_code = 1 << (code_length);
00129     stop_code = clear_code + 1;
00130     reset_code_length = code_length;
00131 
00132     // Create a dictionary large enough to hold "code_length" entries.
00133     // Once the dictionary overflows, code_length increases
00134     dictionary = (dictionary_entry_t *) malloc(sizeof(dictionary_entry_t) * (1 << (code_length + 1)));
00135     if (dictionary == NULL)
00136         return not_enough_ram;
00137     // Initialize the first 2^code_len entries of the dictionary with their
00138     // indices.  The rest of the entries will be built up dynamically.
00139 
00140     // Technically, it shouldn't be necessary to initialize the
00141     // dictionary.  The spec says that the encoder "should output a
00142     // clear code as the first code in the image data stream".  It doesn't
00143     // say must, though...
00144     for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
00145         dictionary[dictionary_ind].byte = (uint8_t) dictionary_ind;
00146         // XXX this only works because prev is a 32-bit int (> 12 bits)
00147         dictionary[dictionary_ind].prev = -1;
00148         dictionary[dictionary_ind].len = 1;
00149     }
00150     // 2^code_len + 1 is the special "end" code; don't give it an entry here
00151     dictionary_ind++;
00152     dictionary_ind++;
00153 
00154     // TODO verify that the very last byte is clear_code + 1
00155     while (input_length) {
00156         code = 0x0;
00157         // Always read one more bit than the code length
00158         for (i = 0; i < (code_length + 1); i++) {
00159             // This is different than in the file read example; that
00160             // was a call to "next_bit"
00161             bit = (*input & mask) ? 1 : 0;
00162             mask <<= 1;
00163             if (mask == 0x100) {
00164                 mask = 0x01;
00165                 input++;
00166                 input_length--;
00167             }
00168             code = code | (bit << i);
00169         }
00170         if (code == clear_code) {
00171             code_length = reset_code_length;
00172             dictionary = (dictionary_entry_t *) realloc(dictionary,
00173                 sizeof(dictionary_entry_t) * (1 << (code_length + 1)));
00174             for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++) {
00175                 dictionary[dictionary_ind].byte = (uint8_t) dictionary_ind;
00176                 // XXX this only works because prev is a 32-bit int (> 12 bits)
00177                 dictionary[dictionary_ind].prev = -1;
00178                 dictionary[dictionary_ind].len = 1;
00179             }
00180             dictionary_ind++;
00181             dictionary_ind++;
00182             prev = -1;
00183             continue;
00184         } else if (code == stop_code) {
00185             if (input_length > 1) {
00186                 free(dictionary);
00187                 return not_supported_format;
00188             }
00189             break;
00190         }
00191 
00192         // Update the dictionary with this character plus the _entry_
00193         // (character or string) that came before it
00194         if ((prev > -1) && (code_length < 12)) {
00195             if (code > dictionary_ind) {
00196                 //fprintf(stderr, "code = %.02x, but dictionary_ind = %.02x\n", code, dictionary_ind);
00197                 free(dictionary);
00198                 return not_supported_format;
00199             }
00200             if (code == dictionary_ind) {
00201                 int ptr = prev;
00202 
00203                 while (dictionary[ptr].prev != -1) {
00204                     ptr = dictionary[ptr].prev;
00205                 }
00206                 dictionary[dictionary_ind].byte = dictionary[ptr].byte;
00207             } else {
00208                 int ptr = code;
00209                 while (dictionary[ptr].prev != -1) {
00210                     ptr = dictionary[ptr].prev;
00211                 }
00212                 dictionary[dictionary_ind].byte = dictionary[ptr].byte;
00213             }
00214             dictionary[dictionary_ind].prev = prev;
00215             dictionary[dictionary_ind].len = dictionary[prev].len + 1;
00216             dictionary_ind++;
00217             // GIF89a mandates that this stops at 12 bits
00218             if ((dictionary_ind == (1 << (code_length + 1))) &&
00219                 (code_length < 11)) {
00220                 code_length++;
00221                 dictionary = (dictionary_entry_t *) realloc(dictionary,
00222                     sizeof(dictionary_entry_t) * (1 << (code_length + 1)));
00223             }
00224         }
00225         prev = code;
00226         // Now copy the dictionary entry backwards into "out"
00227         match_len = dictionary[code].len;
00228         while (code != -1) {
00229             int pos = dictionary[code].len - 1;
00230             out[pos] = dictionary[code].byte;
00231             //INFO("%p out[%d] = %02X\r\n", out, pos, out[pos]);
00232             if (dictionary[code].prev == code) {
00233                 free(dictionary);
00234                 return not_supported_format;
00235             }
00236             code = dictionary[code].prev;
00237         }
00238         out += match_len;
00239     }
00240     free(dictionary);
00241     return noerror;
00242 }
00243 
00244 
00245 /// read a block
00246 /// 
00247 /// @param[in] fh is the handle to the gif file.
00248 /// @param[in] data is a pointer to a pointer to the data being processed.
00249 /// @return -1 on failure, 0 when done, or >0 as the length of a processed data block
00250 ///
00251 int GraphicsDisplay::read_gif_sub_blocks(FILE * fh, unsigned char **data) {
00252     int data_length;
00253     int index;
00254     unsigned char block_size;
00255 
00256     // Everything following are data sub-blocks, until a 0-sized block is encountered.
00257     data_length = 0;
00258     *data = NULL;
00259     index = 0;
00260 
00261     while (1) {
00262         if (read_filesystem_bytes(&block_size, 1, fh) < 1) {
00263             return -1;
00264         }
00265         if (block_size == 0) {
00266             break;
00267         }
00268         data_length += block_size;
00269         *data = (unsigned char *) realloc(*data, data_length);
00270         if (data) {
00271             if (read_filesystem_bytes(*data + index, block_size, fh) < block_size) {
00272                 return -1;
00273             }
00274             index += block_size;
00275         } else {
00276             return -1;
00277         }
00278     }
00279     return data_length;
00280 }
00281 
00282 // color table is encoded as 24-bit values, but we don't need that much space, since 
00283 // the RA8875 is configured as either 8 or 16-bit color.
00284 //
00285 RetCode_t GraphicsDisplay::readColorTable(color_t * colorTable, int colorTableSize, FILE * fh) {
00286     struct {
00287         uint8_t r;
00288         uint8_t g;
00289         uint8_t b;
00290     } rgb;
00291     while (colorTableSize--) {
00292         if (read_filesystem_bytes(&rgb, 3, fh) < 3)
00293             return not_supported_format;
00294         *colorTable++ = RGB(rgb.r, rgb.g, rgb.b);
00295     }
00296     return noerror;
00297 }
00298 
00299 RetCode_t GraphicsDisplay::readGIFImageDescriptor(FILE * fh, gif_image_descriptor_t * imageDescriptor) {
00300     if (read_filesystem_bytes(imageDescriptor, gif_image_descriptor_size, fh) < gif_image_descriptor_size)
00301         return not_supported_format;
00302     INFO("gif_image_descriptor\r\n");
00303     INFO("       left: %d\r\n", imageDescriptor->image_left_position);
00304     INFO("        top: %d\r\n", imageDescriptor->image_top_position);
00305     INFO("      width: %d\r\n", imageDescriptor->image_width);
00306     INFO("     height: %d\r\n", imageDescriptor->image_height);
00307     INFO("     fields: %02Xx\r\n", imageDescriptor->fields);
00308     INFO("          lct: %2d\r\n", (imageDescriptor->fields & 0x80) ? 1 : 0);
00309     INFO("          res: %2d\r\n", (imageDescriptor->fields & 0x40) ? 1 : 0);
00310     INFO("         sort: %2d\r\n", (imageDescriptor->fields & 0x20) ? 1 : 0);
00311     INFO("          res: %2d\r\n", ((imageDescriptor->fields & 0x18) >> 3));
00312     INFO("      lct siz: %2d\r\n", 1 << ((imageDescriptor->fields & 0x07) + 1));
00313     if (imageDescriptor->fields & 0x80) {
00314         local_color_table_size = 1 << ((imageDescriptor->fields & 0x07) + 1);
00315         local_color_table = (color_t *) malloc(sizeof(color_t) * local_color_table_size);
00316         if (local_color_table == NULL)
00317             return not_enough_ram;
00318         if (readColorTable(local_color_table, local_color_table_size, fh) != noerror) {
00319             free(local_color_table);
00320             local_color_table = NULL;
00321             return not_supported_format;
00322         }
00323     }
00324     return noerror;
00325 }
00326 
00327 /// Process the image section of the GIF file
00328 ///
00329 /// @param[in] fh is the handle to the file.
00330 /// @param[in] uncompress_gifed_data is a pointer to a pointer to where the data will be placed.
00331 /// @returns true if all went well.
00332 ///
00333 RetCode_t GraphicsDisplay::process_gif_image_descriptor(FILE * fh, uint8_t ** uncompress_gifed_data, int width, int height) {
00334     int compressed_data_length;
00335     unsigned char *compressed_data = NULL;
00336     unsigned char lzw_code_size;
00337     int uncompress_gifed_data_length = 0;
00338     RetCode_t res = not_supported_format;
00339     local_color_table_size = 0;
00340 
00341     if (read_filesystem_bytes(&lzw_code_size, 1, fh) < 1)
00342         goto done;
00343     INFO("lzw_code_size\r\n");
00344     INFO("   lzw code: %d\r\n", lzw_code_size);
00345     compressed_data_length = read_gif_sub_blocks(fh, &compressed_data);
00346     if (compressed_data_length > 0) {
00347         uncompress_gifed_data_length = width * height;
00348         *uncompress_gifed_data = (unsigned char *) malloc(uncompress_gifed_data_length);
00349         if (*uncompress_gifed_data == NULL)
00350             return not_enough_ram;
00351         res = uncompress_gif(lzw_code_size, compressed_data, compressed_data_length, *uncompress_gifed_data);
00352     }
00353 done:
00354     if (compressed_data)
00355         free(compressed_data);
00356     return res;
00357 }
00358 
00359 
00360 int GraphicsDisplay::process_gif_extension(FILE * fh) {
00361     extension_t extension;
00362     graphic_control_extension_t gce;
00363     application_extension_t application;
00364     plaintext_extension_t plaintext;
00365     unsigned char *extension_data = NULL;
00366     /* int extension_data_length; */
00367 
00368     if (read_filesystem_bytes(&extension, extension_size, fh) < extension_size) {
00369         return 0;
00370     }
00371     INFO("extension\r\n");
00372     INFO("        code: %d\r\n", extension.extension_code);
00373     INFO("  block size: %d\r\n", extension.block_size);
00374     switch (extension.extension_code) {
00375         case GRAPHIC_CONTROL:
00376             if (read_filesystem_bytes(&gce, graphic_control_extension_size, fh) < graphic_control_extension_size) {
00377                 return 0;
00378             }
00379             INFO("graphic_control_extension\r\n");
00380             INFO("      fields: %d\r\n", gce.fields);
00381             INFO("  delay time: %d\r\n", gce.delay_time);
00382             INFO(" transparent: %d\r\n", gce.transparent_color_index);
00383             break;
00384         case APPLICATION_EXTENSION:
00385             if (read_filesystem_bytes(&application, application_extension_size, fh) < application_extension_size) {
00386                 return 0;
00387             }
00388             HexDump("application", (const uint8_t *) &application, sizeof(application));
00389             break;
00390         case COMMENT_EXTENSION:
00391             // comment extension; do nothing - all the data is in the
00392             // sub-blocks that follow.
00393             break;
00394         case PLAINTEXT_EXTENSION:
00395             if (read_filesystem_bytes(&plaintext, plaintext_extension_size, fh) < plaintext_extension_size) {
00396                 return 0;
00397             }
00398             HexDump("plaintext", (const uint8_t *) &plaintext, sizeof(plaintext));
00399             break;
00400         default:
00401             return (0);
00402     }
00403     // All extensions are followed by data sub-blocks; even if it's
00404     // just a single data sub-block of length 0
00405     /* extension_data_length = */ read_gif_sub_blocks(fh, &extension_data);
00406     if (extension_data != NULL)
00407         free(extension_data);
00408     return 1;
00409 }
00410 
00411 
00412 RetCode_t GraphicsDisplay::_RenderGIF(loc_t ScreenX, loc_t ScreenY, FILE * fh) {
00413     //int color_resolution_bits;
00414     global_color_table_size = 0;
00415     local_color_table_size = 0;
00416 
00417     if (GetGIFHeader(fh) != noerror)
00418         return not_supported_format;
00419     //color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
00420     if (screen_descriptor.fields & 0x80) {
00421         // If bit 7 is set, the next block is a global color table; read it
00422         global_color_table_size = 1 << ((screen_descriptor.fields & 0x07) + 1);
00423         global_color_table = (color_t *) malloc(sizeof(color_t) * global_color_table_size);
00424         if (global_color_table == NULL)
00425             return not_enough_ram;
00426         // XXX this could conceivably return a short count...
00427         if (readColorTable(global_color_table, global_color_table_size, fh) != noerror) {
00428             return not_supported_format;
00429         }
00430         HexDump("Global Color Table", (const uint8_t *) global_color_table, 3 * global_color_table_size);
00431     }
00432     unsigned char block_type = 0x0;
00433     uint8_t * uncompress_gifed_data = NULL;
00434     gif_image_descriptor_t gif_image_descriptor;        // the image fragment
00435     while (block_type != TRAILER) {
00436         if (read_filesystem_bytes(&block_type, sizeof(block_type), fh) < sizeof(block_type))
00437             return not_supported_format;
00438         INFO("block type: %02X", block_type);
00439         
00440         switch (block_type) {
00441             case IMAGE_DESCRIPTOR:
00442                 if (readGIFImageDescriptor(fh, &gif_image_descriptor) != noerror)
00443                     return not_supported_format;
00444                 if (process_gif_image_descriptor(fh, &uncompress_gifed_data, gif_image_descriptor.image_width, gif_image_descriptor.image_height) == noerror) {
00445                     if (uncompress_gifed_data) {
00446                         // Ready to render to the screen
00447                         INFO("Render to (%d,%d)\r\n", ScreenX, ScreenY);
00448                         color_t * active_color_table = (local_color_table) ? local_color_table : global_color_table;
00449 
00450                         // create a local image buffer for this fragment
00451                         color_t * cbmp = (color_t *) malloc(sizeof(color_t) * gif_image_descriptor.image_width * gif_image_descriptor.image_height);
00452                         if (cbmp == NULL)
00453                             return not_enough_ram;
00454                         for (int i = 0; i < gif_image_descriptor.image_width * gif_image_descriptor.image_height; i++) {
00455                             int cIndex = uncompress_gifed_data[i];
00456                             cbmp[i] = active_color_table[cIndex];
00457                         }
00458                         // Write Fragment to Screen
00459                         #if 0
00460                         INFO("Render fragment: (%d,%d), offset: (%d,%d), w x h (%d,%d)\r\n",
00461                             ScreenX, ScreenY,
00462                             gif_image_descriptor.image_left_position,
00463                             gif_image_descriptor.image_top_position,
00464                             gif_image_descriptor.image_width,
00465                             gif_image_descriptor.image_height);
00466                         for (uint16_t y = 0; y < gif_image_descriptor.image_height; y++) {
00467                             for (uint16_t x = 0; x < gif_image_descriptor.image_width; x++) {
00468                                 INFO("%04X ", cbmp[y * gif_image_descriptor.image_height + x]);
00469                             }
00470                             INFO("\r\n");
00471                         }
00472                         #else
00473                         rect_t restore = windowrect;
00474                         window(ScreenX + gif_image_descriptor.image_left_position,
00475                             ScreenY + gif_image_descriptor.image_top_position,
00476                             gif_image_descriptor.image_width,
00477                             gif_image_descriptor.image_height);
00478                         pixelStream(cbmp, screen_descriptor.width * screen_descriptor.height,
00479                             ScreenX + gif_image_descriptor.image_left_position,
00480                             ScreenY + gif_image_descriptor.image_top_position);
00481                         window(restore);
00482                         #endif
00483                         // end write
00484                         free(cbmp);
00485                     }
00486                     if (local_color_table) {
00487                         free(local_color_table);
00488                         local_color_table = NULL;
00489                     }
00490                 } else {
00491                     return not_supported_format;
00492                 }
00493                 break;
00494             case EXTENSION_INTRODUCER:
00495                 if (!process_gif_extension(fh))
00496                     return not_supported_format;
00497                 break;
00498             case TRAILER:
00499                 break;
00500             default:
00501                 return not_supported_format;
00502         }
00503     }
00504     return noerror;
00505 }
00506 
00507 
00508 // hasGIFHeader determines if it is a GIF file
00509 //
00510 // This reads a few bytes of the file and determines if they have the
00511 // GIF89a signature. GIF87a is not supported.
00512 //
00513 // @param fh is a file handle.
00514 // @returns true if it is GIF89a.
00515 //
00516 bool GraphicsDisplay::hasGIFHeader(FILE * fh) {
00517     char GIF_Header[6];
00518     if (read_filesystem_bytes(GIF_Header, sizeof(GIF_Header), fh) != sizeof(GIF_Header))
00519         return false;
00520     if (strncmp("GIF89a", GIF_Header, sizeof(GIF_Header)))
00521         return false;
00522     return true;
00523 }
00524 
00525 
00526 RetCode_t GraphicsDisplay::GetGIFHeader(FILE * fh) {
00527     if (read_filesystem_bytes(&screen_descriptor, gif_screen_descriptor_size, fh) < gif_screen_descriptor_size) {
00528         return not_supported_format;
00529     }
00530     screen_descriptor_isvalid = true;
00531     INFO("screen_descriptor\r\n");
00532     INFO("      width: %d\r\n", screen_descriptor.width);
00533     INFO("     height: %d\r\n", screen_descriptor.height);
00534     INFO("     fields: %02Xx\r\n", screen_descriptor.fields);
00535     INFO("          gct: %2d\r\n", (screen_descriptor.fields & 0x80) ? 1 : 0);
00536     INFO("          res: %2d\r\n", ((screen_descriptor.fields & 0x70) >> 4) + 1);
00537     INFO("         sort: %2d\r\n", (screen_descriptor.fields & 0x08) ? 1 : 0);
00538     INFO("      gct siz: %2d\r\n", 1 << ((screen_descriptor.fields & 0x07) + 1));
00539     INFO("   back clr: %d\r\n", screen_descriptor.background_color_index);
00540     INFO("    pix rat: %d\r\n", screen_descriptor.pixel_aspect_ratio);
00541     return noerror;
00542 }
00543 
00544 
00545 RetCode_t GraphicsDisplay::GetGIFMetrics(gif_screen_descriptor_t * imageDescriptor, const char * Name_GIF) {
00546     RetCode_t ret = not_supported_format;
00547 
00548     if (screen_descriptor_isvalid) {
00549         ret = noerror;
00550     } else {
00551         FILE *fh = fopen(Name_GIF, "rb");
00552         if (fh) {
00553             ret = GetGIFHeader(fh);
00554             fclose(fh);
00555         }
00556     }
00557     if (ret == noerror)
00558         *imageDescriptor = screen_descriptor;
00559     return ret;
00560 }
00561 
00562 
00563 RetCode_t GraphicsDisplay::RenderGIFFile(loc_t x, loc_t y, const char *Name_GIF) {
00564     RetCode_t rt = file_not_found;
00565 
00566     INFO("Opening {%s}", Name_GIF);
00567     screen_descriptor_isvalid = false;
00568     FILE *fh = fopen(Name_GIF, "rb");
00569     if (fh) {
00570         if (hasGIFHeader(fh)) {
00571             rt = _RenderGIF(x, y, fh);
00572         }
00573         fclose(fh);
00574         if (global_color_table)
00575             free(global_color_table);
00576         if (local_color_table)
00577             free(local_color_table);
00578     }
00579     return rt;
00580 }
00581 
00582