All files / Rindu/utils getMainColorFromImage.ts

98.27% Statements 57/58
93.33% Branches 14/15
100% Functions 2/2
100% Lines 54/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98    45x   45x   10x           6x 6x 1x     5x 1x 1x   4x 4x 4x   4x 4x 4x 4x 4x 4x   4x 4x       4x 4x   4x   4x   4x 4x   4x   400x   400x   300x 300x 300x   300x 300x   100x 100x 100x   100x   100x 1x 1x     100x     4x 4x   4x 1x 1x 1x       4x   4x 1x     4x   4x   4x      
import { Dispatch, SetStateAction } from "react";
 
import { rgbToHex } from "utils";
 
const cache: Record<string, string> = {};
 
export function getMainColorFromImage(
  imageId: string,
  callback: Dispatch<SetStateAction<string>> | ((color: string) => void),
  config?: { sampleSize?: number; qualityReduction?: number },
  document: Document = window.document
): void {
  const img = document.querySelector(`#${imageId}`) as HTMLImageElement;
  if (!img) {
    return;
  }
 
  if (cache[img.src]) {
    callback(cache[img.src]);
    return;
  }
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  Iif (!ctx) return;
 
  const image = new Image();
  image.crossOrigin = "anonymous";
  image.src = img.src;
  image.onload = () => {
    const sampleSize = config?.sampleSize || 100;
    const qualityReduction = config?.qualityReduction || 10;
 
    const scaledWidth = Math.max(1, Math.floor(image.width / qualityReduction));
    const scaledHeight = Math.max(
      1,
      Math.floor(image.height / qualityReduction)
    );
    canvas.width = scaledWidth;
    canvas.height = scaledHeight;
 
    ctx.drawImage(image, 0, 0, scaledWidth, scaledHeight);
 
    const imageData = ctx.getImageData(0, 0, scaledWidth, scaledHeight).data;
 
    const colorCounts: Record<string, number> = {};
    const colorValues: Record<string, { r: number; g: number; b: number }> = {};
 
    for (let i = 0; i < sampleSize; i++) {
      const randomIndex =
        Math.floor(Math.random() * (scaledWidth * scaledHeight)) * 4;
 
      if (imageData[randomIndex + 3] === 0) continue;
 
      const r = imageData[randomIndex];
      const g = imageData[randomIndex + 1];
      const b = imageData[randomIndex + 2];
 
      const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
      if (luma > 240 || luma < 15) continue;
 
      const simplifiedR = Math.floor(r / 8) * 8;
      const simplifiedG = Math.floor(g / 8) * 8;
      const simplifiedB = Math.floor(b / 8) * 8;
 
      const colorKey = `${simplifiedR},${simplifiedG},${simplifiedB}`;
 
      if (!colorCounts[colorKey]) {
        colorCounts[colorKey] = 0;
        colorValues[colorKey] = { r, g, b };
      }
 
      colorCounts[colorKey]++;
    }
 
    let dominantColorKey = Object.keys(colorCounts)[0];
    let maxCount = 0;
 
    for (const colorKey in colorCounts) {
      if (colorCounts[colorKey] > maxCount) {
        maxCount = colorCounts[colorKey];
        dominantColorKey = colorKey;
      }
    }
 
    let dominantColor = { r: 122, g: 122, b: 122 };
 
    if (dominantColorKey) {
      dominantColor = colorValues[dominantColorKey];
    }
 
    const hex = rgbToHex(dominantColor);
 
    cache[img.src] = hex;
 
    callback(hex);
  };
}