Укрощение режимов наложения в CSS


Как-то раз мне пришла в голову идея воссоздать такой эффект выборочного обесцвечивания на CSS:

Кадр из фильма «Город грехов»

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

Вот что получилось в результате экспериментов:

Оригинал фотографии взят здесь https://unsplash.com/@luiskcortes

Демо тут https://codepen.io/juwain/pen/mxLJYj.

Как это работает. Сразу стало понятно, что для решения нужно изготовить под конкретную картинку SVG-маску, затем наложить поверх цветной картинки чёрно-белую с вырезанной частью. В итоге, через дырку в чёрно-белом изображении будет просвечивать цветная. Решение с изготовлением маски вручную было отброшено из-за неуниверсальности.

В поисках универсального способа я решил попробовать сделать «маску на лету» из самого же оригинального изображения с помощью режимов наложения (blend modes).

Вкратце про режимы наложения. По умолчанию слои в CSS располагаются друг поверх друга и не «просвечивают»:

Нормальный режим наложения

А можно сделать так, чтобы слои не просто показывались один над другим, а «смешивались» по определённому алгоритму:

Умножающий режим наложения

Режимы наложения пришли в CSS из Фотошопа благодаря сотрудникам компании Adobe. Но не все фотошоповские режимы наложения переехали в CSS — всего в CSS есть 16 вариантов смешивания слоёв. Не буду вдаваться в подробности, с режимами наложения можно поиграть тут.


Ок, к селективному обесцвечиванию.

Сверху на изображение накладывается его же копия с помощью псевдоэлемента:

.photo {
--source: url(some-url-string);
/* это нижний слой */
background-image: var(--source);
}
.photo::after {
/* это верхний слой */
background-image: var(--source);
}

image

Дальше у верхнего слоя режим наложения меняется на lighten. При таком режиме тёмные участки начинают «просвечивать», а светлые — наоборот остаются непрозрачными:

Режим наложения lighten

Режим наложения между слоями включается свойством mix-blend-mode.

.photo::after {
mix-blend-mode: lighten;
}

Получилось то, что нужно: тёмный фон верхнего слоя стал прозрачным, а светлые участки — нет:

image

Далее верхний слой обесцвечивается с помощью CSS-фильтра:

.photo::after {
filter: grayscale(1);
}

Теперь верхний слой накладывается светлыми обесцвеченными участками на нижнюю цветную фотографию:

image

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

В этом снова помогут режимы наложения. Помимо свойства mix-blend-mode, «смешивающее» два разных слоя, есть ещё одно свойство, меняющее режим наложения — это background-blend-mode. Оно задаёт режим смешивания фоновых элементов одного слоя: изображения, фонового цвета, градиента.

Итак, верхнему слою задаётся серый фоновый цвет и режим смешивания картинки с ним hard-light. Это комбинированный режим, он чем-то похож на режим умножения — если коротко, он делает цвета ярче. Применительно в чёрно-белому изображению, этот режим сделает его светлые части более контрастными.

В итоге получается желаемый результат:

image


Остаётся разобраться, какие именно цвета в таком сочетании режимов наложения делаются прозрачными, а какие нет.

Для этого я сделал демо со слоями с полосатыми линейными градиентами, и вот что получилось:

Вживую можно посмотреть в https://codepen.io/juwain/pen/vRjaQb

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

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