1use crate::makepad_draw::*;
2use std::collections::HashMap;
3use std::error::Error;
4use makepad_zune_jpeg::JpegDecoder;
5use makepad_zune_png::{post_process_image, PngDecoder};
6use std::fmt;
7use std::io::prelude::*;
8use std::fs::File;
9use std::path::{Path,PathBuf};
10use std::cell::RefCell;
11use std::sync::Arc;
12
13pub use makepad_zune_png::error::PngDecodeErrors;
14pub use makepad_zune_jpeg::errors::DecodeErrors as JpgDecodeErrors;
15
16#[derive(Live, LiveHook, Clone, Copy)]
17#[live_ignore]
18pub enum ImageFit {
19 #[pick] Stretch,
20 Horizontal,
21 Vertical,
22 Smallest,
23 Biggest,
24 Size
25}
26
27impl Default for ImageFit {
28 fn default() -> Self {
29 ImageFit::Stretch
30 }
31}
32
33#[derive(Debug, Default, Clone)]
34pub struct ImageBuffer {
35 pub width: usize,
36 pub height: usize,
37 pub data: Vec<u32>,
38 pub animation: Option<TextureAnimation>,
39}
40
41impl ImageBuffer {
42 pub fn new(in_data: &[u8], width: usize, height: usize) -> Result<ImageBuffer, ImageError> {
43 let mut out = Vec::new();
44 let pixels = width * height;
45 out.resize(pixels, 0u32);
46 match in_data.len() / pixels {
48 4 => for i in 0..pixels {
49 let r = in_data[i*4];
50 let g = in_data[i*4+1];
51 let b = in_data[i*4+2];
52 let a = in_data[i*4+3];
53 out[i] = ((a as u32)<<24) | ((r as u32)<<16) | ((g as u32)<<8) | ((b as u32)<<0);
54 }
55 3 => for i in 0..pixels {
56 let r = in_data[i*3];
57 let g = in_data[i*3+1];
58 let b = in_data[i*3+2];
59 out[i] = 0xff000000 | ((r as u32)<<16) | ((g as u32)<<8) | ((b as u32)<<0);
60 }
61 2 => for i in 0..pixels {
62 let r = in_data[i*2];
63 let a = in_data[i*2+1];
64 out[i] = ((a as u32)<<24) | ((r as u32)<<16) | ((r as u32)<<8) | ((r as u32)<<0);
65 }
66 1 => for i in 0..pixels {
67 let r = in_data[i];
68 out[i] = ((0xff as u32)<<24) | ((r as u32)<<16) | ((r as u32)<<8) | ((r as u32)<<0);
69 }
70 unsupported => {
71 return Err(ImageError::InvalidPixelAlignment(unsupported));
72 }
73 }
74 Ok(ImageBuffer {
75 width,
76 height,
77 data: out,
78 animation: None
79 })
80 }
81
82 pub fn into_new_texture(self, cx:&mut Cx)->Texture{
83 let texture = Texture::new_with_format(cx, TextureFormat::VecBGRAu8_32 {
84 width: self.width,
85 height: self.height,
86 data: Some(self.data),
87 updated: TextureUpdated::Full,
88 });
89 texture.set_animation(cx, self.animation);
90 texture
91 }
92
93 pub fn from_png(data: &[u8]) -> Result<Self, ImageError> {
94 let mut decoder = PngDecoder::new(data);
95 decoder.decode_headers()?;
96
97 if decoder.is_animated() {
98 return Ok(Self::decode_animated_png(&mut decoder)?);
99 }
100
101 let image = decoder.decode()?;
102 let decoded_data = image.u8().ok_or(
103 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
104 "Failed to decode PNG image data as a slice of u8 bytes"
105 )),
106 )?;
107 let (width, height) = decoder.get_dimensions().ok_or(
108 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
109 "Failed to get PNG image dimensions"
110 ))
111 )?;
112 Self::new(&decoded_data, width, height)
113 }
114
115 fn decode_animated_png(decoder: &mut PngDecoder<&[u8]>) -> Result<ImageBuffer, ImageError> {
116 let colorspace = decoder.get_colorspace().ok_or(
117 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
118 "Failed to get animated PNG colorspace"
119 ))
120 )?;
121 let (width, height) = decoder.get_dimensions().ok_or(
122 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
123 "Failed to get animated PNG image dimensions"
124 ))
125 )?;
126 let actl_info = decoder.actl_info().ok_or(
127 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
128 "Failed to get animated PNG actl info"
129 ))
130 )?;
131
132 let num_components = colorspace.num_components();
133 let mut output = vec![0; width * height * num_components];
134 let fits_horizontal = Cx::max_texture_width() / width;
135 let total_width = fits_horizontal * width;
136 let total_height = ((actl_info.num_frames as usize / fits_horizontal) + 1) * height;
137 let mut final_buffer = ImageBuffer::default();
138 final_buffer.data.resize(total_width * total_height, 0);
139 final_buffer.width = total_width;
140 final_buffer.height = total_height;
141 let mut cx = 0;
142 let mut cy = 0;
143 final_buffer.animation = Some(TextureAnimation {
144 width,
145 height,
146 num_frames: actl_info.num_frames as usize
147 });
148 let mut previous_frame = None;
149 while decoder.more_frames() {
150 decoder.decode_headers()?;
153 let frame = decoder.frame_info().expect("to have already been decoded");
157 let pix = decoder.decode_raw()?;
159 let info = decoder.get_info().ok_or(
162 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
163 "Failed to get animated PNG image info"
164 ))
165 )?;
166 post_process_image(
168 &info,
169 colorspace,
170 &frame,
171 &pix,
172 previous_frame.as_deref(),
173 &mut output,
174 None
175 )?;
176 previous_frame = Some(pix);
177 match num_components {
178 4 => {
179 for y in 0..height {
180 for x in 0..width {
181 let r = output[y * width * 4 + x * 4 + 0];
182 let g = output[y * width * 4 + x * 4 + 1];
183 let b = output[y * width * 4 + x * 4 + 2];
184 let a = output[y * width * 4 + x * 4 + 3];
185 final_buffer.data[(y+cy) * total_width + (x+cx)] = ((a as u32)<<24) | ((r as u32)<<16) | ((g as u32)<<8) | ((b as u32)<<0);
186 }
187 }
188 }
189 3 => {
190 for y in 0..height {
191 for x in 0..width {
192 let r = output[y * width * 3 + x * 3 + 0];
193 let g = output[y * width * 3 + x * 3 + 1];
194 let b = output[y * width * 3 + x * 3 + 2];
195 final_buffer.data[(y+cy) * total_width + (x+cx)] = 0xff000000 | ((r as u32)<<16) | ((g as u32)<<8) | ((b as u32)<<0);
196 }
197 }
198 }
199 _ => {
200 return Err(ImageError::InvalidPixelAlignment(num_components));
201 }
202 }
203 cx += width;
204 if cx >= total_width {
205 cy += height;
206 cx = 0
207 }
208 }
209 Ok(final_buffer)
210 }
211
212 pub fn from_jpg(data: &[u8]) -> Result<Self, ImageError> {
213 let mut decoder = JpegDecoder::new(&*data);
214 match decoder.decode() {
215 Ok(data) => {
216 let info = decoder.info().ok_or(
217 ImageError::JpgDecode(JpgDecodeErrors::FormatStatic(
218 "Failed to decode JPG image info"
219 )),
220 )?;
221 ImageBuffer::new(&data, info.width as usize, info.height as usize)
222 },
223 Err(err) => Err(ImageError::JpgDecode(err)),
224 }
225 }
226}
227
228pub enum ImageCacheEntry{
229 Loaded(Texture),
230 Loading(usize, usize),
231}
232
233#[derive(Debug)]
234pub struct AsyncImageLoad{
235 pub image_path: PathBuf,
236 pub result: RefCell<Option<Result<ImageBuffer, ImageError>>>
237}
238
239pub struct ImageCache {
240 map: HashMap<PathBuf, ImageCacheEntry>,
241 pub thread_pool: Option<TagThreadPool<PathBuf>>,
242}
243
244impl ImageCache {
245 pub fn new() -> Self {
246 Self {
247 map: HashMap::new(),
248 thread_pool: None,
249 }
250 }
251}
252
253
254#[derive(Debug)]
256pub enum ImageError {
257 EmptyData,
259 InvalidPixelAlignment(usize),
262 JpgDecode(JpgDecodeErrors),
264 PathNotFound(PathBuf),
266 PngDecode(PngDecodeErrors),
268 UnsupportedFormat,
271}
272
273pub enum AsyncLoadResult{
274 Loading(usize,usize),
275 Loaded,
276}
277
278impl Error for ImageError {}
279
280impl From<PngDecodeErrors> for ImageError {
281 fn from(value: PngDecodeErrors) -> Self {
282 Self::PngDecode(value)
283 }
284}
285
286impl std::fmt::Display for ImageError {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 write!(f, "{self:?}")
289 }
290}
291
292pub trait ImageCacheImpl {
293 fn get_texture(&self, id:usize) -> &Option<Texture>;
294 fn set_texture(&mut self, texture: Option<Texture>,id: usize);
295
296 fn lazy_create_image_cache(&mut self,cx: &mut Cx) {
297 if !cx.has_global::<ImageCache>() {
298 cx.set_global(ImageCache::new());
299 }
300 }
301
302 fn load_png_from_data(&mut self, cx: &mut Cx, data: &[u8], id:usize) -> Result<(), ImageError> {
303 match ImageBuffer::from_png(&*data){
304 Ok(data)=>{
305 self.set_texture(Some(data.into_new_texture(cx)), id);
306 Ok(())
307 }
308 Err(err)=>{
309 Err(err)
310 }
311 }
312 }
313
314 fn load_jpg_from_data(&mut self, cx: &mut Cx, data: &[u8], id:usize) -> Result<(), ImageError> {
315 match ImageBuffer::from_jpg(&*data){
316 Ok(data)=>{
317 self.set_texture(Some(data.into_new_texture(cx)), id);
318 Ok(())
319 }
320 Err(err)=>{
321 Err(err)
322 }
323 }
324 }
325
326 fn image_size_by_data(data:&[u8], image_path:&Path)-> Result<(usize,usize), ImageError> {
327 if image_path.extension().map(|s| s == "jpg").unwrap_or(false) {
328 let mut decoder = JpegDecoder::new(&*data);
329 decoder.decode_headers().unwrap();
330 let image_info = decoder.info().unwrap();
331 return Ok((image_info.width as usize,image_info.height as usize))
332 }
333 else if image_path.extension().map(|s| s == "png").unwrap_or(false) {
334 let mut decoder = PngDecoder::new(data);
335 decoder.decode_headers()?;
336 let (width,height) = decoder.get_dimensions().ok_or(
337 ImageError::PngDecode(PngDecodeErrors::GenericStatic(
338 "Failed to get animated PNG image dimensions"
339 ))
340 )?;
341 return Ok((width,height))
342
343 } else {
344 return Err(ImageError::UnsupportedFormat)
345 }
346 }
347
348 fn image_size_by_path(image_path:&Path)-> Result<(usize,usize), ImageError> {
349 if let Ok(mut f) = File::open(image_path){
350 let mut data = vec![0u8;1024]; match f.read(&mut data) {
352 Ok(_len) => {
353 Self::image_size_by_data(&data, image_path)
354 }
355 Err(err) => {
356 error!("load_image_file_by_path: Resource not found {:?} {}", image_path, err);
357 return Err(ImageError::PathNotFound(image_path.into()))
358 }
359 }
360 }
361 else{
362 error!("load_image_file_by_path: File not found {:?}", image_path);
363 return Err(ImageError::PathNotFound(image_path.into()))
364 }
365 }
366
367 fn process_async_image_load(&mut self, cx:&mut Cx, image_path: &Path, result: Result<ImageBuffer, ImageError>)->bool{
368 if let Ok(data) = result{
370 let texture = data.into_new_texture(cx);
371 cx.get_global::<ImageCache>().map.insert(image_path.into(), ImageCacheEntry::Loaded(texture.clone()));
372 }
373 false
374 }
375
376 fn load_image_from_cache(&mut self, cx:&mut Cx, image_path: &Path, id: usize)->bool{
377 if let Some(texture) = cx.get_global::<ImageCache>().map.get(image_path){
378 match texture{
379 ImageCacheEntry::Loaded(texture)=>{
380 self.set_texture(Some(texture.clone()), id);
381 return true
382 }
383 _=>()
384 }
385 }
386 false
387 }
388
389 fn load_image_from_data_async_impl(
390 &mut self,
391 cx: &mut Cx,
392 image_path: &Path,
393 data: Arc<Vec<u8>>,
394 id: usize,
395 ) -> Result<AsyncLoadResult, ImageError> {
396 if let Some(texture) = cx.get_global::<ImageCache>().map.get(image_path){
397 match texture{
398 ImageCacheEntry::Loaded(texture)=>{
399 let texture = texture.clone();
400 self.set_texture(Some(texture), id);
403 Ok(AsyncLoadResult::Loaded)
404 }
405 ImageCacheEntry::Loading(w,h)=>{
406 Ok(AsyncLoadResult::Loading(*w, *h))
407 }
408 }
409 }
410 else{
411 if cx.get_global::<ImageCache>().thread_pool.is_none(){
412 cx.get_global::<ImageCache>().thread_pool = Some(TagThreadPool::new(cx, cx.cpu_cores().max(3) - 2))
413 }
414 let (w,h) = Self::image_size_by_data(&*data, image_path)?;
415 cx.get_global::<ImageCache>().map.insert(image_path.into(), ImageCacheEntry::Loading(w,h));
417
418 cx.get_global::<ImageCache>().thread_pool.as_mut().unwrap().execute_rev(image_path.into(), move |image_path|{
419 if image_path.extension().map(|s| s == "jpg").unwrap_or(false) {
420 match ImageBuffer::from_jpg(&*data){
421 Ok(data)=>{
422 Cx::post_action(AsyncImageLoad{
423 image_path,
424 result: RefCell::new(Some(Ok(data)))
425 });
426 }
427 Err(err)=>{
428 Cx::post_action(AsyncImageLoad{
429 image_path,
430 result: RefCell::new(Some(Err(err)))
431 });
432 }
433 }
434 } else if image_path.extension().map(|s| s == "png").unwrap_or(false) {
435 match ImageBuffer::from_png(&*data){
436 Ok(data)=>{
437 Cx::post_action(AsyncImageLoad{
438 image_path,
439 result: RefCell::new(Some(Ok(data)))
440 });
441 }
442 Err(err)=>{
443 Cx::post_action(AsyncImageLoad{
444 image_path,
445 result: RefCell::new(Some(Err(err)))
446 });
447 }
448 }
449 } else {
450 Cx::post_action(AsyncImageLoad{
451 image_path,
452 result: RefCell::new(Some(Err(ImageError::UnsupportedFormat)))
453 });
454 }
455 });
456 Ok(AsyncLoadResult::Loading(w, h))
457 }
458 }
459
460 fn load_image_file_by_path_async_impl(
461 &mut self,
462 cx: &mut Cx,
463 image_path: &Path,
464 id: usize,
465 ) -> Result<AsyncLoadResult, ImageError> {
466 if let Some(texture) = cx.get_global::<ImageCache>().map.get(image_path){
467 match texture{
468 ImageCacheEntry::Loaded(texture)=>{
469 let texture = texture.clone();
470 self.set_texture(Some(texture), id);
473 Ok(AsyncLoadResult::Loaded)
474 }
475 ImageCacheEntry::Loading(w,h)=>{
476 Ok(AsyncLoadResult::Loading(*w, *h))
477 }
478 }
479 }
480 else{
481 if cx.get_global::<ImageCache>().thread_pool.is_none(){
482 cx.get_global::<ImageCache>().thread_pool = Some(TagThreadPool::new(cx, cx.cpu_cores().max(3) - 2))
483 }
484 let (w,h) = Self::image_size_by_path(image_path)?;
485 cx.get_global::<ImageCache>().map.insert(image_path.into(), ImageCacheEntry::Loading(w,h));
487
488 cx.get_global::<ImageCache>().thread_pool.as_mut().unwrap().execute_rev(image_path.into(), move |image_path|{
489 if let Ok(mut f) = File::open(&image_path){
490 let mut data = Vec::new();
491 match f.read_to_end(&mut data) {
492 Ok(_len) => {
493 if image_path.extension().map(|s| s == "jpg").unwrap_or(false) {
494 match ImageBuffer::from_jpg(&*data){
495 Ok(data)=>{
496 Cx::post_action(AsyncImageLoad{
497 image_path,
498 result: RefCell::new(Some(Ok(data)))
499 });
500 }
501 Err(err)=>{
502 Cx::post_action(AsyncImageLoad{
503 image_path,
504 result: RefCell::new(Some(Err(err)))
505 });
506 }
507 }
508 } else if image_path.extension().map(|s| s == "png").unwrap_or(false) {
509 match ImageBuffer::from_png(&*data){
510 Ok(data)=>{
511 Cx::post_action(AsyncImageLoad{
512 image_path,
513 result: RefCell::new(Some(Ok(data)))
514 });
515 }
516 Err(err)=>{
517 Cx::post_action(AsyncImageLoad{
518 image_path,
519 result: RefCell::new(Some(Err(err)))
520 });
521 }
522 }
523 } else {
524 Cx::post_action(AsyncImageLoad{
525 image_path,
526 result: RefCell::new(Some(Err(ImageError::UnsupportedFormat)))
527 });
528 }
529 }
530 Err(_err) => {
531 Cx::post_action(AsyncImageLoad{
532 image_path: image_path.clone(),
533 result: RefCell::new(Some(Err(ImageError::PathNotFound(image_path))))
534 });
535 }
536 }
537 }
538 else{
539 Cx::post_action(AsyncImageLoad{
540 image_path: image_path.clone(),
541 result: RefCell::new(Some(Err(ImageError::PathNotFound(image_path))))
542 });
543 }
544 });
545 Ok(AsyncLoadResult::Loading(w, h))
546 }
547 }
548
549 fn load_image_file_by_path_and_data(&mut self, cx:&mut Cx, data:&[u8], id:usize, image_path:&Path)-> Result<(), ImageError> {
550 if image_path.extension().map(|s| s == "jpg").unwrap_or(false) {
551 match ImageBuffer::from_jpg(&*data){
552 Ok(data)=>{
553 let texture = data.into_new_texture(cx);
554 cx.get_global::<ImageCache>().map.insert(image_path.into(), ImageCacheEntry::Loaded(texture.clone()));
555 self.set_texture(Some(texture), id);
556 Ok(())
557 }
558 Err(err)=>{
559 error!("load_image_file_by_path_and_data: Cannot load jpeg image from path: {:?} {}", image_path, err);
560 Err(err)
561 }
562 }
563 } else if image_path.extension().map(|s| s == "png").unwrap_or(false) {
564 match ImageBuffer::from_png(&*data){
565 Ok(data)=>{
566 let texture = data.into_new_texture(cx);
567 cx.get_global::<ImageCache>().map.insert(image_path.into(), ImageCacheEntry::Loaded(texture.clone()));
568 self.set_texture(Some(texture), id);
569 Ok(())
570 }
571 Err(err)=>{
572 error!("load_image_file_by_path_and_data: Cannot load png image from path: {:?} {}", image_path, err);
573 Err(err)
574 }
575 }
576 } else {
577 error!("load_image_file_by_path_and_data: Image format not supported {:?}", image_path);
578 Err(ImageError::UnsupportedFormat)
579 }
580 }
581
582 fn load_image_file_by_path(
583 &mut self,
584 cx: &mut Cx,
585 image_path: &Path,
586 id: usize,
587 ) -> Result<(), ImageError> {
588 if let Some(ImageCacheEntry::Loaded(texture)) = cx.get_global::<ImageCache>().map.get(image_path){
589 self.set_texture(Some(texture.clone()), id);
590 Ok(())
591 }
592 else{
593 if let Ok(mut f) = File::open(image_path){
594 let mut data = Vec::new();
595 match f.read_to_end(&mut data) {
596 Ok(_len) => {
597 self.load_image_file_by_path_and_data(cx, &data, id, image_path)
598 }
599 Err(err) => {
600 error!("load_image_file_by_path: Resource not found {:?} {}", image_path, err);
601 Err(ImageError::PathNotFound(image_path.into()))
602 }
603 }
604 }
605 else{
606 error!("load_image_file_by_path: File not found {:?}", image_path);
607 Err(ImageError::PathNotFound(image_path.into()))
608 }
609 }
610 }
611
612 fn load_image_dep_by_path(
613 &mut self,
614 cx: &mut Cx,
615 image_path: &str,
616 id: usize,
617 ) -> Result<(), ImageError> {
618 let p_image_path = Path::new(image_path);
619 if let Some(ImageCacheEntry::Loaded(texture)) = cx.get_global::<ImageCache>().map.get(p_image_path){
620 self.set_texture(Some(texture.clone()), id);
621 Ok(())
622 }
623 else{
624 match cx.take_dependency(image_path) {
625 Ok(data) => {
626 self.load_image_file_by_path_and_data(cx, &data, id, p_image_path)
627 }
628 Err(err) => {
629 error!("load_image_dep_by_path: Resource not found {} {}", image_path, err);
630 Err(ImageError::PathNotFound(image_path.into()))
631 }
632 }
633 }
634 }
635}