<!--  
SVG DUOTONE FILTERS
1. feColorMatrix to grayscale the image 
2. feComponentTransfer to calculate the RGB values of the two colors in the gradient map
   ex: #01283c == R(1) G(40) B(60). Divide each RGB value by 255 to get the matrix value

   <feFuncR type="table" tableValues="(1/255 = 0.003921568627) 0.007843137255"></feFuncR>
   <feFuncG type="table" tableValues="(40/255 = 0.1568627451) 0.3803921569"></feFuncG>
   <feFuncB type="table" tableValues="(60/255 = 0.2352941176) 0.5725490196"></feFuncB>
   <feFuncA type="table" tableValues="0 1"></feFuncA>
-->
<article class="box-image-duotone">
    <svg class="duotone-filters" xmlns="http://www.w3.org/2000/svg">	
        <filter id="duotone_1" class="duotone-filter" data-light-color="#0033B2" data-dark-color="#FFB500">
            <feColorMatrix type="matrix"  result="gray" values="
                    1 0 0 0 0
                    1 0 0 0 0
                    1 0 0 0 0
                    0 0 0 1 0">
            </feColorMatrix>

            <feComponentTransfer class="duotone-color-change" color-interpolation-filters="sRGB" result="duotone">
                <feFuncR class="duotone-red" type="table" tableValues=""></feFuncR>
                <feFuncG class="duotone-green" type="table" tableValues=""></feFuncG>
                <feFuncB class="duotone-blue" type="table" tableValues=""></feFuncB>
                <feFuncA type="table" tableValues="0 1"></feFuncA>
            </feComponentTransfer>
        </filter>
    </svg>
    <div class="box-image-duotone__image-container">
        <div class="box-image-duotone__image" style="background-image: url(https://images.unsplash.com/photo-1498206005704-36d87df55231?ixlib&#x3D;rb-0.3.5&amp;ixid&#x3D;eyJhcHBfaWQiOjEyMDd9&amp;s&#x3D;f85ad7268a727c31aae1ac78ef399677&amp;auto&#x3D;format&amp;fit&#x3D;crop&amp;w&#x3D;1986&amp;q&#x3D;80); filter: url(#duotone_1);"></div>
        <div class="box-image__content">
            <h2 class="heading heading--column ">Column header</h2>

            <p>Box Image Duotone content.</p>
        </div>
    </div>
</article>
<!--  
SVG DUOTONE FILTERS
1. feColorMatrix to grayscale the image 
2. feComponentTransfer to calculate the RGB values of the two colors in the gradient map
   ex: #01283c == R(1) G(40) B(60). Divide each RGB value by 255 to get the matrix value

   <feFuncR type="table" tableValues="(1/255 = 0.003921568627) 0.007843137255"></feFuncR>
   <feFuncG type="table" tableValues="(40/255 = 0.1568627451) 0.3803921569"></feFuncG>
   <feFuncB type="table" tableValues="(60/255 = 0.2352941176) 0.5725490196"></feFuncB>
   <feFuncA type="table" tableValues="0 1"></feFuncA>
-->
<article class="box-image-duotone">
    <svg class="duotone-filters" xmlns="http://www.w3.org/2000/svg">	
        <filter id="duotone_{{ boxImage.filter.id }}" class="duotone-filter" data-light-color="{{ boxImage.filter.colorLight }}" data-dark-color="{{ boxImage.filter.colorDark }}">
            <feColorMatrix type="matrix"  result="gray" values="
                    1 0 0 0 0
                    1 0 0 0 0
                    1 0 0 0 0
                    0 0 0 1 0">
            </feColorMatrix>

            <feComponentTransfer class="duotone-color-change" color-interpolation-filters="sRGB" result="duotone">
                <feFuncR class="duotone-red" type="table" tableValues=""></feFuncR>
                <feFuncG class="duotone-green" type="table" tableValues=""></feFuncG>
                <feFuncB class="duotone-blue" type="table" tableValues=""></feFuncB>
                <feFuncA type="table" tableValues="0 1"></feFuncA>
            </feComponentTransfer>
        </filter>
    </svg>
    <div class="box-image-duotone__image-container">
        <div class="box-image-duotone__image" style="background-image: url({{ boxImage.url }}); filter: url(#duotone_{{boxImage.filter.id}});"></div>
        <div class="box-image__content">
			{{ render '@heading--column' header }}
			{{#block "content"}}
				<p>{{ boxImage.content }}</p>
			{{/block}}
		</div>
    </div>
</article>
{
  "heading": {
    "text": "Box Image Duotone"
  },
  "boxImage": {
    "url": "https://images.unsplash.com/photo-1498206005704-36d87df55231?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=f85ad7268a727c31aae1ac78ef399677&auto=format&fit=crop&w=1986&q=80",
    "content": "Box Image Duotone content.",
    "filter": {
      "id": "1",
      "colorLight": "#0033B2",
      "colorDark": "#FFB500"
    }
  }
}
  • Content:
    /*
     * Module: Box Image
     */
    
    :root{
        --box-background-color: var(--color-white);
        --box-border-radius: 4px;
        --box-content-opacity: 25%;
        --box-min-height: 300px;
        --box-padding: 20px;
        --box-paragraph-spacing-top: 15px;
        --box-shadow: 2px 5px 15px 0 rgba(0,0,0, .15);
    }
    
    
    .duotone-filters{
        visibility: hidden;
        height: 0;
        display: block;
    }
    
    .box-image-duotone{
        position: relative;
        width: 100%;
        box-shadow: var(--box-shadow);
        &__image{
            position: relative;
            background-repeat: no-repeat;
            background-size: cover;
            min-height: var(--box-min-height);
            border-radius: var(--box-border-radius);
        }
    }
  • URL: /components/raw/box-image-duotone/box-image-duotone.css
  • Filesystem Path: src/02-molecules/box/box-image-duotone/box-image-duotone.css
  • Size: 681 Bytes
  • Content:
    const filterElements = document.getElementsByClassName(
      'duotone-filter',
    );
    const boxImages = document.getElementsByClassName(
      'box-image-duotone__image',
    );
    // IE fallback begins if statement. Does not support grayscale yet
    if (isIE()) {
      // No grayscale implemented yet
      // We can mimick the duotone by using a gradient
      for (let i = 0; i < boxImages.length; i++) {
        let url;
        if (boxImages[i].style.backgroundImage != '') {
          // Get only the url of the current background-image as it will need to be passed again
          // alongside the linear-gradient
          url = boxImages[i].style.backgroundImage
            .split('url("')[1]
            .split('")')[0];
          boxImages[i].style.backgroundImage = `linear-gradient(
                rgba(${convertHex(
        filterElements[i].attributes[2].value,
        60,
      )}),
                rgba(${convertHex(
        filterElements[i].attributes[3].value,
        60,
      )})
                ),url(${url})`;
        }
      }
    } else {
      for (let index = 0; index < filterElements.length; index++) {
        const filter = filterElements[index];
        let rgbValueLight = hexToRgb(filter.dataset.lightColor);
        let rgbValueDark = hexToRgb(filter.dataset.darkColor);
    
        for (let key in rgbValueLight) {
          if (rgbValueLight.hasOwnProperty(key)) {
            rgbValueLight[key] = rgbValueLight[key] / 255;
          }
        }
        for (let key2 in rgbValueDark) {
          if (rgbValueDark.hasOwnProperty(key2)) {
            rgbValueDark[key2] = rgbValueDark[key2] / 255;
          }
        }
        let filterColorChange = filter.getElementsByClassName(
          'duotone-color-change',
        );
        for (
          let index = 0;
          index < filterColorChange.length;
          index++
        ) {
          let duotoneRed = filter.getElementsByClassName(
            'duotone-red',
          );
          let duotoneGreen = filter.getElementsByClassName(
            'duotone-green',
          );
          let duotoneBlue = filter.getElementsByClassName(
            'duotone-blue',
          );
          duotoneRed[0].setAttribute(
            'tableValues',
            rgbValueLight.r + ' ' + rgbValueDark.r,
          );
          duotoneGreen[0].setAttribute(
            'tableValues',
            rgbValueLight.g + ' ' + rgbValueDark.g,
          );
          duotoneBlue[0].setAttribute(
            'tableValues',
            rgbValueLight.b + ' ' + rgbValueDark.b,
          );
        }
      }
    }
    function hexToRgb(hex) {
        // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
        var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
        hex = hex.replace(shorthandRegex, function(m, r, g, b) {
            return r + r + g + g + b + b;
        });
        var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    
        if (result) {
            return {
                r: parseInt(result[1], 16),
                g: parseInt(result[2], 16),
                b: parseInt(result[3], 16)
            };
        } 
            return null;
        
    }
    
    // A new function for converting hex to rgb including an opacity option. Seems the hexToRgb() does not work for the IE11 workaround
    function convertHex(hex, opacity) {
      hex = hex.replace('#', '');
      let r = parseInt(hex.substring(0, 2), 16);
      let g = parseInt(hex.substring(2, 4), 16);
      let b = parseInt(hex.substring(4, 6), 16);
    
      let result = `${r  },${  g  },${  b  },${  opacity / 100  }`;
      return result;
    }
    
    /* Sample function that returns boolean in case the browser is Internet Explorer */
    function isIE() {
      let ua = navigator.userAgent;
      /* MSIE used to detect old browsers and Trident used to newer ones */
      let is_ie = ua.indexOf('MSIE ') > -1 || ua.indexOf('Trident/') > -1;
      return is_ie;
    }
    
  • URL: /components/raw/box-image-duotone/box-image-duotone.js
  • Filesystem Path: src/02-molecules/box/box-image-duotone/box-image-duotone.js
  • Size: 3.6 KB

Box Image Duotone

How it works

The image has a SVG filter applied via CSS. The filter specifies that the image should first be converted to grayscale, and afterwards the grayscale’s colors are replaced (using the feComponentTransfer) with the colors specified in the config file.

Calculating the colors

In order to have the light and dark colors specified in the config, we have to convert the colors from HEX to RGB, and afterwards convert the RGB values to a decimal value between 0 and 1. In the Fractal setup, this is done with JS, however this would be done serverside, so the values are ready when the HTML is loaded.

Support

SVG filter are not supported in IE11, but since we are using the CSS filter property, the image will just be displayed without any of the filters on older browsers.