makepad_widgets/
image_cache.rs

1use crate::{makepad_draw::*};
2use std::collections::HashMap;
3use makepad_zune_jpeg::JpegDecoder;
4use makepad_zune_png::PngDecoder;
5
6
7#[derive(Live, LiveHook)]
8#[live_ignore]
9pub enum ImageFit{
10    #[pick] Stretch,
11    Horizontal,
12    Vertical,
13    Smallest,
14    Biggest
15}
16
17
18#[derive(Default, Clone)] 
19pub struct ImageBuffer {
20    pub width: usize,
21    pub height: usize,
22    pub data: Vec<u32>,
23}
24
25impl ImageBuffer {
26    pub fn new(in_data: &[u8], width: usize,height: usize) -> Result<ImageBuffer, String> {
27        let mut out = Vec::new();
28        let pixels = width * height;
29        out.resize(pixels, 0u32);
30        // input pixel packing
31        if in_data.len() /  pixels== 3{
32            for i in 0..pixels{
33                let r = in_data[i*3];
34                let g = in_data[i*3+1];
35                let b = in_data[i*3+2];
36                out[i] = 0xff000000 | ((r as u32)<<16) | ((g as u32)<<8) | ((b as u32)<<0);
37            }
38        }
39        else if in_data.len() / pixels == 4{
40            for i in 0..pixels{
41                let r = in_data[i*4];
42                let g = in_data[i*4+1];
43                let b = in_data[i*4+2];
44                let a = in_data[i*4+3];
45                out[i] = ((a as u32)<<24) | ((r as u32)<<16) | ((g as u32)<<8) | ((b as u32)<<0);
46            }
47        }
48        else{
49            return Err("ImageBuffer::new Image buffer pixel alignment not 3 or 4".to_string())
50        }
51        Ok(ImageBuffer {
52            width,
53            height,
54            data: out
55        })
56    }
57    
58    pub fn into_new_texture(self, cx:&mut Cx)->Texture{
59        let texture = Texture::new(cx);
60        self.into_texture(cx, &texture);
61        texture
62    }
63    
64    pub fn into_texture(mut self, cx:&mut Cx, texture:&Texture){
65        texture.set_desc(
66            cx,
67            TextureDesc {
68                format: TextureFormat::ImageBGRA,
69                width: Some(self.width),
70                height: Some(self.height),
71            },
72        );
73        texture.swap_image_u32(cx, &mut self.data);
74    }
75    
76    
77    pub fn from_png(
78        data: &[u8]
79    ) -> Result<Self, String> {
80        let mut decoder = PngDecoder::new(data);
81        match decoder.decode() {
82            Ok(image) => {
83                if let Some(data) = image.u8(){
84                    let (width,height) = decoder.get_dimensions().unwrap();
85                    ImageBuffer::new(&data, width as usize, height as usize)
86                }
87                else{
88                    Err("Error decoding PNG: image data empty".to_string())
89                }
90            }
91            Err(err) => {
92                Err(format!("Error decoding PNG: {:?}", err))
93            }
94        }
95    }
96
97    pub fn from_jpg(
98        data: &[u8]
99    ) -> Result<Self, String> {
100        let mut decoder = JpegDecoder::new(&*data);
101        // decode the file
102        match decoder.decode() {
103            Ok(data) => {
104                let info = decoder.info().unwrap();
105                ImageBuffer::new(&data, info.width as usize, info.height as usize)
106            },
107            Err(err) => {
108                Err(format!("Error decoding JPG: {:?}", err))
109            }
110        }
111    }
112}
113
114pub struct ImageCache {
115    map: HashMap<String, Texture>,
116}
117
118impl ImageCache {
119    pub fn new() -> Self {
120        Self {
121            map: HashMap::new(),
122        }
123    }
124}
125
126pub trait ImageCacheImpl {
127    fn get_texture(&self) -> &Option<Texture>;
128    fn set_texture(&mut self, texture: Option<Texture>);
129
130    fn lazy_create_image_cache(&mut self,cx: &mut Cx) {
131        if !cx.has_global::<ImageCache>() {
132            cx.set_global(ImageCache::new());
133        }
134    }
135
136
137    fn load_png_from_data(&mut self, cx:&mut Cx, data:&[u8]){
138        match ImageBuffer::from_png(&*data){
139            Ok(data)=>{
140                if let Some(texture) = self.get_texture(){
141                    data.into_texture(cx, texture);
142                }
143                else{
144                    self.set_texture(Some(data.into_new_texture(cx)));
145                }
146            }
147            Err(err)=>{
148                error!("load_png_from_data: Cannot load png image from data {}", err);
149            }
150        }
151    }
152    
153    fn load_jpg_from_data(&mut self, cx:&mut Cx, data:&[u8]){
154        match ImageBuffer::from_jpg(&*data){
155            Ok(data)=>{
156                if let Some(texture) = self.get_texture(){
157                    data.into_texture(cx, texture);
158                }
159                else{
160                    self.set_texture(Some(data.into_new_texture(cx)));
161                }
162            }
163            Err(err)=>{
164                error!("load_jpg_from_data: Cannot load png image from data {}", err);
165            }
166        }
167    }
168
169    fn load_image_dep_by_path(
170        &mut self,
171        cx: &mut Cx,
172        image_path: &str,
173    ) {
174        if let Some(texture) = cx.get_global::<ImageCache>().map.get(image_path){
175            self.set_texture(Some(texture.clone()));
176        }
177        else{
178            match cx.get_dependency(image_path) {
179                Ok(data) => {
180                    if image_path.ends_with(".jpg") {
181                        match ImageBuffer::from_jpg(&*data){
182                            Ok(data)=>{
183                                let texture = data.into_new_texture(cx);
184                                cx.get_global::<ImageCache>().map.insert(image_path.to_string(), texture.clone());
185                                self.set_texture(Some(texture));
186                            }
187                            Err(err)=>{
188                                error!("load_image_dep_by_path: Cannot load jpeg image from path: {} {}",image_path, err);
189                            }
190                        }
191                    } else if image_path.ends_with(".png") {
192                        match ImageBuffer::from_png(&*data){
193                            Ok(data)=>{
194                                let texture = data.into_new_texture(cx);
195                                cx.get_global::<ImageCache>().map.insert(image_path.to_string(), texture.clone());
196                                self.set_texture(Some(texture));
197                            }
198                            Err(err)=>{
199                                error!("load_image_dep_by_path: Cannot load png image from path: {} {}",image_path, err);
200                            }
201                        }
202                    } else {
203                        error!("load_image_dep_by_path: Image format not supported {}",image_path);
204                    }
205                }
206                Err(err) => {
207                    error!("load_image_dep_by_path:  Resource not found {} {}",image_path, err);
208                }
209            }
210        }
211    }
212}