base/threading: remove ScopedTracker placed for experiments
[chromium-blink-merge.git] / ui / accessibility / extensions / colorenhancer / src / cvd.js
blob1bf7daa6ca3dbfd3bc5a873f40a8cb0c220ed76c
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 /**
7  * Global exports.  Used by popup to show effect of filter during setup.
8  */
9 (function(exports) {
10   var curDelta = 0;
11   var curSeverity = 0;
12   var curType = 'PROTANOMALY';
13   var curSimulate = false;
14   var curEnable = false;
15   var curFilter = 0;
18   // ======= 3x3 matrix ops =======
20   /**
21    * The 3x3 identity matrix.
22    * @const {object}
23    */
24   var IDENTITY_MATRIX_3x3 = [
25     [1, 0, 0],
26     [0, 1, 0],
27     [0, 0, 1]
28   ];
31   /**
32    * Adds two matrices.
33    * @param {!object} m1 A 3x3 matrix.
34    * @param {!object} m2 A 3x3 matrix.
35    * @return {!object} The 3x3 matrix m1 + m2.
36    */
37   function add3x3(m1, m2) {
38     var result = [];
39     for (var i = 0; i < 3; i++) {
40       result[i] = [];
41       for (var j = 0; j < 3; j++) {
42         result[i].push(m1[i][j] + m2[i][j]);
43       }
44     }
45     return result;
46   }
49   /**
50    * Subtracts one matrix from another.
51    * @param {!object} m1 A 3x3 matrix.
52    * @param {!object} m2 A 3x3 matrix.
53    * @return {!object} The 3x3 matrix m1 - m2.
54    */
55   function sub3x3(m1, m2) {
56     var result = [];
57     for (var i = 0; i < 3; i++) {
58       result[i] = [];
59       for (var j = 0; j < 3; j++) {
60         result[i].push(m1[i][j] - m2[i][j]);
61       }
62     }
63     return result;
64   }
67   /**
68    * Multiplies one matrix with another.
69    * @param {!object} m1 A 3x3 matrix.
70    * @param {!object} m2 A 3x3 matrix.
71    * @return {!object} The 3x3 matrix m1 * m2.
72    */
73   function mul3x3(m1, m2) {
74     var result = [];
75     for (var i = 0; i < 3; i++) {
76       result[i] = [];
77       for (var j = 0; j < 3; j++) {
78         var sum = 0;
79         for (var k = 0; k < 3; k++) {
80           sum += m1[i][k] * m2[k][j];
81         }
82         result[i].push(sum);
83       }
84     }
85     return result;
86   }
89   /**
90    * Multiplies a matrix with a number.
91    * @param {!object} m A 3x3 matrix.
92    * @param {!number} k A scalar multiplier.
93    * @return {!object} The 3x3 matrix m * k.
94    */
95   function mul3x3Scalar(m, k) {
96     var result = [];
97     for (var i = 0; i < 3; i++) {
98       result[i] = [];
99       for (var j = 0; j < 3; j++) {
100         result[i].push(k * m[i][j]);
101       }
102     }
103     return result;
104   }
107   // ======= 3x3 matrix utils =======
109   /**
110    * Makes the SVG matrix string (of 20 values) for a given matrix.
111    * @param {!object} m A 3x3 matrix.
112    * @return {!string} The SVG matrix string for m.
113    */
114   function svgMatrixStringFrom3x3(m) {
115     var outputRows = [];
116     for (var i = 0; i < 3; i++) {
117       outputRows.push(m[i].join(' ') + ' 0 0');
118     }
119     // Add the alpha row
120     outputRows.push('0 0 0 1 0');
121     return outputRows.join(' ');
122   }
125   /**
126    * Makes a human readable string for a given matrix.
127    * @param {!object} m A 3x3 matrix.
128    * @return {!string} A human-readable string for m.
129    */
130   function humanReadbleStringFrom3x3(m) {
131       var result = '';
132       for (var i = 0; i < 3; i++) {
133           result += (i ? ', ' : '') + '[';
134           for (var j = 0; j < 3; j++) {
135               result += (j ? ', ' : '') + m[i][j].toFixed(2);
136           }
137           result += ']';
138       }
139       return result;
140   }
143   // ======= CVD parameters =======
144   /**
145    * Parameters for simulating color vision deficiency.
146    * Source:
147    *     http://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html
148    * Original Research Paper:
149    *     http://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/Machado_Oliveira_Fernandes_CVD_Vis2009_final.pdf
150    *
151    * @enum {string}
152    */
153   var cvdSimulationParams = {
154     PROTANOMALY: [
155       [0.4720, -1.2946, 0.9857],
156       [-0.6128, 1.6326, 0.0187],
157       [0.1407, -0.3380, -0.0044],
158       [-0.1420, 0.2488, 0.0044],
159       [0.1872, -0.3908, 0.9942],
160       [-0.0451, 0.1420, 0.0013],
161       [0.0222, -0.0253, -0.0004],
162       [-0.0290, -0.0201, 0.0006],
163       [0.0068, 0.0454, 0.9990]
164     ],
165     DEUTERANOMALY: [
166       [0.5442, -1.1454, 0.9818],
167       [-0.7091, 1.5287, 0.0238],
168       [0.1650, -0.3833, -0.0055],
169       [-0.1664, 0.4368, 0.0056],
170       [0.2178, -0.5327, 0.9927],
171       [-0.0514, 0.0958, 0.0017],
172       [0.0180, -0.0288, -0.0006],
173       [-0.0232, -0.0649, 0.0007],
174       [0.0052, 0.0360, 0.9998]
175     ],
176     TRITANOMALY: [
177       [0.4275, -0.0181, 0.9307],
178       [-0.2454, 0.0013, 0.0827],
179       [-0.1821, 0.0168, -0.0134],
180       [-0.1280, 0.0047, 0.0202],
181       [0.0233, -0.0398, 0.9728],
182       [0.1048, 0.0352, 0.0070],
183       [-0.0156, 0.0061, 0.0071],
184       [0.3841, 0.2947, 0.0151],
185       [-0.3685, -0.3008, 0.9778]
186     ]
187   };
190   // TODO(mustaq): This should be nuked, see getCvdCorrectionMatrix().
191   var cvdCorrectionParams = {
192     PROTANOMALY: {
193       addendum: [
194         [0.0, 0.0, 0.0],
195         [0.7, 1.0, 0.0],
196         [0.7, 0.0, 1.0]
197       ],
198       delta_factor: [
199         [0.0, 0.0, 0.0],
200         [0.3, 0.0, 0.0],
201         [-0.3, 0.0, 0.0]
202       ]
203     },
204     DEUTERANOMALY: {
205       addendum: [
206         [0.0, 0.0, 0.0],
207         [0.7, 1.0, 0.0],
208         [0.7, 0.0, 1.0]
209       ],
210       delta_factor: [
211         [0.0, 0.0, 0.0],
212         [0.3, 0.0, 0.0],
213         [-0.3, 0.0, 0.0]
214       ]
215     },
216     TRITANOMALY: {
217       addendum: [
218         [1.0, 0.0, 0.7],
219         [0.0, 1.0, 0.7],
220         [0.0, 0.0, 0.0]
221       ],
222       delta_factor: [
223         [0.0, 0.0, 0.3],
224         [0.0, 0.0, -0.3],
225         [0.0, 0.0, 0.0]
226       ]
227     }
228   };
231   // =======  CVD matrix builders =======
233   /**
234    * Returns a 3x3 matrix for simulating the given type of CVD with the given
235    * severity.
236    * @param {string} cvdType Type of CVD, either "PROTANOMALY" or
237    *     "DEUTERANOMALY" or "TRITANOMALY".
238    * @param {number} severity A real number in [0,1] denoting severity.
239    */
240   function getCvdSimulationMatrix(cvdType, severity) {
241     var cvdSimulationParam = cvdSimulationParams[cvdType];
242     var severity2 = severity * severity;
243     var matrix = [];
244     for (var i = 0; i < 3; i++) {
245       var row = [];
246       for (var j = 0; j < 3; j++) {
247         var paramRow = i*3+j;
248         var val = cvdSimulationParam[paramRow][0] * severity2
249                 + cvdSimulationParam[paramRow][1] * severity
250                 + cvdSimulationParam[paramRow][2];
251         row.push(val);
252       }
253       matrix.push(row);
254     }
255     return matrix;
256   }
259   /**
260    * Returns a 3x3 matrix for correcting the given type of CVD using the given
261    * color adjustment.
262    * @param {string} cvdType Type of CVD, either "PROTANOMALY" or
263    *     "DEUTERANOMALY" or "TRITANOMALY".
264    * @param {number} delta A real number in [0,1] denoting color adjustment.
265    */
266   function getCvdCorrectionMatrix(cvdType, delta) {
267     cvdCorrectionParam = cvdCorrectionParams[cvdType];
268     // TODO(mustaq): Perhaps nuke full-matrix operations after experiment.
269     return add3x3(cvdCorrectionParam['addendum'],
270                   mul3x3Scalar(cvdCorrectionParam['delta_factor'], delta));
271   }
274   /**
275    * Returns the 3x3 matrix to be used for the given settings.
276    * @param {string} cvdType Type of CVD, either "PROTANOMALY" or
277    *     "DEUTERANOMALY" or "TRITANOMALY".
278    * @param {number} severity A real number in [0,1] denoting severity.
279    * @param {number} delta A real number in [0,1] denoting color adjustment.
280    * @param {boolean} simulate Whether to simulate the CVD type.
281    * @param {boolean} enable Whether to enable color filtering.
282    */
283   function getEffectiveCvdMatrix(cvdType, severity, delta, simulate, enable) {
284     if (!enable) {
285       //TODO(mustaq): we should remove matrices at the svg level
286       return IDENTITY_MATRIX_3x3;
287     }
289     var effectiveMatrix = getCvdSimulationMatrix(cvdType, severity);
291     if (!simulate) {
292       var cvdCorrectionMatrix = getCvdCorrectionMatrix(cvdType, delta);
293       var tmpProduct = mul3x3(cvdCorrectionMatrix, effectiveMatrix);
295       effectiveMatrix = sub3x3(
296           add3x3(IDENTITY_MATRIX_3x3, cvdCorrectionMatrix),
297           tmpProduct);
298     }
300     return effectiveMatrix;
301   }
304   // ======= Page linker =======
306   /** @const {string} */
307   var SVG_DEFAULT_MATRIX =
308     '1 0 0 0 0 ' +
309     '0 1 0 0 0 ' +
310     '0 0 1 0 0 ' +
311     '0 0 0 1 0';
313   var svgContent =
314     '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">' +
315     '  <defs>' +
316     '    <filter id="cvd_extension_0">' +
317     '      <feColorMatrix id="cvd_matrix_0" type="matrix" values="' +
318         SVG_DEFAULT_MATRIX + '"/>' +
319     '    </filter>' +
320     '    <filter id="cvd_extension_1">' +
321     '      <feColorMatrix id="cvd_matrix_1" type="matrix" values="' +
322         SVG_DEFAULT_MATRIX + '"/>' +
323     '    </filter>' +
324     '  </defs>' +
325     '</svg>';
327   /**
328    * Checks for svg filter matrix presence and append to DOM if not present.
329    */
330   function addSvgIfMissing() {
331     var wrap = document.getElementById('cvd_extension_svg_filter');
332     if (!wrap) {
333       wrap = document.createElement('span');
334       wrap.id = 'cvd_extension_svg_filter';
335       wrap.setAttribute('hidden', '');
336       wrap.innerHTML = svgContent;
337       document.body.appendChild(wrap);
338     }
339   }
341   /**
342    * Updates the SVG filter based on the RGB correction/simulation matrix.
343    * @param {!Object} matrix  3x3 RGB transformation matrix.
344    */
345   function setFilter(matrix) {
346     addSvgIfMissing();
347     var next = 1 - curFilter;
349     debugPrint('update: matrix#' + next + '=' +
350         humanReadbleStringFrom3x3(matrix));
352     var matrixElem = document.getElementById('cvd_matrix_' + next);
353     matrixElem.setAttribute('values', svgMatrixStringFrom3x3(matrix));
355     var html = document.documentElement;
356     html.classList.remove('filter' + curFilter);
357     html.classList.add('filter' + next);
359     curFilter = next;
360   }
362   /**
363    * Updates the SVG matrix using the current settings.
364    */
365   function update() {
366     if (!document.body) {
367       document.addEventListener('DOMContentLoaded', update);
368       return;
369     }
371     var effectiveMatrix = getEffectiveCvdMatrix(
372         curType, curSeverity, curDelta * 2 - 1, curSimulate, curEnable);
374     setFilter(effectiveMatrix);
376     // TODO(kevers): Check if a call to getComputedStyle is sufficient to force
377     // an update.
378     window.scrollBy(0, 1);
379     window.scrollBy(0, -1);
380   }
383   /**
384    * Process request from background page.
385    * @param {!object} request An object containing color filter parameters.
386    */
387   function onExtensionMessage(request) {
388     debugPrint('onExtensionMessage: ' + JSON.stringify(request));
389     var changed = false;
391     if (request['type'] !== undefined) {
392       var type = request.type;
393       if (curType != type) {
394         curType = type;
395         changed = true;
396       }
397     }
399     if (request['severity'] !== undefined) {
400       var severity = request.severity;
401       if (curSeverity != severity) {
402         curSeverity = severity;
403         changed = true;
404       }
405     }
407     if (request['delta'] !== undefined) {
408       var delta = request.delta;
409       if (curDelta != delta) {
410         curDelta = delta;
411         changed = true;
412       }
413     }
415     if (request['simulate'] !== undefined) {
416       var simulate = request.simulate;
417       if (curSimulate != simulate) {
418         curSimulate = simulate;
419         changed = true;
420       }
421     }
423     if (request['enable'] !== undefined) {
424       var enable = request.enable;
425       if (curEnable != enable) {
426         curEnable = enable;
427         changed = true;
428       }
429     }
431     if (changed)
432       update();
433   }
436   /**
437    * Prepare to process background messages and let it know to send initial
438    * values.
439    */
440   exports.initializeExtension = function () {
441     chrome.extension.onRequest.addListener(onExtensionMessage);
442     chrome.extension.sendRequest({'init': true}, onExtensionMessage);
443   };
445   /**
446    * Generate SVG filter for color enhancement based on type and severity using
447    * default color adjustment.
448    * @param {string} type Type type of color vision defficiency (CVD).
449    * @param {number} severity The degree of CVD ranging from 0 for normal
450    *     vision to 1 for dichromats.
451    */
452   exports.getDefaultCvdCorrectionFilter = function(type, severity) {
453       return getEffectiveCvdMatrix(type, severity, 0, false, true);
454   };
456   /**
457    * Adds support for a color enhancement filter.
458    * @param {!Object} matrix 3x3 RGB transformation matrix.
459    */
460   exports.injectColorEnhancementFilter = function(matrix) {
461     setFilter(matrix);
462   };
464   /**
465    * Clears color correction filter.
466    */
467   exports.clearColorEnhancementFilter = function() {
468     var html = document.documentElement;
469     html.classList.remove('filter0');
470     html.classList.remove('filter1');
471   };
472 })(this);
474 this.initializeExtension();