Как определить лучший цвет фона для div, содержащего изображение?

У меня есть несколько логотипов в моих изображениях. Эти логотипы будут импортированы на этот сайт. Каждый логотип имеет случайный цвет (может быть белым, черным, серым, красным, синим, зеленым и т. д.) и имеет прозрачный фон. Например:

Код ниже будет применяться для отображения логотипа на странице сайта:

.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background-color: white; /* can be changed */
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class = "images">
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/b7yHv.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/IhCH1.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/tYjdM.png" class = "magic-image" />
  </div>
</div>

Проблема в том, что когда я делаю цвет фона .magic-box белым, белый логотип не отображается. Если для фона установлен черный цвет, черный логотип не виден и т. д.

.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background-color: black; /* can be changed */ 
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class = "images">
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/b7yHv.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/IhCH1.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/tYjdM.png" class = "magic-image" />
  </div>
</div>

Как определить наиболее подходящий background-color для применения к каждому .magic-box элементу программно?

Примечание. Цвет фона может быть разным для каждого изображения.

розовый...........

DCR 11.04.2023 20:31

@DCR Если у меня розовый логотип, его тоже не видно.

Jordy 11.04.2023 20:34

для этого есть несколько встроенных колориметрических инструментов, или вы можете изучить их с помощью книг Йоханнеса Иттена.

Mister Jojo 11.04.2023 20:34

@MisterJojo Можно ли применять колориметрию с помощью кода?

Jordy 11.04.2023 20:37

эти инструменты сделаны с помощью кода js, так что это возможно.

Mister Jojo 11.04.2023 20:38

Вы не сможете сделать это, используя только CSS. Если вам действительно нужно сделать это автоматически, вы можете нарисовать каждый логотип на холсте, чтобы подобрать его цвет, а затем просмотреть все цвета и выбрать тот, который достаточно отличается от всех (конечно, такого не может быть). в теории).

A Haworth 11.04.2023 23:04

@AHaworth Независимо от того, что это нельзя решить только с помощью CSS, реализация кода JS также принимается. Эти логотипы являются результатом не изображения, а существующего логотипа. Он может содержать текст или значок сплошного цвета.

Jordy 12.04.2023 02:26

Учитывая случайный набор логотипов, как вы узнаете, что подходящий фон найден? т.е. какая формула. Вы ищете какой-нибудь цвет, контрастирующий со всеми логотипами одинаково?

A Haworth 12.04.2023 08:28

@AHaworth Цвет фона / контраст каждого логотипа может быть другим, если это возможно. Как я могу определить цвет фона на основе цвета логотипа?

Jordy 12.04.2023 08:43

А, это намного проще. Нарисуйте логотип на холсте, выполните цикл поиска первого непрозрачного байта. Найдите его цвет. Затем сделайте что-нибудь простое, например инвертируйте его, или более сложное посмотрите, как, например, WCAG определяет подходящий контраст.

A Haworth 12.04.2023 08:48

@AHaworth Логотип — это не результат рисунка, а существующие изображения, такие как это.

Jordy 12.04.2023 09:04

Я говорю о рисовании изображения на холсте HTML - я не понимаю, почему это проблема.

A Haworth 12.04.2023 09:05

О, извините за недоразумение. Можете проиллюстрировать ваш смысл с помощью рабочей скрипки?

Jordy 12.04.2023 09:11

Будет ли это ответом на ваш вопрос? stackoverflow.com/questions/1855884/…

Rohìt Jíndal 14.04.2023 08:27

Дайте изображению описательное имя, прочитайте имя изображения из атрибута scr и на его основе установите цвет фона. Или, поскольку логотип — это просто текст, не используйте изображение, просто сделайте это в HTML/Js.

RDU 20.04.2023 09:11
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
5
15
499
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

@джорди

Я не думаю, что один цвет фона подходит для всех логотипов, потому что белый — яркий, а серый — темнее, черный — темнее. Вы должны выбрать минимум два или максимум три цвета фона для этих логотипов.

Попробуйте это для выбора цветов. https://coolors.co/contrast-checker

выберите цвет текста для логотипа и цвет фона для фона.

ashirhusaain 11.04.2023 21:12

Я думаю, что ваше решение хорошее, но для ручной работы. Я хочу сделать это автоматически.

Jordy 12.04.2023 02:27

Если вы уже знаете цвет логотипа, вы можете использовать colord для расчета контраста между логотипом и любым заданным цветом. Затем вы можете зациклить сравнение, регулируя цвет фона, пока не достигнете пригодного для использования значения.

Имейте в виду, что вам нужно будет использовать плагин a11y для доступа к .contrast()

Что-то вроде:

let contrast = 0;
let backgroundColor = logoColor;
const logoBrightness = colord(logoColor).brightness()
while (contrast < 4.5) {
  contrast = colord(logoColor).contrast(backgroundColor);
  if (logoBrightness <= 0.5) { 
      backgroundColor = colord(backgroundColor).lighten(0.05);
  } else backgroundColor = colord(backgroundColor).darken(0.05);
}

Вы можете заменить 4.5 на любое желаемое количество контраста. То, как вы примените это значение к элементу backgroundColor, будет зависеть от ваших фреймворков и предпочтений.

1.) Добавить тень

Я знаю, что это может быть простым и, возможно, глупым решением, но добавление drop-shadow также может решить проблему. Каждый логотип будет виден. Конечно, это зависит от вашего сайта, вписывается ли это в дизайн.

.magic-image {
  filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
  -webkit-filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
}

@media (prefers-color-scheme: dark) {
  .magic-image {
    filter: drop-shadow(0px 0px 7px rgba(255, 255, 255, 0.4));
    -webkit-filter: drop-shadow(0px 0px 7px rgba(255, 255, 255, 0.4));
  }
}

Добавлено к вашему примеру:

.magic-box {
  width: 200px;
  height: 100px;
  border: 1px solid black;
  position: relative;
  border-radius: 20px;
  background-color: white; /* can be changed */
}

.magic-image {
  max-height: 100%;
  max-width: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
  -webkit-filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
}

@media (prefers-color-scheme: dark) {

  body,
  .magic-box {
    background-color: #000;
  }

  .magic-image {
    filter: drop-shadow(0px 0px 12px rgba(255, 255, 255, 0.6));
    -webkit-filter: drop-shadow(0px 0px 12px rgba(255, 255, 255, 0.6));
  }
}

.images {
  display: flex;
}
<div class = "images">
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/b7yHv.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/IhCH1.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/tYjdM.png" class = "magic-image" />
  </div>
</div>

2.) Используйте оттенки серого и/или яркость

Именно по этой причине на многих веб-сайтах логотипы отображаются черно-белыми. Является ли это хорошим или плохим решением, зависит от вашего собственного дизайна. В любом случае идея есть.

Уменьшая яркость, вы можете сделать белые части изображения более серыми, так что все ваши изображения будут идеально смотреться на белом фоне. Оттенки серого необязательны, но для более последовательного вида — чтобы не было заметно выбеливания элементов — рекомендую использовать его.

.magic-image {
  filter: brightness(50%) grayscale(50%);
  -webkit-filter: brightness(50%) grayscale(50%);
}

Добавлено к вашему примеру:

.magic-box {
  width: 200px;
  height: 100px;
  border: 1px solid black;
  position: relative;
  border-radius: 20px;
  background-color: white; /* can be changed */
}

.magic-image {
  max-height: 100%;
  max-width: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  filter: brightness(50%) grayscale(50%);
  -webkit-filter: brightness(50%) grayscale(50%);
}

.images {
  display: flex;
}
<div class = "images">
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/b7yHv.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/IhCH1.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/tYjdM.png" class = "magic-image" />
  </div>
</div>
Ответ принят как подходящий

1. JS: добавить цвет фона в соответствии со средней яркостью

Этот подход визуализирует изображение размером 1x1 пиксель <canvas>, чтобы получить среднюю яркость/яркость, чтобы найти подходящий цвет фона.

function adjustBG(image, grayscale = true) {
    // try to fix CORS issues
    image.crossOrigin = "anonymous";

    // draw image on canvas
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    let img = new Image();

    img.src = image.src;
    img.crossOrigin = "anonymous"; // ADDED
    img.onload = function () {
        canvas.width = 1;
        canvas.height = 1;
        ctx.imageSmoothingEnabled = true;
        ctx.drawImage(img, 0, 0, 1, 1);

        // calculate average color form 1x1 px canvas
        let color = ctx.getImageData(0, 0, 1, 1).data;
        let [r, g, b, a] = [color[0], color[1], color[2], color[3]];

        // calculate relative luminance
        let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
        let contrast = +((255 - luminance) / 255).toFixed(2);

        let bg = [255 - luminance, 255 - luminance, 255 - luminance].map(
            (val) => {
                return +val.toFixed(0);
            }
        );
        let filters = [];

        // optional convert all to grayscale
        if (grayscale) {
            filters.push(`grayscale(1)`);
        }

        // add background color if image is very bright
        if (luminance > 160 && contrast < 0.5) {
            //console.info(bg, luminance)
            image.style.backgroundColor = `rgb(${bg.join(",")})`;
        } else {
            image.style.backgroundColor = `rgb(255,255,255)`;
        }

        // enhance contrast
        if (contrast < 0.5) {
            let newContrast = contrast ? 1/contrast : 1;
            filters.push(`contrast(${newContrast })`);
        }

        image.style.filter = filters.join(" ");
    };

let images = document.querySelectorAll("img");

addBGColor(true);

function revert() {
  images.forEach((img) => {
    img.style.removeProperty("background-color");
    img.style.removeProperty("filter");
  });
}

function addBGColor(grayscale = true) {
  images.forEach((img) => {
    adjustBG(img, grayscale);
  });
}

function adjustBG(image, grayscale = true) {
  // try to fix CORS issues
  image.crossOrigin = "anonymous";

  // draw image on canvas
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  let img = new Image();

  img.src = image.src;
  img.crossOrigin = "anonymous"; // ADDED
  img.onload = function() {
    canvas.width = 1;
    canvas.height = 1;
    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(img, 0, 0, 1, 1);

    // calculate average color form 1x1 px canvas
    let color = ctx.getImageData(0, 0, 1, 1).data;
    let [r, g, b, a] = [color[0], color[1], color[2], color[3]];

    // calculate relative luminance
    let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    let contrast = +((255 - luminance) / 255).toFixed(2);

    let bg = [255 - luminance, 255 - luminance, 255 - luminance].map(
      (val) => {
        return +val.toFixed(0);
      }
    );
    let filters = [];

    // optional convert all to grayscale
    if (grayscale) {
      filters.push(`grayscale(1)`);
    }

    // add background color if image is very bright
    if (luminance > 160 && contrast < 0.5) {
      //console.info(bg, luminance)
      image.style.backgroundColor = `rgb(${bg.join(",")})`;
    } else {
      image.style.backgroundColor = `rgb(255,255,255)`;
    }

    // enhance contrast
    if (contrast < 0.5) {
      let newContrast = contrast ? 1 / contrast : 1;
      filters.push(`contrast(${newContrast })`);
    }

    image.style.filter = filters.join(" ");
  };
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}
<p><button onclick = "revert()">revert</button> <button onclick = "addBGColor(true)">Add BG color (grayscale)</button> <button onclick = "addBGColor(false)">Add BG color (no change)</button></p>


<div class = "logos">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">


  <img src = "https://i.imgur.com/qO6ZdET.png" crossorigin = "anonymous" data-contrast = "0.69" data-bg = "175,175,175" style = "background-color: rgb(175, 175, 175);">



</div>

Рене ван дер Ленде поднял в комментариях несколько важных моментов:

  • вы можете столкнуться с проблемами CORS, поэтому вам следует добавить image.crossOrigin = 'anonymous'
  • в предыдущей версии была ошибка в расчете контраста
  • приведенный выше пример дает очень неточное вычисление среднего цвета

1.2 Усовершенствованный расчет среднего цвета

Основываясь на комментарии Рене ван дер Ленде, я изменил скрипт mediumcolor.js и добавил некоторые функции.

Следующий фрагмент также проверяет прозрачность изображения, а также цветовой диапазон только в оттенках серого.

Однако этот скрипт значительно медленнее для более детального определения цвета.

let images = document.querySelectorAll("img");

function revert() {
    images.forEach((img) => {
        img.style.removeProperty('background-color');
        img.style.removeProperty('filter');
    });
}



function addBGColor(options = { grayscale: true, complementraryColor: false, enhanceContrast: true }) {
    images.forEach((img) => {
        adjustBG(img, options);
    });
}


function adjustBG(image, options = { grayscale: true, complementraryColor: false, enhanceContrast: true }) {


    let grayscale = options.grayscale;
    let complementraryColor = options.complementraryColor;
    let enhanceContrast = options.enhanceContrast;

    // try to fix CORS issues 
    image.crossOrigin = 'anonymous';

    image.addEventListener('load', e=>{

        // check transparency
        let hasTransparency = checkImgTransparency(image);
        let isGray = checkImgGrayScale(image);

        // skip opaque images
        if (!hasTransparency) {
            console.info('nothing to do', image.src);
            return false
        }

        // get average color based on flattened transparency
        let { r, g, b, a, colors } = getAverageColor(image, 24, 24, false);

        // calculate relative luminance
        let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
        let contrast = +((255 - luminance) / 255).toFixed(2);
        let bg = [255 - luminance, 255 - luminance, 255 - luminance];

        // calculate complementary color
        let colComplemantary = [255 - r, 255 - g, 255 - b];
        let filters = [];

        // optional convert all to grayscale
        if (grayscale && !isGray) {
            filters.push(`grayscale(1)`)
        }

        // add background color if image is very bright
        let contrastAdjust = 1 + (luminance / 255);
        let colorBG = !complementraryColor ? 'rgb(255,255,255)' : `rgb(${colComplemantary.join(',')})`;

        image.setAttribute('data-contrast', contrast);
        image.setAttribute('data-bg', bg.join(','));

        // almost white
        if (luminance > 170 && contrast < 0.5) {
            colorBG = `rgb(${bg.join(',')})`;
        }

        // enhance contrast
        if (enhanceContrast && contrast < 0.5) {
            let newContrast = contrast ? 1/contrast : 1;
            filters.push(`contrast(${newContrast })`);
        }

        // apply styles
        image.style.backgroundColor = colorBG;
        image.style.filter = filters.join(' ');

    })


    // if image is ready loaded
    let isloaded = image.complete;
    if (isloaded) {
        image.dispatchEvent(new Event('load'));
    } 

}




/**
 * based on 
 * https://matkl.github.io/average-color/average-color.js
 */


function getAverageColor(img, width=24, height=24, flattenTransparency = false) {

  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;
  canvas.width = width;
  canvas.height = height;

  //document.body.appendChild(canvas)

  // flatten transparency
  if (flattenTransparency) {
    //add rect
    ctx.fillStyle = "rgb(255,255, 255)";
    ctx.fillRect(0, 0, width, height);
  }

  ctx.drawImage(img, 0, 0, width, height);
  let imageData = ctx.getImageData(0, 0, width, height);
  let data = imageData.data;
  let [rT, gT, bT, aT] = [0, 0, 0, 0];
  let colLength = data.length/4;

  // get colors
  let colors = [];
  for (let i = 0; i < data.length; i += 4) {

    r = data[i];
    g = data[i + 1];
    b = data[i + 2];
    a = data[i + 3];

    // exclude transparent colors
    if (a>128){
      rT += r;
      gT += g;
      bT += b;
      aT += a;
    } else{
      colLength--;
    }

    // count colors
    let colStr = [r, g, b].join('_');
   colors.push(colStr)
    
  }

  // calculate average color
  rT = Math.floor(rT / colLength);
  gT = Math.floor(gT / colLength);
  bT = Math.floor(bT / colLength);
  aT = Math.floor(aT / colLength);

  // remove duplicates
  colors = [...new Set(colors)];

  return { r: rT, g: gT, b: bT, a: aT , colors: colors.length};
}



function colorIsgrayscale(r, g, b, tolerance = 0.25) {
  let isGray = false;
  let rT = +(r * tolerance).toFixed(0);
  let gT = +(g * tolerance).toFixed(0);
  let bT = +(b * tolerance).toFixed(0);

  let colorAverage = (rT + gT + bT) / 3;
  if (colorAverage == rT && colorAverage == gT && colorAverage == bT) {
    isGray = true;
  }
  return isGray;
}


function checkImgGrayScale(img, tolerance = 0.9) {
  let isGrayscale = false;
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;
  let [w, h] = [8, 8];

  ctx.drawImage(img, 0, 0, w, h);
  let imageData = ctx.getImageData(0, 0, w, h);
  let data = imageData.data;
  let gray = 0;

  for (let i = 0; i < data.length; i += 4) {
    let r = data[i];
    let g = data[i + 1];
    let b = data[i + 2];
    let isGray = colorIsgrayscale(r, g, b);
    if (isGray){
      gray++;
    }
  }

  if (gray===data.length/4){
    isGrayscale = true;
  }
  return isGrayscale;
}


function checkImgTransparency(img) {
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;

  ctx.drawImage(img, 0, 0, 3, 3);
  let imageData = ctx.getImageData(0, 0, 2, 2);
  let data = imageData.data;
  let hasAlpha = data[3] < 255 ? true : false;

  return hasAlpha;
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}
<p><button onclick = "revert()">revert</button>
   <button onclick = "addBGColor({grayscale: true, complementraryColor: false, enhanceContrast: false})">Add BG color (grayscale)</button> <button onclick = "addBGColor({grayscale: false, complementraryColor: true, enhanceContrast: false})">Add BG color (complementary color)</button></p>

<div class = "logos">

  
  <img alt = "logo white" src = "https://i.postimg.cc/FzFQ3n3D/b7yHv.png" class = "magic-image" />

  <img alt = "logo black" src = "https://i.postimg.cc/J0TCqcQm/IhCH1.png" class = "magic-image" />

  
  <img src = "https://i.imgur.com/qO6ZdET.png" >
  
  

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">

</div>

2. JS: улучшите контраст, применив фильтры CSS

Подобно первому подходу, мы анализируем контрастность и яркость изображения, визуализируя элемент <canvas> для вычисления соответствующих значений свойств фильтра для улучшения contrast(2) или инвертируя очень яркие цвета через invert(1)

let images = document.querySelectorAll("img");

function fixImgColors() {
  images.forEach((img) => {
    adjustLightColors(img);
  });
}

function revert() {
    images.forEach((img) => {
        img.style.removeProperty('background-color');
        img.style.removeProperty('filter');
    });
}

function adjustLightColors(image) {
  // draw image on canvas 
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  let img = new Image();
  img.src = image.src;
  img.onload = function() {
    canvas.width = 1;
    canvas.height = 1;
    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(img, 0, 0, 1, 1);

    // calculate average color form 1x1 px canvas
    let color = ctx.getImageData(0, 0, 1, 1).data;
    let [r, g, b] = [color[0], color[1], color[2]];

    // calculate relative luminance
    let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    // invert if image is very bright
    let filterInvert = luminance > 128 ? 1 : 0;
    let contrast = Math.ceil(1 + (luminance / 255));
    image.style.filter = `invert(${filterInvert}) grayscale(1) contrast(${contrast}`;
  };
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}
<p><button onclick = "revert()">revert</button>   <button onclick = "fixImgColors()">Adjust colors</button></p>

<div class = "logos">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">

</div>


<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style = "position:absolute">
    <defs>
      <filter id='fillBlack' filterUnits='userSpaceOnUse'>
        <feFlood flood-color='#000' result='flood' />
        <feComposite in='flood' in2='SourceAlpha' operator='in' />
      </filter>
    </defs>
  </svg>

3. Только CSS: улучшите контраст, применив фильтр svg.

Этот подход основан на SVG-флуд-фильтре

Вставьте скрытый svg в тело HTML:

  <svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style = "position:absolute">
    <defs>
      <filter id='fillBlack' filterUnits='userSpaceOnUse'>
        <feFlood flood-color='#000' result='flood' />
        <feComposite in='flood' in2='SourceAlpha' operator='in' />
      </filter>
    </defs>
  </svg>

и сослаться на этот фильтр, определив класс CSS.

  .fillBlack {
      filter: url('#fillBlack');
    }

Разве мы не можем использовать dataURL?

К сожалению, хром в настоящее время борется с некоторыми фильтрами, когда они встроены в виде URL-адресов данных.

В Firefox вы также можете использовать это:

.fillBlack {
  filter: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><filter id='fillBlack' filterUnits='userSpaceOnUse'><feFlood flood-color='black' result='flood'/><feComposite in='flood' in2='SourceAlpha' operator='in'/></filter></svg>#fillBlack");
}

let images = document.querySelectorAll("img");

function applySvgFilter() {
  images.forEach((img) => {
    img.classList.add('fillBlack');
  });
}

function revert() {
  images.forEach((img) => {
    img.classList.remove('fillBlack');
  });
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}

.fillBlack {
  filter: url('#fillBlack');
}
<p><button onclick = "revert()">revert</button> <button onclick = "applySvgFilter()">Apply svg filter</button></p>


<div class = "logos">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src = "data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">

</div>

<!-- hidden svg filter definition -->
<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style = "position:absolute">
    <defs>
      <filter id='fillBlack' filterUnits='userSpaceOnUse'>
        <feFlood flood-color='#000' result='flood' />
        <feComposite in='flood' in2='SourceAlpha' operator='in' />
      </filter>
    </defs>
  </svg>

(JavaScript в приведенном выше примере просто помогает проиллюстрировать эффект «до/после»).

Мне нравятся ваши решения, но метод 1 кажется ошибочным. Используя это изображение с <img src = ".." style = "width: 1px">, он показывает один белый пиксель, в то время как рассчитанный средний цвет, проходящий через все пиксели, представляет собой rgb (47, 63, 74), очень темный ненасыщенный синий (colorhexa.com). Вероятно, из-за масштабирования HTML по умолчанию от источника 50% 50%. В твоем случае. если центр изображения в основном одноцветный (transparent выше), то и средний. Не уверен, что происходит, используя метод 1. здесь, на Codepen.

Rene van der Lende 20.04.2023 17:31

Я использовал This , чтобы получить средний цвет... ( medium-color.js)

Rene van der Lende 20.04.2023 17:34

Мне нравится первый способ, но он ломает это изображение. Ты можешь починить это?

Jordy 21.04.2023 10:05

@Jordy: простите меня - мне потребовалось время, чтобы обнаружить мой глупый недостаток: я пересмотрел фрагмент и добавил более продвинутый сценарий (добавляя новые функции). Основной проблемой предыдущего скрипта была ошибка в расчете контраста (белый был 1, а черный 0 - а должно быть наоборот).

herrstrietzel 22.04.2023 21:04

@ Рене ван дер Ленде: большое спасибо за ваш отзыв. Я, наконец, заставил его работать и добавил дополнительный скриптовый подход, основанный на medium-color.js (но я добавил некоторые функции, такие как определение альфа-канала, выравнивание прозрачности и т. д.). Хотя расчет среднего цвета очень неточен, теперь он работает, чтобы решить, какой цвет больше подходит: черный или белый. (Вычисление контраста тоже было неверным) См. также исправленное codepen

herrstrietzel 22.04.2023 21:11

Я еще не закончил с этим, не говоря конкретно о вашем решении. Вы заметили, что ОП упоминается в первом абзаце. случайный цвет (может быть белый, черный, серый, красный, синий, зеленый и т.д.) и в параг. после упоминания первого фрагмента. Когда цвет фона установлен на черный, черный логотип не виден и так далее. Это заставило меня предположить, что БГ должен быть любого цвета. Для меня единственным вариантом кажется преобразование цветов в HSL, усреднение их и добавление 180 градусов (чтобы получить дополнительный цвет) и использование результата в качестве цвета BG. Среднее RGB и HSL дают огромную разницу.

Rene van der Lende 23.04.2023 15:49

Усреднение 3 пикселей чистого красного, зеленого и синего дает rgb(85,85,85) (очень темно-серый) и hsl(120,100%,50%) (лаймовый HTML). Два очень разных цвета, как и в случае с HSL, меняется только оттенок. Несмотря на то, что подход HSL с использованием значений imgData RGB кажется более правильным, он приводит к медленному процессу, когда сначала приходится преобразовывать каждый пиксель в HSL. Как я уже сказал: просто размышляю/воздействую. Я вернусь с демонстрацией, когда закончу. Модифицировал мой SO/a/76023635 с 3-х пиксельным изображением и вашими исправлениями.

Rene van der Lende 23.04.2023 15:49

Простым ответом может быть использование градиента. Это не идеально, но просто и не требует много кода.

.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background-image: linear-gradient(white, black);
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class = "images">
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/b7yHv.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/IhCH1.png" class = "magic-image" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/tYjdM.png" class = "magic-image" />
  </div>
</div>

Но, конечно, это не выглядит фантастически и немного мягко.


Возможно, что-то вроде этого, которое может принимать ввод цвета и генерировать противоположный цвет, который всегда будет иметь контраст. (Код из этого ТАКОГО вопроса)

Тем не менее, единственное предположение, которое я здесь делаю, заключается в том, что логотипы всегда будут однотонными и что у вас будет возможность вывести HEX-код этого цвета где-нибудь на элементе. В противном случае потребуется написать более сложный код анализа изображений.

function invertColor(hex, bw) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    var r = parseInt(hex.slice(0, 2), 16),
        g = parseInt(hex.slice(2, 4), 16),
        b = parseInt(hex.slice(4, 6), 16);
    if (bw) {
        // https://stackoverflow.com/a/3943023/112731
        return (r * 0.299 + g * 0.587 + b * 0.114) > 186
            ? '#000000'
            : '#FFFFFF';
    }
    // invert color components
    r = (255 - r).toString(16);
    g = (255 - g).toString(16);
    b = (255 - b).toString(16);
    // pad each with zeros and return
    return "#" + padZero(r) + padZero(g) + padZero(b);
}

function padZero(str, len) {
    len = len || 2;
    var zeros = new Array(len).join('0');
    return (zeros + str).slice(-len);
}

//-------

document.querySelectorAll('.magic-box').forEach(box => {
  const img = box.querySelector('.magic-image');
  const logoColor = img.getAttribute('data-color');
  
  box.style.backgroundColor = invertColor(logoColor, true);
  
});
.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background: #FFF;
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class = "images">
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/b7yHv.png" class = "magic-image" data-color = "#FFFFFF" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/IhCH1.png" class = "magic-image" data-color = "#000000" />
  </div>
  <div class = "magic-box">
    <img src = "https://i.stack.imgur.com/tYjdM.png" class = "magic-image" data-color = "#808080" />
  </div>
</div>

Похоже, пришло время проверить контраст между логотипом и фоном. Вот средство проверки доступности цвета, которое позволяет легче увидеть, когда два цвета достаточно контрастны.

На SO есть несколько других тем, объясняющих, как рассчитать контрастность в javascript: Как программно рассчитать коэффициент контрастности между двумя цветами?

Вы можете получить значения HSL для цветов, а затем отрегулировать яркость фона так, чтобы он находился на самом дальнем расстоянии от яркости логотипа.

то есть: let backgroundLightness = (logoLightness + 50) % 100 если бы у логотипа была яркость 20, фон мог бы быть серым с яркостью 70 hsl(0,0,70)

Другие вопросы по теме