Search code examples
javascripthtmlcssimagecolors

How to determine the best background color for a div that contains an image?


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.sstatic.net/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.sstatic.net/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.sstatic.net/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.sstatic.net/b7yHv.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.sstatic.net/IhCH1.png" class="magic-image" />
  </div>
  <div class="magic-box">
    <img src="https://i.sstatic.net/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


Solution

  • 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).