rework keyframe hide popup, keyframe auto render, textbox set_selection wide text
[goodguy/history.git] / cinelerra-5.1 / plugins / overlay / overlay.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 "clip.h"
24 #include "bchash.h"
25 #include "edlsession.h"
26 #include "filexml.h"
27 #include "guicast.h"
28 #include "keyframe.h"
29 #include "language.h"
30 #include "overlayframe.h"
31 #include "pluginvclient.h"
32 #include "vframe.h"
33
34 #include <string.h>
35 #include <stdint.h>
36
37
38 class Overlay;
39 class OverlayWindow;
40
41
42 class OverlayConfig
43 {
44 public:
45         OverlayConfig();
46
47         static const char* mode_to_text(int mode);
48         int mode;
49
50         static const char* direction_to_text(int direction);
51         int direction;
52         enum
53         {
54                 BOTTOM_FIRST,
55                 TOP_FIRST
56         };
57
58         static const char* output_to_text(int output_layer);
59         int output_layer;
60         enum
61         {
62                 TOP,
63                 BOTTOM
64         };
65 };
66
67 class OverlayMode : public BC_PopupMenu
68 {
69 public:
70         OverlayMode(Overlay *plugin,
71                 int x,
72                 int y);
73         void create_objects();
74         int handle_event();
75         Overlay *plugin;
76 };
77
78 class OverlayDirection : public BC_PopupMenu
79 {
80 public:
81         OverlayDirection(Overlay *plugin,
82                 int x,
83                 int y);
84         void create_objects();
85         int handle_event();
86         Overlay *plugin;
87 };
88
89 class OverlayOutput : public BC_PopupMenu
90 {
91 public:
92         OverlayOutput(Overlay *plugin,
93                 int x,
94                 int y);
95         void create_objects();
96         int handle_event();
97         Overlay *plugin;
98 };
99
100
101 class OverlayWindow : public PluginClientWindow
102 {
103 public:
104         OverlayWindow(Overlay *plugin);
105         ~OverlayWindow();
106
107         void create_objects();
108
109
110         Overlay *plugin;
111         OverlayMode *mode;
112         OverlayDirection *direction;
113         OverlayOutput *output;
114 };
115
116 class Overlay : public PluginVClient
117 {
118 public:
119         Overlay(PluginServer *server);
120         ~Overlay();
121
122
123         PLUGIN_CLASS_MEMBERS(OverlayConfig);
124
125         int process_buffer(VFrame **frame,
126                 int64_t start_position,
127                 double frame_rate);
128         int is_realtime();
129         int is_multichannel();
130         int is_synthesis();
131         void save_data(KeyFrame *keyframe);
132         void read_data(KeyFrame *keyframe);
133         void update_gui();
134         int handle_opengl();
135
136         OverlayFrame *overlayer;
137         VFrame *temp;
138         int current_layer;
139         int output_layer;
140         int input_layer;
141 };
142
143 OverlayConfig::OverlayConfig()
144 {
145         mode = TRANSFER_NORMAL;
146         direction = OverlayConfig::BOTTOM_FIRST;
147         output_layer = OverlayConfig::TOP;
148 }
149
150 const char* OverlayConfig::mode_to_text(int mode)
151 {
152         switch(mode) {
153         case TRANSFER_NORMAL:           return _("Normal");
154         case TRANSFER_ADDITION:         return _("Addition");
155         case TRANSFER_SUBTRACT:         return _("Subtract");
156         case TRANSFER_MULTIPLY:         return _("Multiply");
157         case TRANSFER_DIVIDE:           return _("Divide");
158         case TRANSFER_REPLACE:          return _("Replace");
159         case TRANSFER_MAX:              return _("Max");
160         case TRANSFER_MIN:              return _("Min");
161         case TRANSFER_AVERAGE:          return _("Average");
162         case TRANSFER_DARKEN:           return _("Darken");
163         case TRANSFER_LIGHTEN:          return _("Lighten");
164         case TRANSFER_DST:              return _("Dst");
165         case TRANSFER_DST_ATOP:         return _("DstAtop");
166         case TRANSFER_DST_IN:           return _("DstIn");
167         case TRANSFER_DST_OUT:          return _("DstOut");
168         case TRANSFER_DST_OVER:         return _("DstOver");
169         case TRANSFER_SRC:              return _("Src");
170         case TRANSFER_SRC_ATOP:         return _("SrcAtop");
171         case TRANSFER_SRC_IN:           return _("SrcIn");
172         case TRANSFER_SRC_OUT:          return _("SrcOut");
173         case TRANSFER_SRC_OVER:         return _("SrcOver");
174         case TRANSFER_OR:               return _("Or");
175         case TRANSFER_XOR:              return _("Xor");
176         default:                        break;
177         }
178         return _("Normal");
179 }
180
181 const char* OverlayConfig::direction_to_text(int direction)
182 {
183         switch(direction)
184         {
185                 case OverlayConfig::BOTTOM_FIRST: return _("Bottom first");
186                 case OverlayConfig::TOP_FIRST:    return _("Top first");
187         }
188         return "";
189 }
190
191 const char* OverlayConfig::output_to_text(int output_layer)
192 {
193         switch(output_layer)
194         {
195                 case OverlayConfig::TOP:    return _("Top");
196                 case OverlayConfig::BOTTOM: return _("Bottom");
197         }
198         return "";
199 }
200
201
202
203
204
205
206
207
208
209 OverlayWindow::OverlayWindow(Overlay *plugin)
210  : PluginClientWindow(plugin,
211         300,
212         160,
213         300,
214         160,
215         0)
216 {
217         this->plugin = plugin;
218 }
219
220 OverlayWindow::~OverlayWindow()
221 {
222 }
223
224 void OverlayWindow::create_objects()
225 {
226         int x = 10, y = 10;
227
228         BC_Title *title;
229         add_subwindow(title = new BC_Title(x, y, _("Mode:")));
230         add_subwindow(mode = new OverlayMode(plugin,
231                 x + title->get_w() + 5,
232                 y));
233         mode->create_objects();
234
235         y += 30;
236         add_subwindow(title = new BC_Title(x, y, _("Layer order:")));
237         add_subwindow(direction = new OverlayDirection(plugin,
238                 x + title->get_w() + 5,
239                 y));
240         direction->create_objects();
241
242         y += 30;
243         add_subwindow(title = new BC_Title(x, y, _("Output layer:")));
244         add_subwindow(output = new OverlayOutput(plugin,
245                 x + title->get_w() + 5,
246                 y));
247         output->create_objects();
248
249         show_window();
250         flush();
251 }
252
253
254
255
256
257
258
259 OverlayMode::OverlayMode(Overlay *plugin, int x, int y)
260  : BC_PopupMenu(x, y, 150,
261         OverlayConfig::mode_to_text(plugin->config.mode), 1)
262 {
263         this->plugin = plugin;
264 }
265
266 void OverlayMode::create_objects()
267 {
268         for(int i = 0; i < TRANSFER_TYPES; i++)
269                 add_item(new BC_MenuItem(OverlayConfig::mode_to_text(i)));
270 }
271
272 int OverlayMode::handle_event()
273 {
274         char *text = get_text();
275
276         for(int i = 0; i < TRANSFER_TYPES; i++)
277         {
278                 if(!strcmp(text, OverlayConfig::mode_to_text(i)))
279                 {
280                         plugin->config.mode = i;
281                         break;
282                 }
283         }
284
285         plugin->send_configure_change();
286         return 1;
287 }
288
289
290 OverlayDirection::OverlayDirection(Overlay *plugin,
291         int x,
292         int y)
293  : BC_PopupMenu(x,
294         y,
295         150,
296         OverlayConfig::direction_to_text(plugin->config.direction),
297         1)
298 {
299         this->plugin = plugin;
300 }
301
302 void OverlayDirection::create_objects()
303 {
304         add_item(new BC_MenuItem(
305                 OverlayConfig::direction_to_text(
306                         OverlayConfig::TOP_FIRST)));
307         add_item(new BC_MenuItem(
308                 OverlayConfig::direction_to_text(
309                         OverlayConfig::BOTTOM_FIRST)));
310 }
311
312 int OverlayDirection::handle_event()
313 {
314         char *text = get_text();
315
316         if(!strcmp(text,
317                 OverlayConfig::direction_to_text(
318                         OverlayConfig::TOP_FIRST)))
319                 plugin->config.direction = OverlayConfig::TOP_FIRST;
320         else
321         if(!strcmp(text,
322                 OverlayConfig::direction_to_text(
323                         OverlayConfig::BOTTOM_FIRST)))
324                 plugin->config.direction = OverlayConfig::BOTTOM_FIRST;
325
326         plugin->send_configure_change();
327         return 1;
328 }
329
330
331 OverlayOutput::OverlayOutput(Overlay *plugin,
332         int x,
333         int y)
334  : BC_PopupMenu(x,
335         y,
336         100,
337         OverlayConfig::output_to_text(plugin->config.output_layer),
338         1)
339 {
340         this->plugin = plugin;
341 }
342
343 void OverlayOutput::create_objects()
344 {
345         add_item(new BC_MenuItem(
346                 OverlayConfig::output_to_text(
347                         OverlayConfig::TOP)));
348         add_item(new BC_MenuItem(
349                 OverlayConfig::output_to_text(
350                         OverlayConfig::BOTTOM)));
351 }
352
353 int OverlayOutput::handle_event()
354 {
355         char *text = get_text();
356
357         if(!strcmp(text,
358                 OverlayConfig::output_to_text(
359                         OverlayConfig::TOP)))
360                 plugin->config.output_layer = OverlayConfig::TOP;
361         else
362         if(!strcmp(text,
363                 OverlayConfig::output_to_text(
364                         OverlayConfig::BOTTOM)))
365                 plugin->config.output_layer = OverlayConfig::BOTTOM;
366
367         plugin->send_configure_change();
368         return 1;
369 }
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390 REGISTER_PLUGIN(Overlay)
391
392
393
394
395
396
397 Overlay::Overlay(PluginServer *server)
398  : PluginVClient(server)
399 {
400
401         overlayer = 0;
402         temp = 0;
403 }
404
405
406 Overlay::~Overlay()
407 {
408
409         if(overlayer) delete overlayer;
410         if(temp) delete temp;
411 }
412
413
414
415 int Overlay::process_buffer(VFrame **frame,
416         int64_t start_position,
417         double frame_rate)
418 {
419         load_configuration();
420
421         EDLSession* session = get_edlsession();
422         int interpolation_type = session ? session->interpolation_type : NEAREST_NEIGHBOR;
423
424         int step = config.direction == OverlayConfig::BOTTOM_FIRST ?  -1 : 1;
425         int layers = get_total_buffers();
426         input_layer = config.direction == OverlayConfig::BOTTOM_FIRST ? layers-1 : 0;
427         output_layer = config.output_layer == OverlayConfig::TOP ?  0 : layers-1;
428         VFrame *output = frame[output_layer];
429
430         current_layer = input_layer;
431         read_frame(output, current_layer,  // Direct copy the first layer
432                          start_position, frame_rate, get_use_opengl());
433
434         if( --layers > 0 ) {    // need 2 layers to do overlay
435                 if( !temp )
436                         temp = new VFrame(0, -1, frame[0]->get_w(), frame[0]->get_h(),
437                                         frame[0]->get_color_model(), -1);
438
439                 while( --layers >= 0 ) {
440                         current_layer += step;
441                         read_frame(temp, current_layer,
442                                  start_position, frame_rate, get_use_opengl());
443
444                         if(get_use_opengl()) {
445                                 run_opengl();
446                                 continue;
447                         }
448
449                         if(!overlayer)
450                                 overlayer = new OverlayFrame(get_project_smp() + 1);
451                         
452                         overlayer->overlay(output, temp,
453                                 0, 0, output->get_w(), output->get_h(),
454                                 0, 0, output->get_w(), output->get_h(),
455                                 1, config.mode, interpolation_type);
456                 }
457         }
458
459         return 0;
460 }
461
462 int Overlay::handle_opengl()
463 {
464 #ifdef HAVE_GL
465         static const char *get_pixels_frag =
466                 "uniform sampler2D src_tex;\n"
467                 "uniform sampler2D dst_tex;\n"
468                 "uniform vec2 dst_tex_dimensions;\n"
469                 "uniform vec3 chroma_offset;\n"
470                 "void main()\n"
471                 "{\n"
472                 "       vec4 dst_color = texture2D(dst_tex, gl_FragCoord.xy / dst_tex_dimensions);\n"
473                 "       vec4 src_color = texture2D(src_tex, gl_TexCoord[0].st);\n"
474                 "       src_color.rgb -= chroma_offset;\n"
475                 "       dst_color.rgb -= chroma_offset;\n";
476
477         static const char *put_pixels_frag =
478                 "       result.rgb += chroma_offset;\n"
479                 "       gl_FragColor = result;\n"
480                 "}\n";
481
482
483 // NORMAL
484 static const char *blend_normal_frag =
485         "       vec4 result = mix(src_color, src_color, src_color.a);\n";
486
487 // ADDITION
488 static const char *blend_add_frag =
489         "       vec4 result = dst_color + src_color;\n"
490         "       result = clamp(result, 0.0, 1.0);\n";
491
492 // SUBTRACT
493 static const char *blend_subtract_frag =
494         "       vec4 result = dst_color - src_color;\n"
495         "       result = clamp(result, 0.0, 1.0);\n";
496
497 // MULTIPLY
498 static const char *blend_multiply_frag =
499         "       vec4 result = dst_color * src_color;\n";
500
501 // DIVIDE
502 static const char *blend_divide_frag =
503         "       vec4 result = dst_color / src_color;\n"
504         "       if(src_color.r == 0.) result.r = 1.0;\n"
505         "       if(src_color.g == 0.) result.g = 1.0;\n"
506         "       if(src_color.b == 0.) result.b = 1.0;\n"
507         "       if(src_color.a == 0.) result.a = 1.0;\n"
508         "       result = clamp(result, 0.0, 1.0);\n";
509
510 // MAX
511 static const char *blend_max_frag =
512         "       vec4 result = max(src_color, dst_color);\n";
513
514 // MIN
515 static const char *blend_min_frag =
516         "       vec4 result = min(src_color, dst_color);\n";
517
518 // AVERAGE
519 static const char *blend_average_frag =
520         "       vec4 result = (src_color + dst_color) * 0.5;\n";
521
522 // DARKEN
523 static const char *blend_darken_frag =
524         "       vec4 result = vec4(src_color.rgb * (1.0 - dst_color.a) +"
525                         " dst_color.rgb * (1.0 - src_color.a) +"
526                         " min(src_color.rgb, dst_color.rgb), "
527                         " src_color.a + dst_color.a - src_color.a * dst_color.a);\n"
528         "       result = clamp(result, 0.0, 1.0);\n";
529
530 // LIGHTEN
531 static const char *blend_lighten_frag =
532         "       vec4 result = vec4(src_color.rgb * (1.0 - dst_color.a) +"
533                         " dst_color.rgb * (1.0 - src_color.a) +"
534                         " max(src_color.rgb, dst_color.rgb), "
535                         " src_color.a + dst_color.a - src_color.a * dst_color.a);\n"
536         "       result = clamp(result, 0.0, 1.0);\n";
537
538 // DST
539 static const char *blend_dst_frag =
540         "       vec4 result = dst_color;\n";
541
542 // DST_ATOP
543 static const char *blend_dst_atop_frag =
544         "       vec4 result = vec4(src_color.rgb * dst_color.a + "
545                         "(1.0 - src_color.a) * dst_color.rgb, dst_color.a);\n";
546
547 // DST_IN
548 static const char *blend_dst_in_frag =
549         "       vec4 result = src_color * dst_color.a;\n";
550
551 // DST_OUT
552 static const char *blend_dst_out_frag =
553         "       vec4 result = src_color * (1.0 - dst_color.a);\n";
554
555 // DST_OVER
556 static const char *blend_dst_over_frag =
557         "       vec4 result = vec4(src_color.rgb + (1.0 - src_color.a) * dst_color.rgb, "
558                         " dst_color.a + src_color.a - dst_color.a * src_color.a);\n";
559
560 // SRC
561 static const char *blend_src_frag =
562         "       vec4 result = src_color;\n";
563
564 // SRC_ATOP
565 static const char *blend_src_atop_frag =
566         "       vec4 result = vec4(dst_color.rgb * src_color.a + "
567                         "src_color.rgb * (1.0 - dst_color.a), src_color.a);\n";
568
569 // SRC_IN
570 static const char *blend_src_in_frag =
571         "       vec4 result = dst_color * src_color.a;\n";
572
573 // SRC_OUT
574 static const char *blend_src_out_frag =
575         "       vec4 result = dst_color * (1.0 - src_color.a);\n";
576
577 // SRC_OVER
578 static const char *blend_src_over_frag =
579         "       vec4 result = vec4(dst_color.rgb + (1.0 - dst_color.a) * src_color.rgb, "
580                                 "dst_color.a + src_color.a - dst_color.a * src_color.a);\n";
581
582 // OR
583 static const char *blend_or_frag =
584         "       vec4 result = src_color + dst_color - src_color * dst_color;\n";
585
586 // XOR
587 static const char *blend_xor_frag =
588         "       vec4 result = vec4(dst_color.rgb * (1.0 - src_color.a) + "
589                         "(1.0 - dst_color.a) * src_color.rgb, "
590                         "dst_color.a + src_color.a - 2.0 * dst_color.a * src_color.a);\n";
591
592 static const char * const overlay_shaders[TRANSFER_TYPES] = {
593                 blend_normal_frag,      // TRANSFER_NORMAL
594                 blend_add_frag,         // TRANSFER_ADDITION
595                 blend_subtract_frag,    // TRANSFER_SUBTRACT
596                 blend_multiply_frag,    // TRANSFER_MULTIPLY
597                 blend_divide_frag,      // TRANSFER_DIVIDE
598                 blend_src_frag,         // TRANSFER_REPLACE
599                 blend_max_frag,         // TRANSFER_MAX
600                 blend_min_frag,         // TRANSFER_MIN
601                 blend_average_frag,     // TRANSFER_AVERAGE
602                 blend_darken_frag,      // TRANSFER_DARKEN
603                 blend_lighten_frag,     // TRANSFER_LIGHTEN
604                 blend_dst_frag,         // TRANSFER_DST
605                 blend_dst_atop_frag,    // TRANSFER_DST_ATOP
606                 blend_dst_in_frag,      // TRANSFER_DST_IN
607                 blend_dst_out_frag,     // TRANSFER_DST_OUT
608                 blend_dst_over_frag,    // TRANSFER_DST_OVER
609                 blend_src_frag,         // TRANSFER_SRC
610                 blend_src_atop_frag,    // TRANSFER_SRC_ATOP
611                 blend_src_in_frag,      // TRANSFER_SRC_IN
612                 blend_src_out_frag,     // TRANSFER_SRC_OUT
613                 blend_src_over_frag,    // TRANSFER_SRC_OVER
614                 blend_or_frag,          // TRANSFER_OR
615                 blend_xor_frag          // TRANSFER_XOR
616         };
617
618         glDisable(GL_BLEND);
619         VFrame *dst = get_output(output_layer);
620         VFrame *src = temp;
621
622         switch( config.mode ) {
623         case TRANSFER_REPLACE:
624         case TRANSFER_SRC:
625 // Direct copy layer
626                 src->to_texture();
627                 dst->enable_opengl();
628                 dst->init_screen();
629                 src->draw_texture();
630                 break;
631         case TRANSFER_NORMAL:
632 // Move destination to screen
633                 if( dst->get_opengl_state() != VFrame::SCREEN ) {
634                         dst->to_texture();
635                         dst->enable_opengl();
636                         dst->init_screen();
637                         dst->draw_texture();
638                 }
639                 src->to_texture();
640                 dst->enable_opengl();
641                 dst->init_screen();
642                 glEnable(GL_BLEND);
643                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
644                 src->draw_texture();
645                 break;
646         default:
647                 src->to_texture();
648                 dst->to_texture();
649                 dst->enable_opengl();
650                 dst->init_screen();
651                 src->bind_texture(0);
652                 dst->bind_texture(1);
653                 const char *shader_stack[] = { 0, 0, 0 };
654                 int current_shader = 0;
655
656                 shader_stack[current_shader++] = get_pixels_frag;
657                 shader_stack[current_shader++] = overlay_shaders[config.mode];
658                 shader_stack[current_shader++] = put_pixels_frag;
659
660                 unsigned int shader_id = 0;
661                 shader_id = VFrame::make_shader(0,
662                         shader_stack[0],
663                         shader_stack[1],
664                         shader_stack[2],
665                         0);
666
667                 glUseProgram(shader_id);
668                 glUniform1i(glGetUniformLocation(shader_id, "src_tex"), 0);
669                 glUniform1i(glGetUniformLocation(shader_id, "dst_tex"), 1);
670                 glUniform2f(glGetUniformLocation(shader_id, "dst_tex_dimensions"),
671                                 (float)dst->get_texture_w(), (float)dst->get_texture_h());
672                 float chroma_offset = BC_CModels::is_yuv(dst->get_color_model()) ? 0.5 : 0.0;
673                 glUniform3f(glGetUniformLocation(shader_id, "chroma_offset"),
674                                 0.0, chroma_offset, chroma_offset);
675
676                 src->draw_texture();
677                 glUseProgram(0);
678
679                 glActiveTexture(GL_TEXTURE1);
680                 glDisable(GL_TEXTURE_2D);
681                 break;
682         }
683
684         glActiveTexture(GL_TEXTURE0);
685         glDisable(GL_TEXTURE_2D);
686         glDisable(GL_BLEND);
687
688 // get the data before something else uses the screen
689         dst->screen_to_ram();
690 #endif
691         return 0;
692 }
693
694
695 const char* Overlay::plugin_title() { return _("Overlay"); }
696 int Overlay::is_realtime() { return 1; }
697 int Overlay::is_multichannel() { return 1; }
698 int Overlay::is_synthesis() { return 1; }
699
700
701
702 NEW_WINDOW_MACRO(Overlay, OverlayWindow)
703
704
705
706 int Overlay::load_configuration()
707 {
708         KeyFrame *prev_keyframe;
709         prev_keyframe = get_prev_keyframe(get_source_position());
710         read_data(prev_keyframe);
711         return 0;
712 }
713
714
715 void Overlay::save_data(KeyFrame *keyframe)
716 {
717         FileXML output;
718
719 // cause data to be stored directly in text
720         output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
721         output.tag.set_title("OVERLAY");
722         output.tag.set_property("MODE", config.mode);
723         output.tag.set_property("DIRECTION", config.direction);
724         output.tag.set_property("OUTPUT_LAYER", config.output_layer);
725         output.append_tag();
726         output.tag.set_title("/OVERLAY");
727         output.append_tag();
728         output.terminate_string();
729 }
730
731 void Overlay::read_data(KeyFrame *keyframe)
732 {
733         FileXML input;
734
735         input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
736
737         while(!input.read_tag())
738         {
739                 if(input.tag.title_is("OVERLAY"))
740                 {
741                         config.mode = input.tag.get_property("MODE", config.mode);
742                         config.direction = input.tag.get_property("DIRECTION", config.direction);
743                         config.output_layer = input.tag.get_property("OUTPUT_LAYER", config.output_layer);
744                 }
745         }
746 }
747
748 void Overlay::update_gui()
749 {
750         if(thread)
751         {
752                 thread->window->lock_window("Overlay::update_gui");
753                 ((OverlayWindow*)thread->window)->mode->set_text(OverlayConfig::mode_to_text(config.mode));
754                 ((OverlayWindow*)thread->window)->direction->set_text(OverlayConfig::direction_to_text(config.direction));
755                 ((OverlayWindow*)thread->window)->output->set_text(OverlayConfig::output_to_text(config.output_layer));
756                 thread->window->unlock_window();
757         }
758 }
759
760
761
762
763