1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::error::*;
use image::{DynamicImage, GenericImageView, ImageFormat, Rgba};
use serde::{Deserialize, Serialize};
use std::{fs::File, path::PathBuf};
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct ImageGeneration {
pub icon_path: PathBuf,
pub out_icon_name: String,
pub output_path: PathBuf,
pub force: bool,
}
impl ImageGeneration {
pub fn gen_mipmap_res_from_icon(&self) -> Result<()> {
let image = image::open(&self.icon_path)?;
let (width, height) = image.dimensions();
if width != height || height % 2 != 0 {
return Err(Error::WidthAndHeightDifSizes);
}
for (name, size) in get_icon_sizes() {
let scaled = image.thumbnail(size, size);
let img = Self::round_image(&scaled, size);
self.write_image(&name, img)?;
}
Ok(())
}
fn round_image(scaled: &DynamicImage, size: u32) -> DynamicImage {
let border = size as f64 / 25.0;
let radius = size as f64 / 2.0;
let cxy = size as f64 / 2.0 - 1.0;
let mut img = scaled.to_rgba8();
for pix in img.clone().enumerate_pixels() {
let s = (pix.0 as f64 - cxy).hypot(pix.1 as f64 - cxy);
if s > radius - border {
img.put_pixel(pix.0, pix.1, Rgba([0, 0, 0, 0]));
}
}
img.into()
}
pub fn write_image(&self, mipmap_name: &str, scaled: DynamicImage) -> Result<()> {
let mipmap_dirs = self.output_path.join(format!("mipmap-{}", mipmap_name));
if mipmap_dirs.exists() {
if self.force {
std::fs::remove_dir_all(&mipmap_dirs).ok();
std::fs::create_dir_all(&mipmap_dirs)?;
}
} else {
std::fs::create_dir_all(&mipmap_dirs)?;
}
let mut output = File::create(mipmap_dirs.join(&self.out_icon_name))?;
scaled.write_to(&mut output, ImageFormat::Png)?;
Ok(())
}
}
fn get_icon_sizes() -> Vec<(String, u32)> {
vec![
(MipmapDpi::Xxxhdpi.to_string(), 192),
(MipmapDpi::Xxhdpi.to_string(), 144),
(MipmapDpi::Xhdpi.to_string(), 96),
(MipmapDpi::Hdpi.to_string(), 72),
(MipmapDpi::Mdpi.to_string(), 48),
(MipmapDpi::Ldpi.to_string(), 36),
]
}
pub enum MipmapDpi {
Xxxhdpi,
Xxhdpi,
Xhdpi,
Hdpi,
Mdpi,
Ldpi,
}
impl ToString for MipmapDpi {
fn to_string(&self) -> String {
match self {
Self::Xxxhdpi => "xxxhdpi".to_string(),
Self::Xxhdpi => "xxhdpi".to_string(),
Self::Xhdpi => "xhdpi".to_string(),
Self::Hdpi => "hdpi".to_string(),
Self::Mdpi => "mdpi".to_string(),
Self::Ldpi => "ldpi".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env::current_dir;
#[test]
fn test_icon_gen() {
let tempfile = tempfile::tempdir().unwrap();
let res_dir_path = tempfile.path().to_path_buf();
let icon_path = current_dir()
.unwrap()
.parent()
.unwrap()
.parent()
.unwrap()
.join("assets")
.join("images")
.join("icon.png");
let image_generation = ImageGeneration {
icon_path,
out_icon_name: "ic_launcher.png".to_owned(),
output_path: res_dir_path.join("res"),
force: false,
};
image_generation.gen_mipmap_res_from_icon().unwrap();
assert!(res_dir_path
.join("res")
.join("mipmap-hdpi")
.join("ic_launcher.png")
.exists())
}
}