Library to control a Graphics TFT connected to 4-wire SPI - revised for the Raio RA8875 Display Controller.
Dependents: FRDM_RA8875_mPaint RA8875_Demo RA8875_KeyPadDemo SignalGenerator ... more
Fork of SPI_TFT by
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 = SetWindow(ScreenX + gif_image_descriptor.image_left_position, 00490 ScreenY + gif_image_descriptor.image_top_position, 00491 ScreenX + gif_image_descriptor.image_left_position + gif_image_descriptor.image_width, 00492 ScreenY + gif_image_descriptor.image_top_position + gif_image_descriptor.image_height); 00493 pixelStream(cbmp, screen_descriptor.width * screen_descriptor.height, 00494 ScreenX + gif_image_descriptor.image_left_position, 00495 ScreenY + gif_image_descriptor.image_top_position); 00496 SetWindow(restore); 00497 #endif 00498 // end write 00499 free(cbmp); 00500 } 00501 if (local_color_table) { 00502 free(local_color_table); 00503 local_color_table = NULL; 00504 } 00505 } 00506 break; 00507 case EXTENSION_INTRODUCER: 00508 if (!process_gif_extension(fh)) { 00509 INFO("not supported format"); 00510 return not_supported_format; 00511 } 00512 break; 00513 case TRAILER: 00514 break; 00515 default: 00516 INFO("not supported format"); 00517 return not_supported_format; 00518 } 00519 } 00520 INFO("no error"); 00521 return noerror; 00522 } 00523 00524 00525 // hasGIFHeader determines if it is a GIF file 00526 // 00527 // This reads a few bytes of the file and determines if they have the 00528 // GIF89a signature. GIF87a is not supported. 00529 // 00530 // @param fh is a file handle. 00531 // @returns true if it is GIF89a. 00532 // 00533 bool GraphicsDisplay::hasGIFHeader(FILE * fh) { 00534 char GIF_Header[6]; 00535 if (read_filesystem_bytes(GIF_Header, sizeof(GIF_Header), fh) != sizeof(GIF_Header)) 00536 return false; 00537 if (strncmp("GIF89a", GIF_Header, sizeof(GIF_Header))) 00538 return false; 00539 return true; 00540 } 00541 00542 00543 RetCode_t GraphicsDisplay::GetGIFHeader(FILE * fh) { 00544 if (read_filesystem_bytes(&screen_descriptor, gif_screen_descriptor_size, fh) < gif_screen_descriptor_size) { 00545 return not_supported_format; 00546 } 00547 screen_descriptor_isvalid = true; 00548 INFO("screen_descriptor"); 00549 INFO(" width: %d", screen_descriptor.width); 00550 INFO(" height: %d", screen_descriptor.height); 00551 INFO(" fields: %02Xx", screen_descriptor.fields); 00552 INFO(" gct: %2d", (screen_descriptor.fields & 0x80) ? 1 : 0); 00553 INFO(" res: %2d", ((screen_descriptor.fields & 0x70) >> 4) + 1); 00554 INFO(" sort: %2d", (screen_descriptor.fields & 0x08) ? 1 : 0); 00555 INFO(" gct siz: %2d", 1 << ((screen_descriptor.fields & 0x07) + 1)); 00556 INFO(" back clr: %d", screen_descriptor.background_color_index); 00557 INFO(" pix rat: %d", screen_descriptor.pixel_aspect_ratio); 00558 return noerror; 00559 } 00560 00561 00562 RetCode_t GraphicsDisplay::GetGIFMetrics(gif_screen_descriptor_t * imageDescriptor, const char * Name_GIF) { 00563 RetCode_t ret = not_supported_format; 00564 00565 if (screen_descriptor_isvalid) { 00566 ret = noerror; 00567 } else { 00568 FILE *fh = fopen(Name_GIF, "rb"); 00569 if (fh) { 00570 ret = GetGIFHeader(fh); 00571 fclose(fh); 00572 } 00573 } 00574 if (ret == noerror) 00575 *imageDescriptor = screen_descriptor; 00576 return ret; 00577 } 00578 00579 00580 RetCode_t GraphicsDisplay::RenderGIFFile(loc_t x, loc_t y, const char *Name_GIF) { 00581 RetCode_t rt = file_not_found; 00582 00583 INFO("Opening {%s}", Name_GIF); 00584 screen_descriptor_isvalid = false; 00585 FILE *fh = fopen(Name_GIF, "rb"); 00586 if (fh) { 00587 if (hasGIFHeader(fh)) { 00588 rt = _RenderGIF(x, y, fh); 00589 } 00590 fclose(fh); 00591 if (global_color_table) 00592 free(global_color_table); 00593 if (local_color_table) 00594 free(local_color_table); 00595 } 00596 return rt; 00597 } 00598 00599
Generated on Tue Jul 12 2022 17:28:36 by 1.7.2