Эмулировать наложение цвета в Photoshop с помощью фильтров CSS?

У меня есть значок, цвет которого я хотел бы изменить с помощью CSS. Он находится в оптимизированном для данных SVG, встроенном в CSS.

Обычно это невозможно. Вот почему были изобретены иконочные шрифты; их основное преимущество перед SVG заключается в возможности получать правила color и text-shadow из CSS. Итак, CSS-фильтры теперь могут делать обе вещи для произвольные изображения и теперь они работают во всех браузерах Blink, Webkit и Gecko, и можно ожидать от будущего IE/Spartan.

Замена text-shadow проста; просто используйте фильтр drop-shadow.

Однако окрашивание изображения в определенный цвет оказалось очень сложным, несмотря на наличие всех необходимых фильтров. Моя теория до сих пор такова:

  • Используйте contrast(0), чтобы превратить все изображение в сплошной серый, сохраняя при этом альфа-канал (вики Mozilla говорит, что он станет черным, но во всех браузерах он становится серым, должно быть, это опечатка).
  • Используйте sepia(1), так как мы не можем работать с оттенок/насыщенность серого изображения. Это гарантирует, что все изображение состоит из эталонного цвета, с которым мы можем выполнять математические операции; в частности, #AC9977.

На этом этапе мы должны иметь возможность превратить все изображение из сплошного #AC9977 в любой желаемый цвет с помощью hue-rotate, saturate и brightness.

Во-первых, какие цветовые координаты используют браузеры? Мне не удалось найти смысл в спецификации, чтобы убедиться, что она использует HSL (Легкость) или HSV (Значение), но поскольку HSB (Яркость) — это другое название HSV, я полагаю, что он использует HSV. Кроме того, использование чего-то вроде brightness(999) насыщает цвета (вместо того, чтобы делать их белыми), что происходит в HSV, но не в HSL.

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

Поскольку это не то, что нужно делать вручную, мы будем использовать препроцессор LESS:

.colorize(@color) {
    @sepiaGrey: #AC9977;
    @hOffset: (hsvhue(@color) - hsvhue(@sepiaGrey)) * 1deg;
    @sRatio: unit(hsvsaturation(@color) / hsvsaturation(@sepiaGrey));
    @vRatio: unit(hsvvalue(@color) / hsvvalue(@sepiaGrey));
    -webkit-filter: contrast(0) sepia(1) hue-rotate(@hOffset) saturate(@sRatio) brightness(@vRatio);
    filter: contrast(0) sepia(1) hue-rotate(@hOffset) saturate(@sRatio) brightness(@vRatio);
}

Это, в моем понимании, должно работать. Но это не так. Почему и как заставить это работать?

Пример того, чего я пытаюсь достичь

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

пример

Я планирую реализовать это как LESS mixin, который принимает аргумент цвета, но просто достаточно руководства по логике функций HSB.


person Camilo Martin    schedule 05.04.2015    source источник
comment
Я не совсем уверен, чего вы пытаетесь достичь. Что именно вы подразумеваете под заливкой сплошным цветом? Не могли бы вы сделать макет в Photoshop или что-то подобное того, как должен выглядеть результат?   -  person Bram Vanroy    schedule 05.04.2015
comment
PS отсутствие яркости, кажется, в результате обеспечивает сплошной цвет, я тоже не уверен, почему, но, похоже, он идет больше в том направлении, в котором вы хотите.   -  person Bram Vanroy    schedule 05.04.2015
comment
@BramVanroy Рассмотрим изображения в примере. Я хочу, чтобы они состояли только из определенного цвета (в примере #729FCF), сохраняя при этом альфа-канал. Например, рассмотрим значок, представленный в SVG, который изначально белый, и вы хотите, чтобы он приобрел оттенок красного или зеленого для разных случаев. В какой-то степени я могу более или менее оценить цвет на глаз, но я пытаюсь сделать что-то, что можно будет использовать повторно и не нужно догадываться, и в моем элементарном понимании цветов я создал алгоритм, который не кажется, работает.   -  person Camilo Martin    schedule 05.04.2015
comment
Добавлен пример @BramVanroy.   -  person Camilo Martin    schedule 05.04.2015


Ответы (3)


Иногда я пытался добиться того, чего вы хотите, и не преуспел.

В любом случае у вас есть альтернатива, используя режимы наложения:

div {
    background-color: green;
    mix-blend-mode: color;
    position: absolute;
    width: 200px;
    height: 400px;
}
<div></div>
    <img src="http://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/Hsl-hsv_models.svg/400px-Hsl-hsv_models.svg.png" height="400">

Мне не хватает требования прозрачности. Давай еще раз попробуем :-). Недостаток: нужно установить изображение 2 раза.

#test1 {
  background: linear-gradient(red, red), url("http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png");
  width: 100%;
  height: 500px;
  background-blend-mode: hue;
  -webkit-mask-image: url("http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png");
}

body {
  background-color: lightblue;
}
<div id="test1">
</div>

Ok; скажем, желаемый результат таков: у вас есть изображение, которое будет действовать как маска. Вы хотите использовать эту маску, чтобы установить цветное наложение поверх существующего изображения, но вы хотите, чтобы цвет был указан в стилях CSS, чтобы его было легко редактировать.

Если вы можете изменить изображения, чтобы использовать канал яркости вместо альфа-канала, следующий пример может быть вашим решением: вам нужен фильтр с серым и черным цветами, например этот

файл маски

.test {
        width: 200px;
    height: 200px;
    display: inline-block;
    background-image: url(http://i.stack.imgur.com/kxKXy.png); 
    background-size: cover;
    background-blend-mode: exclusion;
    mix-blend-mode: hard-light;
}

.testred {
    background-color: red;
}

.testblue {
    background-color: blue;
}

body {
    background: repeating-linear-gradient(45deg, lightblue 0px, lightyellow 50px);
}
<div class="test testred"></div>
<div class="test testblue"></div>

person vals    schedule 05.04.2015
comment
Пример, который вы показываете, окрашивает изображение (и сам по себе довольно крут; не знал о mix-blend-mode), но, похоже, это только часть решения (превращение целого значка в всего один определенный цвет), и, насколько я понимаю, это не сработает для изображений с прозрачностью (тоже подкрасит все, что находится за ними). - person Camilo Martin; 05.04.2015
comment
Я вижу вашу правку. Немного грустно, что это работает только на webkit, так как это аккуратно. Однако более серьезная проблема заключается в том, что если вы рассмотрите пример, который я привел, результатом для red будет что-то вот так — подумайте о наложении цвета из Photoshop. - person Camilo Martin; 06.04.2015
comment
Это выглядит многообещающе! Однако результат резко отличается от того, как выглядит эффект Color Overlay, понимаете, что я имею в виду . Но в целом это может быть указателем в правильном направлении. - person Camilo Martin; 06.04.2015
comment
Измененный пример, теперь используется черно-серый фильтр и другие режимы наложения. - person vals; 06.04.2015
comment
Поздравляем. Это действительно решение, которое приводит к желаемому визуальному выводу и, безусловно, заслуживает одобрения, несмотря на ограничения, которые оно вызывает (теперь нужно редактировать каждый файл изображения на стороне сервера, и оно не может изящно деградировать без нескольких изображений, и в этом случае дополнительное предварительно окрашенное изображение также могло бы сработать). Из-за этих ограничений я не могу принять ответ в его нынешнем виде, хотя я удивлен работой, которую вы там проделали. Я пытался комбинировать его с фильтрами css (для создания черного на сером изображении), но кажется, что они и их странная математика срабатывают после смешивания. - person Camilo Martin; 06.04.2015
comment
Я знаю, что это далеко не готовый к производству код. Я просто пытался изучить возможности, чтобы получить ожидаемый результат. Очень жаль, что маскировка не получает более широкой поддержки. Удачного кодирования :-) - person vals; 07.04.2015
comment
Спасибо за попытку, правда. Я довольно упрям, поэтому я буду продолжать и, надеюсь, выясню, почему hue-rotate был закодирован самим дьяволом - просто фильтров HSB должно быть достаточно, и в конце концов я могу решить эту проблему и опубликовать отвечать. В любом случае, ваше решение очень помогло мне понять, что теперь мы можем использовать режимы наложения! Посмотрите, что можно сделать с экраном и парой градиентов. - person Camilo Martin; 08.04.2015
comment
Это превосходный ответ, который дал мне возможность расширить FontAwesome прозрачными изображениями PNG для изображений, которых еще нет в FontAwesome. Я все еще настраиваю его, но спасибо! - person Dave Land; 09.02.2017

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

(-webkit-)filter: contrast(0) sepia(1) hue-rotate(X) saturate(Y) brightness(Z);

Другими словами, в идеале мы должны уметь выражать любой цвет в виде координат оттенка, насыщенности и яркости относительно серого цвета сепии (#AC9977).

Хотя я до сих пор не нашел способ сделать это (и не уверен, что это возможно), мне удалось сделать реализацию, которая принимает любой оттенок чистых цветов (R, G, B, C, M, Y) или любой нейтральный цвет. (белый, черный и серый). Некоторые оптимизированы (например, черный — это просто brightness(0)). Кроме того, если указанный вами цвет имеет прозрачность, эта прозрачность будет добавлена ​​как фильтр opacity.

На данный момент это код (написанный на LESS):

// Filter prefixer.
.filter(@filters) { -webkit-filter+_: @filters; filter+_: @filters; }

// Helper that conditionally adds opacity filter when color calls for it.
._conditional-opacity(@color) when (alpha(@color) < 1) {
  .filter(round(opacity(alpha(@color)), 3));
}

// Helper that adds a brightness filter when necessary.
._conditional-brightness(@channel) when (@channel < 255) {
  .filter(brightness(round(@channel / 255, 3)));
}

// Special case for pure black.
.colorize(@color) when (fade(@color, 100%) = #000) {
  .filter(brightness(0));
  ._conditional-opacity(@color);
}

// Special case for pure grey and off-by-one-grey.
.colorize(@color) when (fade(@color, 100%) = #7F7F7F),
                       (fade(@color, 100%) = #808080) {
  .filter(contrast(0));
  ._conditional-opacity(@color);
}

// Special case for shades of pure red.
.colorize(@color) when (red(@color) > 0)
                   and (green(@color) = 0)
                   and (blue(@color) = 0) {
  .filter(contrast(0) sepia(1) saturate(999));
  ._conditional-brightness(red(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure green.
.colorize(@color) when (red(@color) = 0)
                   and (green(@color) > 0)
                   and (blue(@color) = 0) {
  .filter(contrast(0) sepia(1) hue-rotate(99deg) saturate(999));
  ._conditional-brightness(green(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure blue.
.colorize(@color) when (red(@color) = 0)
                   and (green(@color) = 0)
                   and (blue(@color) > 0) {
  .filter(contrast(0) sepia(1) hue-rotate(199deg) saturate(999));
  ._conditional-brightness(blue(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure cyan.
.colorize(@color) when (red(@color) = 0)
                   and (green(@color) > 0)
                   and (blue(@color) = green(@color)) {
  .filter(contrast(0) sepia(1) invert(1) saturate(999));
  ._conditional-brightness(blue(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure magenta.
.colorize(@color) when (red(@color) = blue(@color))
                   and (green(@color) = 0)
                   and (blue(@color) > 0) {
  .filter(contrast(0) sepia(1) hue-rotate(-99deg) saturate(999));
  ._conditional-brightness(red(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure yellow.
.colorize(@color) when (red(@color) > 0)
                   and (green(@color) = red(@color))
                   and (blue(@color) = 0) {
  .filter(contrast(0) sepia(1) hue-rotate(199deg) saturate(999) invert(1));
  ._conditional-brightness(green(@color));
  ._conditional-opacity(@color);
}

// Special case for shades of pure grey and white.
.colorize(@color) when (red(@color) = green(@color))
                   and (green(@color) = blue(@color))
                   and not (blue(@color) = 0) // We've optimized these before.
                   and not (blue(@color) = 127)
                   and not (blue(@color) = 128) {
  .filter(contrast(0) brightness(round(blue(@color) / 255 * 2 + .00765, 3)));
  ._conditional-opacity(@color);
}

.colorize(@color) when (default()) {
  // General case not figured out yet.
}

Если вы хотите поиграть с ним, вот codepen (он автоматически компилирует LESS).

Обратите внимание, что этого недостаточно, и если вы опубликуете ответ, который лучше (включая использование другого метода для решения проблемы), я могу принять ваш и не приму свой собственный, если он не может представлять какой-либо заданный цвет (который он в настоящее время не могу; и я, возможно, отказался от этого на данный момент).

person Camilo Martin    schedule 12.04.2015

Вам нужно будет создать фильтр SVG, на который ссылается фильтр CSS, чтобы приблизиться к этому. Это не настоящая смесь наложения — для этого требуется режим наложения. Но я думаю, что это на самом деле дает вам то, что вы хотите. Имейте в виду, что вращение оттенков в фильтрах в основном нарушено - это всего лишь приближение в пространстве RGB, которое ОЧЕНЬ неправильно передает насыщенные цвета. (На самом деле он использует оригинальную математику SVG Filter под обложками).

<svg width="800px" height="600px">
<defs>
  <filter id="fakeOverlay">
    <feColorMatrix type="luminanceToAlpha" result="L2A"/>
    <feFlood flood-color="cyan" result="colorfield"/>
    <feBlend mode="multiply" in="L2A" in2="colorfield"/>
    <feComposite operator="in" in2="SourceGraphic"/>
  </filter>
  </defs>
<image filter="url(#fakeOverlay)" width="800" height="400" xlink:href="http://i.stack.imgur.com/Mboab.png"/>
  
</svg>

person Michael Mullany    schedule 20.06.2015
comment
Выполнимо ли это без преобразования изображений в SVG с жестко запрограммированными цветами в разметке страницы? - person Camilo Martin; 24.06.2015
comment
Есть два способа сделать это без преобразования ваших исходных изображений в сами SVG: 1) со встроенным svg и сделать так, чтобы ваш элемент изображения ссылался на внешний png — как я сделал выше, или 2) ссылаться на этот фильтр svg через фильтр css (-webkit -фильтр и т. д.) синтаксис. Первый будет работать во всех последних браузерах, а также в IE10 и выше, последний будет работать только в браузерах, поддерживающих фильтры css. - person Michael Mullany; 25.06.2015
comment
Что ж, весь смысл в том, чтобы сделать это более удобным и/или более элегантным способом, чем жестко кодировать дополнительную разметку в HTML (эти элементы SVG нужно будет обновлять каждый раз, когда я тоже хочу другой цвет). Я ценю усилия, но моей первоначальной мотивацией было найти способ вернуть эти стилистические проблемы обратно в таблицу стилей и остаться там. Я бы даже не возражал поместить SVG как data: в CSS только для фильтра, но, похоже, это не интерпретируется как использование URL-адреса. - person Camilo Martin; 27.06.2015
comment
Ну, вы можете иметь отдельный файл SVG только с определениями фильтров. Я думаю, что вы чрезмерно ограничиваете свою проблему. Я не знаю точного варианта использования, но просто возможность сделать это динамически кросс-браузерным — огромная победа по сравнению с тем, чтобы делать это в Photoshop. - person Michael Mullany; 27.06.2015
comment
Динамически это хорошо, но я хочу сказать, что если мне нужно сделать еще один запрос или изменить разметку, чтобы добавить помощники по стилю, это превосходит цель не просто добавления другого изображения data:uri'd (svg) в CSS ( и пропуская дополнительные запросы/модификации разметки). Это не может быть просто техническое демонстрационное решение. - person Camilo Martin; 27.06.2015
comment
Средняя производственная веб-страница имеет 120 запросов — я бы ослабил это требование. - person Michael Mullany; 27.06.2015
comment
Не дай бог, 120 запросов? Это приемлемо только на сайте потокового видео или что-то в этом роде. Для обычного веб-сайта вы можете объединить все CSS, объединить весь javascript и поместить большинство изображений (кроме аватаров, миниатюр и т. Д.) В CSS как data-uris, кэшировать CSS и JS на навсегда плюс один день, и клиент выиграл даже не нужно 304 их. По моему опыту, это делает все молниеносным. Единственные дополнительные запросы позже загружаются во время рендеринга страницы. 120 запросов — это абсолютное безумие, и на мобильных устройствах они будут загружаться как дерьмо. - person Camilo Martin; 27.06.2015
comment
keynote.com/performance-indexes/mobile-retail-us ( на самом деле это в среднем 96 запросов) Я не говорю, что это правильно - я говорю, что это то, что есть. - person Michael Mullany; 28.06.2015
comment
Здравствуйте, я знаю, что это старый ответ, но я по-другому скопировал ваш код в файл css, и он накладывает изображение, но цвет черный, а не голубой. Можно ли указать другие значения в цвете заливки в этом решении? Вот суть моей попытки, которая накладывает только черный цвет gist.github.com/Modicrumb/c7eff02ff79bc19ad42c6890c73426f8 . - person Royalty; 15.06.2017
comment
Задайте новый вопрос с тегом svg-filters с полным рабочим примером, показывающим, как он ломается для вас, и я попробую его. - person Michael Mullany; 15.06.2017
comment
Я задал здесь новый вопрос: stackoverflow.com/questions/44573919/ - person Royalty; 15.06.2017