00001 #include "clip.h"
00002 #include "bchash.h"
00003 #include "filexml.h"
00004 #include "keyframe.h"
00005 #include "language.h"
00006 #include "picon_png.h"
00007 #include "timeavg.h"
00008 #include "timeavgwindow.h"
00009 #include "vframe.h"
00010
00011
00012
00013
00014 #include <stdint.h>
00015 #include <string.h>
00016
00017
00018
00019
00020 REGISTER_PLUGIN(TimeAvgMain)
00021
00022
00023
00024
00025
00026 TimeAvgConfig::TimeAvgConfig()
00027 {
00028 frames = 1;
00029 mode = TimeAvgConfig::AVERAGE;
00030 paranoid = 0;
00031 nosubtract = 0;
00032 }
00033
00034 void TimeAvgConfig::copy_from(TimeAvgConfig *src)
00035 {
00036 this->frames = src->frames;
00037 this->mode = src->mode;
00038 this->paranoid = src->paranoid;
00039 this->nosubtract = src->nosubtract;
00040 }
00041
00042 int TimeAvgConfig::equivalent(TimeAvgConfig *src)
00043 {
00044 return frames == src->frames &&
00045 mode == src->mode &&
00046 paranoid == src->paranoid &&
00047 nosubtract == src->nosubtract;
00048 }
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062 TimeAvgMain::TimeAvgMain(PluginServer *server)
00063 : PluginVClient(server)
00064 {
00065 PLUGIN_CONSTRUCTOR_MACRO
00066 accumulation = 0;
00067 history = 0;
00068 history_size = 0;
00069 history_start = -0x7fffffff;
00070 history_frame = 0;
00071 history_valid = 0;
00072 prev_frame = -1;
00073 }
00074
00075 TimeAvgMain::~TimeAvgMain()
00076 {
00077 PLUGIN_DESTRUCTOR_MACRO
00078
00079 if(accumulation) delete [] accumulation;
00080 if(history)
00081 {
00082 for(int i = 0; i < config.frames; i++)
00083 delete history[i];
00084 delete [] history;
00085 }
00086 if(history_frame) delete [] history_frame;
00087 if(history_valid) delete [] history_valid;
00088 }
00089
00090 char* TimeAvgMain::plugin_title() { return N_("Time Average"); }
00091 int TimeAvgMain::is_realtime() { return 1; }
00092
00093
00094 NEW_PICON_MACRO(TimeAvgMain)
00095
00096 SHOW_GUI_MACRO(TimeAvgMain, TimeAvgThread)
00097
00098 SET_STRING_MACRO(TimeAvgMain)
00099
00100 RAISE_WINDOW_MACRO(TimeAvgMain);
00101
00102
00103
00104 int TimeAvgMain::process_buffer(VFrame *frame,
00105 int64_t start_position,
00106 double frame_rate)
00107 {
00108 int h = frame->get_h();
00109 int w = frame->get_w();
00110 int color_model = frame->get_color_model();
00111
00112 load_configuration();
00113
00114
00115 if(!accumulation)
00116 {
00117 accumulation = new unsigned char[w *
00118 h *
00119 cmodel_components(color_model) *
00120 MAX(sizeof(float), sizeof(int))];
00121 clear_accum(w, h, color_model);
00122 }
00123
00124 if(!config.nosubtract)
00125 {
00126
00127 if(history)
00128 {
00129 if(config.frames != history_size)
00130 {
00131 VFrame **history2;
00132 int64_t *history_frame2;
00133 int *history_valid2;
00134 history2 = new VFrame*[config.frames];
00135 history_frame2 = new int64_t[config.frames];
00136 history_valid2 = new int[config.frames];
00137
00138
00139 int i, j;
00140 for(i = 0, j = 0; i < config.frames && j < history_size; i++, j++)
00141 {
00142 history2[i] = history[j];
00143 history_frame2[i] = history_frame[i];
00144 history_valid2[i] = history_valid[i];
00145 }
00146
00147
00148 for( ; j < history_size; j++)
00149 {
00150 subtract_accum(history[j]);
00151 delete history[j];
00152 }
00153 delete [] history;
00154 delete [] history_frame;
00155 delete [] history_valid;
00156
00157
00158
00159 for( ; i < config.frames; i++)
00160 {
00161 history2[i] = new VFrame(0, w, h, color_model);
00162 history_frame2[i] = -0x7fffffff;
00163 history_valid2[i] = 0;
00164 }
00165
00166 history = history2;
00167 history_frame = history_frame2;
00168 history_valid = history_valid2;
00169
00170 history_size = config.frames;
00171 }
00172 }
00173 else
00174
00175 {
00176 history = new VFrame*[config.frames];
00177 for(int i = 0; i < config.frames; i++)
00178 history[i] = new VFrame(0, w, h, color_model);
00179 history_size = config.frames;
00180 history_frame = new int64_t[config.frames];
00181 bzero(history_frame, sizeof(int64_t) * config.frames);
00182 history_valid = new int[config.frames];
00183 bzero(history_valid, sizeof(int) * config.frames);
00184 }
00185
00186
00187
00188
00189
00190
00191
00192 int64_t *new_history_frames = new int64_t[history_size];
00193 for(int i = 0; i < history_size; i++)
00194 {
00195 new_history_frames[history_size - i - 1] = start_position - i;
00196 }
00197
00198
00199 int no_change = 1;
00200 for(int i = 0; i < history_size; i++)
00201 {
00202
00203 if(history_valid[i])
00204 {
00205 int got_it = 0;
00206 for(int j = 0; j < history_size; j++)
00207 {
00208
00209 if(history_frame[i] == new_history_frames[j])
00210 {
00211 got_it = 1;
00212 break;
00213 }
00214 }
00215
00216
00217 if(!got_it)
00218 {
00219 subtract_accum(history[i]);
00220 history_valid[i] = 0;
00221 no_change = 0;
00222 }
00223 }
00224 }
00225
00226 if(config.paranoid && no_change)
00227 {
00228 for(int i = 0; i < history_size; i++)
00229 {
00230 history_valid[i] = 0;
00231 }
00232 clear_accum(w, h, color_model);
00233 }
00234
00235
00236 for(int i = 0; i < history_size; i++)
00237 {
00238
00239 int got_it = 0;
00240 for(int j = 0; j < history_size; j++)
00241 {
00242 if(history_valid[j] && history_frame[j] == new_history_frames[i])
00243 {
00244 got_it = 1;
00245 break;
00246 }
00247 }
00248
00249
00250 if(!got_it)
00251 {
00252
00253 for(int j = 0; j < history_size; j++)
00254 {
00255 if(!history_valid[j])
00256 {
00257
00258 history_frame[j] = new_history_frames[i];
00259 history_valid[j] = 1;
00260 read_frame(history[j],
00261 0,
00262 history_frame[j],
00263 frame_rate);
00264 add_accum(history[j]);
00265 break;
00266 }
00267 }
00268 }
00269 }
00270 delete [] new_history_frames;
00271 }
00272 else
00273
00274 {
00275
00276 if(config.paranoid && prev_frame == start_position ||
00277 prev_frame < 0)
00278 {
00279 prev_frame = start_position - config.frames + 1;
00280 prev_frame = MAX(0, prev_frame);
00281 clear_accum(w, h, color_model);
00282 }
00283
00284 for(int64_t i = prev_frame; i <= start_position; i++)
00285 {
00286 read_frame(frame,
00287 0,
00288 i,
00289 frame_rate);
00290 add_accum(frame);
00291 printf("TimeAvgMain::process_buffer 1 %lld %lld %lld\n", prev_frame, start_position, i);
00292 }
00293
00294 prev_frame = start_position;
00295 }
00296
00297
00298
00299
00300
00301
00302
00303
00304 transfer_accum(frame);
00305
00306 printf("TimeAvgMain::process_buffer 2\n");
00307
00308
00309 return 0;
00310 }
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322 #define CLEAR_ACCUM(type, components, chroma) \
00323 { \
00324 type *row = (type*)accumulation; \
00325 if(chroma) \
00326 { \
00327 for(int i = 0; i < w * h; i++) \
00328 { \
00329 *row++ = 0x0; \
00330 *row++ = chroma; \
00331 *row++ = chroma; \
00332 if(components == 4) *row++ = 0x0; \
00333 } \
00334 } \
00335 else \
00336 { \
00337 bzero(row, w * h * sizeof(type) * components); \
00338 } \
00339 }
00340
00341
00342 void TimeAvgMain::clear_accum(int w, int h, int color_model)
00343 {
00344 switch(color_model)
00345 {
00346 case BC_RGB888:
00347 CLEAR_ACCUM(int, 3, 0x0)
00348 break;
00349 case BC_RGB_FLOAT:
00350 CLEAR_ACCUM(float, 3, 0x0)
00351 break;
00352 case BC_RGBA8888:
00353 CLEAR_ACCUM(int, 4, 0x0)
00354 break;
00355 case BC_RGBA_FLOAT:
00356 CLEAR_ACCUM(float, 4, 0x0)
00357 break;
00358 case BC_YUV888:
00359 CLEAR_ACCUM(int, 3, 0x80)
00360 break;
00361 case BC_YUVA8888:
00362 CLEAR_ACCUM(int, 4, 0x80)
00363 break;
00364 case BC_YUV161616:
00365 CLEAR_ACCUM(int, 3, 0x8000)
00366 break;
00367 case BC_YUVA16161616:
00368 CLEAR_ACCUM(int, 4, 0x8000)
00369 break;
00370 }
00371 }
00372
00373
00374 #define SUBTRACT_ACCUM(type, \
00375 accum_type, \
00376 components, \
00377 chroma) \
00378 { \
00379 if(config.mode == TimeAvgConfig::OR) \
00380 { \
00381 for(int i = 0; i < h; i++) \
00382 { \
00383 accum_type *accum_row = (accum_type*)accumulation + \
00384 i * w * components; \
00385 type *frame_row = (type*)frame->get_rows()[i]; \
00386 for(int j = 0; j < w; j++) \
00387 { \
00388 if(components == 4) \
00389 { \
00390 frame_row += 3; \
00391 if(*frame_row++) \
00392 { \
00393 *accum_row++ = 0; \
00394 *accum_row++ = chroma; \
00395 *accum_row++ = chroma; \
00396 *accum_row++ = 0; \
00397 } \
00398 } \
00399 else \
00400 { \
00401 if(*frame_row++ != 0 || \
00402 *frame_row++ != chroma || \
00403 *frame_row++ != chroma) \
00404 { \
00405 *accum_row++ = 0; \
00406 *accum_row++ = chroma; \
00407 *accum_row++ = chroma; \
00408 } \
00409 } \
00410 } \
00411 } \
00412 } \
00413 else \
00414 { \
00415 for(int i = 0; i < h; i++) \
00416 { \
00417 accum_type *accum_row = (accum_type*)accumulation + \
00418 i * w * components; \
00419 type *frame_row = (type*)frame->get_rows()[i]; \
00420 for(int j = 0; j < w; j++) \
00421 { \
00422 *accum_row++ -= *frame_row++; \
00423 *accum_row++ -= (accum_type)*frame_row++ - chroma; \
00424 *accum_row++ -= (accum_type)*frame_row++ - chroma; \
00425 if(components == 4) *accum_row++ -= *frame_row++; \
00426 } \
00427 } \
00428 } \
00429 }
00430
00431
00432 void TimeAvgMain::subtract_accum(VFrame *frame)
00433 {
00434
00435 if(config.nosubtract) return;
00436 int w = frame->get_w();
00437 int h = frame->get_h();
00438
00439 switch(frame->get_color_model())
00440 {
00441 case BC_RGB888:
00442 SUBTRACT_ACCUM(unsigned char, int, 3, 0x0)
00443 break;
00444 case BC_RGB_FLOAT:
00445 SUBTRACT_ACCUM(float, float, 3, 0x0)
00446 break;
00447 case BC_RGBA8888:
00448 SUBTRACT_ACCUM(unsigned char, int, 4, 0x0)
00449 break;
00450 case BC_RGBA_FLOAT:
00451 SUBTRACT_ACCUM(float, float, 4, 0x0)
00452 break;
00453 case BC_YUV888:
00454 SUBTRACT_ACCUM(unsigned char, int, 3, 0x80)
00455 break;
00456 case BC_YUVA8888:
00457 SUBTRACT_ACCUM(unsigned char, int, 4, 0x80)
00458 break;
00459 case BC_YUV161616:
00460 SUBTRACT_ACCUM(uint16_t, int, 3, 0x8000)
00461 break;
00462 case BC_YUVA16161616:
00463 SUBTRACT_ACCUM(uint16_t, int, 4, 0x8000)
00464 break;
00465 }
00466 }
00467
00468
00469
00470
00471 #define ADD_ACCUM(type, accum_type, components, chroma, max) \
00472 { \
00473 if(config.mode == TimeAvgConfig::OR) \
00474 { \
00475 for(int i = 0; i < h; i++) \
00476 { \
00477 accum_type *accum_row = (accum_type*)accumulation + \
00478 i * w * components; \
00479 type *frame_row = (type*)frame->get_rows()[i]; \
00480 for(int j = 0; j < w; j++) \
00481 { \
00482 if(components == 4) \
00483 { \
00484 accum_type opacity = frame_row[3]; \
00485 accum_type transparency = max - opacity; \
00486 *accum_row = (opacity * *frame_row + transparency * *accum_row) / max; \
00487 accum_row++; \
00488 frame_row++; \
00489 *accum_row = chroma + (opacity * (*frame_row - chroma) + transparency * (*accum_row - chroma)) / max; \
00490 accum_row++; \
00491 frame_row++; \
00492 *accum_row = chroma + (opacity * (*frame_row - chroma) + transparency * (*accum_row - chroma)) / max; \
00493 accum_row++; \
00494 frame_row++; \
00495 *accum_row = MAX(*frame_row, *accum_row); \
00496 accum_row++; \
00497 frame_row++; \
00498 } \
00499 else \
00500 if(sizeof(type) == 4) \
00501 { \
00502 if(frame_row[0] > 0.001 || \
00503 frame_row[1] > 0.001 || \
00504 frame_row[2] > 0.001) \
00505 { \
00506 *accum_row++ = *frame_row++; \
00507 *accum_row++ = *frame_row++; \
00508 *accum_row++ = *frame_row++; \
00509 } \
00510 else \
00511 { \
00512 frame_row += 3; \
00513 accum_row += 3; \
00514 } \
00515 } \
00516 else \
00517 if(chroma) \
00518 { \
00519 if(frame_row[0]) \
00520 { \
00521 *accum_row++ = *frame_row++; \
00522 *accum_row++ = *frame_row++; \
00523 *accum_row++ = *frame_row++; \
00524 } \
00525 else \
00526 { \
00527 frame_row += 3; \
00528 accum_row += 3; \
00529 } \
00530 } \
00531 else \
00532 { \
00533 if(frame_row[0] || \
00534 frame_row[1] || \
00535 frame_row[2]) \
00536 { \
00537 *accum_row++ = *frame_row++; \
00538 *accum_row++ = *frame_row++; \
00539 *accum_row++ = *frame_row++; \
00540 } \
00541 else \
00542 { \
00543 frame_row += 3; \
00544 accum_row += 3; \
00545 } \
00546 } \
00547 } \
00548 } \
00549 } \
00550 else \
00551 { \
00552 for(int i = 0; i < h; i++) \
00553 { \
00554 accum_type *accum_row = (accum_type*)accumulation + \
00555 i * w * components; \
00556 type *frame_row = (type*)frame->get_rows()[i]; \
00557 for(int j = 0; j < w; j++) \
00558 { \
00559 *accum_row++ += *frame_row++; \
00560 *accum_row++ += (accum_type)*frame_row++ - chroma; \
00561 *accum_row++ += (accum_type)*frame_row++ - chroma; \
00562 if(components == 4) *accum_row++ += *frame_row++; \
00563 } \
00564 } \
00565 } \
00566 }
00567
00568
00569 void TimeAvgMain::add_accum(VFrame *frame)
00570 {
00571 int w = frame->get_w();
00572 int h = frame->get_h();
00573
00574 switch(frame->get_color_model())
00575 {
00576 case BC_RGB888:
00577 ADD_ACCUM(unsigned char, int, 3, 0x0, 0xff)
00578 break;
00579 case BC_RGB_FLOAT:
00580 ADD_ACCUM(float, float, 3, 0x0, 1.0)
00581 break;
00582 case BC_RGBA8888:
00583 ADD_ACCUM(unsigned char, int, 4, 0x0, 0xff)
00584 break;
00585 case BC_RGBA_FLOAT:
00586 ADD_ACCUM(float, float, 4, 0x0, 1.0)
00587 break;
00588 case BC_YUV888:
00589 ADD_ACCUM(unsigned char, int, 3, 0x80, 0xff)
00590 break;
00591 case BC_YUVA8888:
00592 ADD_ACCUM(unsigned char, int, 4, 0x80, 0xff)
00593 break;
00594 case BC_YUV161616:
00595 ADD_ACCUM(uint16_t, int, 3, 0x8000, 0xffff)
00596 break;
00597 case BC_YUVA16161616:
00598 ADD_ACCUM(uint16_t, int, 4, 0x8000, 0xffff)
00599 break;
00600 }
00601 }
00602
00603 #define TRANSFER_ACCUM(type, accum_type, components, chroma, max) \
00604 { \
00605 if(config.mode == TimeAvgConfig::AVERAGE) \
00606 { \
00607 accum_type denominator = config.frames; \
00608 for(int i = 0; i < h; i++) \
00609 { \
00610 accum_type *accum_row = (accum_type*)accumulation + \
00611 i * w * components; \
00612 type *frame_row = (type*)frame->get_rows()[i]; \
00613 for(int j = 0; j < w; j++) \
00614 { \
00615 *frame_row++ = *accum_row++ / denominator; \
00616 *frame_row++ = (*accum_row++ - chroma) / denominator + chroma; \
00617 *frame_row++ = (*accum_row++ - chroma) / denominator + chroma; \
00618 if(components == 4) *frame_row++ = *accum_row++ / denominator; \
00619 } \
00620 } \
00621 } \
00622 else \
00623 if(config.mode == TimeAvgConfig::ACCUMULATE) \
00624 { \
00625 for(int i = 0; i < h; i++) \
00626 { \
00627 accum_type *accum_row = (accum_type*)accumulation + \
00628 i * w * components; \
00629 type *frame_row = (type*)frame->get_rows()[i]; \
00630 for(int j = 0; j < w; j++) \
00631 { \
00632 if(sizeof(type) < 4) \
00633 { \
00634 accum_type r = *accum_row++; \
00635 accum_type g = *accum_row++ + chroma; \
00636 accum_type b = *accum_row++ + chroma; \
00637 *frame_row++ = CLIP(r, 0, max); \
00638 *frame_row++ = CLIP(g, 0, max); \
00639 *frame_row++ = CLIP(b, 0, max); \
00640 if(components == 4) \
00641 { \
00642 accum_type a = *accum_row++; \
00643 *frame_row++ = CLIP(a, 0, max); \
00644 } \
00645 } \
00646 else \
00647 { \
00648 *frame_row++ = *accum_row++; \
00649 *frame_row++ = *accum_row++ + chroma; \
00650 *frame_row++ = *accum_row++ + chroma; \
00651 if(components == 4) \
00652 { \
00653 *frame_row++ = *accum_row++; \
00654 } \
00655 } \
00656 } \
00657 } \
00658 } \
00659 else \
00660 { \
00661 for(int i = 0; i < h; i++) \
00662 { \
00663 accum_type *accum_row = (accum_type*)accumulation + \
00664 i * w * components; \
00665 type *frame_row = (type*)frame->get_rows()[i]; \
00666 for(int j = 0; j < w; j++) \
00667 { \
00668 *frame_row++ = *accum_row++; \
00669 *frame_row++ = *accum_row++; \
00670 *frame_row++ = *accum_row++; \
00671 if(components == 4) *frame_row++ = *accum_row++; \
00672 } \
00673 } \
00674 } \
00675 }
00676
00677
00678 void TimeAvgMain::transfer_accum(VFrame *frame)
00679 {
00680 int w = frame->get_w();
00681 int h = frame->get_h();
00682
00683 switch(frame->get_color_model())
00684 {
00685 case BC_RGB888:
00686 TRANSFER_ACCUM(unsigned char, int, 3, 0x0, 0xff)
00687 break;
00688 case BC_RGB_FLOAT:
00689 TRANSFER_ACCUM(float, float, 3, 0x0, 1)
00690 break;
00691 case BC_RGBA8888:
00692 TRANSFER_ACCUM(unsigned char, int, 4, 0x0, 0xff)
00693 break;
00694 case BC_RGBA_FLOAT:
00695 TRANSFER_ACCUM(float, float, 4, 0x0, 1)
00696 break;
00697 case BC_YUV888:
00698 TRANSFER_ACCUM(unsigned char, int, 3, 0x80, 0xff)
00699 break;
00700 case BC_YUVA8888:
00701 TRANSFER_ACCUM(unsigned char, int, 4, 0x80, 0xff)
00702 break;
00703 case BC_YUV161616:
00704 TRANSFER_ACCUM(uint16_t, int, 3, 0x8000, 0xffff)
00705 break;
00706 case BC_YUVA16161616:
00707 TRANSFER_ACCUM(uint16_t, int, 4, 0x8000, 0xffff)
00708 break;
00709 }
00710 }
00711
00712
00713 int TimeAvgMain::load_defaults()
00714 {
00715 char directory[BCTEXTLEN], string[BCTEXTLEN];
00716
00717 sprintf(directory, "%stimeavg.rc", BCASTDIR);
00718
00719
00720 defaults = new BC_Hash(directory);
00721 defaults->load();
00722
00723 config.frames = defaults->get("FRAMES", config.frames);
00724 config.mode = defaults->get("MODE", config.mode);
00725 config.paranoid = defaults->get("PARANOID", config.paranoid);
00726 config.nosubtract = defaults->get("NOSUBTRACT", config.nosubtract);
00727 return 0;
00728 }
00729
00730 int TimeAvgMain::save_defaults()
00731 {
00732 defaults->update("FRAMES", config.frames);
00733 defaults->update("MODE", config.mode);
00734 defaults->update("PARANOID", config.paranoid);
00735 defaults->update("NOSUBTRACT", config.nosubtract);
00736 defaults->save();
00737 return 0;
00738 }
00739
00740 int TimeAvgMain::load_configuration()
00741 {
00742 KeyFrame *prev_keyframe;
00743 TimeAvgConfig old_config;
00744 old_config.copy_from(&config);
00745
00746 prev_keyframe = get_prev_keyframe(get_source_position());
00747 read_data(prev_keyframe);
00748 return !old_config.equivalent(&config);
00749 }
00750
00751 void TimeAvgMain::save_data(KeyFrame *keyframe)
00752 {
00753 FileXML output;
00754
00755
00756 output.set_shared_string(keyframe->data, MESSAGESIZE);
00757 output.tag.set_title("TIME_AVERAGE");
00758 output.tag.set_property("FRAMES", config.frames);
00759 output.tag.set_property("MODE", config.mode);
00760 output.tag.set_property("PARANOID", config.paranoid);
00761 output.tag.set_property("NOSUBTRACT", config.nosubtract);
00762 output.append_tag();
00763 output.tag.set_title("/TIME_AVERAGE");
00764 output.append_tag();
00765 output.terminate_string();
00766 }
00767
00768 void TimeAvgMain::read_data(KeyFrame *keyframe)
00769 {
00770 FileXML input;
00771
00772 input.set_shared_string(keyframe->data, strlen(keyframe->data));
00773
00774 int result = 0;
00775
00776 while(!input.read_tag())
00777 {
00778 if(input.tag.title_is("TIME_AVERAGE"))
00779 {
00780 config.frames = input.tag.get_property("FRAMES", config.frames);
00781 config.mode = input.tag.get_property("MODE", config.mode);
00782 config.paranoid = input.tag.get_property("PARANOID", config.paranoid);
00783 config.nosubtract = input.tag.get_property("NOSUBTRACT", config.nosubtract);
00784 }
00785 }
00786 }
00787
00788
00789 void TimeAvgMain::update_gui()
00790 {
00791 if(thread)
00792 {
00793 if(load_configuration())
00794 {
00795 thread->window->lock_window("TimeAvgMain::update_gui");
00796 thread->window->total_frames->update(config.frames);
00797 thread->window->accum->update(config.mode == TimeAvgConfig::ACCUMULATE);
00798 thread->window->avg->update(config.mode == TimeAvgConfig::AVERAGE);
00799 thread->window->inclusive_or->update(config.mode == TimeAvgConfig::OR);
00800 thread->window->paranoid->update(config.paranoid);
00801 thread->window->no_subtract->update(config.nosubtract);
00802 thread->window->unlock_window();
00803 }
00804 }
00805 }
00806
00807
00808
00809
00810
00811
00812
00813
00814
00815