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 =======
9 var curType = 'PROTANOMALY';
10 var curSimulate = false;
11 var curEnable = false;
15 // ======= 3x3 matrix ops =======
18 * The 3x3 identity matrix.
21 var IDENTITY_MATRIX_3x3 = [
30 * @param {!object} m1 A 3x3 matrix.
31 * @param {!object} m2 A 3x3 matrix.
32 * @return {!object} The 3x3 matrix m1 + m2.
34 function add3x3(m1, m2) {
36 for (var i = 0; i < 3; i++) {
38 for (var j = 0; j < 3; j++) {
39 result[i].push(m1[i][j] + m2[i][j]);
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.
52 function sub3x3(m1, m2) {
54 for (var i = 0; i < 3; i++) {
56 for (var j = 0; j < 3; j++) {
57 result[i].push(m1[i][j] - m2[i][j]);
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.
70 function mul3x3(m1, m2) {
72 for (var i = 0; i < 3; i++) {
74 for (var j = 0; j < 3; j++) {
76 for (var k = 0; k < 3; k++) {
77 sum += m1[i][k] * m2[k][j];
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.
92 function mul3x3Scalar(m, k) {
94 for (var i = 0; i < 3; i++) {
96 for (var j = 0; j < 3; j++) {
97 result[i].push(k * m[i][j]);
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.
111 function svgMatrixStringFrom3x3(m) {
113 for (var i = 0; i < 3; i++) {
114 outputRows.push(m[i].join(' ') + ' 0 0');
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.
127 function humanReadbleStringFrom3x3(m) {
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);
140 // ======= CVD parameters =======
142 * Parameters for simulating color vision deficiency.
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
150 var cvdSimulationParams = {
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]
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]
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]
187 // TODO(mustaq): This should be nuked, see getCvdCorrectionMatrix().
188 var cvdCorrectionParams = {
228 // ======= CVD matrix builders =======
231 * Returns a 3x3 matrix for simulating the given type of CVD with the given
233 * @param {string} cvdType Type of CVD, either "PROTANOMALY" or "DEUTERANOMALY"
235 * @param {number} severity A real number in [0,1] denoting severity.
237 function getCvdSimulationMatrix(cvdType, severity) {
238 var cvdSimulationParam = cvdSimulationParams[cvdType];
239 var severity2 = severity * severity;
241 for (var i = 0; i < 3; i++) {
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];
257 * Returns a 3x3 matrix for correcting the given type of CVD using the given
259 * @param {string} cvdType Type of CVD, either "PROTANOMALY" or "DEUTERANOMALY"
261 * @param {number} delta A real number in [0,1] denoting color adjustment.
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"
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.
280 function getEffectiveCvdMatrix(cvdType, severity, delta, simulate, enable) {
282 //TODO(mustaq): we should remove matrices at the svg level
283 return IDENTITY_MATRIX_3x3;
286 var effectiveMatrix = getCvdSimulationMatrix(cvdType, severity);
289 var cvdCorrectionMatrix = getCvdCorrectionMatrix(cvdType, delta);
290 var tmpProduct = mul3x3(cvdCorrectionMatrix, effectiveMatrix);
292 effectiveMatrix = sub3x3(
293 add3x3(IDENTITY_MATRIX_3x3, cvdCorrectionMatrix),
297 return effectiveMatrix;
301 // ======= Page linker =======
303 /** @const {string} */
304 var SVG_DEFAULT_MATRIX =
311 '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">' +
313 ' <filter id="cvd_extension_0">' +
314 ' <feColorMatrix id="cvd_matrix_0" type="matrix" values="' +
315 SVG_DEFAULT_MATRIX + '"/>' +
317 ' <filter id="cvd_extension_1">' +
318 ' <feColorMatrix id="cvd_matrix_1" type="matrix" values="' +
319 SVG_DEFAULT_MATRIX + '"/>' +
325 * Checks for svg filter matrix presence and append to DOM if not present.
327 function addSvgIfMissing() {
328 var wrap = document.getElementById('cvd_extension_svg_filter');
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);
339 * Updates the SVG filter based on the RGB correction/simulation matrix.
340 * @param {!Object} matrix 3x3 RGB transformation matrix.
342 function setFilter(matrix) {
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);
360 * Updates the SVG matrix using the current settings.
363 if (!document.body) {
364 document.addEventListener('DOMContentLoaded', update);
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
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.
385 function onExtensionMessage(request) {
386 debugPrint('onExtensionMessage: ' + JSON.stringify(request));
389 if (request['type'] !== undefined) {
390 var type = request.type;
391 if (curType != type) {
397 if (request['severity'] !== undefined) {
398 var severity = request.severity;
399 if (curSeverity != severity) {
400 curSeverity = severity;
405 if (request['delta'] !== undefined) {
406 var delta = request.delta;
407 if (curDelta != delta) {
413 if (request['simulate'] !== undefined) {
414 var simulate = request.simulate;
415 if (curSimulate != simulate) {
416 curSimulate = simulate;
421 if (request['enable'] !== undefined) {
422 var enable = request.enable;
423 if (curEnable != enable) {
435 * Prepare to process background messages and let it know to send initial
438 (function initialize() {
439 chrome.extension.onRequest.addListener(onExtensionMessage);
440 chrome.extension.sendRequest({'init': true}, onExtensionMessage);
444 * Global exports. Used by popup to show effect of filter during setup.
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.
454 exports.getDefaultCvdCorrectionFilter = function(type, severity) {
455 return getEffectiveCvdMatrix(type, severity, 0, false, true);
459 * Adds support for a color enhancement filter.
460 * @param {!Object} matrix 3x3 RGB transformation matrix.
462 exports.injectColorEnhancementFilter = function(matrix) {
467 * Clears color correction filter.
469 exports.clearColorEnhancementFilter = function() {
470 var html = document.documentElement;
471 html.classList.remove('filter0');
472 html.classList.remove('filter1');