4 * Copyright (C) 2020 Adam Williams <broadcast at earthling dot net>
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.
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.
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
22 #include "bccmodels.h"
25 #include "colorspace.h"
26 #include "pluginserver.h"
27 #include "preferences.h"
33 REGISTER_PLUGIN(ColorSpaceMain)
35 ColorSpaceConfig::ColorSpaceConfig()
38 inp_colorspace = BC_COLORS_BT601;
39 inp_colorrange = BC_COLORS_JPEG;
40 out_colorspace = BC_COLORS_BT709;
41 out_colorrange = BC_COLORS_JPEG;
44 ColorSpaceMain::ColorSpaceMain(PluginServer *server)
45 : PluginVClient(server)
55 ColorSpaceMain::~ColorSpaceMain()
61 const char* ColorSpaceMain::plugin_title() { return N_("ColorSpace"); }
62 int ColorSpaceMain::is_realtime() { return 1; }
65 NEW_WINDOW_MACRO(ColorSpaceMain, ColorSpaceWindow)
68 void ColorSpaceMain::update_gui()
72 ColorSpaceWindow *window = (ColorSpaceWindow *)thread->window;
73 window->lock_window();
75 window->unlock_window();
79 int ColorSpaceMain::load_configuration()
81 KeyFrame *prev_keyframe = get_prev_keyframe(get_source_position());
82 read_data(prev_keyframe);
87 void ColorSpaceMain::save_data(KeyFrame *keyframe)
91 // cause data to be stored directly in text
92 output.set_shared_output(keyframe->xbuf);
93 output.tag.set_title("COLORSPACE");
94 output.tag.set_property("INVERSE", config.inverse);
95 output.tag.set_property("INP_COLORSPACE", config.inp_colorspace);
96 output.tag.set_property("INP_COLORRANGE", config.inp_colorrange);
97 output.tag.set_property("OUT_COLORSPACE", config.out_colorspace);
98 output.tag.set_property("OUT_COLORRANGE", config.out_colorrange);
100 output.tag.set_title("/COLORSPACE");
102 output.append_newline();
103 output.terminate_string();
106 void ColorSpaceMain::read_data(KeyFrame *keyframe)
109 input.set_shared_input(keyframe->xbuf);
112 while( !(result = input.read_tag()) ) {
113 if( input.tag.title_is("COLORSPACE") ) {
114 config.inverse = input.tag.
115 get_property("INVERSE", config.inverse);
116 config.inp_colorspace = input.tag.
117 get_property("INP_COLORSPACE", config.inp_colorspace);
118 config.inp_colorrange = input.tag.
119 get_property("INP_COLORRANGE", config.inp_colorrange);
120 config.out_colorspace = input.tag.
121 get_property("OUT_COLORSPACE", config.out_colorspace);
122 config.out_colorrange = input.tag.
123 get_property("OUT_COLORRANGE", config.out_colorrange);
133 memset(eqns, 0, sizeof(eqns));
134 memset(luts, 0, sizeof(luts));
135 for( int i=0; i<3; ++i ) {
136 imin[i] = omin[i] = 0; imax[i] = omax[i] = 0xff;
137 izro[i] = ozro[i] = 0; irng[i] = orng[i] = 0xff;
138 izrf[i] = ozrf[i] = 0; omnf[i] = 0; omxf[i] = 1;
147 void XTable::create_table(lut_t **lut, int len, float *vars)
149 int s = len == 0x100 ? (24-8) : (24-16);
150 for( int i=0; i<3; ++i ) {
151 lut_t imn = imin[i], imx = imax[i];
152 lut_t omn = omin[i], omx = omax[i];
153 double irng = imx+1 - imn;
154 double orng = omx+1 - omn;
155 double r = vars[i] * orng / irng;
156 lut_t izr = izro[i], v = r*(imn-izr);
157 lut_t *tbl = lut[i]; int k;
158 for( k=0; (k<<s)<imn; ++k ) tbl[k] = v;
159 for( ; (v=k<<s)<imx; ++k ) tbl[k] = r*(v-izr);
160 for( v=r*(imx-izr); k<len; ++k ) tbl[k] = v;
164 void XTable::create_tables(int len)
166 for( int i=0; i<3; ++i )
167 create_table(luts[i], len, eqns[i]);
171 void XTable::alloc_lut(int len)
173 if( this->len == len ) return;
174 for( int i=0; i<3; ++i ) {
175 for( int j=0; j<3; ++j ) {
176 delete [] luts[i][j];
177 luts[i][j] = len>0 ? new lut_t[len] : 0;
183 // these eqns were derived using python sympy
184 // and the conversion forms in bccolors.h
186 >>> from sympy import *
187 >>> var('iKr,iKg,iKb,oKr,oKg,oKb,R,G,B,Y,U,V,Py,Pr,Pb')
188 (iKr, iKg, iKb, oKr, oKg, oKb, R, G, B, Y, U, V, Py, Pr, Pb)
190 >>> Y = iKr * R + iKg * G + iKb * B
191 >>> U = - 0.5*iKr/(1-iKb)*R - 0.5*iKg/(1-iKb)*G + 0.5*B
192 >>> V = 0.5*R - 0.5*iKg/(1-iKr)*G - 0.5*iKb/(1-iKr)*B
194 >>> r = Y + V * 2*(1-oKr)
195 >>> g = Y - V * 2*oKr*(1-oKr)/oKg - U * 2*oKb*(1-oKb)/oKg
196 >>> b = Y + U * 2*(1-oKb)
198 >>> factor(r,(R,G,B))
199 -1.0*(B*(1.0*iKb*iKr - 1.0*iKb*oKr) + G*(1.0*iKg*iKr - 1.0*iKg*oKr)
200 + R*(1.0*iKr**2 - 1.0*iKr*oKr + 1.0*oKr - 1.0))/(1 - iKr)
201 >>> factor((1.0*iKr**2 - 1.0*iKr*oKr + 1.0*oKr - 1.0))
202 1.0*(iKr - 1)*(iKr - oKr + 1)
203 >>> factor((1.0*iKg*iKr - 1.0*iKg*oKr))
205 >>> factor((1.0*iKb*iKr - 1.0*iKb*oKr))
208 >>> factor(g,(R,G,B)) strlen(result)=778
209 results: eqn terms r*R + g*G + b*B, where r,g,b are the eqns coefs.
210 with some renaming, this can be done symetrically with Y,U,V
211 each coef eqn r,g,b can be reduced using factor([r,g,b])
212 which creates eqns forms that simplify to the used calculation
213 >>> factor(y,(Y,U,V))
214 results in: y*Y + u*U + v*V which y,u,v are the eqns factors
215 with same simplify and use
218 // yuv->(cs)->rgb->(cs)->yuv
219 int XTable::init_yuv2yuv(double iKr, double iKb, double oKr, double oKb)
221 double iKg = 1 - iKr - iKb;
222 double oKg = 1 - oKr - oKb;
224 Yy = iKg*(oKb + oKg + oKr) / d;
225 Uy = 2*(iKb - 1)*(iKb*oKg - oKb*iKg) / d;
226 Vy = -2*(iKr - 1)*(iKg*oKr - oKg*iKr) / d;
228 Yu = -0.5*iKg*(oKb + oKg + oKr - 1) / d;
229 Uu = -(iKb - 1)*(iKb*oKg - oKb*iKg + iKg) / d;
230 Vu = (iKr - 1)*(iKg*oKr - oKg*iKr) / d;
232 Yv = -0.5*iKg*(oKb + oKg + oKr - 1) / d;
233 Uv = -(iKb - 1)*(iKb*oKg - oKb*iKg) / d;
234 Vv = (iKr - 1)*(iKg*oKr - iKg - oKg*iKr) / d;
239 int XTable::init_rgb2yuv(double Kr, double Kb)
241 double Kg = 1 - Kr - Kb;
245 Ru = -0.5*Kr / (1 - Kb);
246 Gu = -0.5*Kg / (1 - Kb);
249 Gv = -0.5*Kg / (1 - Kr);
250 Bv = -0.5*Kb / (1 - Kr);
255 int XTable::init_yuv2rgb(double Kr, double Kb)
257 double Kg = 1 - Kr - Kb;
262 Ug = -2*Kr*(1 - Kr) / Kg;
263 Vg = -2*Kb*(1 - Kb) / Kg;
270 // rgb->(cs)->yuv->(cs)->rgb
271 int XTable::init_rgb2rgb(double iKr, double iKb, double oKr, double oKb)
273 double iKg = 1 - iKr - iKb;
274 double oKg = 1 - oKr - oKb;
275 double d = (1 - iKr);
276 Rr = -(iKr - 1)*(iKr - oKr + 1) / d;
277 Gr = -iKg*(iKr - oKr) / d;
278 Br = -iKb*(iKr - oKr) / d;
279 d = (oKg*(1 - iKb)*(1 - iKr));
280 Rg = (iKr - 1)*(iKb*oKg*iKr + iKb*oKr*oKr - iKb*oKr +
281 oKb*oKb*iKr - oKb*iKr - oKg*iKr - oKr*oKr + oKr) / d;
282 Gg = iKg*(iKb*oKg*iKr - iKb*oKg + iKb*oKr*oKr - iKb*oKr +
283 oKb*oKb*iKr - oKb*oKb - oKb*iKr + oKb - oKg*iKr +
284 oKg - oKr*oKr + oKr) / d;
285 Bg = (iKb - 1)*(iKb*oKg*iKr - iKb*oKg + iKb*oKr*oKr -
286 iKb*oKr + oKb*oKb*iKr - oKb*oKb - oKb*iKr + oKb) / d;
288 Rb = -iKr*(iKb - oKb) / d;
289 Gb = -iKg*(iKb - oKb) / d;
290 Bb = -(iKb - 1)*(iKb - oKb + 1) / d;
294 void XTable::init(int len, int inv,
295 int inp_model, int inp_space, int inp_range,
296 int out_model, int out_space, int out_range)
298 if( this->typ >= 0 && this->len == len && this->inv == inv &&
299 this->inp_model == inp_model && this->out_model == out_model &&
300 this->inp_space == inp_space && this->out_space == out_space &&
301 this->inp_range == inp_range && this->out_range == out_range )
306 this->inp_model = inp_model; this->out_model = out_model;
307 this->inp_space = inp_space; this->out_space = out_space;
308 this->inp_range = inp_range; this->out_range = out_range;
310 double iKr = BT601_Kr, iKb = BT601_Kb;
311 double oKr = BT601_Kr, oKb = BT601_Kb;
312 int impg = 0, ompg = 0;
313 switch( inp_space ) {
315 case BC_COLORS_BT601: iKr = BT601_Kr; iKb = BT601_Kb; break;
316 case BC_COLORS_BT709: iKr = BT709_Kr; iKb = BT709_Kb; break;
317 case BC_COLORS_BT2020: iKr = BT2020_Kr; iKb = BT2020_Kb; break;
319 switch( out_space ) {
321 case BC_COLORS_BT601: oKr = BT601_Kr; oKb = BT601_Kb; break;
322 case BC_COLORS_BT709: oKr = BT709_Kr; oKb = BT709_Kb; break;
323 case BC_COLORS_BT2020: oKr = BT2020_Kr; oKb = BT2020_Kb; break;
326 int iyuv = BC_CModels::is_yuv(inp_model);
327 int oyuv = BC_CModels::is_yuv(out_model);
329 (oyuv ? init_yuv2yuv(iKr,iKb, oKr,oKb) :
330 init_yuv2rgb(iKr,iKb)) :
331 (oyuv ? init_rgb2yuv(oKr,oKb) :
332 init_rgb2rgb(iKr,iKb, oKr, oKb));
334 switch( inp_range ) {
336 case BC_COLORS_JPEG: impg = 0; break;
337 case BC_COLORS_MPEG: impg = 1; break;
339 switch( out_range ) {
341 case BC_COLORS_JPEG: ompg = 0; break;
342 case BC_COLORS_MPEG: ompg = 1; break;
345 // mpg ? mpeg : jpeg/rgb
346 imin[0] = impg ? 0x100000 : 0x000000;
347 imin[1] = impg ? 0x100000 : 0x000000;
348 imin[2] = impg ? 0x100000 : 0x000000;
349 imax[0] = impg ? 0xebffff : 0xffffff;
350 imax[1] = impg ? 0xf0ffff : 0xffffff;
351 imax[2] = impg ? 0xf0ffff : 0xffffff;
352 omin[0] = ompg ? 0x100000 : 0x000000;
353 omin[1] = ompg ? 0x100000 : 0x000000;
354 omin[2] = ompg ? 0x100000 : 0x000000;
355 omax[0] = ompg ? 0xebffff : 0xffffff;
356 omax[1] = ompg ? 0xf0ffff : 0xffffff;
357 omax[2] = ompg ? 0xf0ffff : 0xffffff;
359 izro[1] = iyuv ? 0x800000 : imin[1];
360 izro[2] = iyuv ? 0x800000 : imin[2];
362 ozro[1] = oyuv ? 0x800000 : omin[1];
363 ozro[2] = oyuv ? 0x800000 : omin[2];
364 for( int i=0; i<3; ++i ) {
365 irng[i] = imax[i]+1 - imin[i];
366 orng[i] = omax[i]+1 - omin[i];
368 izrf[i] = (float)izro[i] / sz;
369 ozrf[i] = (float)ozro[i] / sz;
370 omnf[i] = (float)omin[i] / sz;
371 omxf[i] = (float)(omax[i]+1) / sz;
377 // prescale eqns for opengl
378 for( int i=0; i<3; ++i ) {
379 float *eqn = eqns[i];
380 float s = (float)orng[i] / irng[i];
381 for( int j=0; j<3; ++j ) eqn[j] *= s;
384 printf("XTable::init len=%06x\n"
385 " impg=%d, ompg=%d, iyuv=%d, oyuv=%d\n"
386 " imin=%06x,%06x,%06x, imax=%06x,%06x,%06x\n"
387 " omin=%06x,%06x,%06x, omax=%06x,%06x,%06x\n"
388 " izro=%06x,%06x,%06x, ozro=%06x,%06x,%06x\n"
389 " izrf=%0.3f,%0.3f,%0.3f, ozrf=%0.3f,%0.3f,%0.3f\n"
390 " omnf=%0.3f,%0.3f,%0.3f, omxf=%0.3f,%0.3f,%0.3f\n"
391 " eqns= %6.3f,%6.3f,%6.3f\n"
392 " %6.3f,%6.3f,%6.3f\n"
393 " %6.3f,%6.3f,%6.3f\n",
394 len, impg, ompg, iyuv, oyuv,
395 imin[0], imin[1], imin[2], imax[0], imax[1], imax[2],
396 omin[0], omin[1], omin[2], omax[0], omax[1], omax[2],
397 izro[0], izro[1], izro[2], ozro[0], ozro[1], ozro[2],
398 izrf[0], izrf[0], izrf[0], ozrf[0], ozrf[0], ozrf[0],
399 omnf[0], omnf[0], omnf[0], omxf[0], omxf[0], omxf[0],
400 eqns[0][0], eqns[0][1], eqns[0][2],
401 eqns[1][0], eqns[1][1], eqns[1][2],
402 eqns[2][0], eqns[2][1], eqns[2][2]);
407 out = (inp-izro)*eqns + ozro
408 inverse: invert(eqns), swap(inp,out), swap(izro,ozro)
409 inp = (out-ozro)*iqns + izro
411 int XTable::inverse()
415 // [ g h i ]] inverse =
416 // 1/(a(ei-fh) + b(fg-di) + c(dh-eg)) *
417 // [[ ei-fh, ch-bi, bf-ce ],
418 // [ fg-di, ai-cg, cd-af ],
419 // [ dh-eg, bg-ah, ae-bd ]]
420 float a = eqns[0][0], b = eqns[0][1], c = eqns[0][2];
421 float d = eqns[1][0], e = eqns[1][1], f = eqns[1][2];
422 float g = eqns[2][0], h = eqns[2][1], i = eqns[2][2];
423 float s = a*(e*i-f*h) + b*(f*g-d*i) + c*(d*h-e*g);
425 if( s < eps ) return 1;
427 eqns[0][0] = s*(e*i-f*h); eqns[0][1] = s*(c*h-b*i); eqns[0][2] = s*(b*f-c*e);
428 eqns[1][0] = s*(f*g-d*i); eqns[1][1] = s*(a*i-c*g); eqns[1][2] = s*(c*d-a*f);
429 eqns[2][0] = s*(d*h-e*g); eqns[2][1] = s*(b*g-a*h); eqns[2][2] = s*(a*e-b*d);
430 for( int v,i=0; i<3; ++i ) {
431 v = imin[i]; imin[i] = omin[i]; omin[i] = v;
432 v = imax[i]; imax[i] = omax[i]; omax[i] = v;
433 v = irng[i]; irng[i] = orng[i]; orng[i] = v;
434 v = izro[i]; izro[i] = ozro[i]; ozro[i] = v;
436 omnf[i] = (float)omin[i] / sz;
437 omxf[i] = (float)(omax[i]+1) / sz;
438 izrf[i] = (float)izro[i] / sz;
439 ozrf[i] = (float)ozro[i] / sz;
445 #define PROCESS_LUTS(type, comps) { \
446 for( int y=row1; y<row2; ++y ) { \
447 type *ip = (type*)irows[y]; \
448 type *op = (type*)orows[y]; \
449 for( int x=0; x<w; ++x ) { \
450 for( int i=0; i<3; ++i ) { \
451 lut_t omn = omin[i], omx = omax[i]; \
452 lut_t **lut = luts[i], v = ozro[i]; \
453 for( int j=0; j<3; ++j ) v += lut[j][ip[j]]; \
454 op[i] = (v<omn ? omn : v>omx ? omx : v) >> s; \
456 ip += comps; op += comps; \
461 #define PROCESS_EQNS(type, comps) { \
462 for( int y=row1; y<row2; ++y ) { \
463 type *ip = (type*)irows[y]; \
464 type *op = (type*)orows[y]; \
465 for( int x=0; x<w; ++x ) { \
466 for( int i=0; i<3; ++i ) { \
467 float omn = omnf[i], omx = omxf[i]; \
468 type v = ozrf[i]; float *eqn = eqns[i]; \
469 for( int j=0; j<3; ++j ) v += eqn[j]*(ip[j] - izrf[j]); \
470 op[i] = v<omn ? omn : v>omx ? omx : v; \
472 ip += comps; op += comps; \
477 void XTable::process(VFrame *inp, VFrame *out, int row1, int row2)
479 int s = len == 0x100 ? (24-8) : (24-16);
480 int w = inp->get_w(), comps = 3;
481 uint8_t **irows = inp->get_rows();
482 uint8_t **orows = out->get_rows();
484 switch( inp->get_color_model() ) {
490 PROCESS_LUTS(uint8_t, comps);
492 case BC_RGBA16161616:
493 case BC_YUVA16161616:
497 PROCESS_LUTS(uint16_t, comps);
502 PROCESS_EQNS(float, comps);
507 int ColorSpaceMain::process_realtime(VFrame *input, VFrame *output)
509 load_configuration();
510 //printf(" inv=%d, ispc=%d, irng=%d, ospc=%d, orng=%d\n", config.inverse,
511 // config.inp_colorspace, config.inp_colorrange,
512 // config.out_colorspace, config.out_colorrange);
513 if( input->get_color_model() == output->get_color_model() &&
514 config.inp_colorspace == config.out_colorspace &&
515 config.inp_colorrange == config.out_colorrange )
518 int color_model = input->get_color_model();
519 // opengl < 0, float == 0, tables > 0
520 int len = get_use_opengl() ? -1 :
521 BC_CModels::is_float(color_model) ? 0 :
522 BC_CModels::calculate_pixelsize(color_model)/
523 BC_CModels::components(color_model) == 2 ?
525 if( !engine && len >= 0 ) {
526 int cpus = output->get_w()*output->get_h() / 0x80000 + 1;
527 int max_cpus = BC_Resources::machine_cpus / 2;
528 if( cpus > max_cpus ) cpus = max_cpus;
529 if( cpus < 1 ) cpus = 1;
530 engine = new ColorSpaceEngine(this, cpus);
533 if( !xtable ) xtable = new XTable();
534 xtable->init(len, config.inverse,
535 color_model, config.inp_colorspace, config.inp_colorrange,
536 color_model, config.out_colorspace, config.out_colorrange);
537 inp = input; out = output;
538 if( get_use_opengl() )
541 engine->process_packages();
546 void ColorSpaceUnit::process_package(LoadPackage *package)
548 ColorSpacePackage *pkg = (ColorSpacePackage*)package;
549 plugin->xtable->process(plugin->inp, plugin->out, pkg->row1, pkg->row2);
552 ColorSpaceEngine::ColorSpaceEngine(ColorSpaceMain *plugin, int cpus)
553 // : LoadServer(1, 1)
554 : LoadServer(cpus, cpus)
556 this->plugin = plugin;
559 void ColorSpaceEngine::init_packages()
561 int row = 0, h = plugin->inp->get_h();
562 for( int i=0, n=get_total_packages(); i<n; ) {
563 ColorSpacePackage *pkg = (ColorSpacePackage*)get_package(i);
565 pkg->row2 = row = (++i * h) / n;
569 LoadClient* ColorSpaceEngine::new_client()
571 return new ColorSpaceUnit(plugin, this);
574 LoadPackage* ColorSpaceEngine::new_package()
576 return new ColorSpacePackage();
579 ColorSpacePackage::ColorSpacePackage()
584 ColorSpaceUnit::ColorSpaceUnit(ColorSpaceMain *plugin, ColorSpaceEngine *engine)
587 this->plugin = plugin;
592 int ColorSpaceMain::handle_opengl()
595 static const char *colorspace_frag =
596 "uniform sampler2D tex;\n"
597 "uniform mat3 eqns;\n"
598 "uniform vec3 izrf, ozrf;\n"
599 "uniform vec3 omnf, omxf;\n"
602 " gl_FragColor = texture2D(tex, gl_TexCoord[0].st);\n"
603 " gl_FragColor.rgb = clamp((gl_FragColor.rgb-izrf)*eqns + ozrf, omnf, omxf);\n"
606 inp->enable_opengl();
607 inp->bind_texture(0);
609 unsigned int frag_shader = VFrame::make_shader(0, colorspace_frag, 0);
610 if( xtable && frag_shader > 0 ) {
611 glUseProgram(frag_shader);
612 glUniform1i(glGetUniformLocation(frag_shader, "tex"), 0);
613 glUniformMatrix3fv(glGetUniformLocation(frag_shader, "eqns"), 1, false, xtable->eqns[0]);
614 glUniform3fv(glGetUniformLocation(frag_shader, "izrf"), 1, xtable->izrf);
615 glUniform3fv(glGetUniformLocation(frag_shader, "ozrf"), 1, xtable->ozrf);
616 glUniform3fv(glGetUniformLocation(frag_shader, "omnf"), 1, xtable->omnf);
617 glUniform3fv(glGetUniformLocation(frag_shader, "omxf"), 1, xtable->omxf);
620 out->enable_opengl();
621 VFrame::init_screen(out->get_w(), out->get_h());
624 out->set_opengl_state(VFrame::SCREEN);