Fruit average color


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


Фон


Рассчитываем средний цвет фотографии и устанавливаем цвет подложки. Пример


Background


Градиент


Средний цвет высчитывается у верхней или нижней части картинки и используется в подложке для текста. Между картинкой и подложкой установлен плавный градиент. Стиль Яндекс.Дзена. Пример


Gradient


Градиент в стиле Minecraft — средний цвет высчитывается частями (горизональными полосками). Пример


Minecraft gradient


Рамка


Как багет у картины, средний цвет высчитывается отдельно у каждой из сторон фотографии.
Пример


Border


Тень


Вычисленный средний цвет используется в задании цвета падающей тени. Пример


Box shadow


В CSS у элемента можно задать несколько теней. Для каждой из сторон фотографии вычислим средний цвет и установим отдельную тень. Пример


Box shadow, 4 sides


Видео


Предыдущий пример применим в динамике для видео. Пример


Video, box shadow, 4 sides


Разделим стороны экрана на большее количество частей (30), в которых вычислим средний цвет для отбрасываемой тени, совсем как у Philips Ambilight. Пример


Video, Ambilight


Текстовая фотография


Фотографию заполняем текстом, под каждым символом вычисляем средний цвет и применяем его к символу. Пример


Text photo


Использование


Средний цвет в примерах вычисляется с помощью небольшого пакета «fast-average-color». При подсчёте цвета учитывается прозрачность. По умолчанию используется квадратичный алгоритм, т.к. при простом усреднении цвет становится более тёмным.


npm i -D fast-average-color


Примеры


Для получения среднего цвета из загруженных картинок, видео и canvas’a используется метод .getColor():


<html>
<body>
    ...
    <div class="image-container">
        <img src="image.png" />
        <div>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit,
            sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </div>
    </div>
    <script src="https://unpkg.com/fast-average-color/dist/index.min.js"></script>
    <script>
        window.addEventListener('load', function() {
            var
                fac = new FastAverageColor(),
                container = document.querySelector('.image-container'),
                color = fac.getColor(container.querySelector('img'));

            container.style.backgroundColor = color.rgba;
            container.style.color = color.isDark ? '#fff' : '#000';

            console.log(color);
            // {
            //     error: null,
            //     rgb: 'rgb(255, 0, 0)',
            //     rgba: 'rgba(255, 0, 0, 1)',
            //     hex: '#ff0000',
            //     hexa: '#ff0000ff',
            //     value: [255, 0, 0, 255],
            //     isDark: true,
            //     isLight: false
            // }
        }, false);
    </script>
</body>
</html>

А для картинок, которые находятся в процессе загрузки — .getColorAsync():


<html>
<body>
    ...
    <div class="image-container">
        <img src="image.png" />
        <div>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit,
            sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
        </div>
    </div>
    <script src="https://unpkg.com/fast-average-color/dist/index.min.js"></script>
    <script>
        var
            fac = new FastAverageColor(),
            container = document.querySelector('.image-container');

        fac.getColorAsync(container.querySelector('img'), function(color) {
            container.style.backgroundColor = color.rgba;
            container.style.color = color.isDark ? '#fff' : '#000';

            console.log(color);
            // {
            //     error: null,
            //     rgb: 'rgb(255, 0, 0)',
            //     rgba: 'rgba(255, 0, 0, 1)',
            //     hex: '#ff0000',
            //     hexa: '#ff0000ff',
            //     value: [255, 0, 0, 255],
            //     isDark: true,
            //     isLight: false
            // }
        });
    </script>
</body>
</html>

Для картинок и видео с разных доменов стоит не забывать про CORS.


Ссылки:


Комментарии (17)


  1. ThisMan
    05.08.2018 22:21

    Пример с рамкой выглядит клево, только если у сторон близкие цвета, иначе получается 4 вариант с костром, который смотрится не очень


  1. dom1n1k
    05.08.2018 23:50

    Насколько я понимаю, «квадратичный» алгоритм — это переход в «почти линейное» пространство RGB с грубым учетом гаммы. Гамма принимается равной 2, что конечно же дает очень приблизительный результат, но гораздо лучше, чем если не учитывать её вовсе.


    1. AngReload
      06.08.2018 06:05

      Странно что в примерах сравниваются настройки simple и sqrt с усреднением цветов в lab. Lab для этого не подходит, лучше бы показали сравнение настоящим линейным усреднением.


      1. dom1n1k
        06.08.2018 09:24

        Почему lab не подходит?


        1. AngReload
          06.08.2018 10:47

          Цель у всех этих рамок создать environment, как бы свет идущий с вне поля чёткого зрения, но из той же среды.
          По этому усредняться цвет должен так же как это произошло бы в реальном оптическом эксперименте, то есть линейно.


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


          Так, например, сравним гамму: у линейного света — 1, у SRGB ? 2.2, у Lab — 3. И в первом примере, где смешиваются черный и белый, это видно: у Lab получается даже более тёмный цвет, чем у SRGB. Далее, при смешивании синего и жёлтого у Lab получается розовый.


  1. cruzo
    06.08.2018 11:12

    На первый взгляд скрипт круче color-thief получился. Здорово, кстати, у вас выглядит amdient light.


  1. BubaVV
    06.08.2018 11:48
    +1

    А как такой алгоритм обрабатывает очень пестрые фотографии, типа такой?
    image


    1. Odrin
      06.08.2018 12:57

      У меня вышло #a2806b


  1. shybovycha
    06.08.2018 13:06
    +2

    Есть разница между "средним цветом" и "цветом, встречающимся наиболее часто" — так же, как и между медианой, средним и модой: https://www.purplemath.com/modules/meanmode.htm


    В вашем алгоритме используется среднее значение. Но, как и написано в указанной мною статье, среднее значение может даже и не появляться в выборке:


    data = [ 13, 13, 13, 13, 14, 14, 16, 18, 21 ]
    
    mean(data) = 15
    mode(data) = 13

    Поэтому у вас на примерах небо темнее полоски на фоне, а на картинке с листиками фоном вообще болотный цвет.


    Я понимаю, для разных задач — разные решения, и в некоторых случаях подобное решение может быть оправдано затратами времени. Но тем не более, может стоит попробовать и немного другой подход?


  1. anprs
    06.08.2018 14:56

    Прочитал заголовок «Градиент». Посмотрел на картинку, не понял. Прочитал абзац «Средний цвет высчитывается у верхней или нижней части картинки и используется в подложке для текста. Между картинкой и подложкой установлен плавный градиент». Присмотрелся к картинкам и понял что эту подложку я считал частью фотографии. Как мне кажется, это наиболее органичное применение «среднего цвета» из всех приведённых примеров


  1. amarao
    06.08.2018 15:50

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

    Вот несколько интересных идей:

    1. Минимальное расстояние (сумма расстояний) по цвету до каждого пиксела картинки. Высчитываем цветовое расстояние (например, как квадратный корень из суммы квадратов H, S, L) для пиксела, суммируем его, минимизируем, получаем «самый близкий ко всем». Для разноцветья — серый, для фотографий с общим цветом (например, синеватых) — усреднённо синеватый.

    2. Минимизируем расстояние в пятимерном пространстве, где три кординаты — HSL, а ещё две — расстояние на картинке. Таким образом, чем дальше пиксел от рамочки, тем меньше он влияет на её цвет.

    Можно придумать кучу вариантов. Уход в градиентики и прочие «нарисовать рамочку» уже не так интересно.


  1. Kuorell
    06.08.2018 21:15

    А для чего вы умножение цвет на alpha? (выглядит так, будто вы и сходите из предположегия, что любая картинка на черном фоне (мб стоит передавать предполагаемый цвет фона и смешивать?)


    1. hcodes Автор
      06.08.2018 21:20

      1. AngReload
        06.08.2018 22:02

        На хабре есть статья про premultiplied alpha — https://habr.com/post/328386/


  1. GCU
    06.08.2018 21:15

    По логике sqrt алгоритм — это гамма-коррекция 2. Почему бы не использозать страндартную 2.2?


    1. AngReload
      06.08.2018 22:14

      Наверное, побоялись что будет медленно. Но сравнения скорости нету. И можно ведь заранее посчитать все 256 значений, а потом просто доставать результат по индексу массива.
      Кстати, ести точно, то стандартная не просто 2.2, а такая формула:
      image


  1. GCU
    07.08.2018 13:15

    Можно считать количество каждого встречающегося значения 0-255 в массиве и по этой статистике уже пересчитывать разные варианты «усреднения», не сканируюя всю картинку заново, раз «усреднение» всё равно поканальное. Хотя это скорее оптимизация для больших размеров картинки.
    Поскольку это производные от изображения данные, их можно рассчитать заранее (Каким-нибудь ImageMagick, например) и передавать уже готовые рядом с картинкой. Онлайн-рассчёт это хорошо, но пользователи устройств с питанием от аккумулятора, возможно, будут недовольны раходом батареи :(