Emulate Photoshop's Color Overlay with CSS Filters?

I have an icon that I would like to change color using CSS. This is in optimized SVG SVG, data optimized in CSS.

This was usually not possible. That is why icon fonts were invented; their main advantage over SVG is to get CSS color and text-shadow rules. Well, CSS filters are now capable of doing both things on arbitrary images, and now they work in all Blink, Webkit and Gecko browsers , and can be expected for the future IE / Spartan .

A text-shadow replacement is simple; just use drop-shadow filter .

Coloring an image in a specific color, however, turned out to be very difficult, despite all the necessary filters. My theory so far is as follows:

  • Use contrast(0) to turn the whole image solid gray while preserving the alpha channel (Mozilla wiki say it will turn black, but in all browsers it turns gray, it should be a typo).
  • Use sepia(1) because we cannot work with hue / saturation in a gray image. This ensures that the entire image is composed of a reference color that we can do the math on; in particular, #AC9977 .

At this point, we should be able to turn the entire image from solid #AC9977 into any color we #AC9977 using hue-rotate , saturate and brightness .

First, what color coordinates are used by browsers? I could not find the meaning of spec to make sure that if it uses HSL (Lightness) or HSV (value) , but since HSB (Brightness) is a different name for HSV, I assume that it uses HSV. Also, using something like brightness(999) saturates colors (instead of making them white), which will happen in HSV, but not in HSL.

Based on this assumption, we will act as follows:

  • Calculate the hue difference between #AC9977 and the color we want, and use hue-rotate .
  • Calculate the saturation difference between them and use saturate .
  • Calculate the difference in brightness between the two, and use brightness .

Since this is not material that needs to be done manually, we will use the LESS preprocessor :

 .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); } 

This, in my opinion, should work. But it is not . Why and how to make it work?

An example of what I'm trying to achieve

Consider the icon as an image or element (background image, CSS-based form, etc.), with any color and shape defined by transparency (rather than a rectangular image that you can simply overlay). I want to make it completely composed of a certain color with CSS (presumably using filters ).

example

I plan to implement this as a LESS mixin that takes a color argument, but just a guide to the HSB function logic is enough.

+9
css3 image-manipulation hsv css-filters
Apr 05 '15 at 15:06
source share
4 answers

I sometimes tried to achieve what you want, and did not succeed.

You have an alternative using blending modes:

 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"> 

I miss the requirement of transparency. Try again:-). Disadvantage: you need to set the image 2 times.

 #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; say the desired result: you have an image that will act like a mask. You want to use this mask to set the color overlay on top of an existing image, but you want the color to be specified in CSS styles so that it is easy to edit.

If you are good at changing images to use the channel you use, this is luminosity instead of alpha, the next example might be your solution, you need a filter with gray and black colors, for example

mask file

 .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> 
+2
Apr 05 '15 at 16:40
source share

You will need to create an SVG filter that the CSS filter refers to in order to get closer to this. This is not a true overlay blend that requires blend-mode. But I think that in fact you get what you want. Keep in mind that the color change in the filters is mostly broken - this is only an approximation in the RGB space that receives saturated colors VERY incorrectly. (In fact, the original math SVG Filter is used under the covers).

 <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> 
+1
Jun 20 '15 at 1:02
source share

Check out this CSS masked article: http://thenittygritty.co/css-masking

0
Apr 05 '15 at 16:42
source share

I have advanced a little in math, but they are not very good; Ideally, I believe that any color can be represented as a maximum in the following CSS filters:

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

In other words, ideally, we should be able to express any color as the coordinates of hue, saturation, and brightness relative to sepia gray ( #AC9977 ).

While I have not yet found a way to do this (and I'm not sure if this is possible), I managed to implement an implementation that accepts any shade of pure colors (R, G, B, C, M, Y) or any neutral color (white, black and gray). Some of them are optimized (for example, black is brightness(0) ). In addition, if the color you specify has transparency, this transparency will be added as an opacity filter.

This is the code so far (written in 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. } 

If you want to play with it, here is the code-code (it automatically compiles LESS).

Please note that this is not efficient enough, and if you send an answer that is better (including using another method to solve the problem), I can accept yours and not accept it if it cannot represent any given color (which he cannot now, and I may have refused him at the moment).

0
Apr 12
source share



All Articles