5e269aa084d6da57546c1333cd347b920cd7c649
[goodguy/history.git] / cinelerra-5.0 / plugins / threshold / threshold.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 <string>
23 #include <string.h>
24
25 #include "clip.h"
26 #include "bchash.h"
27 #include "filexml.h"
28 #include "histogramengine.h"
29 #include "language.h"
30 #include "cicolors.h"
31 #include "threshold.h"
32 #include "thresholdwindow.h"
33 #include "vframe.h"
34
35 ;
36 using ::std::string;
37
38
39
40 ThresholdConfig::ThresholdConfig()
41 {
42         reset();
43 }
44
45 int ThresholdConfig::equivalent(ThresholdConfig &that)
46 {
47         return EQUIV(min, that.min) &&
48                 EQUIV(max, that.max) &&
49                 plot == that.plot &&
50                 low_color == that.low_color &&
51                 mid_color == that.mid_color &&
52                 high_color == that.high_color;
53 }
54
55 void ThresholdConfig::copy_from(ThresholdConfig &that)
56 {
57         min = that.min;
58         max = that.max;
59         plot = that.plot;
60         low_color = that.low_color;
61         mid_color = that.mid_color;
62         high_color = that.high_color;
63 }
64
65 // General purpose scale function.
66 template<typename T>
67 T interpolate(const T & prev, const double & prev_scale, const T & next, const double & next_scale)
68 {
69         return static_cast<T>(prev * prev_scale + next * next_scale);
70 }
71
72 void ThresholdConfig::interpolate(ThresholdConfig &prev,
73         ThresholdConfig &next,
74         int64_t prev_frame, 
75         int64_t next_frame, 
76         int64_t current_frame)
77 {
78         double next_scale = (double)(current_frame - prev_frame) / 
79                 (next_frame - prev_frame);
80         double prev_scale = (double)(next_frame - current_frame) / 
81                 (next_frame - prev_frame);
82
83         min = ::interpolate(prev.min, prev_scale, next.min, next_scale);
84         max = ::interpolate(prev.max, prev_scale, next.max, next_scale);
85         plot = prev.plot;
86
87         low_color =  ::interpolate(prev.low_color,  prev_scale, next.low_color,  next_scale);
88         mid_color =  ::interpolate(prev.mid_color,  prev_scale, next.mid_color,  next_scale);
89         high_color = ::interpolate(prev.high_color, prev_scale, next.high_color, next_scale);
90 }
91
92 void ThresholdConfig::reset()
93 {
94         min = 0.0;
95         max = 1.0;
96         plot = 1;
97         low_color.set (0x0,  0x0,  0x0,  0xff);
98         mid_color.set (0xff, 0xff, 0xff, 0xff);
99         high_color.set(0x0,  0x0,  0x0,  0xff);
100 }
101
102 void ThresholdConfig::boundaries()
103 {
104         CLAMP(min, HISTOGRAM_MIN, max);
105         CLAMP(max, min, HISTOGRAM_MAX);
106 }
107
108
109
110
111
112
113
114
115 REGISTER_PLUGIN(ThresholdMain)
116
117 ThresholdMain::ThresholdMain(PluginServer *server)
118  : PluginVClient(server)
119 {
120         engine = 0;
121         threshold_engine = 0;
122 }
123
124 ThresholdMain::~ThresholdMain()
125 {
126         delete engine;
127         delete threshold_engine;
128 }
129
130 int ThresholdMain::is_realtime()
131 {
132         return 1;
133 }
134
135 const char* ThresholdMain::plugin_title() 
136
137         return _("Threshold"); 
138 }
139
140
141
142 LOAD_CONFIGURATION_MACRO(ThresholdMain, ThresholdConfig)
143
144
145
146
147
148
149
150 int ThresholdMain::process_buffer(VFrame *frame,
151         int64_t start_position,
152         double frame_rate)
153 {
154         load_configuration();
155
156         int use_opengl = get_use_opengl() &&
157                 (!config.plot || !gui_open());
158
159         read_frame(frame,
160                 0,
161                 get_source_position(),
162                 get_framerate(),
163                 use_opengl);
164
165         if(use_opengl) return run_opengl();
166
167         send_render_gui(frame);
168
169         if(!threshold_engine)
170                 threshold_engine = new ThresholdEngine(this);
171         threshold_engine->process_packages(frame);
172         
173         return 0;
174 }
175
176 void ThresholdMain::save_data(KeyFrame *keyframe)
177 {
178         FileXML file;
179         file.set_shared_output(keyframe->get_data(), MESSAGESIZE);
180         file.tag.set_title("THRESHOLD");
181         file.tag.set_property("MIN", config.min);
182         file.tag.set_property("MAX", config.max);
183         file.tag.set_property("PLOT", config.plot);
184         config.low_color.set_property(file.tag,  "LOW_COLOR");
185         config.mid_color.set_property(file.tag,  "MID_COLOR");
186         config.high_color.set_property(file.tag, "HIGH_COLOR");
187         file.append_tag();
188         file.tag.set_title("/THRESHOLD");
189         file.append_tag();
190         file.terminate_string();
191 }
192
193 void ThresholdMain::read_data(KeyFrame *keyframe)
194 {
195         FileXML file;
196         const char *data = keyframe->get_data();
197         file.set_shared_input((char *)data, strlen(data));
198         int result = 0;
199         while(!result)
200         {
201                 result = file.read_tag();
202                 if(!result)
203                 {
204                         config.min = file.tag.get_property("MIN", config.min);
205                         config.max = file.tag.get_property("MAX", config.max);
206                         config.plot = file.tag.get_property("PLOT", config.plot);
207                         config.low_color = config.low_color.get_property(file.tag, "LOW_COLOR");
208                         config.mid_color = config.mid_color.get_property(file.tag, "MID_COLOR");
209                         config.high_color = config.high_color.get_property(file.tag, "HIGH_COLOR");
210                 }
211         }
212         config.boundaries();
213 }
214
215 NEW_WINDOW_MACRO(ThresholdMain,ThresholdWindow);
216
217 void ThresholdMain::update_gui()
218 {
219         if(thread)
220         {
221                 thread->window->lock_window("ThresholdMain::update_gui");
222                 if(load_configuration())
223                 {
224                         ThresholdWindow *window = (ThresholdWindow*) thread->window;
225                         window->min->update(config.min);
226                         window->max->update(config.max);
227                         window->plot->update(config.plot);
228                         window->update_low_color();
229                         window->update_mid_color();
230                         window->update_high_color();
231                         window->low_color_thread->update_gui(config.low_color.getRGB(), config.low_color.a);
232                         window->mid_color_thread->update_gui(config.mid_color.getRGB(), config.mid_color.a);
233                         window->high_color_thread->update_gui(config.high_color.getRGB(), config.high_color.a);
234                 }
235                 thread->window->unlock_window();
236         }
237 }
238
239 void ThresholdMain::render_gui(void *data)
240 {
241         if(thread)
242         {
243                 calculate_histogram((VFrame*)data);
244                 ThresholdWindow *window = (ThresholdWindow*) thread->window;
245                 window->lock_window("ThresholdMain::render_gui");
246                 window->canvas->draw();
247                 window->unlock_window();
248         }
249 }
250
251 void ThresholdMain::calculate_histogram(VFrame *frame)
252 {
253         if(!engine) engine = new HistogramEngine(get_project_smp() + 1,
254                 get_project_smp() + 1);
255         engine->process_packages(frame);
256 }
257
258 int ThresholdMain::handle_opengl()
259 {
260 #ifdef HAVE_GL
261         static const char *rgb_shader = 
262                 "uniform sampler2D tex;\n"
263                 "uniform float min;\n"
264                 "uniform float max;\n"
265                 "uniform vec4 low_color;\n"
266                 "uniform vec4 mid_color;\n"
267                 "uniform vec4 high_color;\n"
268                 "void main()\n"
269                 "{\n"
270                 "       vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n"
271                 "       float v = dot(pixel.rgb, vec3(0.299, 0.587, 0.114));\n"
272                 "       if(v < min)\n"
273                 "               pixel = low_color;\n"
274                 "       else if(v < max)\n"
275                 "               pixel = mid_color;\n"
276                 "       else\n"
277                 "               pixel = high_color;\n"
278                 "       gl_FragColor = pixel;\n"
279                 "}\n";
280
281         static const char *yuv_shader = 
282                 "uniform sampler2D tex;\n"
283                 "uniform float min;\n"
284                 "uniform float max;\n"
285                 "uniform vec4 low_color;\n"
286                 "uniform vec4 mid_color;\n"
287                 "uniform vec4 high_color;\n"
288                 "void main()\n"
289                 "{\n"
290                 "       vec4 pixel = texture2D(tex, gl_TexCoord[0].st);\n"
291                 "       if(pixel.r < min)\n"
292                 "               pixel = low_color;\n"
293                 "       else if(pixel.r < max)\n"
294                 "               pixel = mid_color;\n"
295                 "       else\n"
296                 "               pixel = high_color;\n"
297                 "       gl_FragColor = pixel;\n"
298                 "}\n";
299
300         get_output()->to_texture();
301         get_output()->enable_opengl();
302
303         unsigned int shader = 0;
304         int color_model = get_output()->get_color_model();
305         bool is_yuv = BC_CModels::is_yuv(color_model);
306         bool has_alpha = BC_CModels::has_alpha(color_model);
307         if(is_yuv)
308                 shader = VFrame::make_shader(0, yuv_shader, 0);
309         else
310                 shader = VFrame::make_shader(0, rgb_shader, 0);
311
312         if(shader > 0)
313         {
314                 glUseProgram(shader);
315                 glUniform1i(glGetUniformLocation(shader, "tex"), 0);
316                 glUniform1f(glGetUniformLocation(shader, "min"), config.min);
317                 glUniform1f(glGetUniformLocation(shader, "max"), config.max);
318
319                 if (is_yuv)
320                 {
321                         float y_low,  u_low,  v_low;
322                         float y_mid,  u_mid,  v_mid;
323                         float y_high, u_high, v_high;
324
325                         YUV::rgb_to_yuv_f((float)config.low_color.r / 0xff,
326                                           (float)config.low_color.g / 0xff,
327                                           (float)config.low_color.b / 0xff,
328                                           y_low,
329                                           u_low,
330                                           v_low);
331                         u_low += 0.5;
332                         v_low += 0.5;
333                         YUV::rgb_to_yuv_f((float)config.mid_color.r / 0xff,
334                                           (float)config.mid_color.g / 0xff,
335                                           (float)config.mid_color.b / 0xff,
336                                           y_mid,
337                                           u_mid,
338                                           v_mid);
339                         u_mid += 0.5;
340                         v_mid += 0.5;
341                         YUV::rgb_to_yuv_f((float)config.high_color.r / 0xff,
342                                           (float)config.high_color.g / 0xff,
343                                           (float)config.high_color.b / 0xff,
344                                           y_high,
345                                           u_high,
346                                           v_high);
347                         u_high += 0.5;
348                         v_high += 0.5;
349
350                         glUniform4f(glGetUniformLocation(shader, "low_color"),
351                                     y_low, u_low, v_low,
352                                     has_alpha ? (float)config.low_color.a / 0xff : 1.0);
353                         glUniform4f(glGetUniformLocation(shader, "mid_color"),
354                                     y_mid, u_mid, v_mid,
355                                     has_alpha ? (float)config.mid_color.a / 0xff : 1.0);
356                         glUniform4f(glGetUniformLocation(shader, "high_color"),
357                                     y_high, u_high, v_high,
358                                     has_alpha ? (float)config.high_color.a / 0xff : 1.0);
359                 } else {
360                         glUniform4f(glGetUniformLocation(shader, "low_color"),
361                                     (float)config.low_color.r / 0xff,
362                                     (float)config.low_color.g / 0xff,
363                                     (float)config.low_color.b / 0xff,
364                                     has_alpha ? (float)config.low_color.a / 0xff : 1.0);
365                         glUniform4f(glGetUniformLocation(shader, "mid_color"),
366                                     (float)config.mid_color.r / 0xff,
367                                     (float)config.mid_color.g / 0xff,
368                                     (float)config.mid_color.b / 0xff,
369                                     has_alpha ? (float)config.mid_color.a / 0xff : 1.0);
370                         glUniform4f(glGetUniformLocation(shader, "high_color"),
371                                     (float)config.high_color.r / 0xff,
372                                     (float)config.high_color.g / 0xff,
373                                     (float)config.high_color.b / 0xff,
374                                     has_alpha ? (float)config.high_color.a / 0xff : 1.0);
375                 }
376         }
377
378         get_output()->init_screen();
379         get_output()->bind_texture(0);
380         get_output()->draw_texture();
381         glUseProgram(0);
382         get_output()->set_opengl_state(VFrame::SCREEN);
383 #endif
384         return 0;
385 }
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 ThresholdPackage::ThresholdPackage()
409  : LoadPackage()
410 {
411         start = end = 0;
412 }
413
414
415
416
417
418
419
420
421
422
423
424 ThresholdUnit::ThresholdUnit(ThresholdEngine *server)
425  : LoadClient(server)
426 {
427         this->server = server;
428 }
429
430 // Coerces pixel component to int.
431 static inline int get_component(unsigned char v)
432 {
433         return (v << 8) | v;
434 }
435
436 static inline int get_component(float v)
437 {
438         return (int)(v * 0xffff);
439 }
440
441 static inline int get_component(uint16_t v)
442 {
443         return v;
444 }
445
446 // Rescales value in range [0, 255] to range appropriate to TYPE.
447 template<typename TYPE>
448 static TYPE scale_to_range(int v)
449 {
450         return v;  // works for unsigned char, override for the rest.
451 }
452
453 template<>
454 inline float scale_to_range(int v)
455 {
456         return (float) v / 0xff;
457 }
458
459 template<>
460 inline uint16_t scale_to_range(int v)
461 {
462         return v << 8 | v;
463 }
464
465 static inline void rgb_to_yuv(YUV & yuv,
466                               unsigned char   r, unsigned char   g, unsigned char   b,
467                               unsigned char & y, unsigned char & u, unsigned char & v)
468 {
469         yuv.rgb_to_yuv_8(r, g, b, y, u, v);
470 }
471
472 static inline void rgb_to_yuv(YUV & yuv,
473                               float   r, float   g, float   b,
474                               float & y, float & u, float & v)
475 {
476         yuv.rgb_to_yuv_f(r, g, b, y, u, v);
477 }
478
479 static inline void rgb_to_yuv(YUV & yuv,
480                               uint16_t   r, uint16_t   g, uint16_t   b,
481                               uint16_t & y, uint16_t & u, uint16_t & v)
482 {
483         yuv.rgb_to_yuv_16(r, g, b, y, u, v);
484 }
485
486 template<typename TYPE, int COMPONENTS, bool USE_YUV>
487 void ThresholdUnit::render_data(LoadPackage *package)
488 {
489         const ThresholdPackage *pkg = (ThresholdPackage*)package;
490         const ThresholdConfig *config = & server->plugin->config;
491         VFrame *data = server->data;
492         const int min = (int)(config->min * 0xffff);
493         const int max = (int)(config->max * 0xffff);
494         const int w = data->get_w();
495         //const int h = data->get_h();
496         
497         const TYPE r_low = scale_to_range<TYPE>(config->low_color.r);
498         const TYPE g_low = scale_to_range<TYPE>(config->low_color.g);
499         const TYPE b_low = scale_to_range<TYPE>(config->low_color.b);
500         const TYPE a_low = scale_to_range<TYPE>(config->low_color.a);
501         
502         const TYPE r_mid = scale_to_range<TYPE>(config->mid_color.r);
503         const TYPE g_mid = scale_to_range<TYPE>(config->mid_color.g);
504         const TYPE b_mid = scale_to_range<TYPE>(config->mid_color.b);
505         const TYPE a_mid = scale_to_range<TYPE>(config->mid_color.a);
506         
507         const TYPE r_high = scale_to_range<TYPE>(config->high_color.r);
508         const TYPE g_high = scale_to_range<TYPE>(config->high_color.g);
509         const TYPE b_high = scale_to_range<TYPE>(config->high_color.b);
510         const TYPE a_high = scale_to_range<TYPE>(config->high_color.a);
511
512         TYPE y_low,  u_low,  v_low;
513         TYPE y_mid,  u_mid,  v_mid;
514         TYPE y_high, u_high, v_high;
515
516         if (USE_YUV)
517         {
518                 rgb_to_yuv(*server->yuv, r_low,  g_low,  b_low,  y_low,  u_low,  v_low);
519                 rgb_to_yuv(*server->yuv, r_mid,  g_mid,  b_mid,  y_mid,  u_mid,  v_mid);
520                 rgb_to_yuv(*server->yuv, r_high, g_high, b_high, y_high, u_high, v_high);
521         }
522
523         for(int i = pkg->start; i < pkg->end; i++)
524         {
525                 TYPE *in_row = (TYPE*)data->get_rows()[i];
526                 TYPE *out_row = in_row;
527                 for(int j = 0; j < w; j++)
528                 {
529                         if (USE_YUV)
530                         {
531                                 const int y = get_component(in_row[0]);
532                                 if (y < min)
533                                 {
534                                         *out_row++ = y_low;
535                                         *out_row++ = u_low;
536                                         *out_row++ = v_low;
537                                         if(COMPONENTS == 4) *out_row++ = a_low;
538                                 }
539                                 else if (y < max)
540                                 {
541                                         *out_row++ = y_mid;
542                                         *out_row++ = u_mid;
543                                         *out_row++ = v_mid;
544                                         if(COMPONENTS == 4) *out_row++ = a_mid;
545                                 }
546                                 else
547                                 {
548                                         *out_row++ = y_high;
549                                         *out_row++ = u_high;
550                                         *out_row++ = v_high;
551                                         if(COMPONENTS == 4) *out_row++ = a_high;
552                                 }
553                         }
554                         else
555                         {
556                                 const int r = get_component(in_row[0]);
557                                 const int g = get_component(in_row[1]);
558                                 const int b = get_component(in_row[2]);
559                                 const int y = (r * 76 + g * 150 + b * 29) >> 8;
560                                 if (y < min)
561                                 {
562                                         *out_row++ = r_low;
563                                         *out_row++ = g_low;
564                                         *out_row++ = b_low;
565                                         if(COMPONENTS == 4) *out_row++ = a_low;
566                                 }
567                                 else if (y < max)
568                                 {
569                                         *out_row++ = r_mid;
570                                         *out_row++ = g_mid;
571                                         *out_row++ = b_mid;
572                                         if(COMPONENTS == 4) *out_row++ = a_mid;
573                                 }
574                                 else
575                                 {
576                                         *out_row++ = r_high;
577                                         *out_row++ = g_high;
578                                         *out_row++ = b_high;
579                                         if(COMPONENTS == 4) *out_row++ = a_high;
580                                 }
581                         }
582                         in_row += COMPONENTS;
583                 }
584         }
585 }
586
587 void ThresholdUnit::process_package(LoadPackage *package)
588 {
589         switch(server->data->get_color_model())
590         {
591                 case BC_RGB888:
592                         render_data<unsigned char, 3, false>(package);
593                         break;
594
595                 case BC_RGB_FLOAT:
596                         render_data<float, 3, false>(package);
597                         break;
598
599                 case BC_RGBA8888:
600                         render_data<unsigned char, 4, false>(package);
601                         break;
602
603                 case BC_RGBA_FLOAT:
604                         render_data<float, 4, false>(package);
605                         break;
606
607                 case BC_YUV888:
608                         render_data<unsigned char, 3, true>(package);
609                         break;
610
611                 case BC_YUVA8888:
612                         render_data<unsigned char, 4, true>(package);
613                         break;
614
615                 case BC_YUV161616:
616                         render_data<uint16_t, 3, true>(package);
617                         break;
618
619                 case BC_YUVA16161616:
620                         render_data<uint16_t, 4, true>(package);
621                         break;
622         }
623 }
624
625
626
627
628
629
630
631
632
633
634
635 ThresholdEngine::ThresholdEngine(ThresholdMain *plugin)
636  : LoadServer(plugin->get_project_smp() + 1,
637         plugin->get_project_smp() + 1)
638 {
639         this->plugin = plugin;
640         yuv = new YUV;
641 }
642
643 ThresholdEngine::~ThresholdEngine()
644 {
645         delete yuv;
646 }
647
648 void ThresholdEngine::process_packages(VFrame *data)
649 {
650         this->data = data;
651         LoadServer::process_packages();
652 }
653
654 void ThresholdEngine::init_packages()
655 {
656         for(int i = 0; i < get_total_packages(); i++)
657         {
658                 ThresholdPackage *package = (ThresholdPackage*)get_package(i);
659                 package->start = data->get_h() * i / get_total_packages();
660                 package->end = data->get_h() * (i + 1) / get_total_packages();
661         }
662 }
663
664 LoadClient* ThresholdEngine::new_client()
665 {
666         return (LoadClient*)new ThresholdUnit(this);
667 }
668
669 LoadPackage* ThresholdEngine::new_package()
670 {
671         return (LoadPackage*)new HistogramPackage;
672 }
673
674
675
676
677
678
679
680
681 RGBA::RGBA()
682 {
683         r = g = b = a = 0;
684 }
685
686 RGBA::RGBA(int r, int g, int b, int a)
687 {
688         this->r = r;
689         this->g = g;
690         this->b = b;
691         this->a = a;
692 }
693
694 void RGBA::set(int r, int g, int b, int a)
695 {
696         this->r = r;
697         this->g = g;
698         this->b = b;
699         this->a = a;
700 }
701
702 void RGBA::set(int rgb, int alpha)
703 {
704         r = (rgb & 0xff0000) >> 16;
705         g = (rgb & 0xff00)   >>  8;
706         b = (rgb & 0xff);
707         a = alpha;
708 }
709
710 int RGBA::getRGB() const
711 {
712         return r << 16 | g << 8 | b;
713 }
714
715 static void init_RGBA_keys(const char * prefix,
716                            string & r_s,
717                            string & g_s,
718                            string & b_s,
719                            string & a_s)
720 {
721         r_s = prefix;
722         g_s = prefix;
723         b_s = prefix;
724         a_s = prefix;
725
726         r_s += "_R";
727         g_s += "_G";
728         b_s += "_B";
729         a_s += "_A";
730 }
731
732 void RGBA::set_property(XMLTag & tag, const char * prefix) const
733 {
734         string r_s, g_s, b_s, a_s;
735         init_RGBA_keys(prefix, r_s, g_s, b_s, a_s);
736
737         tag.set_property(const_cast<char *>(r_s.c_str()), r);
738         tag.set_property(const_cast<char *>(g_s.c_str()), g);
739         tag.set_property(const_cast<char *>(b_s.c_str()), b);
740         tag.set_property(const_cast<char *>(a_s.c_str()), a);
741 }
742
743 RGBA RGBA::get_property(XMLTag & tag, const char * prefix) const
744 {
745         string r_s, g_s, b_s, a_s;
746         init_RGBA_keys(prefix, r_s, g_s, b_s, a_s);
747
748         return RGBA(tag.get_property(const_cast<char *>(r_s.c_str()), r),
749                     tag.get_property(const_cast<char *>(g_s.c_str()), g),
750                     tag.get_property(const_cast<char *>(b_s.c_str()), b),
751                     tag.get_property(const_cast<char *>(a_s.c_str()), a));
752 }
753
754 bool operator==(const RGBA & a, const RGBA & b)
755 {
756         return  a.r == b.r &&
757                 a.g == b.g &&
758                 a.b == b.b &&
759                 a.a == b.a;
760 }
761
762 template<>
763 RGBA interpolate(const RGBA & prev_color, const double & prev_scale, const RGBA &next_color, const double & next_scale)
764 {
765         return RGBA(interpolate(prev_color.r, prev_scale, next_color.r, next_scale),
766                     interpolate(prev_color.g, prev_scale, next_color.g, next_scale),
767                     interpolate(prev_color.b, prev_scale, next_color.b, next_scale),
768                     interpolate(prev_color.a, prev_scale, next_color.a, next_scale));
769 }