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