Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Haze - Real time background blurring

Chris Banes
December 09, 2024

Haze - Real time background blurring

This is an short tech talk which I delivered at work about Haze: https://github.com/chrisbanes/haze

It was to both engineers, UX and PMs, so I intentionally didn't include too much code.

Chris Banes

December 09, 2024
Tweet

More Decks by Chris Banes

Other Decks in Programming

Transcript

  1. Apple HCI Apple provide different ‘materials’ for developers The values

    differ per platform, but the names are consistent They’re a great place to start from https://developer.apple.com/design/human-interface-guidelines/materials
  2. iOS Figma UI Kit Apple provide an of fi cial

    iOS 18 iOS + iPadOS Design File https://www. fi
  3. iOS Figma UI Kit You can see how the materials

    work by drilling down*… https://www. fi * They may not be exactly what is used on iOS, but they look and work very similarly
  4. Fairly easy on Android, we’ve had tools to blur content

    for a while: RenderScript (old, slow) RenderEffect (Android 12+, fast) Foreground blurring
  5. The content behind is blurred. This is the ‘glass effect’

    Background blurring aka backdrop blurring Play me!
  6. No built in way to achieve this on Android. Which

    is why I built… Background blurring Play me!
  7. Haze Glassmorpism for Compose Enables real-time backdrop blurring on Android

    12+, iOS and Desktop The performance cost is minimal (but not zero) Really easy to use
  8. Haze Materials Haze provides multiple different sets of ‘materials’: •

    HazeMaterials • CupertinoMaterials (ala iOS) • FluentMaterials (ala MS)
  9. Haze Progressive Blurring (aka gradient blurring) The effect is commonly

    used on iOS A recent addition, which I spent a long time on
  10. Haze Progressive Blurring (aka gradient blurring) Works via a custom

    blur runtime shader on Android 13+ Learning shader language was fun. It’s basically C
  11. uniform shader content; // 0 for horizontal pass, 1 for

    vertical uniform int direction; uniform half blurRadius; uniform half4 crop; uniform shader mask; const int maxRadius = 150; const half2 directionHorizontal = half2(1.0, 0.0); const half2 directionVertical = half2(0.0, 1.0); half4 main(vec2 coord) { half intensity = mask.eval(coord).a; return blur(coord, mix(0.0, blurRadius, intensity)); } half gaussian(half x, half sigma) { return exp(-(x * x) / (2.0 * sigma * sigma)) / (2.0 * ${PI.toFloat()} * sigma * sigma); } half4 blur(vec2 coord, half radius) { half2 directionVec = direction == 0 ? directionHorizontal : directionVertical; half sigma = max(radius / 2, 1.0);
  12. const half2 directionVertical = half2(0.0, 1.0); half4 main(vec2 coord) {

    half intensity = mask.eval(coord).a; return blur(coord, mix(0.0, blurRadius, intensity)); } half gaussian(half x, half sigma) { return exp(-(x * x) / (2.0 * sigma * sigma)) / (2.0 * ${PI.toFloat()} * sigma * sigma); } half4 blur(vec2 coord, half radius) { half2 directionVec = direction == 0 ? directionHorizontal : directionVertical; half sigma = max(radius / 2, 1.0); half weight = gaussian(0.0, sigma); half4 result = weight * content.eval(coord); half weightSum = weight; // We need to use a constant max size Skia to know the size of the program. We use a large // number, along with a break for (int i = 1; i <= maxRadius; i++) { half halfI = half(i); if (halfI > radius) { break; }
  13. half4 blur(vec2 coord, half radius) { half2 directionVec = direction

    == 0 ? directionHorizontal : directionVertical; half sigma = max(radius / 2, 1.0); half weight = gaussian(0.0, sigma); half4 result = weight * content.eval(coord); half weightSum = weight; // We need to use a constant max size Skia to know the size of the program. We use a large // number, along with a break for (int i = 1; i <= maxRadius; i++) { half halfI = half(i); if (halfI > radius) { break; } half weight = gaussian(halfI, sigma); half2 offset = halfI * directionVec; half2 newCoord = coord - offset; if (newCoord.x >= crop[0] && newCoord.y >= crop[1]) { result += weight * content.eval(newCoord); weightSum += weight; } newCoord = coord + offset;
  14. // number, along with a break for (int i =

    1; i <= maxRadius; i++) { half halfI = half(i); if (halfI > radius) { break; } half weight = gaussian(halfI, sigma); half2 offset = halfI * directionVec; half2 newCoord = coord - offset; if (newCoord.x >= crop[0] && newCoord.y >= crop[1]) { result += weight * content.eval(newCoord); weightSum += weight; } newCoord = coord + offset; if (newCoord.x < crop[2] && newCoord.y < crop[3]) { result += weight * content.eval(newCoord); weightSum += weight; } } result /= weightSum; return result; } 53:
  15. Haze Input Scale Just released this week Speci fi cally

    built for lower- powered devices Haze can scale down content before blurring Less pixels == more performance
  16. Haze Performance I have built a number of benchmarks which

    are regularly They measure frame durations TL;DR: Haze adds ~25% to frame duration on phones TVs don’t use touch input, so the cost should be less