Packages
mob_dev
0.2.1
0.6.23
0.6.22
0.6.21
0.6.20
0.6.19
0.6.18
0.6.17
0.6.16
0.6.15
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.37
0.3.35
0.3.34
0.3.33
0.3.28
0.3.26
0.3.23
0.3.21
0.3.19
0.3.18
0.3.17
0.3.16
0.3.15
0.3.14
0.3.13
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.18
0.2.17
0.2.15
0.2.14
0.2.13
0.2.12
0.2.11
0.2.10
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
Development tooling for the Mob mobile framework
Current section
Files
Jump to
Current section
Files
lib/mob_dev/icon_generator.ex
defmodule MobDev.IconGenerator do
# Image and Avatarz are provided by the parent project (mob_dev is a dev dep).
# They're on the code path at runtime but not visible when mob_dev is compiled
# as a dependency, so suppress the compile-time undefined-module warnings.
@compile {:no_warn_undefined, [Image, Avatarz, Avatarz.Sets.Robot]}
@moduledoc """
Generates app icons for Android and iOS from either a random robot avatar
(using Avatarz) or a provided source image (using Image).
## Android sizes (mipmap buckets)
| Bucket | px |
|-------------|-----|
| mdpi | 48 |
| hdpi | 72 |
| xhdpi | 96 |
| xxhdpi | 144 |
| xxxhdpi | 192 |
## iOS sizes (AppIcon.appiconset subset)
| Usage | px |
|---------------------------|------|
| iPhone Notification 2x | 40 |
| iPhone Notification 3x | 60 |
| iPhone Settings 2x | 58 |
| iPhone Settings 3x | 87 |
| iPhone Spotlight 2x | 80 |
| iPhone Spotlight 3x | 120 |
| iPhone App 2x | 120 |
| iPhone App 3x | 180 |
| iPad Notification 1x | 20 |
| iPad Notification 2x | 40 |
| iPad Settings 1x | 29 |
| iPad Settings 2x | 58 |
| iPad Spotlight 1x | 40 |
| iPad Spotlight 2x | 80 |
| iPad App 1x | 76 |
| iPad App 2x | 152 |
| iPad Pro App 2x | 167 |
| App Store |1024 |
"""
@android_sizes %{
"mipmap-mdpi" => 48,
"mipmap-hdpi" => 72,
"mipmap-xhdpi" => 96,
"mipmap-xxhdpi" => 144,
"mipmap-xxxhdpi" => 192
}
@ios_sizes [20, 29, 40, 58, 60, 76, 80, 87, 120, 152, 167, 180, 1024]
@doc """
Generates a random robot avatar and writes platform icons into `output_dir`.
Creates:
- `output_dir/android/app/src/main/res/<bucket>/ic_launcher.png` for each Android bucket
- `output_dir/ios/Assets.xcassets/AppIcon.appiconset/icon_<px>.png` for each iOS size
- `output_dir/icon_source.png` — the 1024×1024 master
Returns `:ok` on success.
"""
@spec generate_random(output_dir :: String.t()) :: :ok
def generate_random(output_dir) do
renders_path = Path.join(output_dir, ".icon_renders")
File.mkdir_p!(renders_path)
seed = Path.basename(output_dir)
avatar =
Avatarz.render(seed, Avatarz.Sets.Robot, :robot, renders_path)
source_png = Path.join(output_dir, "icon_source.png")
Image.write!(avatar.image, source_png)
resize_for_platforms(source_png, output_dir)
end
@doc """
Resizes an existing image at `source_path` to all platform icon sizes,
writing them into `output_dir`.
Returns `:ok`.
"""
@spec generate_from_source(source_path :: String.t(), output_dir :: String.t()) :: :ok
def generate_from_source(source_path, output_dir) do
resize_for_platforms(source_path, output_dir)
end
@doc """
Returns the map of Android mipmap bucket names to pixel dimensions.
"""
@spec android_sizes() :: %{String.t() => pos_integer()}
def android_sizes, do: @android_sizes
@doc """
Returns the list of iOS icon pixel dimensions.
"""
@spec ios_sizes() :: [pos_integer()]
def ios_sizes, do: @ios_sizes
# ── private ──────────────────────────────────────────────────────────────────
defp resize_for_platforms(source_png, output_dir) do
source = Image.open!(source_png)
write_android_icons(source, output_dir)
write_ios_icons(source, output_dir)
:ok
end
defp write_android_icons(source, output_dir) do
Enum.each(@android_sizes, fn {bucket, px} ->
dest_dir = Path.join([output_dir, "android", "app", "src", "main", "res", bucket])
File.mkdir_p!(dest_dir)
dest = Path.join(dest_dir, "ic_launcher.png")
source
|> Image.thumbnail!(px)
|> Image.write!(dest)
end)
end
defp write_ios_icons(source, output_dir) do
dest_dir = Path.join([output_dir, "ios", "Assets.xcassets", "AppIcon.appiconset"])
File.mkdir_p!(dest_dir)
Enum.each(@ios_sizes, fn px ->
dest = Path.join(dest_dir, "icon_#{px}.png")
source
|> Image.thumbnail!(px)
|> Image.write!(dest)
end)
write_ios_contents_json(dest_dir)
end
defp write_ios_contents_json(dest_dir) do
images =
[
%{idiom: "iphone", scale: "2x", size: "20x20", px: 40},
%{idiom: "iphone", scale: "3x", size: "20x20", px: 60},
%{idiom: "iphone", scale: "2x", size: "29x29", px: 58},
%{idiom: "iphone", scale: "3x", size: "29x29", px: 87},
%{idiom: "iphone", scale: "2x", size: "40x40", px: 80},
%{idiom: "iphone", scale: "3x", size: "40x40", px: 120},
%{idiom: "iphone", scale: "2x", size: "60x60", px: 120},
%{idiom: "iphone", scale: "3x", size: "60x60", px: 180},
%{idiom: "ipad", scale: "1x", size: "20x20", px: 20},
%{idiom: "ipad", scale: "2x", size: "20x20", px: 40},
%{idiom: "ipad", scale: "1x", size: "29x29", px: 29},
%{idiom: "ipad", scale: "2x", size: "29x29", px: 58},
%{idiom: "ipad", scale: "1x", size: "40x40", px: 40},
%{idiom: "ipad", scale: "2x", size: "40x40", px: 80},
%{idiom: "ipad", scale: "1x", size: "76x76", px: 76},
%{idiom: "ipad", scale: "2x", size: "76x76", px: 152},
%{idiom: "ipad", scale: "2x", size: "83.5x83.5", px: 167},
%{idiom: "ios-marketing", scale: "1x", size: "1024x1024", px: 1024}
]
|> Enum.map(fn %{idiom: idiom, scale: scale, size: size, px: px} ->
%{
"filename" => "icon_#{px}.png",
"idiom" => idiom,
"scale" => scale,
"size" => size
}
end)
contents = %{"images" => images, "info" => %{"author" => "mob", "version" => 1}}
json_path = Path.join(dest_dir, "Contents.json")
File.write!(json_path, Jason.encode!(contents, pretty: true))
end
end