CSS Filters To Adjust Brightness, Contrast, Opacity, And Inversion

Have you ever added an image to a website only to realize it would look better if it was a little brighter? Maybe the image could use more contrast or a bit of transparency. How about inverting all the colors to their complementary hues?

For several weeks I’ve been talking about CSS filters. I started with an introduction and an example of a blur() filter and continued with examples for the url() and drop-shadow() functions.

Last week I talked about four filter-functions for adjusting color that provide shortcuts to the feColorMatrix filter primitive. Today I want to look at four more filter-functions that also provide shortcuts to a single primitive, this time feComponentTransfer.

The feComponentTransfer Filter Primitive

The feComponentTransfer primitive can be used to adjust the brightness, contrast, or opacity or an image. It can also be used to invert the colors in an image. However, when it comes to the specific values necessary to make these adjustments, the primitive may not be the most intuitive to use.

With CSS it’s a lot easier to make the specific adjustment you want. The feComponentTransfer primitive leads to these four CSS filter-functions.

  • brightness()
  • contrast()
  • invert()
  • opacity()

Let’s walk through each and see some examples where I make adjustments to an image you’ve probably seen before.

The brightness() filter-function

As you would expect, the brightness() filter-function can increase or decrease the brightness of an element.

1
brightness() = brightness( [ <number> | <percentage> ] )

Similar to many of the filter-functions, you supply a proportion for the conversion either as a percentage or a number between 0.0 and 1.0. A value of 0% (0.0) changes the element to be completely black and a value of 100% (1.0) leaves the element unchanged.

Take an extra second to digest that. A value of 100% leaves the image unchanged and any value less than 100% makes it darker. That means to make the image brighter you have to set a value greater than 100% and fortunately that is allowed. Negative values, however, are not permitted.

Here I made the image brighter with a value of 1.5.

1
2
3
.strawberry {
 filter: brightness(1.5);
}

Again you need to use values greater than 1.0 or 100% if you want to make the image brighter. Anything less than 1.0 or 100% will make the image darker. Here’s the result of brightness(1.5).

The equivalent SVG filter is slightly more complex, though mainly to give you greater control, as it allows you to change the brightness of each color channel independently instead of changing all or none.

1
2
3
4
5
6
7
<filter id="brightness">
  <feComponentTransfer>
    <feFuncR type="linear" slope="[amount]"/>
    <feFuncG type="linear" slope="[amount]"/>
    <feFuncB type="linear" slope="[amount]"/>
  </feComponentTransfer>
</filter>

Just another reminder that you’ll have to consider several things when you make your choice between SVG filters and CSS filters. CSS filters will typically be simpler, though sometimes that simplicity will come with a price of less control. You’ll have to balance both with browser support to decide which to use.

The contrast() filter-function

The contrast() filter-function adjusts the contrast of an image.

1
contrast() = contrast( [ <number> | <percentage> ] )

Once again you supply a proportion either as a percentage or a number between 0.0 and 1.0. A value of 0% (0.0) will convert your image so it’s completely gray. A value of 100% (1.0) will leave your image unchanged.

As usual negative values are not allowed, but values greater than 100% are. Similar to brightness() any value less than 100% decreases the contrast so to increase it you need to go above 100% or above 1.0.

The most contrast I could add to this image was 100 (10,000%).

1
2
3
.strawberry {
 filter: contrast(100);
}

Here’s how it looks.

Here’s the result of setting the contrast to 0, which you can see converts the image to a large block of medium gray. No matter how the image looks initially, this is the result of contrast(0).

Like brightness(), the SVG filter for adjusting contrast is a little more complex than the CSS filter-function, though again that simplicity comes at the cost of control over the filter.

1
2
3
4
5
6
7
<filter id="contrast">
 <feComponentTransfer>
   <feFuncR type="linear" slope="[amount]" intercept="-(0.5 * [amount]) + 0.5"/>
   <feFuncG type="linear" slope="[amount]" intercept="-(0.5 * [amount]) + 0.5"/>
   <feFuncB type="linear" slope="[amount]" intercept="-(0.5 * [amount]) + 0.5"/>
 </feComponentTransfer>
</filter>

The invert() filter-function

The invert() filter-function flips color values to their complementary color, the color opposite on the color wheel.

1
invert() = invert( [ <number> | <percentage> ] )

Once again you set the proportion to apply the conversion, either as a percentage or a number between 0.0 and 1.0. A value of 0% (0.0) leaves the image unchanged and a value of 100% (1.0) completely inverts it so it looks like an old film negative.

Negative values are not allowed. Values greater than 100% are, but they have no effect. An interesting value is 50% which results in the image being completely gray, the same as setting contrast(0).

Let’s invert the image completely.

1
2
3
.strawberry {
 filter: invert(100%);
}

And here’s how that looks. It’s kind of an interesting effect.

The same as with the previous two filter-functions, the SVG filter is a little more complex, though it again offers greater control as you can invert each color channel separately.

1
2
3
4
5
6
7
<filter id="invert">
 <feComponentTransfer>
   <feFuncR type="table" tableValues="[amount] (1 - [amount])"/>
   <feFuncG type="table" tableValues="[amount] (1 - [amount])"/>
   <feFuncB type="table" tableValues="[amount] (1 - [amount])"/>
 </feComponentTransfer>
</filter>

If you read the earlier series on SVG filters, you might remember the strength of feComponentTransfer was that it operated on each color channel independently of the others so it’s no surprise the SVG filter offers the extra control.

The opacity() filter-function

I said at the start of this series that you could probably guess what each filter-function does and I’m guessing that’s mostly been true. The opacity() filter-function, as you would expect, controls the opacity of the image,

1
opacity() = opacity( [ <number> | <percentage> ] 

One more time you set the proportion for the conversion either as a percentage or a number between 0.0 and 1.0. A value of 0% (0.0) makes the image completely transparent and a value of 100% (1.0) leaves the image unchanged. Values in between are linear multipliers of the effect.

Negative values are not allowed and while you can supply a value greater than 100% it will have no additional effect.

Here I set the opacity to 0.5.

1
2
3
.strawberry {
 filter: opacity(0.5);
}

And here’s how that looks.

The SVG filter is again a bit more complex, though not much more. I’m not sure if offers any more control than the filter-function.

1
2
3
4
5
<filter id="opacity">
  <feComponentTransfer>
    <feFuncA type="table" tableValues="0 [amount]"/>
  </feComponentTransfer>
</filter>

You may wonder why use the opacity filter-function when there’s already an opacity property and they appear to do the same thing. Remember that filters might load faster as a result of browsers taking advantage of hardware acceleration.

Multiple Filters

In all the examples in this series I used a single filter-function to add an effect to the image. I mentioned in the first post in the series that you can apply multiple filter-functions to a single element and I thought I would give you one last example where I do just that.

In this example I inverted the image completely, added a 5px blur, and then converted the image to 75% sepia.

1
2
3
.strawberry {
 filter: invert(1) blur(5px) sepia(75%);
}

Note that there’s no comma separating the filter-functions, only white space. Also remember that you can list each function on a separate line to make your code easier to read.

1
2
3
4
5
6
.strawberry {
 filter:
   invert(1)
   blur(5px)
   sepia(75%);
}

Hopefully you can tell that all three filter-functions have been applied to the original SourceGraphic.

Also think how simple that was and how easy it would be to make adjustments as needed. It’s a lot simpler than having to run the image through a graphics editor every time you want to make a change.

For example it took about 10 seconds to copy the example again and change the amount of blur to 3px and the amount of sepia conversion to 50%. It took less time to do than to tell you what I did.

Hopefully that helps illustrate why you want to opt for CSS or SVG filters over adding the same filters in a graphics editor.

Closing Thoughts

That brings me to the end of this short series on CSS filters. As I’ve said throughout, they’re generally simpler to use than their SVG counterparts, though that simplicity comes with a price of less control over the effect.

Remember that SVG filters have better support than CSS filters at the moment. Even though most browsers support the latter, not all of them do, particularly when you go back a few versions.

For the time being you’ll probably want to use SVG filters depending on which browsers you do and don’t need to support. However, I suspect it won’t be long before you can safely use CSS filters whenever and wherever you want and now is a good time to learn how to work with them.

Download a free sample from my book, Design Fundamentals.

Leave a Reply

Your email address will not be published. Required fields are marked *