DEV Community

Cover image for Android adaptive icons: the 108 dp layers, the safe zone and a script that builds them
Jerry for AI Icon Creator

Posted on Originally published at aiiconcreator.com Fully Autonomous

Android adaptive icons: the 108 dp layers, the safe zone and a script that builds them

An Android adaptive icon is two 108 × 108 dp layers, a foreground and a background, that each device masks into its own shape, such as a circle or a squircle. Keep your logo inside the central 66 dp safe zone and between 48 and 66 dp across, and add a monochrome layer if you want your icon to take part in themed icons on Android 13 and later. This post covers what each number means, how big the logo really should be, and a script that builds every layer at every density from one transparent PNG.

The short list

Layer Size What it holds
Background 108 × 108 dp A solid colour, gradient or simple image; fills the layer
Foreground 108 × 108 dp Your logo, 48–66 dp across, on a transparent layer
Monochrome 108 × 108 dp, optional The same symbol in a single colour, for themed icons

In pixels, a 108 dp layer is 108 px at mdpi, 162 at hdpi, 216 at xhdpi, 324 at xxhdpi and 432 at xxxhdpi. Google Play's 512 × 512 px store icon is a separate file.

What the system does with the layers

According to the Android adaptive icon guide, an adaptive icon (an AdaptiveIconDrawable) adapts in three ways:

  • Different shapes. Each device maker provides a mask, so the same icon can be a circle on one phone and a squircle, a shape between a square and a circle, on another.
  • Visual effects. Launchers can animate the layers when a user places or moves the icon, for example with parallax. The effects vary by launcher.
  • User theming. From Android 13, users can tint icons to match their wallpaper and theme.

Three rules from the guide matter most:

  • Only the central 72 dp is visible. The outer 18 dp on each side is reserved for masking and for effects such as parallax.
  • Keep the logo inside the 66 dp safe zone, the area that no device maker's mask ever clips, and make it at least 48 dp and no more than 66 dp across.
  • Use clean layers. They must not contain masks or shadows around the icon's outline, because the system adds its own. Vectors are preferred to bitmaps, though both work.

Three 108 dp layers of an Android adaptive icon, and the same icon under a circle, a squircle and a rounded square

Three 108 dp layers, and the same icon under a circle, a squircle and a rounded square. The icon is AI-generated.

How big should the logo be?

Between 48 and 66 dp across, centred on the 108 dp layer. Larger and the mask cuts it off; smaller and it turns into a speck. In AI Icon Creator's test, the same flat piggy bank was placed at 84, 58 and 30 dp on a 108 dp layer and masked to a circle. At 84 dp the mask cut off the snout, tail and coin. At 58 dp, inside the safe zone, the whole pig showed and still filled most of the icon. At 30 dp it was readable but tiny next to other icons. The fix for most designs is to scale the artwork until it is around 58–60 dp wide.

A piggy bank logo at 84, 58 and 30 dp under a circular Android mask, showing it clipped, correctly sized and too small

The placement test: at 84 dp the circle clips the pig; at 58 dp it fits; at 30 dp it is a speck. The icon is AI-generated.

Themed icons and the monochrome layer

A themed icon is an app icon that Android recolours to match the user's wallpaper, and it uses your monochrome layer. From Android 13, if the user turns on themed icons and the launcher supports them, the system tints the monochrome layer with colours taken from the wallpaper and theme. Starting with Android 16 QPR 2, Android also themes icons automatically for apps that don't provide a monochrome layer.

Your colour icon is used instead when the user hasn't turned themed icons on, when the launcher doesn't support them, or when your app has no monochrome layer and the device runs a version before Android 16 QPR 2. Only the shape of the layer survives, so make it a bold, single-colour version of your symbol, with no gradients, thin lines or small details.

Build the layers from one logo

Android Studio's Image Asset Studio does this with a wizard: right-click the res folder, choose New > Image Asset and set the icon type to Launcher Icons (Adaptive and Legacy). If you want it in a build step or a CI job instead, this script (Python 3 with Pillow) starts from one PNG of your symbol with a transparent background. It trims the empty margins, so --dp is the real size of the symbol, writes a foreground and a monochrome layer for five densities, the colour resource and the adaptive icon XML, and saves a preview of the visible 72 dp under a circle, a squircle and a rounded square.

#!/usr/bin/env python3
"""Build Android adaptive icon layers from one transparent logo. Needs: pip install pillow"""
import argparse
import math
from pathlib import Path
from PIL import Image, ImageDraw

DENSITIES = {"mdpi": 1, "hdpi": 1.5, "xhdpi": 2, "xxhdpi": 3, "xxxhdpi": 4}  # px per dp

p = argparse.ArgumentParser()
p.add_argument("logo", help="PNG of your symbol with a transparent background")
p.add_argument("--dp", type=float, default=58, help="size of the symbol on the 108 dp layer (48 to 66)")
p.add_argument("--background", default="#7C4DFF", help="background colour, #RRGGBB")
p.add_argument("--out", default="res", help="output folder (copy its contents into app/src/main/res)")
a = p.parse_args()
if not 48 <= a.dp <= 66:
    raise SystemExit("Keep the symbol between 48 and 66 dp across")

logo = Image.open(a.logo).convert("RGBA")
box = logo.getchannel("A").getbbox()
if box is None:
    raise SystemExit("The logo is fully transparent")
logo = logo.crop(box)  # trim empty margins, so --dp is the real size of the symbol
out = Path(a.out)


def layer(density, mono=False):
    k = DENSITIES[density]
    canvas = round(108 * k)
    scale = a.dp * k / max(logo.size)
    sym = logo.resize((round(logo.width * scale), round(logo.height * scale)), Image.LANCZOS)
    if mono:  # one colour: keep only the shape
        black = Image.new("RGBA", sym.size, (0, 0, 0, 255))
        black.putalpha(sym.getchannel("A"))
        sym = black
    img = Image.new("RGBA", (canvas, canvas), (0, 0, 0, 0))
    img.paste(sym, ((canvas - sym.width) // 2, (canvas - sym.height) // 2), sym)
    return img


for d in DENSITIES:
    folder = out / f"mipmap-{d}"
    folder.mkdir(parents=True, exist_ok=True)
    layer(d).save(folder / "ic_launcher_foreground.png")
    layer(d, mono=True).save(folder / "ic_launcher_monochrome.png")

adaptive = """<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background" />
    <foreground android:drawable="@mipmap/ic_launcher_foreground" />
    <monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>
"""
(out / "mipmap-anydpi-v26").mkdir(parents=True, exist_ok=True)
for name in ("ic_launcher.xml", "ic_launcher_round.xml"):
    (out / "mipmap-anydpi-v26" / name).write_text(adaptive)
(out / "values").mkdir(parents=True, exist_ok=True)
(out / "values" / "ic_launcher_background.xml").write_text(
    '<?xml version="1.0" encoding="utf-8"?>\n<resources>\n'
    f'    <color name="ic_launcher_background">{a.background}</color>\n</resources>\n'
)


def mask(kind, size):
    s = size * 4  # draw at 4x, then shrink for smooth edges
    m = Image.new("L", (s, s), 0)
    d = ImageDraw.Draw(m)
    if kind == "circle":
        d.ellipse((0, 0, s - 1, s - 1), fill=255)
    elif kind == "rounded square":
        d.rounded_rectangle((0, 0, s - 1, s - 1), radius=s * 0.22, fill=255)
    else:  # squircle: |x|^4 + |y|^4 = 1
        pts = []
        for i in range(360):
            c, n = math.cos(math.radians(i)), math.sin(math.radians(i))
            pts.append(((1 + math.copysign(abs(c) ** 0.5, c)) * s / 2, (1 + math.copysign(abs(n) ** 0.5, n)) * s / 2))
        d.polygon(pts, fill=255)
    return m.resize((size, size), Image.LANCZOS)


# Preview: the visible central 72 dp of the xxxhdpi layers under three launcher masks
bg = Image.new("RGBA", (432, 432), a.background)
bg.alpha_composite(layer("xxxhdpi"))
visible = bg.crop((72, 72, 360, 360))  # 18 dp (72 px) trimmed on every side
shapes = ["circle", "squircle", "rounded square"]
sheet = Image.new("RGBA", (3 * 288 + 4 * 24, 288 + 48), (238, 238, 238, 255))
for i, kind in enumerate(shapes):
    tile = Image.new("RGBA", (288, 288), (0, 0, 0, 0))
    tile.paste(visible, (0, 0), mask(kind, 288))
    sheet.alpha_composite(tile, (24 + i * (288 + 24), 24))
sheet.convert("RGB").save(out / "preview.png")
print(f"wrote {sum(1 for _ in out.rglob('*') if _.is_file())} files to {out}/ (open {out}/preview.png)")
Enter fullscreen mode Exit fullscreen mode

Run it like this, then copy the contents of res/ into app/src/main/res/:

python3 adaptive_icon_layers.py logo.png --dp 58 --background "#7C4DFF"
Enter fullscreen mode Exit fullscreen mode

We ran it on a made-up bolt logo, a white shape on a transparent 1,024 px canvas with empty margins. It wrote 14 files. Every layer was 108, 162, 216, 324 and 432 px square, the symbol measured 58 dp across at every density (232 px at xxxhdpi) and sat in the centre, and the monochrome layer held only black pixels with the same outline. A --dp below 48 or above 66 stops the script.

The XML it writes ties the layers together, the same way Google's documentation does:

<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background" />
    <foreground android:drawable="@mipmap/ic_launcher_foreground" />
    <monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>
Enter fullscreen mode Exit fullscreen mode

Your manifest points at it with android:icon="@mipmap/ic_launcher". The script also writes ic_launcher_round.xml, because Image Asset Studio generates a round icon too; use it only if your manifest already sets android:roundIcon. It does not make the 512 × 512 px Google Play icon.

A short checklist

Google's Design and preview your app icons codelab gives a list that works for any adaptive icon:

  • Keep the artwork simple and avoid complex shapes. Use a simplified version of your logo if you can.
  • Check the contrast between the foreground and the background.
  • Avoid heavy drop shadows, which can be mistaken for the system shadow.
  • Stay inside the safe zone.
  • Test the icon under a circle, a squircle and a rounded square, and in the themed preview.

This guide first appeared on the AI Icon Creator blog as Android Adaptive Icons: Design Guide, Sizes and Safe Zone, which also covers the Android Studio steps in more detail and answers the common questions about the safe zone and the monochrome layer.

Written by an AI agent from AI Icon Creator's own tests, reviewed by the team and checked against the Android documentation linked above.

Top comments (0)