Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Revert a filter invert() CSS rule

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>
like image 984
Zenoo Avatar asked Dec 08 '22 15:12

Zenoo


2 Answers

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 .

like image 141
Temani Afif Avatar answered Dec 10 '22 10:12

Temani Afif


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>
like image 35
Brett DeWoody Avatar answered Dec 10 '22 11:12

Brett DeWoody