unity_asset_decode/sprite/
processor.rs1use super::parser::SpriteParser;
7use super::types::*;
8use crate::error::{BinaryError, Result};
9use crate::object::UnityObject;
10use crate::texture::Texture2D;
11use crate::unity_version::UnityVersion;
12use image::{RgbaImage, imageops};
13
14pub struct SpriteProcessor {
19 parser: SpriteParser,
20 config: SpriteConfig,
21}
22
23impl SpriteProcessor {
24 pub fn new(version: UnityVersion) -> Self {
26 Self {
27 parser: SpriteParser::new(version),
28 config: SpriteConfig::default(),
29 }
30 }
31
32 pub fn with_config(version: UnityVersion, config: SpriteConfig) -> Self {
34 Self {
35 parser: SpriteParser::new(version),
36 config,
37 }
38 }
39
40 pub fn parse_sprite(&self, object: &UnityObject) -> Result<SpriteResult> {
42 self.parser.parse_from_unity_object(object)
43 }
44
45 pub fn process_sprite_with_texture(
47 &self,
48 sprite_object: &UnityObject,
49 texture: &Texture2D,
50 ) -> Result<SpriteResult> {
51 let mut result = self.parse_sprite(sprite_object)?;
52
53 if self.config.extract_images {
54 match self.extract_sprite_image(&result.sprite, texture) {
55 Ok(image_data) => {
56 result = result.with_image(image_data);
57 }
58 Err(e) => {
59 result.add_warning(format!("Failed to extract sprite image: {}", e));
60 }
61 }
62 }
63
64 Ok(result)
65 }
66
67 pub fn extract_sprite_image(&self, sprite: &Sprite, texture: &Texture2D) -> Result<Vec<u8>> {
69 let converter = crate::texture::Texture2DConverter::new(self.parser.version().clone());
71 let texture_image = converter.decode_to_image(texture)?;
72
73 let sprite_rect = sprite.get_rect();
75 let texture_width = texture_image.width();
76 let texture_height = texture_image.height();
77
78 if sprite_rect.x < 0.0
80 || sprite_rect.y < 0.0
81 || sprite_rect.x + sprite_rect.width > texture_width as f32
82 || sprite_rect.y + sprite_rect.height > texture_height as f32
83 {
84 return Err(BinaryError::invalid_data(
85 "Sprite rect is outside texture bounds",
86 ));
87 }
88
89 if let Some((max_width, max_height)) = self.config.max_sprite_size
91 && (sprite_rect.width > max_width as f32 || sprite_rect.height > max_height as f32)
92 {
93 return Err(BinaryError::invalid_data(
94 "Sprite size exceeds maximum allowed size",
95 ));
96 }
97
98 let x = sprite_rect.x as u32;
100 let y = sprite_rect.y as u32;
101 let width = sprite_rect.width as u32;
102 let height = sprite_rect.height as u32;
103
104 let flipped_y = texture_height - y - height;
107
108 let sprite_image =
109 imageops::crop_imm(&texture_image, x, flipped_y, width, height).to_image();
110
111 let final_image = if self.config.apply_transformations {
113 self.apply_sprite_transformations(sprite_image, sprite)?
114 } else {
115 sprite_image
116 };
117
118 let mut png_data = Vec::new();
120 {
121 use image::ImageEncoder;
122 use image::codecs::png::PngEncoder;
123
124 let encoder = PngEncoder::new(&mut png_data);
125 encoder
126 .write_image(
127 final_image.as_raw(),
128 final_image.width(),
129 final_image.height(),
130 image::ExtendedColorType::Rgba8,
131 )
132 .map_err(|e| BinaryError::generic(format!("Failed to encode PNG: {}", e)))?;
133 }
134
135 Ok(png_data)
136 }
137
138 fn apply_sprite_transformations(&self, image: RgbaImage, sprite: &Sprite) -> Result<RgbaImage> {
140 if sprite.offset_x != 0.0 || sprite.offset_y != 0.0 {
142 }
146
147 if sprite.pivot_x != 0.5 || sprite.pivot_y != 0.5 {
149 }
152
153 Ok(image)
154 }
155
156 pub fn process_sprite_atlas(&self, atlas_sprites: &[&UnityObject]) -> Result<SpriteAtlas> {
158 if !self.config.process_atlas {
159 return Err(BinaryError::unsupported("Atlas processing is disabled"));
160 }
161
162 let mut atlas = SpriteAtlas {
163 name: "SpriteAtlas".to_string(),
164 ..Default::default()
165 };
166
167 for sprite_obj in atlas_sprites {
168 let sprite_result = self.parse_sprite(sprite_obj)?;
169 let sprite = sprite_result.sprite;
170
171 let sprite_info = SpriteInfo {
172 name: sprite.name.clone(),
173 rect: sprite.get_rect(),
174 offset: sprite.get_offset(),
175 pivot: sprite.get_pivot(),
176 border: sprite.get_border(),
177 pixels_to_units: sprite.pixels_to_units,
178 is_polygon: sprite.is_polygon,
179 texture_path_id: sprite.render_data.texture_path_id,
180 is_atlas_sprite: sprite.is_atlas_sprite(),
181 };
182
183 atlas.sprites.push(sprite_info);
184
185 if sprite.is_atlas_sprite() {
186 atlas.packed_sprites.push(sprite.name);
187 }
188 }
189
190 Ok(atlas)
191 }
192
193 pub fn get_supported_features(&self) -> Vec<&'static str> {
195 let version = self.parser.version();
196 let mut features = vec!["basic_sprite", "rect", "pivot"];
197
198 if version.major >= 5 {
199 features.push("border");
200 features.push("pixels_to_units");
201 }
202
203 if version.major >= 2017 {
204 features.push("polygon_sprites");
205 features.push("sprite_atlas");
206 }
207
208 if version.major >= 2018 {
209 features.push("sprite_mesh");
210 features.push("sprite_physics");
211 }
212
213 features
214 }
215
216 pub fn is_feature_supported(&self, feature: &str) -> bool {
218 self.get_supported_features().contains(&feature)
219 }
220
221 pub fn config(&self) -> &SpriteConfig {
223 &self.config
224 }
225
226 pub fn set_config(&mut self, config: SpriteConfig) {
228 self.config = config;
229 }
230
231 pub fn version(&self) -> &UnityVersion {
233 self.parser.version()
234 }
235
236 pub fn set_version(&mut self, version: UnityVersion) {
238 self.parser.set_version(version);
239 }
240
241 pub fn validate_sprite(&self, sprite: &Sprite) -> Result<()> {
243 if sprite.rect_width <= 0.0 || sprite.rect_height <= 0.0 {
245 return Err(BinaryError::invalid_data("Sprite has invalid dimensions"));
246 }
247
248 if sprite.pixels_to_units <= 0.0 {
249 return Err(BinaryError::invalid_data(
250 "Sprite has invalid pixels_to_units",
251 ));
252 }
253
254 if sprite.pivot_x < 0.0
256 || sprite.pivot_x > 1.0
257 || sprite.pivot_y < 0.0
258 || sprite.pivot_y > 1.0
259 {
260 return Err(BinaryError::invalid_data("Sprite pivot is out of bounds"));
261 }
262
263 if let Some((max_width, max_height)) = self.config.max_sprite_size
265 && (sprite.rect_width > max_width as f32 || sprite.rect_height > max_height as f32)
266 {
267 return Err(BinaryError::invalid_data(
268 "Sprite size exceeds maximum allowed size",
269 ));
270 }
271
272 Ok(())
273 }
274
275 pub fn get_sprite_stats(&self, sprites: &[&Sprite]) -> SpriteStats {
277 let mut stats = SpriteStats {
278 total_sprites: sprites.len(),
279 ..Default::default()
280 };
281
282 for sprite in sprites {
283 stats.total_area += sprite.get_area();
284
285 if sprite.has_border() {
286 stats.nine_slice_count += 1;
287 }
288
289 if sprite.is_polygon {
290 stats.polygon_count += 1;
291 }
292
293 if sprite.is_atlas_sprite() {
294 stats.atlas_sprite_count += 1;
295 }
296
297 let area = sprite.get_area();
299 if area < 1024.0 {
300 stats.small_sprites += 1;
301 } else if area < 16384.0 {
302 stats.medium_sprites += 1;
303 } else {
304 stats.large_sprites += 1;
305 }
306 }
307
308 if !sprites.is_empty() {
309 stats.average_area = stats.total_area / sprites.len() as f32;
310 }
311
312 stats
313 }
314}
315
316impl Default for SpriteProcessor {
317 fn default() -> Self {
318 Self::new(UnityVersion::default())
319 }
320}
321
322#[derive(Debug, Clone, Default)]
324pub struct SpriteStats {
325 pub total_sprites: usize,
326 pub total_area: f32,
327 pub average_area: f32,
328 pub nine_slice_count: usize,
329 pub polygon_count: usize,
330 pub atlas_sprite_count: usize,
331 pub small_sprites: usize, pub medium_sprites: usize, pub large_sprites: usize, }
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn test_processor_creation() {
342 let version = UnityVersion::default();
343 let processor = SpriteProcessor::new(version);
344 assert_eq!(processor.version(), &UnityVersion::default());
345 }
346
347 #[test]
348 fn test_supported_features() {
349 let version = UnityVersion::parse_version("2020.3.12f1").unwrap();
350 let processor = SpriteProcessor::new(version);
351
352 let features = processor.get_supported_features();
353 assert!(features.contains(&"basic_sprite"));
354 assert!(features.contains(&"polygon_sprites"));
355 assert!(features.contains(&"sprite_atlas"));
356 assert!(processor.is_feature_supported("sprite_mesh"));
357 }
358
359 #[test]
360 fn test_sprite_validation() {
361 let processor = SpriteProcessor::default();
362 let mut sprite = Sprite::default();
363
364 assert!(processor.validate_sprite(&sprite).is_err());
366
367 sprite.rect_width = 100.0;
369 sprite.rect_height = 100.0;
370 assert!(processor.validate_sprite(&sprite).is_ok());
371
372 sprite.pivot_x = 2.0;
374 assert!(processor.validate_sprite(&sprite).is_err());
375 }
376}