File indexing completed on 2024-06-16 04:38:29
0001 #!/usr/bin/node --trace-uncaught 0002 0003 const util = require('util'); 0004 0005 Object.prototype.lp = function(name, value) { 0006 Object.defineProperty(this, name, { value: value }); 0007 return value; 0008 } 0009 0010 Number.prototype.toStdFloat = function(precision=7){ 0011 return this.toPrecision(precision).replace(/([^\.])0+$/, '$1'); 0012 } 0013 0014 Number.prototype.toCFloat = function(precision=7){ 0015 return this.toStdFloat(precision) + (precision <= 7 ? 'f' : ''); 0016 } 0017 0018 class vec { 0019 constructor(x, y, z, w) { 0020 let a = []; 0021 for(const i in arguments) { 0022 const v = arguments[i]; 0023 if(v instanceof Array) 0024 a = a.concat(v); 0025 else if(v instanceof vec) 0026 a = a.concat(v.elem); 0027 else if(typeof(v) == 'number') 0028 a.push(v); 0029 } 0030 this.elem = a.slice(0, 4); 0031 this.ch = ['x', 'y', 'z', 'w'].slice(0, this.elem.length); 0032 // dynamic property array getter - inst.xyz, inst.xyzz, ... will get 0033 const rgb = { x:'r', y:'g', z:'b', w:'a'}; 0034 for(let i = 0, pr = ['']; i < this.elem.length; i++) { 0035 for(let j = 0, n = pr.length; j < n; j++) { 0036 const pre = pr.shift(); 0037 for(let k = 0; k < this.elem.length; k++) { 0038 const key = pre + this.ch[k]; 0039 pr.push(key); 0040 if(pre) 0041 Object.defineProperty(this, key, { get: () => this.val(key) }); 0042 else 0043 this[key] = this.elem[k]; 0044 Object.defineProperty(this, key.replace(/./g, (m) => rgb[m]), { get: () => this.val(key) }); 0045 } 0046 } 0047 } 0048 } 0049 0050 val(name) { return name.length > 1 ? name.split('').map(v => this[v]) : this[name]; } 0051 0052 get size() { return this.elem.length; } 0053 0054 get line() { return { a:-this.y, b:this.x, c:0 }; } 0055 0056 get mag() { return this.lp('mag', Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2))); } 0057 0058 debugDump(name, comment, precision=7) { 0059 if(comment) 0060 console.log(`// ${comment}`); 0061 const p = precision ? (val) => val.toFixed(precision).replace(/^-(0\.0+)$/, '$1') : (val) => val; 0062 console.log(`${name}\t${this.elem.map(v => p(v)).join('\t')}`); 0063 } 0064 0065 angle(other) { 0066 const l1 = this.line; 0067 const l2 = other.line; 0068 const m1 = -l1.a / l1.b; 0069 const m2 = -l2.a / l2.b; 0070 return Math.atan((m1 - m2) / (1. + m1 * m2)); 0071 } 0072 0073 dot(other) { 0074 let res = 0; 0075 for(let i = 0; i < this.size; i++) { 0076 const ch = String.fromCharCode(120 + i); 0077 res += this[ch] * other[ch]; 0078 } 0079 return res; 0080 // return this.mag * other.mag * Math.cos(this.angle(other)); 0081 // return this.x * other.x + this.y * other.y; 0082 } 0083 0084 mul(other) { return new vec(this.elem.map(v => v * other)); } 0085 div(other) { return new vec(this.elem.map(v => v / other)); } 0086 0087 /** 0088 * intersection point of line p1-p2 and line p3-p4 0089 */ 0090 static intersection(p1, p2, p3, p4) { 0091 const x12 = p1.x - p2.x; 0092 const x34 = p3.x - p4.x; 0093 const y12 = p1.y - p2.y; 0094 const y34 = p3.y - p4.y; 0095 const d = x12 * y34 - y12 * x34; 0096 const xy12 = p1.x * p2.y - p1.y * p2.x; 0097 const xy34 = p3.x * p4.y - p3.y * p4.x; 0098 const ix = (xy12 * x34 - x12 * xy34) / d; 0099 const iy = (xy12 * y34 - y12 * xy34) / d; 0100 return new vec(ix, iy); 0101 } 0102 0103 /** 0104 * line through two points [ax + by + c = 0] 0105 */ 0106 static line(x1, y1, x2, y2) { 0107 return { 0108 a: y1 - y2, 0109 b: x2 - x1, 0110 c: x1 * y2 - x2 * y1, 0111 }; 0112 } 0113 } 0114 0115 class mat { 0116 constructor(n_or_array) { 0117 if(arguments.length > 1) { 0118 this.elem = Array.from(arguments); 0119 } else if(n_or_array instanceof Array) { 0120 this.elem = n_or_array; 0121 } else if(typeof(n_or_array) == 'number'){ 0122 this.elem = Array.from(new Array(n_or_array * n_or_array)) 0123 .map((v, i) => i % n_or_array == parseInt(i / n_or_array) ? 1. : 0.); 0124 } else { 0125 throw `mat() should be constructed by numbers list, numbers array or mat size`; 0126 } 0127 } 0128 0129 get length() { return this.elem.length; } 0130 get size() { return this.lp('size', Math.sqrt(this.length)); } 0131 0132 array2D(precision) { 0133 let s = []; 0134 const p = precision ? (val) => parseFloat(val.toPrecision(precision)) : (val) => val; 0135 for(let y = 0; y < this.size; y++) { 0136 s[y] = []; 0137 for(let x = 0; x < this.size; x++) 0138 s[y].push(p(this.elem[y * this.size + x])); 0139 } 0140 return s; 0141 } 0142 debugDump(name, comment, precision=7) { 0143 let s = []; 0144 const p = precision ? (val) => val.toFixed(precision).replace(/^-(0\.0+)$/, '$1') : (val) => val; 0145 for(let y = 0; y < this.size; y++) { 0146 s[y] = []; 0147 for(let x = 0; x < this.size; x++) 0148 s[y].push(p(this.elem[y * this.size + x])); 0149 s[y] = s[y].join('\t'); 0150 } 0151 if(comment) 0152 console.log(`// ${comment}`); 0153 console.log(`${name}\t${s.join('\n\t')}`); 0154 } 0155 glDump(name, comment, precision=7) { 0156 let s = []; 0157 for(let y = 0; y < this.size; y++) 0158 for(let x = 0; x < this.size; x++) 0159 s.push((!x ? '\n\t' : '') + this.elem[y * this.size + x].toStdFloat(precision)); 0160 console.log(`mat${this.size} ${name} = mat${this.size}(${s.join(', ')});`); 0161 } 0162 cFloat(name, comment, precision=7) { 0163 let s = []; 0164 for(let y = 0; y < this.size; y++) 0165 for(let x = 0; x < this.size; x++) 0166 s.push(this.elem[y + x * this.size].toCFloat(precision)); 0167 return { name:name, comment:comment, mat:s }; 0168 } 0169 0170 mul(other) { 0171 return typeof(other) == 'number' ? this.mulScalar(other) : 0172 (other instanceof vec ? this.mulVec(other) : this.mulMat(other)); 0173 } 0174 mulVec(other) { 0175 if(other.size != this.size) 0176 throw `mat${this.size} and vec${other.size} must have same size`; 0177 let res = other.elem.map(() => 0.); 0178 for(let r = 0; r < this.size; r++) { 0179 const ro = r * this.size; 0180 for(let c = 0; c < other.size; c++) 0181 res[r] += other.elem[c] * this.elem[ro + c]; 0182 } 0183 return new vec(res); 0184 } 0185 mulMat(other) { 0186 if(other.size != this.size) 0187 throw `mat${this.size} and mat${other.size} must have same size`; 0188 let res = []; 0189 for(let r = 0; r < this.size; r++) { 0190 for(let c = 0; c < this.size; c++) { 0191 let v = 0.; 0192 for(let k = 0; k < this.size; k++) 0193 v += this.elem[r * this.size + k] * other.elem[k * this.size + c]; 0194 res.push(v); 0195 } 0196 } 0197 return new mat(res); 0198 } 0199 mulScalar(other) { 0200 return new mat(this.elem.map((v, i) => v * other)); 0201 } 0202 0203 trans() { 0204 return new mat(this.elem.map((v, i) => this.elem[parseInt(i / this.size) + (i % this.size) * this.size])); 0205 } 0206 0207 sum(other) { 0208 if(other.size != this.size) 0209 throw `mat${this.size} and mat${other.size} must have same size`; 0210 return new mat(this.elem.map((v, i) => v + other.elem[i])); 0211 } 0212 0213 det() { 0214 let el = this.elem.map(v => v); 0215 for(let i = 0; i < this.size - 1; i++) { 0216 for(let j = i + 1; j < this.size; j++) { 0217 const mul = el[j * this.size + i] / el[i * this.size + i]; 0218 if(!mul || isNaN(mul)) continue; 0219 for(let k = i; k < this.size; k++) 0220 el[j * this.size + k] -= mul * el[i * this.size + k]; 0221 } 0222 } 0223 let d = 1.; 0224 for(let i = 0; i < this.size; i++) 0225 d *= el[this.size * i + i]; 0226 return d; 0227 } 0228 0229 inv() { 0230 let m1 = []; 0231 for(let i = 0; i < this.size; i++) { 0232 let s = this.elem.slice(i * this.size, (i + 1) * this.size); 0233 for(let k = 0; k < this.size; k++) 0234 s.push(i == k ? 1. : 0.); 0235 m1.push(s); 0236 } 0237 mat.solveLinearEquation(m1); 0238 for(let i = 0; i < this.size; i++) 0239 m1[i] = m1[i].slice(this.size); 0240 return new mat(m1.flat()); 0241 } 0242 0243 static solveLinearEquation(sys) { 0244 const len = Math.min(sys.length, sys[0].length - 1); 0245 for(let j = 0; j < len; j++) { 0246 // normalize diagonal 0247 for(let i = 0, n = sys[j][j]; i < sys[j].length; i++) 0248 sys[j][i] /= n; 0249 // zero above 0250 for(let jj = j - 1; jj >= 0; jj--) { 0251 for(let i = j, n = sys[jj][j]; i < sys[j].length; i++) 0252 sys[jj][i] -= n * sys[j][i]; 0253 } 0254 // zero under 0255 for(let jj = j + 1; jj < sys.length; jj++) { 0256 for(let i = j, n = sys[jj][j]; i < sys[j].length; i++) 0257 sys[jj][i] -= n * sys[j][i]; 0258 } 0259 } 0260 return sys.map(el => el[el.length - 1]); 0261 } 0262 } 0263 0264 class ColorCoefficients { 0265 // NOTE: fullRange is ignored... matrix is always full range 0266 constructor(Kr, Kg, Kb, fullRange) { 0267 this.Kr = Kr; 0268 this.Kg = Kg; 0269 this.Kb = Kb; 0270 this.fullRange = fullRange; 0271 } 0272 0273 mat3_RGB_RGB() { 0274 return new mat( 0275 1., 0., 0., 0276 0., 1., 0., 0277 0., 0., 1. 0278 ); 0279 } 0280 0281 // Y'CbCr (full range) and Y'PbPr 0282 mat3_YUV_RGB() { 0283 /* 0284 Rec. 709 calls for luma range 0...219, offset +16 at the interface. Cb and Cr are 0285 scaled individually to range ±112, an excursion 224/219 of luma, offset +128 at 0286 the interface. Codes 0 and 255 are prohibited. 0287 */ 0288 0289 return new mat( 0290 1.0, 0.0, 2.0 * (1.0 - this.Kr), 0291 1.0, -2.0 * (1.0 - this.Kb) * (this.Kb / this.Kg), -2.0 * (1.0 - this.Kr) * (this.Kr / this.Kg), 0292 1.0, 2.0 * (1.0 - this.Kb), 0.0 0293 ); 0294 0295 /* 0296 const Ymax = this.fullRange ? 255.0 / 256.0 : (16.0 + 219.0) / 254.0; 0297 const Cmax = this.fullRange ? 255.0 / 256.0 : (16.0 + 224.0) / 254.0; 0298 const Umax = (Cmax - this.Kb) / 2.0; // (B'max - Y'(B'max)) / 2.0 0299 const Vmax = (Cmax - this.Kr) / 2.0; // (R'max - Y'(R'max)) / 2.0 0300 0301 // const R = (255 / 219) * (Y - 16) + (255 / 224) * 2 * (1. - this.Kr) * (Cr - 128); 0302 // const G = (255 / 219) * (Y - 16) - (255 / 224) * 2 * (1. - this.Kb) * (this.Kb / this.Kg) * (Cb - 128) - (255 / 224) * 2 * (1. - this.Kr) * (this.Kr / this.Kg) * (Cr - 128); 0303 // const B = (255 / 219) * (Y - 16) + (255 / 224) * 2 * (1. - this.Kb) * (Cb - 128); 0304 0305 return new mat( 0306 1., 0., (1. - this.Kr) / Vmax, 0307 1., -this.Kb * (1. - this.Kb) / (Umax * this.Kg), -this.Kr * (1. - this.Kr) / (Vmax * this.Kg), 0308 1., (1. - this.Kb) / Umax, 0. 0309 ); 0310 */ 0311 } 0312 } 0313 0314 class ColorPrimaries { 0315 /** 0316 * Params are CIE xy (not normalized) chromacity diagram coordinates - ISO 23001-8-18 (7.1) Color primaries 0317 */ 0318 constructor(Rx, Ry, Gx, Gy, Bx, By, Wx, Wy) { 0319 // ColourPrimaries indicates the chromaticity coordinates of the source colour primaries 0320 // in terms of the CIE 1931 definition of x and y as specified by ISO 11664-1. 0321 this.Rx = Rx; 0322 this.Ry = Ry; 0323 this.Rz = 1 - Rx - Ry; 0324 this.Gx = Gx; 0325 this.Gy = Gy; 0326 this.Gz = 1 - Gx - Gy; 0327 this.Bx = Bx; 0328 this.By = By; 0329 this.Bz = 1 - Bx - By; 0330 0331 // CIE chromaticity coordinates (Wx, Wy) represent white point of standardized illuminant of a perfectly 0332 // reflecting (or transmitting) diffuser. The CIE chromaticity coordinates are given for the 2 degree field 0333 // of view (1931). The color swatches represent the hue and RGB of white point, calculated with luminance 0334 // Y=0.54 and the standard observer, assuming correct sRGB display calibration. 0335 // We can use Bradford Matrix to convert XYZ to different (Wx, Wy) 0336 this.Wx = Wx; 0337 this.Wy = Wy; 0338 this.Wz = 1 - Wx - Wy; 0339 } 0340 0341 /** 0342 * Calculate and return RGB Luminance coefficients 0343 */ 0344 get rgbYratios() { 0345 // https://stackoverflow.com/questions/53952959/why-were-the-constants-in-yuv-rgb-chosen-to-be-the-values-they-are/64392724#64392724 0346 // Digital Video and HDTV - Algorithms and Interfaces - C. Poynton (2003) WW - Luminance coefficients - pg.256 0347 return this.lp('rgbYratios', new vec(mat.solveLinearEquation([ 0348 // Ra, Ga, Ba, W 0349 [ this.Rx, this.Gx, this.Bx, this.Wx / this.Wy ], 0350 [ this.Ry, this.Gy, this.By, this.Wy / this.Wy ], 0351 [ this.Rz, this.Gz, this.Bz, this.Wz / this.Wy ], 0352 ]))); 0353 } 0354 0355 /** 0356 * mix of multiple CIE (x, y) chromaticities with Y luminance 0357 */ 0358 mix(x1, y1, Y1, ...args) { 0359 if(arguments.length % 3) 0360 throw 'Wrong number of components... need multiple CIE xyY coordinates'; 0361 0362 /* 0363 https://en.wikipedia.org/wiki/CIE_1931_color_space 0364 0365 The CIE XYZ color space was deliberately designed so that the Y parameter is a measure 0366 of the luminance of a color. The chromaticity is then specified by the two derived parameters x and y, 0367 two of the three normalized values being functions of all three tristimulus values X, Y, and Z: 0368 0369 x = X / (X + Y + Z) 0370 y = Y / (X + Y + Z) 0371 z = Z / (X + Y + Z) = 1 - x - y 0372 0373 The X and Z tristimulus values can be calculated back from the chromaticity values x and y and the Y tristimulus value: 0374 0375 X = Y * x / y 0376 Z = Y * z / y = Y * (1 - x - y) / y 0377 0378 When two or more colors are additively mixed, the x and y chromaticity coordinates of the resulting color (xMix, yMix) 0379 may be calculated from the chromaticities of the mixture components (x1,y1; x2,y2; ...; xn,yn) and their corresponding 0380 luminances (L1, L2, ..., Ln) with the following formulas: 0381 0382 xMix = (x1/y1*L1 + x2/y2*L2 + x3/y3*L3 + ...) / (L1/y1 + L2/y2 + L3/y3 + ...) 0383 yMix = (L1 + L2 + L3 + ...) / (L1/y1 + L2/y2 + L3/y3 + ...) 0384 zMix = 1 - xMix - yMix; // derived from below 0385 ... Ln == Yn 0386 0387 These formulas can be derived from the previously presented definitions of x and y chromaticity coordinates by taking 0388 advantage of the fact that the tristimulus values X, Y, and Z of the individual mixture components are directly additive. 0389 0390 In place of the luminance values (L1, L2, etc.) one can alternatively use any other photometric quantity that is directly 0391 proportional to the tristimulus value Y (naturally meaning that Y itself can also be used as well). 0392 0393 As already mentioned, when two colors are mixed, the resulting color (xMix, yMix) will lie on the straight line segment 0394 that connects these colors on the CIE xy chromaticity diagram. To calculate the mixing ratio of the component colors (x1, y1) 0395 and (x2, y2) that results in a certain (xMix, yMix) on this line segment, one can use the formula: 0396 0397 L1 / L2 = (y1 * (x2 - xMix)) / (y2 * (xMix - x1)) = (y1 * (y2 - yMix)) / (y2 * (yMix - y1)) 0398 0399 Note that, in accordance with the remarks concerning the formulas for xMix and yMix, the mixing ratio L1/L2 may well be 0400 expressed in terms of photometric quantities other than luminance. 0401 */ 0402 0403 let div = 0, xMix = 0, yMix = 0; 0404 for(let i = 0; i < arguments.length; i += 3) { 0405 const x = arguments[i + 0]; 0406 const y = arguments[i + 1]; 0407 const Y = arguments[i + 2]; 0408 div += Y / y; 0409 xMix += Y * x / y; 0410 yMix += Y; 0411 } 0412 xMix /= div; 0413 yMix /= div; 0414 0415 return new vec(xMix, yMix, 1. - xMix - yMix); 0416 } 0417 0418 mat3_YUV_RGB() { 0419 // YUV -> RGB coefficients 0420 const rat = this.rgbYratios; 0421 const Kr = rat.r * this.Ry; 0422 const Kg = rat.g * this.Gy; 0423 const Kb = rat.b * this.By; 0424 0425 const sum = parseFloat((Kr + Kg + Kb).toFixed(6)); 0426 if(sum !== 1.0) 0427 console.warn(`${Kr} + ${Kg} + ${Kb} != 1.0 (${sum})`); 0428 0429 return new ColorCoefficients(Kr, Kg, Kb, false).mat3_YUV_RGB(); 0430 } 0431 0432 /** 0433 * Get luminance Y at CIE (x, y) chromaticity 0434 */ 0435 get_xy_Y(pt) { 0436 const rat = this.rgbYratios; 0437 const Rv = new vec(this.Rx, this.Ry, rat.r * this.Ry); 0438 const Gv = new vec(this.Gx, this.Gy, rat.g * this.Gy); 0439 const Bv = new vec(this.Bx, this.By, rat.b * this.By); 0440 0441 // luminance between p1 and p2 at position x - x must be at line p1-p2 0442 const lumaAtCIExy = (p1, p2, xMix) => { 0443 const a = p1.y * (p2.x - xMix); 0444 const b = p2.y * (xMix - p1.x); 0445 0446 return p1.z * a / (a + b) + p2.z * b / (a + b); 0447 0448 if(Math.abs(a) <= 1e-5) // xMix is at p2 0449 return p2.z; 0450 if(Math.abs(b) <= 1e-5) // xMix is at p1 0451 return p1.z; 0452 return p2.z * a / b + p1.z * b / a; 0453 }; 0454 0455 // p - intersection point between R-G and B-(x, y) 0456 let ip = vec.intersection(Rv, Gv, Bv, pt); 0457 if(Number.isNaN(ip.x)) // can't make line between Bv and (x, y) cause they're same point 0458 return Bv.z; 0459 // luminance at R-G intersection point 0460 ip = new vec(ip.xy, lumaAtCIExy(Rv, Gv, ip.x)); 0461 // luminance at (x, y) 0462 return lumaAtCIExy(ip, Bv, pt.x); 0463 } 0464 0465 // approximation at best - they are not linear transforms 0466 mat4_xyY_RGB() { 0467 return this.mat4_RGB_xyY().inv(); 0468 } 0469 mat4_RGB_xyY() { 0470 const rat = this.rgbYratios; 0471 const cx = new vec(mat.solveLinearEquation([ 0472 [ 1, 1, 1, 1, this.Wx ], 0473 [ 1, 0, 0, 1, this.Rx ], 0474 [ 0, 1, 0, 1, this.Gx ], 0475 [ 0, 0, 1, 1, this.Bx ], 0476 ])); 0477 const cy = new vec(mat.solveLinearEquation([ 0478 [ 1, 1, 1, 1, this.Wy ], 0479 [ 1, 0, 0, 1, this.Ry ], 0480 [ 0, 1, 0, 1, this.Gy ], 0481 [ 0, 0, 1, 1, this.By ], 0482 ])); 0483 return new mat( 0484 cx.r, cx.g, cx.b, cx.a, 0485 cy.r, cy.g, cy.b, cy.a, 0486 rat.r * this.Ry, rat.g * this.Gy, rat.b * this.By, 0., 0487 0., 0., 0., 1. 0488 ); 0489 } 0490 0491 mat3_XYZ_RGB() { return this.mat3_RGB_XYZ().inv(); } 0492 mat3_RGB_XYZ() { 0493 const rat = this.rgbYratios; 0494 return new mat( 0495 rat.r * this.Rx, rat.g * this.Gx, rat.b * this.Bx, 0496 rat.r * this.Ry, rat.g * this.Gy, rat.b * this.By, 0497 rat.r * this.Rz, rat.g * this.Gz, rat.b * this.Bz 0498 ); 0499 } 0500 } 0501 0502 class ColorTransfer { 0503 constructor(funcs, values) { 0504 /* 0505 ISO 23001-8-18 (7.2) Transfer characteristics 0506 TransferCharacteristics indicates the opto-electronic transfer characteristic of the source picture, 0507 as specified in Table 3, as a function of a linear optical intensity input Lc with a nominal real-valued 0508 range, of 0 to 1. For interpretation of entries in Table 3 that are expressed in terms of multiple curve 0509 segments parameterized by the variable α over a region bounded by the variable β or by the variables 0510 β and γ, the values of α and β are defined to be the positive constants necessary for the curve segments 0511 that meet at the value β to have continuity of both value and slope at the value β. The value of γ, when 0512 applicable, is defined to be the positive constant necessary for the associated curve segments to meet at 0513 the value γ. 0514 For example, for TransferCharacteristics equal to 1, 6, 14, or 15, β has the value 0.018053968510807, and 0515 α has the value 1 + 5.5 * β = 1.099296826809442. 0516 */ 0517 0518 this.funcs = funcs.map((v, i) => { 0519 for(const val in values) 0520 v = String(v).replace(new RegExp(`(?<=\\W|^)${val}(?=\\W|$)`, 'g'), `(${values[val]})`); 0521 return this.funcToTree(v); 0522 }); 0523 } 0524 0525 funcString(tree, fMap, pre, post, first) { 0526 if(typeof(tree) === 'number') 0527 return fMap.number(tree); 0528 if(typeof(tree) === 'string') 0529 return this.isOperator(tree) && !first ? ` ${tree} ` : tree; 0530 if(tree.func) 0531 return fMap[tree.func].apply(this, tree.args.map(v => this.funcString(v, fMap))); 0532 0533 let str = ''; 0534 for(let i = 0; i < tree.length; i++) 0535 str += this.funcString(tree[i], fMap, tree[i - 1], tree[i + 1], i < 1); 0536 return tree.length > 1 && ((pre && pre != '+') || post == '*' || post == '/') ? `(${str})` : str; 0537 } 0538 0539 funcGLSL(tree) { 0540 return 'return ' + this.funcString(tree, { 0541 number: v => v.toStdFloat(7), 0542 pow: (b, e) => `pow(${b}, ${e})`, 0543 log: v => `(log(${v}) / log(10.0))` 0544 }) + ';\n'; 0545 } 0546 0547 treeReplaceVar(tree, name, val) { 0548 if(Array.isArray(tree)) { 0549 for(let i = 0; i < tree.length; i++) 0550 tree[i] = this.treeReplaceVar(tree[i], name, val); 0551 return tree; 0552 } 0553 if(tree.func) { 0554 tree.args = this.treeReplaceVar(tree.args, name, val); 0555 return tree; 0556 } 0557 return tree === name ? val : tree; 0558 } 0559 0560 treeContains(tree, name) { 0561 if(Array.isArray(tree)) { 0562 for(let i = 0; i < tree.length; i++) { 0563 if(this.treeContains(tree[i], name)) 0564 return true; 0565 } 0566 return false; 0567 } else if(tree.func) { 0568 return this.treeContains(tree.args, name); 0569 } 0570 0571 return tree === name; 0572 } 0573 0574 funcExtract(tree, name) { 0575 // const ui = v => util.inspect(v, false, null, false); 0576 // console.log(`***** funcExtract(${name})`, ui(tree)); 0577 tree = JSON.parse(JSON.stringify(tree)); // deep clone 0578 let right = []; 0579 const funcInv = { 0580 pow: (l, r) => [ l[0], { func:'pow', args:[r, this.treeSimplify([1.0, '/', l[1]])] } ], 0581 log: (l, r) => [ l[0], { func:'pow', args:[10, this.treeSimplify(r)] } ], 0582 }; 0583 0584 for(;;) { 0585 if(tree.length == 1 && tree[0] === name) 0586 break; 0587 0588 let namePos = -1; 0589 for(let i = 0; i < tree.length; i++) { 0590 if(this.treeContains(tree[i], name)) { 0591 namePos = i; 0592 break; 0593 } 0594 } 0595 0596 if(namePos < 0) { 0597 console.warn(`WARNING: variable '${name}' is not in equation.`); 0598 return [0.0]; 0599 } 0600 0601 if(namePos == 0) { 0602 if(tree.length > 1) { 0603 let oper = tree[1]; 0604 switch(oper) { 0605 case '-': 0606 right = this.treeSimplify(right, true); 0607 right = this.treeSimplify([right, '+', this.treeSimplify(tree.splice(2, tree.length - 2))], true); 0608 break; 0609 case '+': 0610 right = this.treeSimplify(right, true); 0611 right = this.treeSimplify([right, '+', this.treeNegate(tree.splice(2, tree.length - 2))], true); 0612 break; 0613 case '*': 0614 right = this.treeSimplify(right, true); 0615 right = this.treeSimplify([right, '/', this.treeSimplify(tree.splice(2, tree.length - 2))], true); 0616 break; 0617 case '/': 0618 right = this.treeSimplify(right, true); 0619 right = this.treeSimplify([right, '*', this.treeSimplify(tree.splice(2, tree.length - 2))], true); 0620 break; 0621 default: 0622 oper = null; 0623 break; 0624 } 0625 if(oper) { 0626 tree.splice(1, 1); 0627 tree = this.treeSimplify(tree, true); 0628 // console.log(`post${oper}`, ui(tree), '==', ui(right)); 0629 } else { 0630 throw `Unexpected operator ${tree[1]}`; 0631 } 0632 } 0633 if(tree[0] === name) 0634 break; 0635 if(tree[0].func) { 0636 const f = tree[0].func; 0637 // console.log('INV', tree[0], ' == ', ui(right)); 0638 const r = funcInv[f](tree[0].args, right); 0639 tree[0] = r[0]; 0640 tree = this.treeSimplify(tree, true); 0641 right = [r[1]]; 0642 // console.log(`inv-${f}`, ui(tree), '==', ui(right)); 0643 } 0644 } else { 0645 let oper = tree[namePos - 1]; 0646 switch(oper) { 0647 case '-': 0648 if(namePos == 1) { 0649 right = this.treeNegate(right, true); 0650 } else { 0651 right = this.treeSimplify(right, true); 0652 right = this.treeSimplify([right, '+', this.treeSimplify(tree.splice(0, namePos - 1))], true); 0653 } 0654 break; 0655 case '+': 0656 right = this.treeSimplify(right, true); 0657 right = this.treeSimplify([right, '+', this.treeNegate(tree.splice(0, namePos - 1))], true); 0658 break; 0659 case '*': 0660 right = this.treeSimplify(right, true); 0661 right = this.treeSimplify([right, '/', this.treeSimplify(tree.splice(0, namePos - 1))], true); 0662 break; 0663 case '/': 0664 right = this.treeSimplify(right, true); 0665 right = this.treeSimplify([this.treeSimplify(tree.splice(0, namePos - 1)), '/', right], true); 0666 break; 0667 default: 0668 oper = null; 0669 break; 0670 } 0671 if(oper) { 0672 tree.splice(0, 1); 0673 tree = this.treeSimplify(tree, true); 0674 // console.log(`pre${oper}`, ui(tree), '==', ui(right)); 0675 } 0676 } 0677 } 0678 0679 right = this.treeSimplify(right); 0680 // console.log('done:', ui(tree), '==', ui(right)); 0681 0682 return right; 0683 } 0684 0685 isOperator(val) { return ['*', '/', '+', '-'].includes(val); } 0686 0687 funcParams(str) { 0688 let params = []; 0689 0690 while(str && str[0] != ')') { 0691 let r = this.funcProc(str); 0692 str = r[1]; 0693 if(r[0]) 0694 params.push(r[0]); 0695 if(r[2] == ')') 0696 break; 0697 } 0698 0699 return [params, str]; 0700 } 0701 0702 treeNegate(tree, toplevel) { 0703 if(typeof(tree) === 'number') 0704 return -tree; 0705 if(tree[0] == '-') { 0706 tree.splice(0, 1); 0707 return this.treeSimplify(tree, toplevel); 0708 } 0709 return ['-', tree]; 0710 } 0711 0712 treeSimplify(tree, toplevel) { 0713 if(!Array.isArray(tree)) 0714 return tree; 0715 tree = tree.map(v => typeof(v) == 'string' && v.match(/^[0-9\.]+$/) ? parseFloat(v) : v); 0716 // tree = tree.map(v => Array.isArray(v) ? this.treeSimplify(v) : v); 0717 0718 while((Array.isArray(tree[0]) && !tree[0].length) || tree[0] === '+') 0719 tree.shift(); 0720 0721 for(let i = 0; i < tree.length; i++) { 0722 if((i == 0 || this.isOperator(tree[i - 1])) && tree[i] === '-') { 0723 tree.splice(i, 1); 0724 tree[i] = this.treeNegate(tree[i]); 0725 } else if(i && Array.isArray(tree[i]) && tree[i][0] === '-') { 0726 if(tree[i - 1] === '-') { 0727 tree[i - 1] = '+'; 0728 tree[i].splice(0, 1); 0729 tree[i] = this.treeSimplify(tree[i]); 0730 } else if(tree[i - 1] === '+') { 0731 tree[i - 1] = '-'; 0732 tree[i].splice(0, 1); 0733 tree[i] = this.treeSimplify(tree[i]); 0734 } 0735 } 0736 } 0737 0738 const replaceConst = (tree, toplevel) => { 0739 ['*', '/', '+', '-'].forEach(oper => { 0740 for(let i = 1; i < tree.length; i++) { 0741 if(tree[i] !== oper || typeof(tree[i - 1]) !== 'number' || typeof(tree[i + 1]) !== 'number') 0742 continue; 0743 switch(oper) { 0744 case '*': tree[i - 1] *= tree[i + 1]; break; 0745 case '/': tree[i - 1] /= tree[i + 1]; break; 0746 case '+': tree[i - 1] += tree[i + 1]; break; 0747 case '-': tree[i - 1] -= tree[i + 1]; break; 0748 } 0749 tree.splice(i, 2); 0750 i = 1; 0751 } 0752 }); 0753 return toplevel || tree.length != 1 ? tree : tree[0]; 0754 }; 0755 0756 ['*', '/', '+', '-'].forEach(oper => { 0757 for(;;) { 0758 let found = false; 0759 for(let i = 1; i < tree.length - 1;) { 0760 if(tree[i] !== oper) { 0761 i++; 0762 continue; 0763 } 0764 found = true; 0765 tree.splice(i - 1, 0, replaceConst(tree.splice(i - 1, 3))); 0766 } 0767 if(!found) 0768 break; 0769 } 0770 }); 0771 0772 tree = replaceConst(tree, toplevel); 0773 0774 while(tree && (!toplevel || Array.isArray(tree[0])) && tree.length == 1) 0775 tree = tree[0]; 0776 0777 return tree; 0778 } 0779 0780 funcProc(str) { 0781 let tree = []; 0782 let part = ''; 0783 let ch = null; 0784 while(str) { 0785 ch = str[0]; str = str.substr(1); 0786 if(ch == '(') { 0787 if(part) { 0788 let r = this.funcParams(str); 0789 str = r[1]; 0790 tree.push({func:part, args:r[0]}); 0791 part = ''; 0792 } else { 0793 let r = this.funcProc(str); 0794 str = r[1]; 0795 tree.push(r[0]); 0796 } 0797 } else if(ch == ')' || ch == ',') { 0798 break; 0799 } else if(this.isOperator(ch)) { 0800 if(part) 0801 tree.push(part); 0802 part = ''; 0803 tree.push(ch); 0804 } else { 0805 part += ch; 0806 } 0807 } 0808 if(part) 0809 tree.push(part); 0810 return [this.treeSimplify(tree), str, ch]; 0811 } 0812 0813 funcToTree(str) { 0814 return this.funcProc(str.replace(/=(.+)$/, '-($1)').replace(/\s+/g, ''))[0]; 0815 } 0816 0817 treeCalc(tree) { 0818 if(Array.isArray(tree)) { 0819 tree.forEach((v, i) => tree[i] = this.treeCalc(v)); 0820 if(tree[0] == '-') { 0821 tree.splice(0, 1); 0822 tree[0] = -tree[0]; 0823 } 0824 0825 for(let i = 1; i < tree.length; i++) { 0826 if(!this.isOperator(tree[i]) || typeof(tree[i - 1]) !== 'number' || typeof(tree[i + 1]) !== 'number') 0827 throw 'houston we have a problem'; 0828 switch(tree[i]) { 0829 case '*': tree[i - 1] *= tree[i + 1]; break; 0830 case '/': tree[i - 1] /= tree[i + 1]; break; 0831 case '+': tree[i - 1] += tree[i + 1]; break; 0832 case '-': tree[i - 1] -= tree[i + 1]; break; 0833 } 0834 tree.splice(i, 2); 0835 i = 1; 0836 } 0837 0838 return tree.length == 1 ? tree[0] : tree; 0839 } 0840 if(tree.func) { 0841 const func = { 0842 pow: (b, e) => Math.pow(b, e), 0843 log: v => Math.log(v) 0844 }; 0845 return func[tree.func].apply(null, tree.args.map(v => this.treeCalc(v))); 0846 } 0847 return tree; 0848 } 0849 0850 glslShader(comment, precision=7) { 0851 let res = { func:'', funcInv:'', comment:comment }; 0852 0853 for(let i = this.funcs.length - 1; i >= 0; i -= 2) { 0854 if(i) { 0855 const l1 = this.treeCalc(this.funcs[i - 1]).toStdFloat(7); 0856 res.func += `if(vLin < ${l1}) `; 0857 const l2 = this.treeCalc(this.treeReplaceVar(this.funcExtract(this.funcs[i], 'vExp'), 'vLin', this.funcs[i - 1])).toStdFloat(7); 0858 res.funcInv += `if(vExp < ${l2}) `; 0859 } 0860 res.func += this.funcGLSL(this.funcExtract(this.funcs[i], 'vExp')); 0861 res.funcInv += this.funcGLSL(this.funcExtract(this.funcs[i], 'vLin')); 0862 } 0863 res.func = res.func.trim(); 0864 res.funcInv = res.funcInv.trim(); 0865 0866 // res.debug = this.funcs; 0867 // const ui = v => util.inspect(v, false, null, false); 0868 // console.log(ui(res)); 0869 0870 return res; 0871 } 0872 } 0873 0874 const sRGB = new ColorPrimaries(0.640, 0.330, 0.300, 0.600, 0.150, 0.060, 0.31271, 0.32902); 0875 0876 if(0){ 0877 const n = -1/3; 0878 let eq = [ 0879 [ Math.pow(sRGB.Wx / sRGB.Wy, n), sRGB.Wx, sRGB.Wy, 1.0 ], 0880 [ Math.pow(sRGB.Rx / sRGB.Ry, n), sRGB.Rx, sRGB.Ry, 0.21263682167732387 ], 0881 [ Math.pow(sRGB.Gx / sRGB.Gy, n), sRGB.Gx, sRGB.Gy, 0.7151829818412506 ], 0882 [ Math.pow(sRGB.Bx / sRGB.By, n), sRGB.Bx, sRGB.By, 0.07218019648142547 ], // test value 0883 ]; 0884 console.log('xy max Luma', eq); 0885 mat.solveLinearEquation(eq); 0886 console.log('xy max Luma', eq); // bad - not linear 0887 process.exit(); 0888 0889 } 0890 0891 if(0){ 0892 const dump = (name, arr) => { 0893 let s = []; 0894 const p = (val) => parseFloat(val.toFixed(3).replace(/^-(0\.0+)$/, '$1')) || '.'; 0895 for(let y = 0; y < arr.length; y++) { 0896 s[y] = []; 0897 for(let x = 0; x < arr[y].length; x++) 0898 s[y].push(p(arr[y][x])); 0899 s[y] = s[y].join('\t'); 0900 } 0901 console.log(`${name}\t${s.join('\n\t')}`); 0902 }; 0903 0904 const dist = (v1, v2) => Math.sqrt(Math.pow(v1.x - v2.x, 2) + Math.pow(v1.y - v2.y, 2)); 0905 const RGB_XYZ = sRGB.mat3_RGB_XYZ(); 0906 const Rv = new vec(sRGB.Rx, sRGB.Ry, RGB_XYZ.mul(new vec(1, 0, 0)).y); 0907 const Gv = new vec(sRGB.Gx, sRGB.Gy, RGB_XYZ.mul(new vec(0, 1, 0)).y); 0908 const Bv = new vec(sRGB.Bx, sRGB.By, RGB_XYZ.mul(new vec(0, 0, 1)).y); 0909 const Wv = new vec(sRGB.Wx, sRGB.Wy, RGB_XYZ.mul(new vec(1, 1, 1)).y); 0910 0911 const xyY = (r,g,b) => { 0912 const v = RGB_XYZ.mul(new vec(1, 1, 0)); 0913 const sum = v.elem.reduce((a, v) => a + v, 0); 0914 return new vec(v.x/sum, v.y/sum, v.y); 0915 }; 0916 0917 const RGv = xyY(1, 1, 0); 0918 const RBv = xyY(1, 0, 1); 0919 const GBv = xyY(0, 1, 1); 0920 0921 const fromRGB = (r, g, b) => { 0922 const rat = sRGB.rgbYratios; 0923 return r * rat.r * sRGB.Ry 0924 + g * rat.g * sRGB.Gy 0925 + b * rat.b * sRGB.By; 0926 }; 0927 0928 console.log(); 0929 console.log('red Y', fromRGB(1, 0, 0), Rv.z, sRGB.get_xy_Y(new vec(Rv.x, Rv.y))); 0930 console.log('rg Y', fromRGB(1, 1, 0), RGv.z, sRGB.get_xy_Y(new vec(RGv.x, RGv.y))); 0931 console.log('grn Y', fromRGB(0, 1, 0), Gv.z, sRGB.get_xy_Y(new vec(Gv.x, Gv.y))); 0932 console.log('gb Y', fromRGB(1, 1, 0), GBv.z, sRGB.get_xy_Y(new vec(GBv.x, GBv.y))); 0933 console.log('blu Y', fromRGB(0, 0, 1), Bv.z, sRGB.get_xy_Y(new vec(Bv.x, Bv.y))); 0934 console.log('rb Y', fromRGB(1, 1, 0), RBv.z, sRGB.get_xy_Y(new vec(RBv.x, RBv.y))); 0935 console.log('wht Y', fromRGB(1, 1, 1), Wv.z, sRGB.get_xy_Y(new vec(Wv.x, Wv.y))); 0936 process.exit(); 0937 } 0938 0939 if(0) { // test CIE diagram GLSL 0940 console.log(`GLfloat pR[]{ ${sRGB.Rx.toCFloat()}, ${sRGB.Ry.toCFloat()}, ${(sRGB.Ry * sRGB.rgbYratios.r).toCFloat()} };`); 0941 console.log(`GLfloat pG[]{ ${sRGB.Gx.toCFloat()}, ${sRGB.Gy.toCFloat()}, ${(sRGB.Gy * sRGB.rgbYratios.g).toCFloat()} };`); 0942 console.log(`GLfloat pB[]{ ${sRGB.Bx.toCFloat()}, ${sRGB.By.toCFloat()}, ${(sRGB.By * sRGB.rgbYratios.b).toCFloat()} };`); 0943 console.log(`GLfloat pW[]{ ${sRGB.Wx.toCFloat()}, ${sRGB.Wy.toCFloat()}, 1.0f };`); 0944 console.log(`GLfloat csXYZ_RGB[]{ ${sRGB.mat3_XYZ_RGB().cFloat('XYZ_sRGB', 'XYZ -> sRGB colorspace').mat.join(', ')} };`); 0945 console.log(`GLfloat csxyY_RGB[]{ ${sRGB.mat4_xyY_RGB().cFloat('xyY_sRGB', 'xyY -> sRGB colorspace').mat.join(', ')} };`); 0946 console.log(`GLfloat triangle[]{ ${sRGB.Rx}f, ${sRGB.Ry}f, ${sRGB.Gx}f, ${sRGB.Gy}f, ${sRGB.Bx}f, ${sRGB.By}f, ${sRGB.Wx}f, ${sRGB.Wy}f };`) 0947 } 0948 0949 if(0) { 0950 const RGB_XYZ = sRGB.mat3_RGB_XYZ(); 0951 const XYZ_RGB = sRGB.mat3_XYZ_RGB(); 0952 const RGB_xyY = sRGB.mat4_RGB_xyY(); 0953 const xyY_RGB = sRGB.mat4_xyY_RGB(); 0954 0955 if(0) { 0956 RGB_XYZ.mul(new vec(1, 0, 0)).debugDump(`red XYZ`); 0957 RGB_XYZ.mul(new vec(0, 1, 0)).debugDump(`green XYZ`); 0958 RGB_XYZ.mul(new vec(0, 0, 1)).debugDump(`blue XYZ`); 0959 RGB_XYZ.mul(new vec(1, 1, 1)).debugDump(`white XYZ`); 0960 RGB_XYZ.mul(new vec(0, 0, 0)).debugDump(`black XYZ`); 0961 0962 XYZ_RGB.mul(RGB_XYZ.mul(new vec(1, 0, 0))).debugDump(`\nred RGB`); 0963 XYZ_RGB.mul(RGB_XYZ.mul(new vec(0, 1, 0))).debugDump(`green RGB`); 0964 XYZ_RGB.mul(RGB_XYZ.mul(new vec(0, 0, 1))).debugDump(`blue RGB`); 0965 XYZ_RGB.mul(RGB_XYZ.mul(new vec(1, 1, 1))).debugDump(`white RGB`); 0966 XYZ_RGB.mul(RGB_XYZ.mul(new vec(0, 0, 0))).debugDump(`black RGB`); 0967 0968 RGB_xyY.mul(new vec(1, 0, 0, 1)).debugDump(`\nred xyY`); 0969 RGB_xyY.mul(new vec(0, 1, 0, 1)).debugDump(`green xyY`); 0970 RGB_xyY.mul(new vec(0, 0, 1, 1)).debugDump(`blue xyY`); 0971 RGB_xyY.mul(new vec(1, 1, 1, 1)).debugDump(`white xyY`); 0972 RGB_xyY.mul(new vec(0, 0, 0, 1)).debugDump(`black xyY`); 0973 0974 xyY_RGB.mul(RGB_xyY.mul(new vec(1, 0, 0, 1))).debugDump(`\nred RGB`); 0975 xyY_RGB.mul(RGB_xyY.mul(new vec(0, 1, 0, 1))).debugDump(`green RGB`); 0976 xyY_RGB.mul(RGB_xyY.mul(new vec(0, 0, 1, 1))).debugDump(`blue RGB`); 0977 xyY_RGB.mul(RGB_xyY.mul(new vec(1, 1, 1, 1))).debugDump(`white RGB`); 0978 xyY_RGB.mul(RGB_xyY.mul(new vec(0, 0, 0, 1))).debugDump(`black RGB`); 0979 } 0980 0981 if(1) { 0982 xyY_RGB.mul(new vec(sRGB.Rx, sRGB.Ry, sRGB.get_xy_Y(new vec(sRGB.Rx, sRGB.Ry)), 1)).debugDump(`\nred RGB`); 0983 xyY_RGB.mul(new vec(sRGB.Gx, sRGB.Gy, sRGB.get_xy_Y(new vec(sRGB.Gx, sRGB.Gy)), 1)).debugDump(`green RGB`); 0984 xyY_RGB.mul(new vec(sRGB.Bx, sRGB.By, sRGB.get_xy_Y(new vec(sRGB.Bx, sRGB.By)), 1)).debugDump(`blue RGB`); 0985 xyY_RGB.mul(new vec(sRGB.Wx, sRGB.Wy, sRGB.get_xy_Y(new vec(sRGB.Wx, sRGB.Wy)), 1)).debugDump(`white RGB`); 0986 } 0987 0988 if(0) { 0989 const rXYZ = RGB_XYZ.mul(new vec(1, 0, 0)); //.debugDump(`red XYZ`); 0990 const gXYZ = RGB_XYZ.mul(new vec(0, 1, 0)); //.debugDump(`green XYZ`); 0991 const bXYZ = RGB_XYZ.mul(new vec(0, 0, 1)); //.debugDump(`blue XYZ`); 0992 const wXYZ = RGB_XYZ.mul(new vec(1, 1, 1)); //.debugDump(`white XYZ`); 0993 0994 let eq = [ 0995 [ sRGB.Wx, sRGB.Wy, sRGB.Wz, wXYZ.y ], 0996 [ sRGB.Rx, sRGB.Ry, sRGB.Rz, rXYZ.y ], 0997 [ sRGB.Gx, sRGB.Gy, sRGB.Gz, gXYZ.y ], 0998 [ sRGB.Bx, sRGB.By, sRGB.Bz, bXYZ.y ], // test value 0999 ]; 1000 console.log('xy max Luma', eq); 1001 mat.solveLinearEquation(eq); 1002 console.log('xy max Luma', eq); // bad - not linear 1003 process.exit(); 1004 } 1005 1006 if(0) { 1007 const x1 = sRGB.Bx; 1008 const y1 = sRGB.By; 1009 const x2 = sRGB.Wx; 1010 const y2 = sRGB.Wy; 1011 const Y1 = sRGB.get_xy_Y(new vec(x1, y1)); 1012 const Y2 = sRGB.get_xy_Y(new vec(x2, y2)); 1013 for(let r = 0.0; r <= 1.0001; r += .1) { 1014 const L1 = r * Y1; 1015 const L2 = (1. - r) * Y2; 1016 const xMix = (x1/y1*L1 + x2/y2*L2) / (L1/y1 + L2/y2); 1017 const yMix = (L1 + L2) / (L1/y1 + L2/y2); 1018 const Ymix = sRGB.get_xy_Y(new vec(xMix, yMix)); 1019 1020 xyY_RGB.mul(new vec(xMix, yMix, Ymix, 1)).debugDump(`x:${xMix.toFixed(2)} y:${yMix.toFixed(2)} Y:${Ymix.toFixed(2)} ${r.toFixed(2)} RGB`); 1021 1022 const XYZ = new vec(Ymix * xMix / yMix, Ymix, Ymix * (1 - xMix - yMix) / yMix); 1023 XYZ_RGB.mul(XYZ).debugDump(`\t\t TEST RGB`); 1024 XYZ.debugDump(`\t\t TEST XYZ`); 1025 console.log(`\t\t TEST L1: ${L1.toFixed(4)}\t L2: ${L2.toFixed(4)}\t x: ${xMix.toFixed(4)}\t y: ${yMix.toFixed(4)}`); 1026 } 1027 console.log('whole range:'); 1028 for(let x = 0.; x <= 1.0; x += .05) 1029 console.log(x, xyY_RGB.mul(new vec(x, x, 1, 1)).xyz); 1030 } 1031 process.exit(); 1032 } 1033 1034 1035 1036 // Header 1037 console.log('#include <QMap>\n#include <QVector>\n#include <QOpenGLFunctions>\n\nnamespace SubtitleComposer {\n') 1038 1039 // Colorspace conversion from ISO IEC 23001-8:2018 (7.1) data 1040 { 1041 const csm = { 1042 1: new ColorPrimaries(0.640, 0.330, 0.300, 0.600, 0.150, 0.060, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_BT709', 1043 'ITU-R BT.709-5; ITU-R BT.1361; IEC 61966-2-1 sRGB or sYCC; IEC 61966-2-4; SMPTE-RP-177:1993b'), 1044 2: { comment: 'UNSPECIFIED - Image characteristics are unknown or are determined by the application' }, 1045 4: new ColorPrimaries(0.67, 0.33, 0.21, 0.71, 0.14, 0.08, 0.31006, 0.31616).mat3_YUV_RGB().cFloat('AVCOL_PRI_BT470M', 1046 'ITU-R BT.470-6m; US-NTSC-1953; USFCCT-47:2003-73.682a'), 1047 5: new ColorPrimaries(0.64, 0.33, 0.29, 0.60, 0.15, 0.06, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_BT470BG', 1048 'ITU-R BT.470-6bg; ITU-R BT.601-6 625; ITU-R BT.1358 625; ITU-R BT.1700 625 PAL/SECAM'), 1049 6: new ColorPrimaries(0.630, 0.340, 0.310, 0.595, 0.155, 0.070, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_SMPTE170M', 1050 'ITU-R BT.601-6 525; ITU-R BT.1358 525; ITU-R BT.1700 NTSC; SMPTE-170M:2004'), 1051 7: new ColorPrimaries(0.630, 0.340, 0.310, 0.595, 0.155, 0.070, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_SMPTE240M', 1052 'SMPTE-240M:1999'), 1053 8: new ColorPrimaries(0.681, 0.319, 0.243, 0.692, 0.145, 0.049, 0.31006, 0.31616).mat3_YUV_RGB().cFloat('AVCOL_PRI_FILM', 1054 'Generic film (color filters using CIE SI C)'), 1055 9: new ColorPrimaries(0.708, 0.292, 0.170, 0.797, 0.131, 0.046, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_BT2020', 1056 'Rec. ITU-R BT.2020'), 1057 10: new ColorPrimaries(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0/3.0, 1.0/3.0).mat3_YUV_RGB().cFloat('AVCOL_PRI_SMPTE428', 1058 'SMPTE-ST-428-1 (CIE 1931 XYZ as in ISO 11664-1)'), 1059 11: new ColorPrimaries(0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.314, 0.351).mat3_YUV_RGB().cFloat('AVCOL_PRI_SMPTE431', 1060 'SMPTE-RP-431-2:2011'), 1061 12: new ColorPrimaries(0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_SMPTE432', 1062 'SMPTE-EG-432-1:2010'), 1063 22: new ColorPrimaries(0.630, 0.340, 0.295, 0.605, 0.155, 0.077, 0.31271, 0.32902).mat3_YUV_RGB().cFloat('AVCOL_PRI_EBU3213', 1064 'EBU-3213-E:1975'), 1065 }; 1066 console.log('// from ISO IEC 23001-8:2018 (7.1) data') 1067 console.log('const static QMap<int, QVector<GLfloat>> _csm{'); 1068 const n = Object.keys(csm).reduce((ac, v) => Math.max(ac, v), 0); 1069 for(let i = 0; i <= n; i++) { 1070 const e = csm[i]; 1071 if(e && e.mat) 1072 console.log(`\t// ${i} - ${e.comment}\n\t{ ${i}, QVector<GLfloat>{ ${e.mat.join(', ')} }},`); 1073 else 1074 console.log(`\t// ${i} - ${e ? e.comment : 'RESERVED - For future use by ISO/IEC'}`); 1075 } 1076 console.log('};\n'); 1077 } 1078 1079 // Transfer characteristics inverse from ISO IEC 23001-8:2018 (7.2) data 1080 { 1081 const ctf = { 1082 1: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1083 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 0.45, 'lm': 4.5}).glslShader( 1084 'ITU-R BT.709-5; ITU-R BT.1361'), 1085 2: { comment: 'Unspecified - Image characteristics are unknown or are determined by the application' }, 1086 // Assumed display gamma 2.2 - very similar to BT.709 1087 4: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1088 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 1 / 2.2, 'lm': 4.5}).glslShader( 1089 'ITU-R BT.470-6m; US-NTSC-1953; USFCCT-47:2003-73.682a; ITU-R BT.1700:2007 625 PAL/SECAM'), 1090 // Assumed display gamma 2.8 - very similar to sRGB 1091 5: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1092 {'α': 1.055, 'β': 0.0031308, 'γ': 1 / 2.8, 'lm': 12.92}).glslShader( 1093 'ITU-R BT.1700:2007 625 PAL/SECAM; ITU-R BT.470-6bg'), 1094 6: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1095 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 0.45, 'lm': 4.5}).glslShader( 1096 'ITU-R BT.601-6 525/625; ITU-R BT.1358 525/625; ITU-R BT.1700 NTSC; SMPTE-170M:2004'), 1097 7: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1098 {'α': 1.1115, 'β': 0.0228, 'γ': 0.45, 'lm': 4.0}).glslShader( 1099 'SMPTE-240M:1999'), 1100 8: new ColorTransfer(['vExp = vLin'], {}).glslShader( 1101 'Linear transfer characteristics'), 1102 9: new ColorTransfer(['vExp = 1.0 + log(vLin) / 2', 0.01, 'vExp = 0.0'], {}).glslShader( 1103 'Logarithmic transfer characteristic (100:1 range)'), 1104 10: new ColorTransfer(['vExp = 1.0 + log(vLin) / 2.5', Math.sqrt(10) / 1000, 'vExp = 0.0'], {}).glslShader( 1105 'Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range)'), 1106 11: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin', '-β', 'vExp = -α * pow(-vLin, γ) + (α - 1)'], 1107 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 0.45, 'lm': 4.5}).glslShader( 1108 'IEC 61966-2-4'), 1109 12: new ColorTransfer(['vExp = α * pow(vLin, ex) - (α - 1)', 'β', 'vExp = lm * vLin', '-γ', 'vExp = -(α * pow(-4 * vLin, ex) - (α - 1)) / 4'], 1110 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 0.004, 'ex': 0.45, 'lm': 4.5}).glslShader( 1111 'ITU-R BT.1361'), 1112 13: new ColorTransfer(['vExp = α * pow(vLin, 1.0 / γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1113 {'α': 1.055, 'β': 0.0031308, 'γ': 2.4, 'lm': 12.92}).glslShader( 1114 'IEC 61966-2-1 sRGB/sYCC'), 1115 14: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1116 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 0.45, 'lm': 4.5}).glslShader( 1117 'ITU-R BT.2020 (10-bit system)'), 1118 15: new ColorTransfer(['vExp = α * pow(vLin, γ) - (α - 1)', 'β', 'vExp = lm * vLin'], 1119 {'α': '1 + 5.5 * β', 'β': 0.018053968510807, 'γ': 0.45, 'lm': 4.5}).glslShader( 1120 'ITU-R BT.2020 (12-bit system)'), 1121 16: new ColorTransfer(['vExp = pow((c1 + c2 * pow(vLin, n)) / (1 + c3 * pow(vLin, n)), m)'], 1122 {'c1': 'c3 - c2 + 1', 'c2': '2413 / 128', 'c3': '2392 / 128', 'm': '2523 / 32', 'n': '653 / 4096'}).glslShader( 1123 'SMPTE-ST-2084 (for TV 10, 12, 14, and 16-bit systems)'), 1124 17: new ColorTransfer(['vExp = pow(48 * vLin / 52.37, 1 / 2.6)'], 1125 {}).glslShader( 1126 'SMPTE-ST-428-1'), 1127 }; 1128 console.log('// from ISO IEC 23001-8:2018 (7.2) data') 1129 console.log('const static QMap<int, QString> _ctf{'); 1130 const n = Object.keys(ctf).reduce((ac, v) => Math.max(ac, v), 0); 1131 for(let i = 0; i <= n; i++) { 1132 const e = ctf[i]; 1133 if(e && e.func) 1134 console.log(`\t// ${i} - ${e.comment}\n\t{ ${i}, QStringLiteral("${e.func.replaceAll('\n', '"\n\t\t\t"')}") },`); 1135 else 1136 console.log(`\t// ${i} - ${e ? e.comment : 'RESERVED - For future use by ISO/IEC'}`); 1137 } 1138 console.log('};'); 1139 console.log('const static QMap<int, QString> _ctfi{'); 1140 for(let i = 0; i <= n; i++) { 1141 const e = ctf[i]; 1142 if(e && e.funcInv) 1143 console.log(`\t// ${i} - ${e.comment}\n\t{ ${i}, QStringLiteral("${e.funcInv.replaceAll('\n', '"\n\t\t\t"')}") },`); 1144 else 1145 console.log(`\t// ${i} - ${e ? e.comment : 'RESERVED - For future use by ISO/IEC'}`); 1146 } 1147 console.log('};'); 1148 } 1149 1150 // Colorspace conversion and coefficients from ISO IEC 23001-8:2018 (7.3) data 1151 { 1152 const csc = { 1153 0: new ColorCoefficients(0.0, 0.0, 0.0, true).mat3_RGB_RGB().cFloat('AVCOL_SPC_RGB', 1154 'The identity matrix (RGB/XYZ); IEC 61966-2-1 sRGB; SMPTE-ST-428-1; ITU-R BT.709-5'), 1155 1: new ColorCoefficients(0.2126, 0.7152, 0.0722, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_BT709', 1156 'ITU-R BT.709-5; ITU-R BT.1361; IEC 61966-2-1/4 sYCC/xvYCC709; SMPTE-RP-177:1993b'), 1157 2: { comment: 'UNSPECIFIED - Image characteristics are unknown or are determined by the application' }, 1158 4: new ColorCoefficients(0.30, 0.59, 0.111, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_FCC', 1159 'USFCCT-47:2003-73.682a'), 1160 5: new ColorCoefficients(0.299, 0.587, 0.114, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_BT470BG', 1161 'ITU-R BT.470-6bg; ITU-R BT.601-6 625; ITU-R BT.1358 625; ITU-R BT.1700 625 PAL/SECAM; IEC 61966-2-4 xvYCC601'), 1162 6: new ColorCoefficients(0.299, 0.587, 0.114, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_SMPTE170M', 1163 'ITU-R BT.601-6 525; ITU-R BT.1358 525; ITU-R BT.1700 NTSC; SMPTE-170M:2004'), 1164 7: new ColorCoefficients(0.212, 0.701, 0.087, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_SMPTE240M', 1165 'SMPTE-240M:1999'), 1166 8: new ColorCoefficients(0.0, 0.0, 0.0, true).mat3_RGB_RGB().cFloat('AVCOL_SPC_YCGCO', 1167 'ITU-T SG16; Dirac/VC-2 and H.264 FRext'), 1168 9: new ColorCoefficients(0.2627, 0.678, 0.0593, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_BT2020_NCL', 1169 'ITU-R BT.2020 (non-constant luminance)'), 1170 10: new ColorCoefficients(0.2627, 0.678, 0.0593, false).mat3_YUV_RGB().cFloat('AVCOL_SPC_BT2020_CL', 1171 'ITU-R BT.2020 (constant luminance)'), 1172 11: new ColorCoefficients(0.0, 0.0, 0.0, true).mat3_RGB_RGB().cFloat('AVCOL_SPC_SMPTE2085', 1173 'SMPTE-ST-2085:2015'), 1174 // 12-14 are not part of ISO 1175 12: { comment: 'TODO: AVCOL_SPC_CHROMA_DERIVED_NCL defined in FFmpeg?' }, 1176 13: { comment: 'TODO: AVCOL_SPC_CHROMA_DERIVED_CL defined in FFmpeg?' }, 1177 14: { comment: 'TODO: AVCOL_SPC_ICTCP defined in FFmpeg?' }, 1178 }; 1179 console.log('// from ISO IEC 23001-8:2018 (7.3) data') 1180 console.log('const static QMap<int, QVector<GLfloat>> _csc{'); 1181 const n = Object.keys(csc).reduce((ac, v) => Math.max(ac, v), 0); 1182 for(let i = 0; i <= n; i++) { 1183 const e = csc[i]; 1184 if(e && e.mat) 1185 console.log(`\t// ${i} - ${e.comment}\n\t{ ${i}, QVector<GLfloat>{ ${e.mat.join(', ')} }},`); 1186 else 1187 console.log(`\t// ${i} - ${e ? e.comment : 'RESERVED - For future use by ISO/IEC'}`); 1188 } 1189 console.log('};\n'); 1190 } 1191 1192 // Footer 1193 console.log('\n}'); 1194