How can I generate the opposite color according to current color?

Viewed 91314

I'm trying to create a color opposite of current color. I mean if current color is black, then I need to generate white.

Actually I have a text (the color of this text is dynamic, its color can be made at random). That text is into a div and I need to set the opposite color of that text for the background-color of div. I would like that text be clear in the div (color perspective).

The opposite color means: Dark / Bright

I have the current color of text and I can pass it to this function:

var TextColor = #F0F0F0;    // for example (it is a bright color)

function create_opp_color(current color) {

    // create opposite color according to current color

}

create_opp_color(TextColor); // this should be something like "#202020" (or a dark color)

Is there any idea to create create_opp_color() function?

13 Answers

Simple and elegant.

function invertHex(hex) {
  return (Number(`0x1${hex}`) ^ 0xFFFFFF).toString(16).substr(1).toUpperCase()
}

invertHex('00FF00'); // Returns FF00FF

Pure CSS implementation of @Onur's answer bw part.

  <input type="color" oninput="['--r','--g','--b'].forEach((k,i)=>this.nextElementSibling.style.setProperty(k,parseInt(event.target.value.slice(1+i*2,3+i*2),16)))" />
 
  <div style="--r: 0; --g: 0; --b: 0; --c: calc(-1 * ((var(--r) * 0.299 + var(--g) * 0.587 + var(--b) * 0.114) - 186) * 255)">
    <div style="background-color: rgb(var(--r), var(--g), var(--b)); color: rgb(var(--c), var(--c), var(--c))">Test</div>
  </div>

How about, CSS filter: invert(1), it has a decent cross-browser compatibility and it work with text and images or whatever your content is.

For a black and white inverted color add some more filters filter: saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1)

const div = document.querySelector("div");
const b = document.querySelector("b");
const input = document.querySelector("input");

input.oninput = (e) => {
  const color = e.target.value;
  div.style.background = color;
  b.style.color = color;
  b.innerText = color;
}
body {
  font-family: Arial;
  background: #333;
}

div {
  position: relative;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  min-width: 100px;
  padding: .5em 1em;
  border: 2px solid #FFF;
  border-radius: 15px;
  background: #378ad3;
  font-style: normal;
}

b {
  /* Inverting the color */
  /* ᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯᐯ */
  filter: saturate(0) grayscale(1) brightness(.7) contrast(1000%) invert(1);
}

input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
<div>
  <b>#378ad3</b>
  <input type="color" value="#378ad3"/>
</div>

This is a simple function that invert an hexadecimal color

const invertColor = (col) => {
col = col.toLowerCase();
  const colors = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
  let inverseColor = '#'
  col.replace('#','').split('').forEach(i => {
    const index = colors.indexOf(i)
    inverseColor += colors.reverse()[index]
  })
  return inverseColor
}

Codepen example

Simply flipping background color to text color won't work with some middle range values, e.g. 0x808080. I had tried with shifting the color values instead - (v + 0x80) % 0x100. See a demo here.

Agreeing with the comment from miguel-svq - although expecting to see more detailed algorithms for each calculation step.

Function to Invert Color of Element. Gets the luminosity of each and if they are close, inverts text color.

function adjustColor(element) {
    var style = window.getComputedStyle(element);
    var background = new Color(style['background-color']);
    var text = new Color(style['color']);
    if (Math.abs(background.luma - text.luma) < 100) {
        element.style.color = text.inverted.toString();
    }
}

The Color "Class" below. Accepts hex, rgb, rgba (even with percents), and can output to either one as well. Explorer will need polyfills for String.padStart and String.startsWith and the interpolated string in the toString() method will need to be modified using concat instead.

const Color = (function () {
    function toHex(num, padding) { return num.toString(16).padStart(padding || 2); }
    function parsePart(value) {
        var perc = value.lastIndexOf('%');
        return perc < 0 ? value : value.substr(0, perc);
    }
    function Color(data) {
        if (arguments.length > 1) {
            this[0] = arguments[0];
            this[1] = arguments[1];
            this[2] = arguments[2];
            if (arguments.length > 3) { this[3] = arguments[3]; }
        } else if (data instanceof Color || Array.isArray(data)) {
            this[0] = data[0];
            this[1] = data[1];
            this[2] = data[2];
            this[3] = data[3];
        } else if (typeof data === 'string') {
            data = data.trim();
            if (data[0] === "#") {
                switch (data.length) {
                    case 4:
                        this[0] = parseInt(data[1], 16); this[0] = (this[0] << 4) | this[0];
                        this[1] = parseInt(data[2], 16); this[1] = (this[1] << 4) | this[1];
                        this[2] = parseInt(data[3], 16); this[2] = (this[2] << 4) | this[2];
                        break;
                    case 9:
                        this[3] = parseInt(data.substr(7, 2), 16);
                    //Fall Through
                    case 7:
                        this[0] = parseInt(data.substr(1, 2), 16);
                        this[1] = parseInt(data.substr(3, 2), 16);
                        this[2] = parseInt(data.substr(5, 2), 16);
                        break;
                }
            } else if (data.startsWith("rgb")) {
                var parts = data.substr(data[3] === "a" ? 5 : 4, data.length - (data[3] === "a" ? 6 : 5)).split(',');
                this.r = parsePart(parts[0]);
                this.g = parsePart(parts[1]);
                this.b = parsePart(parts[2]);
                if (parts.length > 3) { this.a = parsePart(parts[3]); }
            }
        }
    }
    Color.prototype = {
        constructor: Color,
        0: 255,
        1: 255,
        2: 255,
        3: 255,
        get r() { return this[0]; },
        set r(value) { this[0] = value == null ? 0 : Math.max(Math.min(parseInt(value), 255), 0); },
        get g() { return this[1]; },
        set g(value) { this[1] = value == null ? 0 : Math.max(Math.min(parseInt(value), 255), 0); },
        get b() { return this[2]; },
        set b(value) { this[2] = value == null ? 0 : Math.max(Math.min(parseInt(value), 255), 0); },
        get a() { return this[3] / 255; },
        set a(value) { this[3] = value == null ? 255 : Math.max(Math.min(value > 1 ? value : parseFloat(value) * 255, 255), 0); },
        get luma() { return .299 * this.r + .587 * this.g + .114 * this.b; },
        get inverted() { return new Color(255 - this[0], 255 - this[1], 255 - this[2], this[3]); },
        toString: function (option) {
            if (option === 16) {
                return '#' + toHex(this.r) + toHex(this.g) + toHex(this.b) + (this[3] === 255 ? '' : toHex(this[3]));
            } else if (option === '%') {
                if (this.a !== 1) {
                    return `rgba(${this.r / 255 * 100}%, ${this.b / 255 * 100}%, ${this.g / 255 * 100}%, ${this.a / 255})`;
                } else {
                    return `rgb(${this.r / 255 * 100}%, ${this.b / 255 * 100}%, ${this.g / 255 * 100})%`;
                }
            } else {
                if (this.a !== 1) {
                    return `rgba(${this.r}, ${this.b}, ${this.g}, ${this.a})`;
                } else {
                    return `rgb(${this.r}, ${this.b}, ${this.g})`;
                }
            }
        }
    };

    return Color;
}());

It is possible to convert a HEX color using the snippets

function invertColor(color) {
      return '#' + ("000000" + (0xFFFFFF ^ parseInt(color.substring(1),16)).toString(16)).slice(-6);
  }

Python alternatives of Onur's answer:

def hex_to_rgb(value):
    value = value.lstrip('#')
    lv = len(value)
    return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))

def invertColor(color, bw=False):
    # strip the # from the beginning
    color = color.lstrip('#')

    # convert the string into hex
    color = int(color, 16)

    # invert the three bytes
    # as good as substracting each of RGB component by 255(FF)
    comp_color = 0xFFFFFF ^ color

    # convert the color back to hex by prefixing a #
    comp_color = "#%06X" % comp_color

    rgb_color = hex_to_rgb(comp_color)
    
    if (bw):
        # http://stackoverflow.com/a/3943023/112731
        bw_value = rgb_color[0]*0.299 + rgb_color[0]*0.587 + rgb_color[0]*0.114
        if (bw_value>186):
            comp_color = "#FFFFFF"
        else:
            comp_color = "#000000"

    # return the result
    return comp_color

color = "#fffff1"

print invertColor(color, bw=True)

For Typescript lovers, here what I use:

invertHex(hex: string) {
  if (hex.indexOf('#') === 0) {
    hex = hex.slice(1);
  }

  if (hex.length != 6) {
    console.warn('Hex color must be six hex numbers in length.');
    return '#' + hex;
  }

  hex = hex.toUpperCase();
  const splitNum = hex.split('');
  let resultNum = '';
  const simpleNum = 'FEDCBA9876'.split('');
  const complexNum = {
    A: '5', B: '4', C: '3', D: '2', E: '1', F: '0'
  };

  for (let i = 0; i < 6; i++) {
    if (!isNaN(Number(splitNum[i]))) {
      resultNum += simpleNum[splitNum[i]];
    } else if (complexNum[splitNum[i]]) {
      resultNum += complexNum[splitNum[i]];
    } else {
      console.warn('Hex colors must only include hex numbers 0-9, and A-F');
      return '#' + hex;
    }
  }

  return '#' + resultNum;
}
Related