stack
stack

Reputation: 10218

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

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?

Upvotes: 142

Views: 125632

Answers (15)

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48600

Here's an easy way to invert a color:

const invertColor = (hexValue) => {
  let hex = hexValue.replace(/^#/, '');
  if (hex.length === 3) hex = hex.replace(/./g, '$&$&'); // Expand
  if (hex.length !== 6) throw new Error(`Invalid HEX color: ${hexValue}`);
  return `#${(0xFFFFFF ^ parseInt(hex, 16)).toString(16).padStart(6, '0')}`;
};
  1. Drop the leading #, if present
  2. Expand the value
  3. Check the length, and throw an error if invalid
  4. Run the parsed integer value through and XOR (^) and format it as a hex byte

const briteLvls = 7, colorBands = 6;

const main = () => {
  document.querySelector('#root').insertAdjacentHTML('beforeend', `
    <div class="container">
      ${range(briteLvls).map(briteLvl => `
        <div class="row">
          ${range(colorBands).map(colorBand => {
            const hue = scaleRatio(colorBand, colorBands, 0, 360, false);
            const lum = scaleRatio(briteLvl, briteLvls, 5, 95, true);
            const bgColor = hslToHex(`hsl(${hue}, 100%, ${lum}%)`);
            const fgColor = invertColor(bgColor);
            return `
              <div class="cell" style="background:${bgColor};color: ${fgColor}">
                ${bgColor}
              </div>
            `;
          }).join('')}
        </div>
      `).join('')}
    </div>
  `);
};

const invertColor = (hexValue) => {
  let hex = hexValue.replace(/^#/, '');
  if (hex.length === 3) hex = hex.replace(/./g, '$&$&'); // Expand
  if (hex.length !== 6) throw new Error(`Invalid HEX color: ${hexValue}`);
  return `#${(0xFFFFFF ^ parseInt(hex, 16)).toString(16).padStart(6, '0')}`;
};

const hslToHex = (hsl) => {
  const [h, s, l] = hsl.match(/\d+/g).map(Number);
  const sPercent = s / 100, lPercent = l / 100;
  const c = (1 - Math.abs(2 * lPercent - 1)) * sPercent;
  const x = c * (1 - Math.abs((h / 60) % 2 - 1));
  const m = lPercent - c / 2;
  return `#${(() => {
    if (  0 <= h && h <  60) return [c, x, 0];
    if ( 60 <= h && h < 120) return [x, c, 0];
    if (120 <= h && h < 180) return [0, c, x];
    if (180 <= h && h < 240) return [0, x, c];
    if (240 <= h && h < 300) return [x, 0, c];
    return [c, 0, x];
  })()
    .map(n => numToHexStr(clampMax(n + m, 255)))
    .join('')}`;
};

const scaleRatio = (n, m, min, max, inclusive) =>
  scaleValue(n / (m - (inclusive ? 1 : 0)), [0, 1], [min, max]);

const scaleValue = (value, [sMin, sMax], [tMin, tMax]) => {
    const scale = (tMax - tMin) / (sMax - sMin);
    const capped = Math.min(sMax, Math.max(sMin, value)) - sMin;
    return Math.floor(capped * scale + tMin);
};

const clampMax = (v, max) => clamp(v * max, 0, max);

const clamp = (v, min, max) => Math.min(Math.max(Math.floor(v), min), max);

const numToHexStr = (n) => Math.floor(n).toString(16).padStart(2, '0');

const range = (n) => Array.from({ length: n }).map((_, i) => i);

main();
body {
  display: flex;
  flex-direction: column;
}

.container {
  display: flex;
  flex-direction: column;
}

.row {
  display: flex;
  flex-direction: row;
}

.cell {
  min-width: 3.5rem;
  display: flex;
  align-items: center;
  padding: 0.25rem 0.5rem;
  font-family: monospace;
  text-transform: uppercase;
}
<div id="root"></div>

Upvotes: 0

Tushar Rathod Roy
Tushar Rathod Roy

Reputation: 27

function getContrastColor(color) {
  var hex = color.replace(/#/, '');
  var red = parseInt(hex.substr(0, 2), 16);
  var green = parseInt(hex.substr(2, 2), 16);
  var blue = parseInt(hex.substr(4, 2), 16);

  var luminance = (0.2126  red + 0.7152  green + 0.0722 * blue) / 255;
  return luminance > 0.5 ? '#000000' : '#ffffff';
}

var contrastColor = getContrastColor("#FFFFFF");
console.log(contrastColor);

Upvotes: 0

Fennec
Fennec

Reputation: 1872

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)

Here is a ColorPicker example (Notice the text color):

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

colorPicker.oninput = (e) => {
  const color = e.target.value;
  background.style.background = color;
  invertedText.style.color = color;
  invertedText.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;
}

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>

Upvotes: 14

Onur Yıldırım
Onur Yıldırım

Reputation: 33634

UPDATE: Production-ready code on GitHub.


This is how I'd do it:

  1. Convert HEX to RGB
  2. Invert the R,G and B components
  3. Convert each component back to HEX
  4. Pad each component with zeros and output.
function invertColor(hex) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    // invert color components
    var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16),
        g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16),
        b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16);
    // pad each with zeros and return
    return '#' + padZero(r) + padZero(g) + padZero(b);
}

function padZero(str, len) {
    len = len || 2;
    var zeros = new Array(len).join('0');
    return (zeros + str).slice(-len);
}

Example Output:

enter image description here

Advanced Version:

This has a bw option that will decide whether to invert to black or white; so you'll get more contrast which is generally better for the human eye.

function invertColor(hex, bw) {
    if (hex.indexOf('#') === 0) {
        hex = hex.slice(1);
    }
    // convert 3-digit hex to 6-digits.
    if (hex.length === 3) {
        hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
    }
    if (hex.length !== 6) {
        throw new Error('Invalid HEX color.');
    }
    var r = parseInt(hex.slice(0, 2), 16),
        g = parseInt(hex.slice(2, 4), 16),
        b = parseInt(hex.slice(4, 6), 16);
    if (bw) {
        // https://stackoverflow.com/a/3943023/112731
        return (r * 0.299 + g * 0.587 + b * 0.114) > 186
            ? '#000000'
            : '#FFFFFF';
    }
    // invert color components
    r = (255 - r).toString(16);
    g = (255 - g).toString(16);
    b = (255 - b).toString(16);
    // pad each with zeros and return
    return "#" + padZero(r) + padZero(g) + padZero(b);
}

Example Output:

enter image description here

Upvotes: 362

Supah
Supah

Reputation: 193

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

Upvotes: 3

Guray Celik
Guray Celik

Reputation: 1281

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)

Upvotes: 1

Mamunur Rashid
Mamunur Rashid

Reputation: 1185

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

Upvotes: 5

Jamesgt
Jamesgt

Reputation: 605

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>

Upvotes: 19

abd0991
abd0991

Reputation: 272

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

Upvotes: 0

Gerardlamo
Gerardlamo

Reputation: 1723

Simple and elegant.

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

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

Upvotes: 68

Derek Ziemba
Derek Ziemba

Reputation: 2643

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

Upvotes: 1

sigmaxf
sigmaxf

Reputation: 8482

Simple way to achieve this with CSS:

mix-blend-mode: difference;
color:white;

Upvotes: 33

jason_zhuyx
jason_zhuyx

Reputation: 21

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.

Upvotes: 2

DigiLive
DigiLive

Reputation: 1093

In my understanding of your question, by opposite color you mean inverted color.

InvertedColorComponent = 0xFF - ColorComponent

So for the color red (#FF0000) this means: R = 0xFF or 255 G = 0x00 or 0 B = 0x00 or 0

inverted color red (#00FFFF) is:

R = 0xFF - 0xFF = 0x00 or 255 - 255 = 0
G = 0xFF - 0x00 = 0xFF or 255 - 0 = 255
B = 0xFF - 0x00 = 0xFF or 255 - 0 = 255

Another examples:

Black (#000000) becomes White (#FFFFFF).

Orange (#FFA500) becomes #005AFF

Upvotes: 4

miguel-svq
miguel-svq

Reputation: 2176

Watch out Accesibility (AA/AAA). Colour contrast by itself is useless. Really different colors can have no contrast at all for colour blind people. IMHO a calculation for such a color could go like this:

(Use "HLS" for simplicity)

  • Rotate Hue 180º to get the (maybe useless) maximal color contrast
  • Calculate Brightness Difference.
  • ( Calculate Colour Difference... unnecesary, it's maximal or almost )
  • Calculate Contrast Ratio.
  • If the resulting color complies the requirements calculation ends, if not, loop:
    • If Brightness Difference is not enought increase or decrese calculated color luminosity (L) by a certain amount or ratio (up or down depending on the original colour brightness: > or < than the mid value)
    • Check if it complies your requirements, if it does calculation ends.
    • if luminosity can be increased (or decrased) any more there is no valid color to comply the requirements, just try black and white, take "the best one" of those (probably the one with bigger contrast ratio) and end.

Upvotes: 9

Related Questions