8

I have a whole block of code where the CSS rule filter: invert(0.85) is applied.

Inside this block, I have an image, which of course also follows this CSS rule, and is inverted.

I need to revert this invert() for said image.

Is it possible? I tried invert(1), but the image isn't fully like it was before, it's still a little inverted (due to the first invert being only 0.85 and not 1)

See this example:

body{
  font-size: 0;
}

div{
  display: inline-block;
  width: 50%;
}

.filter{
  filter: invert(0.85);
}

.filter img{
  filter: invert(1);
}
<div class="initial">
  <img src="https://s7d1.scene7.com/is/image/PETCO/cat-category-090617-369w-269h-hero-cutout-d?fmt=png-alpha" alt="">
</div>
<div class="filter">
  <img src="https://s7d1.scene7.com/is/image/PETCO/cat-category-090617-369w-269h-hero-cutout-d?fmt=png-alpha" alt="">
</div>

6
  • unlike other filter or property i guess invert cannot be inverted Commented Feb 11, 2018 at 11:49
  • @TemaniAfif There must be a way to achieve that, somehow :x
    – Zenoo
    Commented Feb 11, 2018 at 12:06
  • i guess you need to combine other filter in order to obtain initial result. well am not experienced with filter :) .. but it's like you apply a matrix transformation ... you cannot simply invert the numbers, you need to invert the whole matrix which can be a bit tricky Commented Feb 11, 2018 at 12:18
  • any progress ? :) ... from my side i studied the other filter and not able to find an accurate thing. Commented Feb 17, 2018 at 14:03
  • @TemaniAfif I'm still trying to understand all this W3C documentation about filters ^^ It's pretty difficult. I'm not giving up though !
    – Zenoo
    Commented Feb 17, 2018 at 14:04

3 Answers 3

19
+50

TL;DR

You cannot revert the invert() filter by applying another invert() or a combination of other filters.


First, I am going to start with a basic example and an explanation: the invert() filter is used to invert the colors by specifying the percentage of the inversion (or a value from 0 to 1). If we use the value 1 we invert completely the colors thus it's easy to get back to initial state as we simply have to apply the same invert again:

.container {
 display:flex;
 justify-content:space-around;
}

.inner {
  height: 200px;
  width: 200px;
  background: 
  linear-gradient(to right, rgb(255,0,0) 50%, rgb(0,255,255) 0) 0 0/100% 50% no-repeat, 
  
  linear-gradient(to right, rgb(50,0,60) 50%, rgb(205,255,195) 0) 0 100%/100% 50% no-repeat;
}
<div class="container" style="filter:invert(1)">
  <div class="inner"></div>
  <div class="inner" style="filter:invert(1)"></div>
</div>

From this example we can also understand how the invert is working with colors. We simply do (255 - x) for each value of the RGB.

Now let's consider the invert with another value, let's take the 0.85:

.container {
  display: inline-block;
}

.inner {
  display: inline-block;
  height: 200px;
  width: 200px;
  background: linear-gradient(to right, rgb(255, 0, 0) 50%, rgb(0, 255, 255) 0) 0 0/100% 50% no-repeat, linear-gradient(to right, rgb(50, 0, 60) 50%, rgb(205, 255, 195) 0) 0 100%/100% 50% no-repeat;
}
<div class="container" style="filter:invert(0.85)">
  <div class="inner"></div>
</div>
<div class="inner"></div>

How does it work?

For the first color (rgb(255,0,0)) we obtain this (rgb(38, 217, 217)) so the calculation is done as follow:

255 - [255*(1-0.85) + x*(2*0.85-1)]

So our goal is to invert this formula:

f(x) = 255 - [255*(1-p) + x*(2*p-1)] , p a value in the range [0,1]

You may clearly notice that it's pretty easy when p=0 as we will have f(x)=x and when p=1 we will have f(x)=255-x. Now let's express the value of x using f(x) (I will use it as y here):

x = 1/(2*p-1) * [255 - (255*(1-p) +y)]

Let's try to make it similar to an invert function. Let's havey=(2*p-1)*y' and We will obtain:

x = 1/(2*p-1) * [255 - (255*(1-p) +(2*p-1)*y')]

which is equivalent to

x = 1/(2*p-1) * f(y') ---> x = K * f(K*y) with K = 1/(2*p-1)

Here f is an invert function using the same value of p and K is a constant calculated based on the value p. We can distinguish 3 situations:

  1. When the value of p is equal to 0.5 the function is no defined and thus the invert cannot be inverted (you can by the way try to apply invert(0.5) to see the result)
  2. When the value of p is greater than 0.5 K is a positive value in the range [1,+infinity].
  3. When the value of p is smaller than 0.5 K is a negative value in the range [-infinity,-1].

So if we omit the first case, K can be set in this way K=1/K' where K' is a value in the range [-1,1]/{0} and abs(K') is in the range of ]0,1]. Then our function can be written as follow:

x = (1/K') * f(y/K') where K' in the range [-1,1] define by (2*p - 1)

At this point we expressed our function with an invert function and a multiplication/division with a value that we can easily compute.

Now we need to find which filter apply a multiplication/division. I know there is the brightness and the contrast filter that uses linear transformation.

If we use brightness(p) the forumla is as follow:

f(x) = x*p;

And if we use contrast(p) the formula will look like this:

f(x) = p*(x - 255/2) + 255/2

This will end up here ...

As said intially we cannot revert invert() using other filters. We can probably approximate this for some values but for other it will impossible (like with 0.5). In other words, when applying the invert() we lose some information that we cannot get back. It's like when, for example, you get a coloured image that you transform into a black & white version. You have no way to put back the initial colors.

UPDATE

Here is some JS code to proove that the above calculation is correct and to see some results. I used canvas in order to draw the image again while applying my function:

Unfortunately I cannot use an image in the snippet for security and cross-browser origin issue so I considerd a gradient

var canvas = document.querySelector('canvas');
var img = document.querySelector('img');
var ctx = canvas.getContext('2d');
//we draw the same image on the canvas (here I will draw the same gradient )
//canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var grd = ctx.createLinearGradient(0, 0, 150, 0);
grd.addColorStop(0, "blue");
grd.addColorStop(1, "red");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 150, 150);

//we get the data of the image
var imgData = canvas.getContext('2d').getImageData(0, 0, 150, 150);
var pix = imgData.data;
var p = 0.85;
// Loop over each pixel and apply the function
for (var i = 0, n = pix.length; i < n; i += 4) {
  pix[i + 0] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 0])));
  pix[i + 1] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 1])))
  pix[i + 2] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 2])))
  // i+3 is alpha (the fourth element)
}
//Draw the image again
imgData.data = pix;
canvas.getContext('2d').putImageData(imgData, 0, 0);
body {
  font-size: 0;
}

div,
canvas {
  display: inline-block;
}

.grad {
  height: 150px;
  width: 150px;
  background: linear-gradient(to right, blue, red);
}

.filter {
  filter: invert(0.85);
}
<div class="grad"></div>
<div class="filter">
  <div class="grad"></div>
  <canvas></canvas>
</div>

As we can see we have 3 images: the original one, the inverted one and the one on where we applied our function to make it back to the original one.

We are having a good result here because the value is close to 1. If we use another value more close to 0.5 will have a bad result because we are close to the limit of where the function is defined:

var canvas = document.querySelector('canvas');
var img = document.querySelector('img');
var ctx = canvas.getContext('2d');
//we draw the same image on the canvas (here I will draw the same gradient )
//canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var grd = ctx.createLinearGradient(0, 0, 150, 0);
grd.addColorStop(0, "blue");
grd.addColorStop(1, "red");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 150, 150);

//we get the data of the image
var imgData = canvas.getContext('2d').getImageData(0, 0, 150, 150);
var pix = imgData.data;
var p = 0.6;
// Loop over each pixel and apply the function
for (var i = 0, n = pix.length; i < n; i += 4) {
  pix[i + 0] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 0])));
  pix[i + 1] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 1])))
  pix[i + 2] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 2])))
  // i+3 is alpha (the fourth element)
}
//Draw the image again
imgData.data = pix;
canvas.getContext('2d').putImageData(imgData, 0, 0);
body {
  font-size: 0;
}

div,
canvas {
  display: inline-block;
}

.grad {
  height: 150px;
  width: 150px;
  background: linear-gradient(to right, blue, red);
}

.filter {
  filter: invert(0.6);
}
<div class="grad"></div>
<div class="filter">
  <div class="grad"></div>
  <canvas></canvas>
</div>

You can use this code in order to create a generic function that will allow you to partially revert the invert() filter:

  1. Using some JS you can easily find the value of p used in the filter.
  2. You can use a specific selector to target only specific images on where you want to apply this logic.
  3. As you can see I created a canvas so the idea is to create it and then hide the image to keep only the canvas.

Here is a more interactive demo:

var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var init = function() {
  var grd = ctx.createLinearGradient(0, 0, 150, 0);
  grd.addColorStop(0, "blue");
  grd.addColorStop(1, "red");
  ctx.fillStyle = grd;
  ctx.fillRect(0, 0, 150, 100);
  var grd2 = ctx.createLinearGradient(0, 0, 0, 150);
  grd2.addColorStop(0, "green");
  grd2.addColorStop(1, "yellow");
  ctx.fillStyle = grd2;
  ctx.fillRect(40, 0, 70, 100);
}
var invert = function(p) {
  //we get the data of the image
  var imgData = canvas.getContext('2d').getImageData(0, 0, 150, 100);
  var pix = imgData.data;
  // Loop over each pixel and apply the function
  for (var i = 0, n = pix.length; i < n; i += 4) {
    pix[i + 0] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 0])));
    pix[i + 1] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 1])))
    pix[i + 2] = Math.round((1 / (2 * p - 1)) * (255 - (255 * (1 - p) + pix[i + 2])))
    // i+3 is alpha (the fourth element)
  }
  //Draw the image again
  imgData.data = pix;
  canvas.getContext('2d').putImageData(imgData, 0, 0);
}
init();
$('input').change(function() {
  var v = $(this).val();
  $('.filter').css('filter', 'invert(' + v + ')');
  init();
  invert(v);
})
p {
  margin: 0;
}

div,
canvas {
  display: inline-block;
}

.grad {
  height: 100px;
  width: 150px;
  background: linear-gradient(to bottom, green, yellow)40px 0/70px 100% no-repeat, linear-gradient(to right, blue, red);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="grad"></div>
<div class="filter">
  <div class="grad"></div>
  <canvas height="100" width="150"></canvas>
</div>
<p>Adjust the value of the invert filter</p>
<input type="range" value="0" min=0 max=1 step=0.05>

From this demo we can also notice that when we are close to the value 0.5 we don't have a good result simply because the filter cannot be inverted at 0.5 and if we refer to previous calculation, the value of K become very big and thus K' is too small .

13
  • 1
    Wow, that's an in-depth answer ^^. I'm still trying to wrap my head around the 255 - [255*(1-0.85) + x*(2*0.85-1)] part, I don't understand how you got that formula.
    – Zenoo
    Commented Feb 15, 2018 at 8:04
  • 1
    @Zenoo well i was helped with some internet search :) i worked before with image processing so i had to refresh my memory by reading few article to understand how the calculation is done and after a simplification i ended with this formula ;) Commented Feb 15, 2018 at 8:11
  • I looked at the W3C definition for the invert() filter, but I must say I still have no clue how you achieved that ^^
    – Zenoo
    Commented Feb 15, 2018 at 8:18
  • @Zenoo well if you insist :) you can see that the invert uses feComponentTransfer and you can read about this one [here] (w3.org/TR/filter-effects/#feComponentTransferElement) and if you continu reading you will end up by this feFunc* that is applied to the 3 colors using the [table type] (w3.org/TR/filter-effects/#valdef-type-table) --> with this you can have some clue ;) Commented Feb 15, 2018 at 8:33
  • 1
    @TemaniAfif Your reasoning is wrong in multiple ways, but they cancel out to arrive at the correct answer: that you can't invert an invert. The simple solution to the problem as you stated it would be to contrast(1/(1-2*p)) the image and then invert(p) the container; those two transforms compose to the identity transform. The real reason this doesn't work is that a. colors are clipped to [0..255] after each transform and b. invert always compresses the input range → the img transform (which precedes invert) has to expand the input range and therefore will run into clipping.
    – Iris Artin
    Commented Nov 16, 2021 at 4:44
3

You'll need to use a combination of other CSS filter()s to revert the changes. Depending on the initial filter() applied, this could be extremely difficult.

In this particular case, with only invert(0.85) being applied, we can try a few other filter()s to get the image back to normal.

Through some experimentation it appears the following will work:

filter: invert(1) contrast(1.3) saturate(1.4);

This is a rough guess, but visually seems to do the trick.

.flex {
  width: 100%;
  display: flex;
}

img {
  width: 100%;
  display: block;
}

.flex > div{
  flex: 1;
}

.filter {
  filter: invert(0.85);
}

.filter img {
  filter: invert(1) contrast(1.3) saturate(1.4);
}

p {
  color: red;
}
<div class="flex">
  <div class="initial">
    <p>Some red text</p>
    <img src="https://s7d1.scene7.com/is/image/PETCO/cat-category-090617-369w-269h-hero-cutout-d?fmt=png-alpha" alt="">
  </div>
  <div class="filter">
    <p>Some red text</p>
    <img src="https://s7d1.scene7.com/is/image/PETCO/cat-category-090617-369w-269h-hero-cutout-d?fmt=png-alpha" alt="">
  </div>
</div>

8
  • My question isn't about how to avoid filtering an image, but to revert the filter already applied to a block, only on some elements of this block. The .filter HAS the filter: invert() rule already applied. I need to revert this for some elements inside.
    – Zenoo
    Commented Feb 13, 2018 at 13:35
  • 1
    As pointed out, it's isn't possible to undo the invert. The closest I can imagine is using a series of other filters to get the image colors somewhat back to their initial, but it will be imprecise. For example, some combination of saturate, invert, etc. Commented Feb 13, 2018 at 13:37
  • No, the .filter class has to have the filter applied to it directly, since the DOM inside it is very complex, applying the invert on .filter * would screw up everything. If what @TemaniAfif said was correct, there must be a combination of other filters which would render as the initial image exactly.
    – Zenoo
    Commented Feb 13, 2018 at 13:40
  • "applying the invert on .filter * would screw up everything" - why? Commented Feb 13, 2018 at 13:42
  • 1
    Thanks for the approximation ! Is there any way to find the exact math behind all this? I'll go for your answer if there isn't, but I'd really like an exact match.
    – Zenoo
    Commented Feb 13, 2018 at 16:13
2

The other answers show the difficulty achieving this with CSS. Here is a basic javascript approach you may find useful. You detect if .filter contains an image, and if it does, remove the class filter.

var filteredDiv = document.querySelectorAll(".filter"),
  len = filteredDiv.length,
  i;

for (i = 0; i < len; i++) {
  if (filteredDiv[i].getElementsByTagName('img').length > 0) {
    filteredDiv[i].classList.remove('filter');
  }
}
body {
  font-size: 0;
}

.filter {
  filter: invert(.85);
}

div {
  display: inline-block;
  width: 200px;
}

img {
  width: 100%;
  height: auto;
}

div>div {
  background: yellow;
  font-size: 2rem;
}
<div class="filter">
  <img src="https://s7d1.scene7.com/is/image/PETCO/cat-category-090617-369w-269h-hero-cutout-d?fmt=png-alpha" alt="">
</div>
<div class="filter">
  <div>Sample text</div>
</div>

3
  • no sure this can be a solution because as commented in the other answer he doesn't want to remove the filter .. the filter should remain applied to the whole block and inside this block he want to revert in for only some element. Commented Feb 19, 2018 at 7:53
  • Thanks, but I can't use Javascript for this issue, plus, your code beats the purpose of the question. The filter has to stay on the block.
    – Zenoo
    Commented Feb 19, 2018 at 7:54
  • @Zenoo Fair enough. I would be surprised if you find a way to do this properly with CSS. Best of luck
    – sol
    Commented Feb 19, 2018 at 8:22

Not the answer you're looking for? Browse other questions tagged or ask your own question.