tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WallpaperTheme.worker.mjs (2963B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* eslint-env webworker */
      6 // eslint-disable-next-line mozilla/reject-import-system-module-from-non-system
      7 import { Color } from "resource://gre/modules/Color.sys.mjs";
      8 import { PromiseWorker } from "resource://gre/modules/workers/PromiseWorker.mjs";
      9 
     10 /**
     11 * Worker for calculating the average relative luminance of an image
     12 * off the main thread.
     13 */
     14 export class WallpaperThemeWorker {
     15  /**
     16   * @function calculateTheme
     17   * @param {{ blob: Blob }} options - Options object containing the image blob.
     18   * @param {Blob} options.blob - The image file blob to analyze.
     19   * @returns {Promise<"dark"|"light">} A promise that resolves to "dark" if the
     20   * average luminance is below the contrast threshold, otherwise "light".
     21   */
     22  async calculateTheme(blob) {
     23    let totalLuminance = 0;
     24    let count = 0;
     25    // Create an offscreen image bitmap
     26    const bitmap = await globalThis.createImageBitmap(blob);
     27    const scale = Math.min(1, 256 / Math.max(bitmap.width, bitmap.height));
     28    const width = Math.round(bitmap.width * scale);
     29    const height = Math.round(bitmap.height * scale);
     30 
     31    // Draw to an off-screen canvas
     32    const canvas = new OffscreenCanvas(width, height);
     33    const ctx = canvas.getContext("2d");
     34    ctx.drawImage(bitmap, 0, 0, width, height);
     35 
     36    // get pixel data
     37    const { data } = ctx.getImageData(0, 0, width, height);
     38 
     39    // The +=1 in these loops means that it will look at every pixel
     40    for (let row = 0; row < height; row += 1) {
     41      for (let column = 0; column < width; column += 1) {
     42        const index = (row * width + column) * 4;
     43        const alpha = data[index + 3];
     44        // Skip transparent pixels
     45        if (alpha > 0) {
     46          const red = data[index];
     47          const green = data[index + 1];
     48          const blue = data[index + 2];
     49          const luminance = new Color(red, green, blue).relativeLuminance;
     50          totalLuminance += luminance;
     51          count++;
     52        }
     53      }
     54    }
     55    const averageLuminance = totalLuminance / count;
     56 
     57    // Threshold taken from Color.sys.mjs module
     58    const CONTRAST_BRIGHTTEXT_THRESHOLD = Math.sqrt(1.05 * 0.05) - 0.05;
     59    return averageLuminance <= CONTRAST_BRIGHTTEXT_THRESHOLD ? "dark" : "light";
     60  }
     61 }
     62 
     63 // Promise worker boiler plate
     64 const wallpaperWorker = new WallpaperThemeWorker();
     65 const worker = new PromiseWorker.AbstractWorker();
     66 worker.dispatch = function (method, args = []) {
     67  return wallpaperWorker[method](...args);
     68 };
     69 worker.postMessage = function (message, ...transfers) {
     70  self.postMessage(message, ...transfers);
     71 };
     72 worker.close = function () {
     73  self.close();
     74 };
     75 self.addEventListener("message", msg => worker.handleMessage(msg));
     76 self.addEventListener("unhandledrejection", function (error) {
     77  throw error.reason;
     78 });