merged hv7 mod
[goodguy/history.git] / cinelerra-5.1 / plugins / chromakey / chromakey.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "bcdisplayinfo.h"
23 #include "bcsignals.h"
24 #include "chromakey.h"
25 #include "clip.h"
26 #include "bchash.h"
27 #include "filexml.h"
28 #include "guicast.h"
29 #include "keyframe.h"
30 #include "language.h"
31 #include "loadbalance.h"
32 #include "playback3d.h"
33 #include "bccolors.h"
34 #include "pluginvclient.h"
35 #include "vframe.h"
36
37 #include <stdint.h>
38 #include <string.h>
39
40
41
42
43
44 ChromaKeyConfig::ChromaKeyConfig()
45 {
46         red = 0.0;
47         green = 0.0;
48         blue = 0.0;
49         threshold = 60.0;
50         use_value = 0;
51         slope = 100;
52 }
53
54
55 void ChromaKeyConfig::copy_from(ChromaKeyConfig &src)
56 {
57         red = src.red;
58         green = src.green;
59         blue = src.blue;
60         threshold = src.threshold;
61         use_value = src.use_value;
62         slope = src.slope;
63 }
64
65 int ChromaKeyConfig::equivalent(ChromaKeyConfig &src)
66 {
67         return (EQUIV(red, src.red) &&
68                 EQUIV(green, src.green) &&
69                 EQUIV(blue, src.blue) &&
70                 EQUIV(threshold, src.threshold) &&
71                 EQUIV(slope, src.slope) &&
72                 use_value == src.use_value);
73 }
74
75 void ChromaKeyConfig::interpolate(ChromaKeyConfig &prev,
76         ChromaKeyConfig &next,
77         int64_t prev_frame,
78         int64_t next_frame,
79         int64_t current_frame)
80 {
81         double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
82         double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
83
84         this->red = prev.red * prev_scale + next.red * next_scale;
85         this->green = prev.green * prev_scale + next.green * next_scale;
86         this->blue = prev.blue * prev_scale + next.blue * next_scale;
87         this->threshold = prev.threshold * prev_scale + next.threshold * next_scale;
88         this->slope = prev.slope * prev_scale + next.slope * next_scale;
89         this->use_value = prev.use_value;
90 }
91
92 int ChromaKeyConfig::get_color()
93 {
94         int red = (int)(CLIP(this->red, 0, 1) * 0xff);
95         int green = (int)(CLIP(this->green, 0, 1) * 0xff);
96         int blue = (int)(CLIP(this->blue, 0, 1) * 0xff);
97         return (red << 16) | (green << 8) | blue;
98 }
99
100
101
102
103
104
105
106 ChromaKeyWindow::ChromaKeyWindow(ChromaKey *plugin)
107  : PluginClientWindow(plugin,
108         320,
109         220,
110         320,
111         220,
112         0)
113 {
114         this->plugin = plugin;
115         color_thread = 0;
116 }
117
118 ChromaKeyWindow::~ChromaKeyWindow()
119 {
120         delete color_thread;
121 }
122
123 void ChromaKeyWindow::create_objects()
124 {
125         int x = 10, y = 10, x1 = 100;
126
127         BC_Title *title;
128         add_subwindow(title = new BC_Title(x, y, _("Color:")));
129         x += title->get_w() + 10;
130         add_subwindow(color = new ChromaKeyColor(plugin, this, x, y));
131         x += color->get_w() + 10;
132         add_subwindow(sample = new BC_SubWindow(x, y, 100, 50));
133         y += sample->get_h() + 10;
134         x = 10;
135
136         add_subwindow(new BC_Title(x, y, _("Slope:")));
137         add_subwindow(slope = new ChromaKeySlope(plugin, x1, y));
138
139         y += 30;
140         add_subwindow(new BC_Title(x, y, _("Threshold:")));
141         add_subwindow(threshold = new ChromaKeyThreshold(plugin, x1, y));
142
143
144         y += 30;
145         add_subwindow(use_value = new ChromaKeyUseValue(plugin, x1, y));
146
147         y += 30;
148         add_subwindow(use_colorpicker = new ChromaKeyUseColorPicker(plugin, this, x1, y));
149
150         color_thread = new ChromaKeyColorThread(plugin, this);
151
152         update_sample();
153         show_window();
154         flush();
155 }
156
157 void ChromaKeyWindow::update_sample()
158 {
159         sample->set_color(plugin->config.get_color());
160         sample->draw_box(0,
161                 0,
162                 sample->get_w(),
163                 sample->get_h());
164         sample->set_color(BLACK);
165         sample->draw_rectangle(0,
166                 0,
167                 sample->get_w(),
168                 sample->get_h());
169         sample->flash();
170 }
171
172 void ChromaKeyWindow::done_event(int result)
173 {
174         color_thread->close_window();
175 }
176
177
178
179
180
181
182
183
184
185 ChromaKeyColor::ChromaKeyColor(ChromaKey *plugin,
186         ChromaKeyWindow *gui,
187         int x,
188         int y)
189  : BC_GenericButton(x,
190         y,
191         _("Color..."))
192 {
193         this->plugin = plugin;
194         this->gui = gui;
195 }
196 int ChromaKeyColor::handle_event()
197 {
198         gui->color_thread->start_window(
199                 plugin->config.get_color(),
200                 0xff);
201         return 1;
202 }
203
204
205
206
207 ChromaKeyThreshold::ChromaKeyThreshold(ChromaKey *plugin, int x, int y)
208  : BC_FSlider(x,
209                         y,
210                         0,
211                         200,
212                         200,
213                         (float)0,
214                         (float)100,
215                         plugin->config.threshold)
216 {
217         this->plugin = plugin;
218         set_precision(0.01);
219 }
220 int ChromaKeyThreshold::handle_event()
221 {
222         plugin->config.threshold = get_value();
223         plugin->send_configure_change();
224         return 1;
225 }
226
227
228 ChromaKeySlope::ChromaKeySlope(ChromaKey *plugin, int x, int y)
229  : BC_FSlider(x,
230                         y,
231                         0,
232                         200,
233                         200,
234                         (float)0,
235                         (float)100,
236                         plugin->config.slope)
237 {
238         this->plugin = plugin;
239         set_precision(0.01);
240 }
241
242 int ChromaKeySlope::handle_event()
243 {
244         plugin->config.slope = get_value();
245         plugin->send_configure_change();
246         return 1;
247 }
248
249
250 ChromaKeyUseValue::ChromaKeyUseValue(ChromaKey *plugin, int x, int y)
251  : BC_CheckBox(x, y, plugin->config.use_value, _("Use value"))
252 {
253         this->plugin = plugin;
254 }
255 int ChromaKeyUseValue::handle_event()
256 {
257         plugin->config.use_value = get_value();
258         plugin->send_configure_change();
259         return 1;
260 }
261
262
263 ChromaKeyUseColorPicker::ChromaKeyUseColorPicker(ChromaKey *plugin,
264         ChromaKeyWindow *gui,
265         int x,
266         int y)
267  : BC_GenericButton(x, y, _("Use color picker"))
268 {
269         this->plugin = plugin;
270         this->gui = gui;
271 }
272
273 int ChromaKeyUseColorPicker::handle_event()
274 {
275         plugin->config.red = plugin->get_red();
276         plugin->config.green = plugin->get_green();
277         plugin->config.blue = plugin->get_blue();
278         gui->update_sample();
279         plugin->send_configure_change();
280         return 1;
281 }
282
283
284
285
286 ChromaKeyColorThread::ChromaKeyColorThread(ChromaKey *plugin, ChromaKeyWindow *gui)
287  : ColorPicker(1, _("Inner color"))
288 {
289         this->plugin = plugin;
290         this->gui = gui;
291 }
292
293 int ChromaKeyColorThread::handle_new_color(int output, int alpha)
294 {
295         plugin->config.red = (float)(output & 0xff0000) / 0xff0000;
296         plugin->config.green = (float)(output & 0xff00) / 0xff00;
297         plugin->config.blue = (float)(output & 0xff) / 0xff;
298         gui->update_sample();
299         plugin->send_configure_change();
300         return 1;
301 }
302
303
304
305
306
307
308
309
310
311
312 ChromaKeyServer::ChromaKeyServer(ChromaKey *plugin)
313  : LoadServer(plugin->PluginClient::smp + 1, plugin->PluginClient::smp + 1)
314 {
315         this->plugin = plugin;
316 }
317 void ChromaKeyServer::init_packages()
318 {
319         for(int i = 0; i < get_total_packages(); i++)
320         {
321                 ChromaKeyPackage *pkg = (ChromaKeyPackage*)get_package(i);
322                 pkg->y1 = plugin->input->get_h() * i / get_total_packages();
323                 pkg->y2 = plugin->input->get_h() * (i + 1) / get_total_packages();
324         }
325
326 }
327 LoadClient* ChromaKeyServer::new_client()
328 {
329         return new ChromaKeyUnit(plugin, this);
330 }
331 LoadPackage* ChromaKeyServer::new_package()
332 {
333         return new ChromaKeyPackage;
334 }
335
336
337
338 ChromaKeyPackage::ChromaKeyPackage()
339  : LoadPackage()
340 {
341 }
342
343 ChromaKeyUnit::ChromaKeyUnit(ChromaKey *plugin, ChromaKeyServer *server)
344  : LoadClient(server)
345 {
346         this->plugin = plugin;
347 }
348
349
350 void ChromaKeyUnit::process_package(LoadPackage *package)
351 {
352         ChromaKeyPackage *pkg = (ChromaKeyPackage*)package;
353
354         int w = plugin->input->get_w();
355
356         float h, s, v;
357         HSV::rgb_to_hsv(plugin->config.red,
358                 plugin->config.green,
359                 plugin->config.blue,
360                 h,
361                 s,
362                 v);
363         //float min_hue = h - plugin->config.threshold * 360 / 100;
364         //float max_hue = h + plugin->config.threshold * 360 / 100;
365
366
367 #define RGB_TO_VALUE(r, g, b) \
368 ((r) * R_TO_Y + (g) * G_TO_Y + (b) * B_TO_Y)
369
370
371 #define OUTER_VARIABLES(plugin) \
372         YUV yuv; \
373         float value = RGB_TO_VALUE(plugin->config.red, \
374                 plugin->config.green, \
375                 plugin->config.blue); \
376         float threshold = plugin->config.threshold / 100; \
377         float min_v = value - threshold; \
378         float max_v = value + threshold; \
379         float r_key = plugin->config.red; \
380         float g_key = plugin->config.green; \
381         float b_key = plugin->config.blue; \
382         int y_key, u_key, v_key; \
383         yuv.rgb_to_yuv_8((int)(r_key * 0xff), (int)(g_key * 0xff), (int)(b_key * 0xff), y_key, u_key, v_key); \
384         float run = plugin->config.slope / 100; \
385         float threshold_run = threshold + run;
386
387         OUTER_VARIABLES(plugin)
388
389
390
391 #define CHROMAKEY(type, components, max, use_yuv) \
392 { \
393         for(int i = pkg->y1; i < pkg->y2; i++) \
394         { \
395                 type *row = (type*)plugin->input->get_rows()[i]; \
396  \
397                 for(int j = 0; j < w; j++) \
398                 { \
399                         float a = 1; \
400  \
401 /* Test for value in range */ \
402                         if(plugin->config.use_value) \
403                         { \
404                                 float current_value; \
405                                 if(use_yuv) \
406                                 { \
407                                         float r = (float)row[0] / max; \
408                                         current_value = r; \
409                                 } \
410                                 else \
411                                 { \
412                                         float r = (float)row[0] / max; \
413                                         float g = (float)row[1] / max; \
414                                         float b = (float)row[2] / max; \
415                                         current_value = RGB_TO_VALUE(r, g, b); \
416                                 } \
417  \
418 /* Full transparency if in range */ \
419                                 if(current_value >= min_v && current_value < max_v) \
420                                 { \
421                                         a = 0; \
422                                 } \
423                                 else \
424 /* Phased out if below or above range */ \
425                                 if(current_value < min_v) \
426                                 { \
427                                         if(min_v - current_value < run) \
428                                                 a = (min_v - current_value) / run; \
429                                 } \
430                                 else \
431                                 if(current_value - max_v < run) \
432                                         a = (current_value - max_v) / run; \
433                         } \
434                         else \
435 /* Use color cube */ \
436                         { \
437                                 float difference; \
438                                 if(use_yuv) \
439                                 { \
440                                         type y = row[0]; \
441                                         type u = row[1]; \
442                                         type v = row[2]; \
443                                         difference = sqrt(SQR(y - y_key) + \
444                                                 SQR(u - u_key) + \
445                                                 SQR(v - v_key)) / max; \
446                                 } \
447                                 else \
448                                 { \
449                                         float r = (float)row[0] / max; \
450                                         float g = (float)row[1] / max; \
451                                         float b = (float)row[2] / max; \
452                                         difference = sqrt(SQR(r - r_key) +  \
453                                                 SQR(g - g_key) + \
454                                                 SQR(b - b_key)); \
455                                 } \
456                                 if(difference < threshold) \
457                                 { \
458                                         a = 0; \
459                                 } \
460                                 else \
461                                 if(difference < threshold_run) \
462                                 { \
463                                         a = (difference - threshold) / run; \
464                                 } \
465  \
466                         } \
467  \
468 /* Multiply alpha and put back in frame */ \
469                         if(components == 4) \
470                         { \
471                                 row[3] = MIN((type)(a * max), row[3]); \
472                         } \
473                         else \
474                         if(use_yuv) \
475                         { \
476                                 row[0] = (type)(a * row[0]); \
477                                 row[1] = (type)(a * (row[1] - (max / 2 + 1)) + max / 2 + 1); \
478                                 row[2] = (type)(a * (row[2] - (max / 2 + 1)) + max / 2 + 1); \
479                         } \
480                         else \
481                         { \
482                                 row[0] = (type)(a * row[0]); \
483                                 row[1] = (type)(a * row[1]); \
484                                 row[2] = (type)(a * row[2]); \
485                         } \
486  \
487                         row += components; \
488                 } \
489         } \
490 }
491
492
493
494
495         switch(plugin->input->get_color_model())
496         {
497                 case BC_RGB_FLOAT:
498                         CHROMAKEY(float, 3, 1.0, 0);
499                         break;
500                 case BC_RGBA_FLOAT:
501                         CHROMAKEY(float, 4, 1.0, 0);
502                         break;
503                 case BC_RGB888:
504                         CHROMAKEY(unsigned char, 3, 0xff, 0);
505                         break;
506                 case BC_RGBA8888:
507                         CHROMAKEY(unsigned char, 4, 0xff, 0);
508                         break;
509                 case BC_YUV888:
510                         CHROMAKEY(unsigned char, 3, 0xff, 1);
511                         break;
512                 case BC_YUVA8888:
513                         CHROMAKEY(unsigned char, 4, 0xff, 1);
514                         break;
515                 case BC_YUV161616:
516                         CHROMAKEY(uint16_t, 3, 0xffff, 1);
517                         break;
518                 case BC_YUVA16161616:
519                         CHROMAKEY(uint16_t, 4, 0xffff, 1);
520                         break;
521         }
522
523 }
524
525
526
527
528
529 REGISTER_PLUGIN(ChromaKey)
530
531
532
533 ChromaKey::ChromaKey(PluginServer *server)
534  : PluginVClient(server)
535 {
536
537         engine = 0;
538 }
539
540 ChromaKey::~ChromaKey()
541 {
542
543         delete engine;
544 }
545
546
547 int ChromaKey::process_buffer(VFrame *frame,
548                 int64_t start_position,
549                 double frame_rate)
550 {
551 SET_TRACE
552
553         load_configuration();
554         this->input = frame;
555         this->output = frame;
556
557         read_frame(frame,
558                 0,
559                 start_position,
560                 frame_rate,
561                 get_use_opengl());
562
563         if(EQUIV(config.threshold, 0))
564         {
565                 return 1;
566         }
567         else
568         {
569                 if(get_use_opengl()) return run_opengl();
570
571                 if(!engine) engine = new ChromaKeyServer(this);
572                 engine->process_packages();
573         }
574 SET_TRACE
575
576         return 1;
577 }
578
579 const char* ChromaKey::plugin_title() { return _("Chroma key"); }
580 int ChromaKey::is_realtime() { return 1; }
581
582 NEW_WINDOW_MACRO(ChromaKey, ChromaKeyWindow)
583
584 LOAD_CONFIGURATION_MACRO(ChromaKey, ChromaKeyConfig)
585
586
587 void ChromaKey::save_data(KeyFrame *keyframe)
588 {
589         FileXML output;
590         output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
591         output.tag.set_title("CHROMAKEY");
592         output.tag.set_property("RED", config.red);
593         output.tag.set_property("GREEN", config.green);
594         output.tag.set_property("BLUE", config.blue);
595         output.tag.set_property("THRESHOLD", config.threshold);
596         output.tag.set_property("SLOPE", config.slope);
597         output.tag.set_property("USE_VALUE", config.use_value);
598         output.append_tag();
599         output.tag.set_title("/CHROMAKEY");
600         output.append_tag();
601         output.append_newline();
602         output.terminate_string();
603 }
604
605 void ChromaKey::read_data(KeyFrame *keyframe)
606 {
607         FileXML input;
608
609         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
610
611         while(!input.read_tag())
612         {
613                 if(input.tag.title_is("CHROMAKEY"))
614                 {
615                         config.red = input.tag.get_property("RED", config.red);
616                         config.green = input.tag.get_property("GREEN", config.green);
617                         config.blue = input.tag.get_property("BLUE", config.blue);
618                         config.threshold = input.tag.get_property("THRESHOLD", config.threshold);
619                         config.slope = input.tag.get_property("SLOPE", config.slope);
620                         config.use_value = input.tag.get_property("USE_VALUE", config.use_value);
621                 }
622         }
623 }
624
625
626
627 void ChromaKey::update_gui()
628 {
629         if(thread)
630         {
631                 load_configuration();
632                 thread->window->lock_window();
633                 ((ChromaKeyWindow*)thread->window)->threshold->update(config.threshold);
634                 ((ChromaKeyWindow*)thread->window)->slope->update(config.slope);
635                 ((ChromaKeyWindow*)thread->window)->use_value->update(config.use_value);
636                 ((ChromaKeyWindow*)thread->window)->update_sample();
637
638                 thread->window->unlock_window();
639         }
640 }
641
642 int ChromaKey::handle_opengl()
643 {
644 #ifdef HAVE_GL
645         OUTER_VARIABLES(this)
646
647
648
649         static const char *uniform_frag =
650                 "uniform sampler2D tex;\n"
651                 "uniform float min_v;\n"
652                 "uniform float max_v;\n"
653                 "uniform float run;\n"
654                 "uniform float threshold;\n"
655                 "uniform float threshold_run;\n"
656                 "uniform vec3 key;\n";
657
658         static const char *get_yuvvalue_frag =
659                 "float get_value(vec4 color)\n"
660                 "{\n"
661                 "       return abs(color.r);\n"
662                 "}\n";
663
664         static const char *get_rgbvalue_frag =
665                 "float get_value(vec4 color)\n"
666                 "{\n"
667                 "       return dot(color.rgb, vec3(0.29900, 0.58700, 0.11400));\n"
668                 "}\n";
669
670         static const char *value_frag =
671                 "void main()\n"
672                 "{\n"
673                 "       vec4 color = texture2D(tex, gl_TexCoord[0].st);\n"
674                 "       float value = get_value(color);\n"
675                 "       float alpha = 1.0;\n"
676                 "\n"
677                 "       if(value >= min_v && value < max_v)\n"
678                 "               alpha = 0.0;\n"
679                 "       else\n"
680                 "       if(value < min_v)\n"
681                 "       {\n"
682                 "               if(min_v - value < run)\n"
683                 "                       alpha = (min_v - value) / run;\n"
684                 "       }\n"
685                 "       else\n"
686                 "       if(value - max_v < run)\n"
687                 "               alpha = (value - max_v) / run;\n"
688                 "\n"
689                 "       gl_FragColor = vec4(color.rgb, alpha);\n"
690                 "}\n";
691
692         static const char *cube_frag =
693                 "void main()\n"
694                 "{\n"
695                 "       vec4 color = texture2D(tex, gl_TexCoord[0].st);\n"
696                 "       float difference = length(color.rgb - key);\n"
697                 "       float alpha = 1.0;\n"
698                 "       if(difference < threshold)\n"
699                 "               alpha = 0.0;\n"
700                 "       else\n"
701                 "       if(difference < threshold_run)\n"
702                 "               alpha = (difference - threshold) / run;\n"
703                 "       gl_FragColor = vec4(color.rgb, min(color.a, alpha));\n"
704                 "}\n";
705
706
707
708         get_output()->to_texture();
709         get_output()->enable_opengl();
710         get_output()->init_screen();
711         const char *shader_stack[] = { 0, 0, 0, 0, 0 };
712         int current_shader = 0;
713
714         shader_stack[current_shader++] = uniform_frag;
715         switch(get_output()->get_color_model())
716         {
717                 case BC_YUV888:
718                 case BC_YUVA8888:
719                         if(config.use_value)
720                         {
721                                 shader_stack[current_shader++] = get_yuvvalue_frag;
722                                 shader_stack[current_shader++] = value_frag;
723                         }
724                         else
725                         {
726                                 shader_stack[current_shader++] = cube_frag;
727                         }
728                         break;
729
730                 default:
731                         if(config.use_value)
732                         {
733                                 shader_stack[current_shader++] = get_rgbvalue_frag;
734                                 shader_stack[current_shader++] = value_frag;
735                         }
736                         else
737                         {
738                                 shader_stack[current_shader++] = cube_frag;
739                         }
740                         break;
741         }
742 SET_TRACE
743
744         unsigned int frag = VFrame::make_shader(0,
745                 shader_stack[0],
746                 shader_stack[1],
747                 shader_stack[2],
748                 shader_stack[3],
749                 0);
750         get_output()->bind_texture(0);
751
752         if(frag)
753         {
754                 glUseProgram(frag);
755                 glUniform1i(glGetUniformLocation(frag, "tex"), 0);
756                 glUniform1f(glGetUniformLocation(frag, "min_v"), min_v);
757                 glUniform1f(glGetUniformLocation(frag, "max_v"), max_v);
758                 glUniform1f(glGetUniformLocation(frag, "run"), run);
759                 glUniform1f(glGetUniformLocation(frag, "threshold"), threshold);
760                 glUniform1f(glGetUniformLocation(frag, "threshold_run"), threshold_run);
761                 if(get_output()->get_color_model() != BC_YUV888 &&
762                         get_output()->get_color_model() != BC_YUVA8888)
763                         glUniform3f(glGetUniformLocation(frag, "key"),
764                                 r_key, g_key, b_key);
765                 else
766                         glUniform3f(glGetUniformLocation(frag, "key"),
767                                 (float)y_key / 0xff, (float)u_key / 0xff, (float)v_key / 0xff);
768
769         }
770 SET_TRACE
771
772         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
773         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
774
775         if(BC_CModels::components(get_output()->get_color_model()) == 3)
776         {
777                 glEnable(GL_BLEND);
778                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
779                 get_output()->clear_pbuffer();
780         }
781 SET_TRACE
782
783         get_output()->draw_texture();
784
785         glUseProgram(0);
786         get_output()->set_opengl_state(VFrame::SCREEN);
787         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
788         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
789         glDisable(GL_BLEND);
790 SET_TRACE
791         return 0;
792 #endif
793 }
794