Смесь цветов
Стандартно любой цвет кодируется шестью шестнадцатеричными цифрами, например белый это ffffff
, красный — ff0000
, зеленый — 00ff00
, синий — 0000ff
. Все остальные цвета получаются их смешением: серый = белый + черный
, желтый = красный + зеленый
и т.д.
Напишите функцию mixColors
, которая принимает два цвета и возвращает новый цвет - их смесь.
Подсказка: смешивать стоит отдельно красную составляющую, отдельно зеленую, отдельно синюю.
Дополнительное задание: улучшить функцию, чтобы в аргументах можно было передавать и цвета в формате #xxxxxx
. Возвращается всегда цвет с ведущим #
.
Решение
Для начала поймем, почему посчитать среднее арифметическое между двумя цветами это нехорошее решение. Сделать это легко - достаточно перевести оба цвета в десятичную систему счисления, сложить и поделить пополам, не забыв округлить. Теперь возьмем два цвета - темно-зеленый #000100
и черный #000000
. Переведем оба числа в десятичную, получим 256 и 0. Сложим, поделим, получим 128. Переведем обратно. Получим синий #000080
. Не совсем то, чего мы ожидали от смешения темно-зеленого и черного. Так получилось потому, что цвета не отображаются в числа линейно. Вот у нас есть насыщенный синий #0000ff
. Прибавим к нему единицу, получим темно-зеленый #000100
. Прибавим еще 255, и цвет превратится в синий с зеленоватым оттенком #001ff
. Что ж, наивное решение не прошло.
Теперь попробуем избавиться от этого эффекта и будем смешивать каждый цвет отдельно. Поскольку строки, которые мы получаем, всегда имею длину строго 6, мы можем смело “отсекать” каждые два символа, и работать только с ними.
Для выделения подстроки из строки существует функция slice.
Теперь это дело нам нужно перевести из шестнадцатеричной строки в десятичное число. Напишем небольшую вспомогательную функцию.
Теперь можно смело смешивать каждый из спектров наших цветов, не боясь, что один повлияет на другой. Поскольку может получится десятичная дробь, округляем в ближайшую сторону.
Стоит заметить, что может получиться число, состоящее всего из одного разряда. При переводе его обратно в цвет нам нужно добавить один ведущий ноль, если требуется. Например, чтобы цвет не выглядел как #fab1
, а как #f0ab01
.
Теперь наш новый спектр можно смело добавлять к конечному цвету.
Конечный код, после небольшого укорочения, может выглядеть примерно так. Здесь я считаю важным оставить комментарий, чтобы облегчить понимание кода себе и другим разработчикам, когда кто-нибудь будет его читать в будущем.
Дополнительное задание
Допишем функцию, чтобы она опционально могла принимать цвета с ведущими ‘#’. Тут все просто. Переименуем текущую функцию в _mixColors
, а новую назовем прежним именем mixColors
. Новая mixColors
должна снова принимать два цвета, если в них есть ведущий #
убирать его, получать результат из _mixColors
, и возвращать его, опять добавив #
.
Альтернативное решение
Альтернативное решение предложил Сергей Рыжук. В нем мы сразу преобразовываем наши цвета в числа, однако не просто считаем среднее, а все также разбиваем его на 3 спектра и работаем с каждым отдельно. Проделано это с помощью побитовых сдвигов. Заметим, что 6 шестнадцатеричных цифр будут представлены как 24 бита, по 4 на каждую цифру. Для получения красного спектра “отбросим” первые (правые) 16 бит, и побитово умножим на 255. Почему именно так? Напишем красный #ff0000
в битах |11111111|00000000|00000000
. Линиями я разделил все три спектра. Отбросим первые 16 бит |тут могут оказаться|не только нули|11111111
. Поскольку слева от единиц могут быть не только нули, побитово умножим наше число на 255 - |000...000|11111111
. Так мы превратим в нули все биты, кроме первых восьми, а их самих не тронем.
Комментарии