Home

Dreamy Blur

05 September 2022

One day I saw a few pictures shared by my friend Fish. I was attracted by their slight blur and the subtle glowing effects, and wondered what kind of filter function was used. But then she told me it's just because the camera lens wasn't wiped clean.

A river in Chengdu
Chengdu. By Fish

I really liked this effect, afterwards I tried to create it with CSS, mixing two identical images together with one of them blurred and semi-transparent.

Now I look back and find two other ways to do so.

Original image

I'll use this photo in the examples below, which was taken in the park last April.

Cycling path in the park
Cycling path in the park

Blend mode

In fact, all those methods are based on the concept of stacking multiple layers like in Photoshop.

The first method I mentioned above was to use a blurred layer on top of the original image, and then merged them with mix-blend-mode.

Cycling path in the park Cycling path in the park
Using mix-blend-mode
<picture>
  <img src="url.png" />
  <img src="url.png" class="blur" />
</picture>

<style>
  picture {
    position: relative;
    display: block;
    overflow: hidden;
  }
  picture .blur {
    position: absolute;
    inset: 0;
    mix-blend-mode: normal;
    filter: blur(3px) opacity(.5) brightness(1.3);
  }
</style>

(I learned later from HN comments that the mix-blend-mode is not needed.)

Backdrop filter

The backdrop-filter property is quite special, it'll use its underneath layer as the filter source if the background of itself is transparent. And it seems to be able to merge layers automatically. We could reduce one img tag with this approach.

Cycling path in the park
Using backdrop-filter
<picture>
  <img src="url.png" />
</picture>

<style>
  picture {
    position: relative;
    display: block;
  }
  picture::after {
    content: '';
    position: absolute;
    inset: 0;
    backdrop-filter: blur(3px) opacity(.5) brightness(1.3);
  }
</style>

SVG filter

SVG filter is hard to understand for most people. But once you've learned how it works you'll see how straightforward it is.

Step 1

First we need to clone the source image and make it blurry with command feGaussianBlur.

<filter>
  <feGaussianBlur stdDeviation="3" />
</filter>

In SVG filter, each command will use the output of its previous command as input by default. The head command will use the element which it applies to as the source input. In this case, the original image.

Step 2

Continue to adjust the blurred result to make it semi-transparent and a little more bright. By achieving that we use command feComponentTransfer to modify its RGBA channel.

<filter>
  <feGaussianBlur stdDeviation="3" />
  <feComponentTransfer>
    <feFuncR type="linear" slope="1.3" />
    <feFuncG type="linear" slope="1.3" />
    <feFuncB type="linear" slope="1.3" />
    <feFuncA type="linear" slope=".5" />
  </feComponentTransfer>
</filter>

Step 3

In the end, merge the transformed result with the original image by using command feBlend.

<filter>
  <feGaussianBlur stdDeviation="3" />
  <feComponentTransfer>
    <feFuncR type="linear" slope="1.3" />
    <feFuncG type="linear" slope="1.3" />
    <feFuncB type="linear" slope="1.3" />
    <feFuncA type="linear" slope=".5" />
  </feComponentTransfer>
  <feBlend in2="SourceGraphic" />
</filter>

The feBlend command accepts two sources, in2 and the omitted in. The original image is specified with SourceGraphic, while the in is passed by default with the result of its previous command. Of course, we may also give each result a name, which would be more readable.

<filter>
  <feGaussianBlur stdDeviation="3" result="blur" />
  <feComponentTransfer in="blur" result="transformed">
    <feFuncR type="linear" slope="1.3" />
    <feFuncG type="linear" slope="1.3" />
    <feFuncB type="linear" slope="1.3" />
    <feFuncA type="linear" slope=".5" />
  </feComponentTransfer>
  <feBlend in="transformed" in2="SourceGraphic" />
</filter>

So there is the final result.

Cycling path in the park
Using SVG filter
<img src="url.png" style="filter: url(#dreamy-blur)"/>

<svg height="0">
  <filter id="dreamy-blur">
    <feGaussianBlur stdDeviation="3" />
    <feComponentTransfer>
      <feFuncR type="linear" slope="1.3" />
      <feFuncG type="linear" slope="1.3" />
      <feFuncB type="linear" slope="1.3" />
      <feFuncA type="linear" slope=".5" />
    </feComponentTransfer>
    <feBlend in2="SourceGraphic" />
  </filter>
</svg>

Applying the effect

The above methods are basically only one that interpreted in different ways. The SVG filter is my favorite since it's most applicable.

I've used it once in one of my artwork.

Some camera effects will emerge together with the mask property. And I think there are more use cases.

Cycling path in the park
Together with mask
.with-mask::after {
  backdrop-filter: blur(5px) brightness(1.3);
  mask: radial-gradient(circle at 50% 67%, #0006, #000 50%);
}

CodePen: https://codepen.io/yuanchuan/pen/LYmEXpP