5

I have a few logos in my image assets. These logos will be imported into this website. Each logo has a random color (can be white, black, gray, red, blue, green, etc.) and has a transparent background. For example:

white logo black logo gray logo

The code below will be applied to display the logo on the website page:

.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background-color: white; /* can be changed */
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class="images">
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/tYjdM.png" class="magic-image" />
  </div>
</div>

The problem is when I make the background color of .magic-box white, then the white logo doesn't show up. When the background color is set to black, the black logo is not visible, and so on.

.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background-color: black; /* can be changed */ 
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class="images">
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/tYjdM.png" class="magic-image" />
  </div>
</div>

How to determine the most appropriate background-color to be applied to every .magic-box element programmatically?

Note: Background color can be different for each image

Jordy
  • 1,802
  • 2
  • 6
  • 25
  • 2
    pink........... – DCR Apr 11 '23 at 18:31
  • @DCR If I have a pink logo, it is not visible too. – Jordy Apr 11 '23 at 18:34
  • there is some inline colorimetrics tools for this, or you can learn it with Johannes Itten books. – Mister Jojo Apr 11 '23 at 18:34
  • @MisterJojo Can colorimetrics be applied by code? – Jordy Apr 11 '23 at 18:37
  • these tools are made with js code, so this is possible. – Mister Jojo Apr 11 '23 at 18:38
  • You won’t be able to do this using CSS alone. If you really need to do it automatically you could draw each logo on a canvas in order to pick up its color and then inspect all the colors and choose one which differs sufficiently from all of them (of course, there may not be such a thing in theory). – A Haworth Apr 11 '23 at 21:04
  • @AHaworth No matter if it can't be solved with only CSS, JS code implementation is accepted too. Those logos are not the result of an image, but an existing logo. It can contain text or an icon with a solid color. – Jordy Apr 12 '23 at 00:26
  • Given a random set of logos, how will you know when a suitable background has been found? I.e. what’s the formula. Are you looking for some color where the contrast with all the logos is the same? – A Haworth Apr 12 '23 at 06:28
  • @AHaworth The background color/contrast of each logo can be different if possible. How can I detect background color based on logo color? – Jordy Apr 12 '23 at 06:43
  • Ah, that’s much easier. Draw the logo on a canvas, loop through finding the first non transparent byte. Find its color. Then do something simple like invert it, or more complex look up how eg WCAG defines a suitable contrast. – A Haworth Apr 12 '23 at 06:48
  • @AHaworth The logo is not the result of a drawing, but it is existing images like [this](https://github.com/pigment/fake-logos). – Jordy Apr 12 '23 at 07:04
  • I am talking about drawing the image onto an HTML canvas - I am not understanding why this is a problem. – A Haworth Apr 12 '23 at 07:05
  • Oh, I am sorry for the misunderstanding. Can illustrate your meaning with a working fiddle? – Jordy Apr 12 '23 at 07:11
  • 1
    Will this answer your question ? https://stackoverflow.com/questions/1855884/determine-font-color-based-on-background-color – Debug Diva Apr 14 '23 at 06:27
  • Give the image a descriptive name, read the image name from the `scr` attribute and based on that set the background color. Or, since the logo is just text, don't use an image, just do it in HTML/Js. – RDU Apr 20 '23 at 07:11

6 Answers6

4

1. JS: add background color according to average brightness

This approach renders the image to a 1x1 px <canvas> to retrieve the average luminance/brightness to find an appropriate background color.

function adjustBG(image, grayscale = true) {
    // try to fix CORS issues
    image.crossOrigin = "anonymous";

    // draw image on canvas
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    let img = new Image();

    img.src = image.src;
    img.crossOrigin = "anonymous"; // ADDED
    img.onload = function () {
        canvas.width = 1;
        canvas.height = 1;
        ctx.imageSmoothingEnabled = true;
        ctx.drawImage(img, 0, 0, 1, 1);

        // calculate average color form 1x1 px canvas
        let color = ctx.getImageData(0, 0, 1, 1).data;
        let [r, g, b, a] = [color[0], color[1], color[2], color[3]];

        // calculate relative luminance
        let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
        let contrast = +((255 - luminance) / 255).toFixed(2);

        let bg = [255 - luminance, 255 - luminance, 255 - luminance].map(
            (val) => {
                return +val.toFixed(0);
            }
        );
        let filters = [];

        // optional convert all to grayscale
        if (grayscale) {
            filters.push(`grayscale(1)`);
        }

        // add background color if image is very bright
        if (luminance > 160 && contrast < 0.5) {
            //console.log(bg, luminance)
            image.style.backgroundColor = `rgb(${bg.join(",")})`;
        } else {
            image.style.backgroundColor = `rgb(255,255,255)`;
        }

        // enhance contrast
        if (contrast < 0.5) {
            let newContrast = contrast ? 1/contrast : 1;
            filters.push(`contrast(${newContrast })`);
        }

        image.style.filter = filters.join(" ");
    };

let images = document.querySelectorAll("img");

addBGColor(true);

function revert() {
  images.forEach((img) => {
    img.style.removeProperty("background-color");
    img.style.removeProperty("filter");
  });
}

function addBGColor(grayscale = true) {
  images.forEach((img) => {
    adjustBG(img, grayscale);
  });
}

function adjustBG(image, grayscale = true) {
  // try to fix CORS issues
  image.crossOrigin = "anonymous";

  // draw image on canvas
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  let img = new Image();

  img.src = image.src;
  img.crossOrigin = "anonymous"; // ADDED
  img.onload = function() {
    canvas.width = 1;
    canvas.height = 1;
    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(img, 0, 0, 1, 1);

    // calculate average color form 1x1 px canvas
    let color = ctx.getImageData(0, 0, 1, 1).data;
    let [r, g, b, a] = [color[0], color[1], color[2], color[3]];

    // calculate relative luminance
    let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
    let contrast = +((255 - luminance) / 255).toFixed(2);

    let bg = [255 - luminance, 255 - luminance, 255 - luminance].map(
      (val) => {
        return +val.toFixed(0);
      }
    );
    let filters = [];

    // optional convert all to grayscale
    if (grayscale) {
      filters.push(`grayscale(1)`);
    }

    // add background color if image is very bright
    if (luminance > 160 && contrast < 0.5) {
      //console.log(bg, luminance)
      image.style.backgroundColor = `rgb(${bg.join(",")})`;
    } else {
      image.style.backgroundColor = `rgb(255,255,255)`;
    }

    // enhance contrast
    if (contrast < 0.5) {
      let newContrast = contrast ? 1 / contrast : 1;
      filters.push(`contrast(${newContrast })`);
    }

    image.style.filter = filters.join(" ");
  };
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}
<p><button onclick="revert()">revert</button> <button onclick="addBGColor(true)">Add BG color (grayscale)</button> <button onclick="addBGColor(false)">Add BG color (no change)</button></p>


<div class="logos">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">


  <img src="https://i.imgur.com/qO6ZdET.png" crossorigin="anonymous" data-contrast="0.69" data-bg="175,175,175" style="background-color: rgb(175, 175, 175);">



</div>

Rene van der Lende raised some important points in the comments:

  • you might run into CORS issues, so you should add image.crossOrigin = 'anonymous'
  • the previous version had an error in the contrast calculation
  • the above example provides a very inaccurate average color calculation

1.2 Advanced average color calculation

Based on Rene van der Lende's comment I've modified the averagecolor.js script and added some features.

The following snippet will also check for transparency in an image as well as grayscale-only color range.

However, this script is significantly slower to to the more detailed color detection.

let images = document.querySelectorAll("img");

function revert() {
    images.forEach((img) => {
        img.style.removeProperty('background-color');
        img.style.removeProperty('filter');
    });
}



function addBGColor(options = { grayscale: true, complementraryColor: false, enhanceContrast: true }) {
    images.forEach((img) => {
        adjustBG(img, options);
    });
}


function adjustBG(image, options = { grayscale: true, complementraryColor: false, enhanceContrast: true }) {


    let grayscale = options.grayscale;
    let complementraryColor = options.complementraryColor;
    let enhanceContrast = options.enhanceContrast;

    // try to fix CORS issues 
    image.crossOrigin = 'anonymous';

    image.addEventListener('load', e=>{

        // check transparency
        let hasTransparency = checkImgTransparency(image);
        let isGray = checkImgGrayScale(image);

        // skip opaque images
        if (!hasTransparency) {
            console.log('nothing to do', image.src);
            return false
        }

        // get average color based on flattened transparency
        let { r, g, b, a, colors } = getAverageColor(image, 24, 24, false);

        // calculate relative luminance
        let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
        let contrast = +((255 - luminance) / 255).toFixed(2);
        let bg = [255 - luminance, 255 - luminance, 255 - luminance];

        // calculate complementary color
        let colComplemantary = [255 - r, 255 - g, 255 - b];
        let filters = [];

        // optional convert all to grayscale
        if (grayscale && !isGray) {
            filters.push(`grayscale(1)`)
        }

        // add background color if image is very bright
        let contrastAdjust = 1 + (luminance / 255);
        let colorBG = !complementraryColor ? 'rgb(255,255,255)' : `rgb(${colComplemantary.join(',')})`;

        image.setAttribute('data-contrast', contrast);
        image.setAttribute('data-bg', bg.join(','));

        // almost white
        if (luminance > 170 && contrast < 0.5) {
            colorBG = `rgb(${bg.join(',')})`;
        }

        // enhance contrast
        if (enhanceContrast && contrast < 0.5) {
            let newContrast = contrast ? 1/contrast : 1;
            filters.push(`contrast(${newContrast })`);
        }

        // apply styles
        image.style.backgroundColor = colorBG;
        image.style.filter = filters.join(' ');

    })


    // if image is ready loaded
    let isloaded = image.complete;
    if (isloaded) {
        image.dispatchEvent(new Event('load'));
    } 

}




/**
 * based on 
 * https://matkl.github.io/average-color/average-color.js
 */


function getAverageColor(img, width=24, height=24, flattenTransparency = false) {

  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;
  canvas.width = width;
  canvas.height = height;

  //document.body.appendChild(canvas)

  // flatten transparency
  if (flattenTransparency) {
    //add rect
    ctx.fillStyle = "rgb(255,255, 255)";
    ctx.fillRect(0, 0, width, height);
  }

  ctx.drawImage(img, 0, 0, width, height);
  let imageData = ctx.getImageData(0, 0, width, height);
  let data = imageData.data;
  let [rT, gT, bT, aT] = [0, 0, 0, 0];
  let colLength = data.length/4;

  // get colors
  let colors = [];
  for (let i = 0; i < data.length; i += 4) {

    r = data[i];
    g = data[i + 1];
    b = data[i + 2];
    a = data[i + 3];

    // exclude transparent colors
    if(a>128){
      rT += r;
      gT += g;
      bT += b;
      aT += a;
    } else{
      colLength--;
    }

    // count colors
    let colStr = [r, g, b].join('_');
   colors.push(colStr)
    
  }

  // calculate average color
  rT = Math.floor(rT / colLength);
  gT = Math.floor(gT / colLength);
  bT = Math.floor(bT / colLength);
  aT = Math.floor(aT / colLength);

  // remove duplicates
  colors = [...new Set(colors)];

  return { r: rT, g: gT, b: bT, a: aT , colors: colors.length};
}



function colorIsgrayscale(r, g, b, tolerance = 0.25) {
  let isGray = false;
  let rT = +(r * tolerance).toFixed(0);
  let gT = +(g * tolerance).toFixed(0);
  let bT = +(b * tolerance).toFixed(0);

  let colorAverage = (rT + gT + bT) / 3;
  if (colorAverage == rT && colorAverage == gT && colorAverage == bT) {
    isGray = true;
  }
  return isGray;
}


function checkImgGrayScale(img, tolerance = 0.9) {
  let isGrayscale = false;
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;
  let [w, h] = [8, 8];

  ctx.drawImage(img, 0, 0, w, h);
  let imageData = ctx.getImageData(0, 0, w, h);
  let data = imageData.data;
  let gray = 0;

  for (let i = 0; i < data.length; i += 4) {
    let r = data[i];
    let g = data[i + 1];
    let b = data[i + 2];
    let isGray = colorIsgrayscale(r, g, b);
    if(isGray){
      gray++;
    }
  }

  if(gray===data.length/4){
    isGrayscale = true;
  }
  return isGrayscale;
}


function checkImgTransparency(img) {
  let canvas = document.createElement('canvas');
  let ctx = canvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;

  ctx.drawImage(img, 0, 0, 3, 3);
  let imageData = ctx.getImageData(0, 0, 2, 2);
  let data = imageData.data;
  let hasAlpha = data[3] < 255 ? true : false;

  return hasAlpha;
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}
<p><button onclick="revert()">revert</button>
   <button onclick="addBGColor({grayscale: true, complementraryColor: false, enhanceContrast: false})">Add BG color (grayscale)</button> <button onclick="addBGColor({grayscale: false, complementraryColor: true, enhanceContrast: false})">Add BG color (complementary color)</button></p>

<div class="logos">

  
  <img alt="logo white" src="https://i.postimg.cc/FzFQ3n3D/b7yHv.png" class="magic-image" />

  <img alt="logo black" src="https://i.postimg.cc/J0TCqcQm/IhCH1.png" class="magic-image" />

  
  <img src="https://i.imgur.com/qO6ZdET.png" >
  
  

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">

</div>

2. JS: enhance contrast by applying CSS filters

Similar to the first approach, we're analyzing the contrast and brightness of the image by rendering a <canvas> element to calculate appropriate filter property values to enhance contrast(2), or inverted very bright colors colors via invert(1)

let images = document.querySelectorAll("img");

function fixImgColors() {
  images.forEach((img) => {
    adjustLightColors(img);
  });
}

function revert() {
    images.forEach((img) => {
        img.style.removeProperty('background-color');
        img.style.removeProperty('filter');
    });
}

function adjustLightColors(image) {
  // draw image on canvas 
  let canvas = document.createElement("canvas");
  let ctx = canvas.getContext("2d");
  let img = new Image();
  img.src = image.src;
  img.onload = function() {
    canvas.width = 1;
    canvas.height = 1;
    ctx.imageSmoothingEnabled = true;
    ctx.drawImage(img, 0, 0, 1, 1);

    // calculate average color form 1x1 px canvas
    let color = ctx.getImageData(0, 0, 1, 1).data;
    let [r, g, b] = [color[0], color[1], color[2]];

    // calculate relative luminance
    let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    // invert if image is very bright
    let filterInvert = luminance > 128 ? 1 : 0;
    let contrast = Math.ceil(1 + (luminance / 255));
    image.style.filter = `invert(${filterInvert}) grayscale(1) contrast(${contrast}`;
  };
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}
<p><button onclick="revert()">revert</button>   <button onclick="fixImgColors()">Adjust colors</button></p>

<div class="logos">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">

</div>


<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style="position:absolute">
    <defs>
      <filter id='fillBlack' filterUnits='userSpaceOnUse'>
        <feFlood flood-color='#000' result='flood' />
        <feComposite in='flood' in2='SourceAlpha' operator='in' />
      </filter>
    </defs>
  </svg>

3. CSS only: enhance contrast by applying svg filter

This approach is based on a SVG flood filter

Inline a hidden svg to your HTML body:

  <svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style="position:absolute">
    <defs>
      <filter id='fillBlack' filterUnits='userSpaceOnUse'>
        <feFlood flood-color='#000' result='flood' />
        <feComposite in='flood' in2='SourceAlpha' operator='in' />
      </filter>
    </defs>
  </svg>

and reference this filter by defining a CSS class.

  .fillBlack {
      filter: url('#fillBlack');
    }

Can't we use a dataURL?

Unfortunately chromium currently struggles with some filters, when embedded as dataURLs.

In Firefox you could also use this:

.fillBlack {
  filter: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><filter id='fillBlack' filterUnits='userSpaceOnUse'><feFlood flood-color='black' result='flood'/><feComposite in='flood' in2='SourceAlpha' operator='in'/></filter></svg>#fillBlack");
}

let images = document.querySelectorAll("img");

function applySvgFilter() {
  images.forEach((img) => {
    img.classList.add('fillBlack');
  });
}

function revert() {
  images.forEach((img) => {
    img.classList.remove('fillBlack');
  });
}
body {
  background: #eee;
}

img {
  width: 10em;
  margin: 1em;
}

.fillBlack {
  filter: url('#fillBlack');
}
<p><button onclick="revert()">revert</button> <button onclick="applySvgFilter()">Apply svg filter</button></p>


<div class="logos">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='20' text-anchor='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 0)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 0, 0)'>LO<tspan fill='rgb(255, 0, 0)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 0)'>LO<tspan fill='rgb(255, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 0, 255)'>LO<tspan fill='rgb(128, 128, 128)'>GO</tspan><tspan fill='rgba(0, 255, 255, 0.4)'>*</tspan></text></svg>">

  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 0)'>LO<tspan fill='rgb(255, 255, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 1)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(255, 255, 255)'>LO<tspan fill='rgb(128, 128, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.5)'>*</tspan></text></svg>">


  <img src="data:image/svg+xml, <svg width='200' height='100' viewBox='0 0 200 100' xmlns='http://www.w3.org/2000/svg'><text x='100' y='50' font-size='60' dy='5' text-anchor='middle' dominant-baseline='middle' font-family='sans-serif' font-weight='bold' fill='rgb(0, 255, 255)'>LO<tspan fill='rgb(255, 200, 255)'>GO</tspan><tspan fill='rgba(255, 255, 255, 0.75)'>*</tspan></text></svg>">

</div>

<!-- hidden svg filter definition -->
<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' style="position:absolute">
    <defs>
      <filter id='fillBlack' filterUnits='userSpaceOnUse'>
        <feFlood flood-color='#000' result='flood' />
        <feComposite in='flood' in2='SourceAlpha' operator='in' />
      </filter>
    </defs>
  </svg>

(The javaScript in the example above just helps to illustrate the "before/after" effect).

herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
  • 1
    I like your solutions, but method 1. seems flawed. Using this [image](https://i.postimg.cc/tg28S2VV/pngegg5.png) with ` it shows a single white pixel, while the calculated average color, looping through all the pixels, is rgb(47, 63, 74), a very dark desaturated blue (colorhexa.com). Probably due to default HTML scaling from origin 50% 50%. In your case. if the image center is largely one color (`transparent` in the above) then the average too. Not sure what is happening using method 1. here on [Codepen](https://codepen.io/renevanderlende/pen/KKGNvjj). – Rene van der Lende Apr 20 '23 at 15:31
  • I used [This](https://matkl.github.io/average-color/) to get the average color... ([average-color.js](https://matkl.github.io/average-color/average-color.js)) – Rene van der Lende Apr 20 '23 at 15:34
  • I like the first method, but it breaks [this image](https://i.imgur.com/qO6ZdET.png). Can you fix it? – Jordy Apr 21 '23 at 08:05
  • 1
    @Jordy: pardon me - took me a while to detect my stupid flaw: I've revised the snippet and added a more advanced script (adding new features). The main problem with the previous script was an error in the contrast calculation (white was 1 and black 0 - but it should be the other way round). – herrstrietzel Apr 22 '23 at 19:04
  • @Rene van der Lende: thank you so much for your feedback. I've finally got it working and added an additional script approach based on average-color.js (but I've added some features like alpha detection, transparency flattening etc.). Although the average color calculation is very inaccurate, it now works to decide whether black or white is more suitable. (The contrast calculation was also wrong) See also revised [codepen](https://codepen.io/herrstrietzel/pen/MWPbEEy) – herrstrietzel Apr 22 '23 at 19:11
  • I'm still not done with this, not specifically regarding your solution, though. Did you notice that OP mentioned in the first parag. *a random color (can be white, black, gray, red, blue, green, etc.)* and in the parag. after the first snippet mentions *When the background color is set to black, the black logo is not visible, and so on.* This made me assume that the BG should be *any* color. For me the only option seems converting the colors to HSL, average them and add 180deg (to get complementary color) and use the result as BG color. Avg RGB vs. HSL yields a massive difference. – Rene van der Lende Apr 23 '23 at 13:49
  • Averaging 3 pixels *pure red, green and blue* yields `rgb(85,85,85)` (*very dark gray*) and `hsl(120,100%,50%)` (*HTML lime*). Two very different colors as with HSL only the hue changes. While seemingly more correct, the HSL approach using `imgData` RGB values results in a slow process having to convert each pixel to HSL first. As I said: just pondering/tinkering. I'll get back with a demo when done. Modded my [SO/a/76023635](https://codepen.io/renevanderlende/pen/KKGNvjj) with 3 pixel images and your corrections. – Rene van der Lende Apr 23 '23 at 13:49
2

If you already know the color of the logo, you can use colord to calculate the contrast between the logo and any given color. You can then loop the comparison, adjusting the background color, until you reach a usable value.

Keep in mind you'll need to use the a11y plugin for access to .contrast()

Something like:

let contrast = 0;
let backgroundColor = logoColor;
const logoBrightness = colord(logoColor).brightness()
while (contrast < 4.5) {
  contrast = colord(logoColor).contrast(backgroundColor);
  if (logoBrightness <= 0.5) { 
      backgroundColor = colord(backgroundColor).lighten(0.05);
  } else backgroundColor = colord(backgroundColor).darken(0.05);
}

You can replace 4.5 with any amount of contrast you desire. How you apply that value to the element's backgroundColor will depend on your frameworks and preferences.

Slbox
  • 10,957
  • 15
  • 54
  • 106
2

1.) Add shadow

I know it may be a simple and perhaps silly solution, but adding a drop-shadow can also help with the problem. Every logo will be visible. Of course, it depends on your website whether this fits into the design.

.magic-image {
  filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
  -webkit-filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
}

@media (prefers-color-scheme: dark) {
  .magic-image {
    filter: drop-shadow(0px 0px 7px rgba(255, 255, 255, 0.4));
    -webkit-filter: drop-shadow(0px 0px 7px rgba(255, 255, 255, 0.4));
  }
}

Added to your example:

.magic-box {
  width: 200px;
  height: 100px;
  border: 1px solid black;
  position: relative;
  border-radius: 20px;
  background-color: white; /* can be changed */
}

.magic-image {
  max-height: 100%;
  max-width: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
  -webkit-filter: drop-shadow(0px 0px 7px rgba(10, 12, 15, 0.4));
}

@media (prefers-color-scheme: dark) {

  body,
  .magic-box {
    background-color: #000;
  }

  .magic-image {
    filter: drop-shadow(0px 0px 12px rgba(255, 255, 255, 0.6));
    -webkit-filter: drop-shadow(0px 0px 12px rgba(255, 255, 255, 0.6));
  }
}

.images {
  display: flex;
}
<div class="images">
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/tYjdM.png" class="magic-image" />
  </div>
</div>

2.) Use grayscale and/or brightness

On many websites, logos are displayed in black and white for this very reason. Whether this is a good or bad solution depends on your own design. In any case, the idea is there.

By decreasing the brightness, you can make the white parts in the image appear grayer, so that all your images will look perfect on a white background. Grayscale is optional, but for a more consistent look - so that the whitening of elements is not noticeable - I recommend using it.

.magic-image {
  filter: brightness(50%) grayscale(50%);
  -webkit-filter: brightness(50%) grayscale(50%);
}

Added to your example:

.magic-box {
  width: 200px;
  height: 100px;
  border: 1px solid black;
  position: relative;
  border-radius: 20px;
  background-color: white; /* can be changed */
}

.magic-image {
  max-height: 100%;
  max-width: 100%;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  filter: brightness(50%) grayscale(50%);
  -webkit-filter: brightness(50%) grayscale(50%);
}

.images {
  display: flex;
}
<div class="images">
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/tYjdM.png" class="magic-image" />
  </div>
</div>
rozsazoltan
  • 2,831
  • 2
  • 7
  • 20
2

The simple answer might be to use a gradient. It's not perfect, but it's simple and doesn't require a lot of code.

.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background-image: linear-gradient(white, black);
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class="images">
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/tYjdM.png" class="magic-image" />
  </div>
</div>

But of course that doesn't look fantastic, and a little bland.


Perhaps something like this which can take a color input and generate the opposite color which will always have contrast. (Code from this SO question)

However, the only assumption I'm making here is that the logos will always be solid colored and that you'd have the ability to output that color HEX code somewhere on the element. Otherwise some more complicated image analysis code would need to be written.

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

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

//-------

document.querySelectorAll('.magic-box').forEach(box => {
  const img = box.querySelector('.magic-image');
  const logoColor = img.getAttribute('data-color');
  
  box.style.backgroundColor = invertColor(logoColor, true);
  
});
.magic-box {
    width: 200px;
    height: 100px;
    border: 1px solid black;
    position: relative;
    border-radius: 20px;
    background: #FFF;
}

.magic-image {
    max-height: 100%;
    max-width: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
}

.images {
  display: flex;
}
<div class="images">
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/b7yHv.png" class="magic-image" data-color="#FFFFFF" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/IhCH1.png" class="magic-image" data-color="#000000" />
  </div>
  <div class="magic-box">
    <img src="https://i.stack.imgur.com/tYjdM.png" class="magic-image" data-color="#808080" />
  </div>
</div>
Chris Barr
  • 29,851
  • 23
  • 95
  • 135
2

This looks like a time you will want to check the contrast between the logo and the background. Here is an color accessibility checker which makes it easier to see when two colors are sufficiently contrasting.

There are some other topics on SO explaining how to calculate contrast in javascript: How to programmatically calculate the contrast ratio between two colors?

You could get the HSL values of the colors and then adjust the lightness on the background so that it is the furthest distance from the logo lightness.

ie: let backgroundLightness = (logoLightness + 50) % 100 if the logo had lightness 20, the background could be gray with lightness 70 hsl(0,0,70)

bristweb
  • 948
  • 14
  • 14
-1

@jordy

I dont think that one background color is suiable for all logos because white is bright and grey, black is darker.You have to chose min two or max three background colors for these logos.

Try this for selecting colors. https://coolors.co/contrast-checker

ashirhusaain
  • 255
  • 2
  • 6