nine_patch_drawable/
lib.rs

1//! # Parse and Scale Nine Patch Bitmaps
2//!
3//! This library provides the core functionality to read nine patch bitmaps
4//! defined in [Android NinePatch drawables][1] and algorithms to scale them.
5//!
6//! [1]: https://developer.android.com/develop/ui/views/graphics/drawables#nine-patch
7
8use std::fmt::Display;
9
10#[derive(Debug, PartialEq)]
11pub struct RectF {
12    pub left: f32,
13    pub top: f32,
14    pub right: f32,
15    pub bottom: f32,
16}
17
18#[derive(Debug, PartialEq, Eq, Clone, Copy)]
19pub enum PatchKind {
20    Unknown,
21    Fixed,
22    Stretching,
23    Tiling,
24}
25
26#[derive(Debug, PartialEq)]
27pub struct Section {
28    pub start: f32,
29    pub len: f32,
30    pub kind: PatchKind,
31}
32
33#[derive(Debug, PartialEq)]
34pub struct Patch {
35    pub source: RectF,
36    pub target: RectF,
37    pub h_kind: PatchKind,
38    pub v_kind: PatchKind,
39}
40
41#[derive(Debug)]
42pub struct NinePatchDrawable {
43    pub width: usize,
44    pub height: usize,
45    pub h_sections: Vec<Section>,
46    pub v_sections: Vec<Section>,
47    pub margin_left: f32,
48    pub margin_top: f32,
49    pub margin_right: f32,
50    pub margin_bottom: f32,
51}
52
53#[derive(Debug)]
54pub enum NinePatchError {
55    InvalidBitmap,
56    InvalidMargin,
57}
58
59impl Display for NinePatchError {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            NinePatchError::InvalidBitmap => write!(f, "invalid bitmap"),
63            NinePatchError::InvalidMargin => write!(f, "invalid margin"),
64        }
65    }
66}
67
68impl NinePatchDrawable {
69    /// Create a new nine patch drawable from a bitmap. The pixel format can be
70    /// either RGBA(8) or BGRA(8).
71    pub fn new(
72        bitmap: &[u8],
73        stride: usize,
74        width: usize,
75        height: usize,
76    ) -> Result<NinePatchDrawable, NinePatchError> {
77        if bitmap.len() != stride * height || stride < width * 4 || width < 3 || height < 3 {
78            return Err(NinePatchError::InvalidBitmap);
79        }
80
81        let top_sections = h_sections(bitmap, 0, width);
82        let left_sections = v_sections(bitmap, 0, stride, height);
83        let right_sections = dbg!(v_sections(bitmap, (width - 1) * 4, stride, height));
84        let bottom_sections = dbg!(h_sections(bitmap, (height - 1) * stride, width));
85
86        if right_sections.len() != 0 && right_sections.len() != 3 {
87            return Err(NinePatchError::InvalidMargin);
88        }
89        if bottom_sections.len() != 0 && bottom_sections.len() != 3 {
90            return Err(NinePatchError::InvalidMargin);
91        }
92
93        Ok(NinePatchDrawable {
94            width,
95            height,
96            h_sections: top_sections,
97            v_sections: left_sections,
98            margin_left: bottom_sections.first().map_or(0.0, |s| s.len),
99            margin_top: right_sections.first().map_or(0.0, |s| s.len),
100            margin_right: bottom_sections.last().map_or(0.0, |s| s.len),
101            margin_bottom: right_sections.last().map_or(0.0, |s| s.len),
102        })
103    }
104
105    pub fn scale_to(&self, width: usize, height: usize) -> Vec<Patch> {
106        assert!(width >= self.width && height >= self.height);
107
108        let stretching_width: f32 = self
109            .h_sections
110            .iter()
111            .filter(|s| s.kind != PatchKind::Fixed)
112            .map(|s| s.len)
113            .sum();
114        let stretching_height: f32 = self
115            .v_sections
116            .iter()
117            .filter(|s| s.kind != PatchKind::Fixed)
118            .map(|s| s.len)
119            .sum();
120        let fixed_width = self.width as f32 - stretching_width;
121        let fixed_height = self.height as f32 - stretching_height;
122        let mut left = 1.0;
123        let mut top = 1.0;
124        let mut prev_bottom = 1.0;
125        let mut patches = vec![];
126        for v in &self.v_sections {
127            for h in &self.h_sections {
128                let source = RectF {
129                    left: h.start + 1.0,
130                    top: v.start + 1.0,
131                    right: h.start + 1.0 + h.len,
132                    bottom: v.start + 1.0 + v.len,
133                };
134                let right = match h.kind {
135                    PatchKind::Fixed => left + h.len,
136                    _ => left + (h.len / stretching_width) * (width as f32 - fixed_width),
137                };
138                let bottom = match v.kind {
139                    PatchKind::Fixed => top + v.len,
140                    _ => top + (v.len / stretching_height) * (height as f32 - fixed_height),
141                };
142                patches.push(Patch {
143                    source,
144                    target: RectF {
145                        left,
146                        top,
147                        right,
148                        bottom,
149                    },
150                    h_kind: h.kind,
151                    v_kind: v.kind,
152                });
153                left = right;
154                prev_bottom = bottom;
155            }
156            left = 1.0;
157            top = prev_bottom;
158        }
159        patches
160    }
161}
162
163fn h_sections(bitmap: &[u8], offset: usize, width: usize) -> Vec<Section> {
164    let mut start = 0.0;
165    let mut len = 0.0;
166    let mut kind = PatchKind::Unknown;
167    let mut sections = vec![];
168    for i in 1..width {
169        let o = offset + i * 4;
170        let i_kind = match (bitmap[o], bitmap[o + 1], bitmap[o + 2]) {
171            (0xFF, 0xFF, 0xFF) => PatchKind::Fixed,
172            _ => PatchKind::Stretching,
173        };
174        if i != (width - 1) && (kind == i_kind || kind == PatchKind::Unknown) {
175            len += 1.0;
176            kind = i_kind;
177        } else {
178            sections.push(Section { start, len, kind });
179            (start, len, kind) = (start + len, 1.0, i_kind);
180        }
181    }
182    sections
183}
184
185fn v_sections(bitmap: &[u8], offset: usize, advance: usize, height: usize) -> Vec<Section> {
186    let mut start = 0.0;
187    let mut len = 0.0;
188    let mut kind = PatchKind::Unknown;
189    let mut sections = vec![];
190    for i in 1..height {
191        let o = offset + i * advance;
192        let i_kind = match (bitmap[o], bitmap[o + 1], bitmap[o + 2]) {
193            (0xFF, 0xFF, 0xFF) => PatchKind::Fixed,
194            _ => PatchKind::Stretching,
195        };
196        if i != (height - 1) && (kind == i_kind || kind == PatchKind::Unknown) {
197            len += 1.0;
198            kind = i_kind;
199        } else {
200            sections.push(Section { start, len, kind });
201            (start, len, kind) = (start + len, 1.0, i_kind);
202        }
203    }
204    sections
205}
206
207#[cfg(test)]
208mod tests;