chromium/ui/accessibility/extensions/colorenhancer/src/matrix.js

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/** Class that represents a 3x3 matrix. */
class Matrix3x3 {
  /** @private */
  constructor() {
    /** @private {!Array<!Array<number>>} */
    this.data_ = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
  }

  // ========== Constructor methods ===========

  /**
   * Returns a new matrix, with elements specified by the elementCalculator
   * function.
   * @param {!function(!Matrix3x3.Index, !Matrix3x3.Index): number}
   *     elementCalculator Given the i and j indices of the element, calculates
   *     the value for the new matrix.
   * @return {!Matrix3x3}
   */
  static fromElementwiseConstruction(elementCalculator) {
    const result = new Matrix3x3();
    for (const i of Matrix3x3.Indices) {
      for (const j of Matrix3x3.Indices) {
        result.data_[i][j] = elementCalculator(i, j);
      }
    }
    return result;
  }

  /**
   * Returns a new matrix with the provided data array.
   * @param {!Array<!Array<number>>} data A 3x3 array of numbers.
   * @return {!Matrix3x3}
   */
  static fromData(data) {
    const result = new Matrix3x3();
    result.data_ = data;
    return result;
  }

  // =============== operations ================

  /**
   * Adds the provided matrix with this matrix, and returns a new matrix
   * containing the result.
   * @param {!Matrix3x3} other
   * @return {!Matrix3x3} The matrix this + other.
   */
  add(other) {
    const adder = (i, j) => this.data_[i][j] + other.data_[i][j];
    return Matrix3x3.fromElementwiseConstruction(adder);
  }

  /**
   * Subtracts the given matrix from this one, and returns a new matrix
   * containing the result.
   * @param {!Matrix3x3} other
   * @return {!Matrix3x3} The matrix this - other.
   */
  subtract(other) {
    const subtracter = (i, j) => this.data_[i][j] - other.data_[i][j];
    return Matrix3x3.fromElementwiseConstruction(subtracter);
  }

  /**
   * Multiplies this matrix times the given matrix, and returns a new matrix
   * containing the result.
   * @param {!Matrix3x3} other
   * @return {!Matrix3x3} The matrix this * other.
   */
  multiply(other) {
    const multiplier = (i, j) => {
      let sum = 0;
      for (const k of Matrix3x3.Indices) {
        sum += this.data_[i][k] * other.data_[k][j];
      }
      return sum;
    };
    return Matrix3x3.fromElementwiseConstruction(multiplier);
  }

  /**
   * Scales this matrix by the provided scalar value, and returns a new matrix
   * containing the result.
   * @param {number} scaleFactor
   * @return {!Matrix3x3} The matrix scaleFactor * this.
   */
  scale(scaleFactor) {
    const scaler = (i, j) => scaleFactor * this.data_[i][j];
    return Matrix3x3.fromElementwiseConstruction(scaler);
  }

  // ============== Utils =============

  /**
   * Returns the value at a given pair of indices.
   * @param {Matrix3x3.Index} i
   * @param {Matrix3x3.Index} j
   * @return {number}
   */
  at(i, j) {
    return this.data_[i][j];
  }

  /**
   * Makes a human readable string for this matrix.
   * @override
   */
  toString() {
    // Fix the decimal precision at 2 digits.
    const fixedPrecisionData =
        this.data_.map(row => row.map(number => number.toFixed(2)));
    return JSON.stringify(fixedPrecisionData);
  }

  /**
   * Makes the SVG matrix string (of 20 values) for this matrix.
   * @return {string} The SVG matrix string.
   */
  toSvgString() {
    const outputRows = [];
    for (const i of Matrix3x3.Indices) {
      outputRows.push(this.data_[i].join(' ') + ' 0 0');
    }
    // Add the alpha row.
    outputRows.push('0 0 0 1 0');
    return outputRows.join(' ');
  }
}

/** @const {!Matrix3x3} */
Matrix3x3.IDENTITY = Matrix3x3.fromData([[1, 0, 0], [0, 1, 0], [0, 0, 1]]);

/** @enum {number} */
Matrix3x3.Index = {
  ZERO: 0,
  ONE: 1,
  TWO: 2,
};

Matrix3x3.Indices = Object.values(Matrix3x3.Index);