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