feather_ui/
resource.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4use std::any::Any;
5use std::sync::Arc;
6
7#[cfg(feature = "jxl")]
8use jxl_oxide::{EnumColourEncoding, JxlImage};
9#[cfg(feature = "svg")]
10use resvg::{tiny_skia, usvg};
11
12use crate::render::atlas;
13use crate::{Error, Pixel, PxDim};
14use guillotiere::euclid::Size2D;
15use std::hash::Hash;
16
17pub(crate) const MIN_AREA: i32 = 64 * 64;
18pub(crate) const MAX_VARIANCE: f32 = 0.1;
19
20pub(crate) fn within_variance(l: i32, r: i32, range: f32) -> bool {
21    let diff = l as f32 / r as f32;
22    diff > (1.0 - range) && diff < (1.0 + range)
23}
24
25/// An empty size request equates to requesting the native size of the image.
26/// One axis being zero equates to requesting the equivalent aspect ratio from
27/// the native aspect ratio.
28#[inline]
29pub fn fill_size(size: atlas::Size, native: atlas::Size) -> atlas::Size {
30    match (size.width, size.height) {
31        (0, 0) => native,
32        (x, 0) => atlas::Size::new(
33            x,
34            (native.height as f32 * (x as f32 / native.width as f32)).round() as i32,
35        ),
36        (0, y) => atlas::Size::new(
37            (native.width as f32 * (y as f32 / native.height as f32)).round() as i32,
38            y,
39        ),
40        _ => size,
41    }
42}
43
44#[inline]
45pub fn fill_dim(size: PxDim, native: PxDim) -> PxDim {
46    match (size.width, size.height) {
47        (0.0, 0.0) => native,
48        (x, 0.0) => PxDim::new(x, native.height * (x / native.width)),
49        (0.0, y) => PxDim::new(native.width * (y / native.height), y),
50        _ => size,
51    }
52}
53
54pub trait Location: crate::DynHashEq + Any + Send + Sync {
55    fn fetch(&self) -> Result<Box<dyn Loader>, Error>;
56}
57
58dyn_clone::clone_trait_object!(Location);
59
60pub trait Loader: std::fmt::Debug + Send + Sync {
61    /// The preload function does most of the work, loading the image into a
62    /// byte buffer of a given width and height
63    fn preload(&self, size: atlas::Size, dpi: f32) -> Result<(Vec<u8>, Size2D<u32, Pixel>), Error>;
64
65    /// The load function simply consumes the byte buffer and loads it into the
66    /// atlas.
67    fn load(
68        &self,
69        driver: &crate::graphics::Driver,
70        data: (Vec<u8>, Size2D<u32, Pixel>),
71        resize: bool,
72    ) -> Result<(atlas::Region, atlas::Size), Error>;
73}
74
75impl Hash for dyn Location {
76    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
77        self.dyn_hash(state);
78    }
79}
80
81impl PartialEq for dyn Location {
82    fn eq(&self, other: &Self) -> bool {
83        self.dyn_eq(other as &dyn Any)
84    }
85}
86
87impl Eq for dyn Location {}
88
89#[cfg(feature = "svg")]
90#[derive(Debug)]
91// resvg requires the DPI when it parses the XML, and we can't store the XML
92// tree directly, so we store the string instead.
93struct SvgXml(String);
94
95impl Location for std::path::PathBuf {
96    fn fetch(&self) -> Result<Box<dyn Loader>, Error> {
97        #[cfg(feature = "svg")]
98        if let Some(extension) = self.extension() {
99            use std::fs::File;
100            use std::io::Read;
101
102            let ext = extension.to_str().unwrap().to_ascii_lowercase();
103            if &ext == "svgz" {
104                let mut buf = Vec::new();
105                {
106                    let mut f = File::open(self)?;
107                    f.read_to_end(&mut buf)?;
108                }
109
110                if buf.starts_with(&[0x1f, 0x8b]) {
111                    buf = usvg::decompress_svgz(&buf)
112                        .map_err(|e| Error::ResourceError(Box::new(e)))?;
113                }
114
115                return Ok(Box::new(SvgXml(
116                    String::from_utf8(buf).map_err(|e| Error::ResourceError(Box::new(e)))?,
117                )));
118            } else if &ext == "svg" {
119                let mut buf = String::new();
120                File::open(self)?.read_to_string(&mut buf)?;
121                return Ok(Box::new(SvgXml(buf)));
122            }
123        }
124
125        #[cfg(feature = "svg")]
126        {
127            use std::fs::File;
128            use std::io::Read;
129
130            let mut f = File::open(self)?;
131            let mut header = [0_u8; 2];
132            if f.read(&mut header)? == 2 && header == [0x1f_u8, 0x8b_u8] {
133                let mut buf: Vec<u8> = header.into();
134                f.read_to_end(&mut buf)?;
135                buf = usvg::decompress_svgz(&buf).map_err(|e| Error::ResourceError(Box::new(e)))?;
136
137                return Ok(Box::new(SvgXml(
138                    String::from_utf8(buf).map_err(|e| Error::ResourceError(Box::new(e)))?,
139                )));
140            }
141        }
142
143        #[cfg(feature = "jxl")]
144        if let Ok(mut img) = JxlImage::builder().open(self) {
145            img.request_color_encoding(EnumColourEncoding::srgb(
146                jxl_oxide::RenderingIntent::Relative,
147            ));
148            return Ok(Box::new(img));
149        }
150
151        // We start by guessing the format from ImageReader, because it only reads a
152        // maximum of 16 bytes from the file.
153        #[cfg(any(
154            feature = "avif",
155            feature = "bmp",
156            feature = "dds",
157            feature = "exr",
158            feature = "ff",
159            feature = "gif",
160            feature = "hdr",
161            feature = "ico",
162            feature = "pnm",
163            feature = "qoi",
164            feature = "tga",
165            feature = "tiff",
166            feature = "webp"
167        ))]
168        {
169            let image = image::ImageReader::open(self)?.with_guessed_format()?;
170
171            match image.format() {
172                Some(image::ImageFormat::Png) | Some(image::ImageFormat::Jpeg) => (),
173                _ => {
174                    return Ok(Box::new(
175                        image
176                            .decode()
177                            .map_err(|e| Error::ResourceError(Box::new(e)))?,
178                    ));
179                }
180            }
181        }
182
183        // If we get here, it's a PNG or JPEG, (or an unsupported format) so we drop the
184        // image to close the file handle, then load it with load_image instead
185        // to correctly convert it to sRGB color space using the embedded color profile.
186        #[cfg(any(feature = "png", feature = "jpeg"))]
187        return Ok(Box::new(load_image::load_path(self).map_err(
188            |e| match e {
189                load_image::Error::UnsupportedFileFormat => Error::UnknownResourceFormat,
190                _ => Error::ResourceError(Box::new(e)),
191            },
192        )?));
193
194        #[allow(unreachable_code)]
195        Err(Error::UnknownResourceFormat)
196    }
197}
198
199#[cfg(feature = "jxl")]
200struct JxlFrameView<'a>(&'a jxl_oxide::FrameBuffer);
201
202#[cfg(feature = "jxl")]
203impl<'a> fast_image_resize::IntoImageView for JxlFrameView<'a> {
204    fn pixel_type(&self) -> Option<fast_image_resize::PixelType> {
205        use fast_image_resize::PixelType;
206        match self.0.channels() {
207            1 => Some(PixelType::F32),
208            2 => Some(PixelType::F32x2),
209            3 => Some(PixelType::F32x3),
210            4 => Some(PixelType::F32x4),
211            _ => None,
212        }
213    }
214
215    fn width(&self) -> u32 {
216        self.0.width() as u32
217    }
218
219    fn height(&self) -> u32 {
220        self.0.height() as u32
221    }
222
223    fn image_view<P: fast_image_resize::PixelTrait>(
224        &self,
225    ) -> Option<impl fast_image_resize::ImageView<Pixel = P>> {
226        if P::pixel_type() == self.pixel_type().unwrap() {
227            return fast_image_resize::images::TypedImageRef::<P>::from_buffer(
228                self.width(),
229                self.height(),
230                bytemuck::cast_slice(self.0.buf()),
231            )
232            .ok();
233        }
234        None
235    }
236}
237
238#[cfg(feature = "jxl")]
239fn gamma_into_linear(input: f32) -> f32 {
240    input.powf(2.2)
241}
242
243#[cfg(feature = "jxl")]
244fn linear_into_gamma(input: f32) -> f32 {
245    input.powf(1.0 / 2.2)
246}
247
248#[cfg(feature = "jxl")]
249fn in_place_map<const N: usize, const FORWARD: bool>(
250    buf: &mut [[f32; N]],
251) -> fast_image_resize::PixelType {
252    let gamma: fn(f32) -> f32 = if FORWARD {
253        gamma_into_linear
254    } else {
255        linear_into_gamma
256    };
257    let color = if FORWARD {
258        crate::color::srgb_to_linear
259    } else {
260        crate::color::linear_to_srgb
261    };
262
263    match N {
264        1 => {
265            for p in buf {
266                p[0] = gamma(p[0]);
267            }
268            fast_image_resize::PixelType::F32
269        }
270        2 => {
271            for p in buf {
272                p[0] = gamma(p[0]);
273            }
274            fast_image_resize::PixelType::F32x2
275        }
276        3 => {
277            for p in buf {
278                p[0] = color(p[0]);
279                p[1] = color(p[1]);
280                p[2] = color(p[2]);
281            }
282            fast_image_resize::PixelType::F32x3
283        }
284        4 => {
285            for p in buf {
286                p[0] = color(p[0]);
287                p[1] = color(p[1]);
288                p[2] = color(p[2]);
289            }
290            fast_image_resize::PixelType::F32x4
291        }
292        _ => unreachable!(),
293    }
294}
295
296#[cfg(feature = "jxl")]
297impl Loader for JxlImage {
298    fn preload(
299        &self,
300        mut size: atlas::Size,
301        _: f32,
302    ) -> Result<(Vec<u8>, Size2D<u32, Pixel>), Error> {
303        use crate::color::sRGB;
304        use wide::f32x4;
305
306        if self.num_loaded_keyframes() == 0 {
307            return Err(Error::ResourceError(Box::new(eyre::eyre!(
308                "JpegXL had no keyframes to load???"
309            ))));
310        }
311
312        size = fill_size(
313            size,
314            atlas::Size::new(self.width() as i32, self.height() as i32),
315        );
316
317        let render = self
318            .render_frame(0)
319            .map_err(|e| Error::ResourceError(Box::new(e)))?;
320        let mut frame = render.image_all_channels();
321
322        // If we're too close to the native size of the image, skip resizing it and
323        // simply store the native size to the atlas.
324        let force_native = within_variance(size.height, self.height() as i32, 0.05)
325            && within_variance(size.width, self.width() as i32, 0.05);
326        let (raw, w, h) = if !(force_native
327            || size.width as u32 > self.width() && size.height as u32 > self.height())
328        {
329            use fast_image_resize::PixelType;
330
331            let inner_format = match frame.channels() {
332                1 => in_place_map::<1, true>(frame.buf_grouped_mut::<1>()),
333                2 => in_place_map::<2, true>(frame.buf_grouped_mut::<2>()),
334                3 => in_place_map::<3, true>(frame.buf_grouped_mut::<3>()),
335                4 => in_place_map::<4, true>(frame.buf_grouped_mut::<4>()),
336                _ => {
337                    return Err(Error::ResourceError(Box::new(eyre::eyre!(
338                        "Channel count not 1-4"
339                    ))));
340                }
341            };
342
343            let mut dst_image = fast_image_resize::images::Image::new(
344                size.width as u32,
345                size.height as u32,
346                inner_format,
347            );
348
349            fast_image_resize::Resizer::new()
350                .resize(
351                    &JxlFrameView(&frame),
352                    &mut dst_image,
353                    &fast_image_resize::ResizeOptions::new()
354                        .use_alpha(true)
355                        .resize_alg(fast_image_resize::ResizeAlg::Convolution(
356                            fast_image_resize::FilterType::CatmullRom,
357                        )),
358                )
359                .map_err(|e| Error::ResourceError(Box::new(e)))?;
360
361            let mut output = unsafe {
362                vec![
363                    std::mem::zeroed::<u8>();
364                    (dst_image.width() * dst_image.height() * 4) as usize
365                ]
366            };
367
368            use fast_image_resize::pixels;
369
370            use crate::color::linear_to_srgb;
371            match dst_image.pixel_type() {
372                PixelType::F32 => process_load_pixels(
373                    output.as_mut_slice(),
374                    dst_image.typed_image::<pixels::F32>().unwrap().pixels(),
375                    |p| sRGB {
376                        rgba: f32x4::new([
377                            linear_into_gamma(p.0),
378                            linear_into_gamma(p.0),
379                            linear_into_gamma(p.0),
380                            1.0,
381                        ]),
382                    },
383                ),
384                PixelType::F32x2 => process_load_pixels(
385                    output.as_mut_slice(),
386                    dst_image.typed_image::<pixels::F32x2>().unwrap().pixels(),
387                    |p| sRGB {
388                        rgba: f32x4::new([
389                            linear_into_gamma(p.0[0]),
390                            linear_into_gamma(p.0[0]),
391                            linear_into_gamma(p.0[0]),
392                            p.0[1],
393                        ]),
394                    },
395                ),
396                PixelType::F32x3 => process_load_pixels(
397                    output.as_mut_slice(),
398                    dst_image.typed_image::<pixels::F32x3>().unwrap().pixels(),
399                    |p| sRGB {
400                        rgba: f32x4::new([
401                            linear_to_srgb(p.0[0]),
402                            linear_to_srgb(p.0[1]),
403                            linear_to_srgb(p.0[2]),
404                            1.0,
405                        ]),
406                    },
407                ),
408                PixelType::F32x4 => process_load_pixels(
409                    output.as_mut_slice(),
410                    dst_image.typed_image::<pixels::F32x4>().unwrap().pixels(),
411                    |p| sRGB {
412                        rgba: f32x4::new([
413                            linear_to_srgb(p.0[0]),
414                            linear_to_srgb(p.0[1]),
415                            linear_to_srgb(p.0[2]),
416                            p.0[3],
417                        ]),
418                    },
419                ),
420                _ => {
421                    return Err(Error::ResourceError(Box::new(eyre::eyre!(
422                        "Channel count not 1-4"
423                    ))));
424                }
425            }
426
427            (output, dst_image.width(), dst_image.height())
428        } else {
429            let mut output =
430                unsafe { vec![std::mem::zeroed::<u8>(); frame.width() * frame.height() * 4] };
431
432            match frame.channels() {
433                1 => process_load_pixels(output.as_mut_slice(), frame.buf(), |p| sRGB {
434                    rgba: f32x4::new([*p, *p, *p, 1.0]),
435                }),
436                2 => {
437                    process_load_pixels(output.as_mut_slice(), frame.buf_grouped::<2>(), |p| sRGB {
438                        rgba: f32x4::new([p[0], p[0], p[0], p[1]]),
439                    })
440                }
441                3 => {
442                    process_load_pixels(output.as_mut_slice(), frame.buf_grouped::<3>(), |p| sRGB {
443                        rgba: f32x4::new([p[0], p[1], p[2], 1.0]),
444                    })
445                }
446                4 => {
447                    process_load_pixels(output.as_mut_slice(), frame.buf_grouped::<4>(), |p| sRGB {
448                        rgba: f32x4::new(*p),
449                    })
450                }
451                _ => {
452                    return Err(Error::ResourceError(Box::new(eyre::eyre!(
453                        "Channel count not 1-4"
454                    ))));
455                }
456            }
457
458            (output, self.width() as u32, self.height() as u32)
459        };
460
461        Ok((raw, Size2D::new(w, h)))
462    }
463    fn load(
464        &self,
465        driver: &crate::graphics::Driver,
466        data: (Vec<u8>, Size2D<u32, Pixel>),
467        resize: bool,
468    ) -> Result<(atlas::Region, atlas::Size), Error> {
469        let region = driver.atlas.write().reserve(
470            &driver.device,
471            atlas::Size::new(data.1.width as i32, data.1.height as i32),
472            if resize { Some(&driver.queue) } else { None },
473        )?;
474
475        queue_atlas_data(
476            &data.0,
477            &region,
478            &driver.queue,
479            data.1.width,
480            data.1.height,
481            &driver.atlas.read(),
482        );
483
484        let native = atlas::Size::new(self.width() as i32, self.height() as i32);
485        Ok((region, native))
486    }
487}
488
489#[cfg(feature = "svg")]
490impl Loader for SvgXml {
491    fn preload(&self, size: atlas::Size, dpi: f32) -> Result<(Vec<u8>, Size2D<u32, Pixel>), Error> {
492        let xml_opt = usvg::roxmltree::ParsingOptions {
493            allow_dtd: true,
494            ..Default::default()
495        };
496        let xml_tree = usvg::roxmltree::Document::parse_with_options(&self.0, xml_opt)
497            .map_err(|e| Error::ResourceError(Box::new(e)))?;
498
499        let mut svg_opt = usvg::Options {
500            dpi,
501            font_size: 12.0, // TODO: change this based on system text-scaling property.
502            ..Default::default()
503        };
504
505        if let Some(sz) = tiny_skia::Size::from_wh(size.width as f32, size.height as f32) {
506            svg_opt.default_size = sz;
507        }
508
509        let svg = usvg::Tree::from_xmltree(&xml_tree, &svg_opt)
510            .map_err(|e| Error::ResourceError(Box::new(e)))
511            .map(Box::new)?;
512
513        // TODO: This rounds, which might not give accurate results. It might instead
514        // need to use ceiling.
515        let svg_size = svg.size();
516        let native_size = PxDim::new(svg_size.width(), svg_size.height());
517        let sizevec = fill_dim(
518            PxDim::new(size.height as f32, size.width as f32),
519            native_size,
520        );
521
522        let t = if sizevec == native_size {
523            tiny_skia::Transform::identity()
524        } else {
525            tiny_skia::Transform::from_scale(
526                sizevec.width / native_size.width,
527                sizevec.height / native_size.height,
528            )
529        };
530
531        let mut pixmap =
532            tiny_skia::Pixmap::new(sizevec.width.ceil() as u32, sizevec.height.ceil() as u32)
533                .unwrap();
534
535        resvg::render(&svg, t, &mut pixmap.as_mut());
536
537        // The pixels are already premultiplied for us, we just have to flip the order.
538        for c in pixmap.data_mut().as_chunks_mut::<4>().0 {
539            c.swap(0, 2);
540        }
541
542        let sz = Size2D::new(pixmap.width(), pixmap.height());
543        Ok((pixmap.take(), sz))
544    }
545
546    fn load(
547        &self,
548        driver: &crate::graphics::Driver,
549        data: (Vec<u8>, Size2D<u32, Pixel>),
550        resize: bool,
551    ) -> Result<(atlas::Region, atlas::Size), Error> {
552        let size = atlas::Size::new(data.1.width as i32, data.1.height as i32);
553        let region = driver.atlas.write().reserve(
554            &driver.device,
555            size,
556            if resize { Some(&driver.queue) } else { None },
557            None,
558        )?;
559
560        driver.atlas.read().queue_data(
561            &data.0,
562            &region,
563            &driver.queue,
564            data.1.width,
565            data.1.height,
566        );
567
568        Ok((region, size))
569    }
570}
571
572#[cfg(any(feature = "png", feature = "jpeg"))]
573struct LoadImageView<'a>(&'a load_image::Image);
574
575#[cfg(any(feature = "png", feature = "jpeg"))]
576fn image_data_as_bytes(data: &load_image::ImageData) -> &[u8] {
577    match data {
578        load_image::ImageData::RGB8(rgbs) => bytemuck::cast_slice(rgbs.as_slice()),
579        load_image::ImageData::RGBA8(rgbas) => bytemuck::cast_slice(rgbas.as_slice()),
580        load_image::ImageData::RGB16(rgbs) => bytemuck::cast_slice(rgbs.as_slice()),
581        load_image::ImageData::RGBA16(rgbas) => bytemuck::cast_slice(rgbas.as_slice()),
582        load_image::ImageData::GRAY8(gray_v08s) => bytemuck::cast_slice(gray_v08s.as_slice()),
583        load_image::ImageData::GRAY16(gray_v08s) => bytemuck::cast_slice(gray_v08s.as_slice()),
584        load_image::ImageData::GRAYA8(gray_alpha_v08s) => {
585            bytemuck::cast_slice(gray_alpha_v08s.as_slice())
586        }
587        load_image::ImageData::GRAYA16(gray_alpha_v08s) => {
588            bytemuck::cast_slice(gray_alpha_v08s.as_slice())
589        }
590    }
591}
592
593#[cfg(any(feature = "png", feature = "jpeg"))]
594impl<'a> fast_image_resize::IntoImageView for LoadImageView<'a> {
595    fn pixel_type(&self) -> Option<fast_image_resize::PixelType> {
596        use fast_image_resize::PixelType;
597        use load_image::ImageData;
598        Some(match self.0.bitmap {
599            ImageData::RGB8(_) => PixelType::U8x3,
600            ImageData::RGBA8(_) => PixelType::U8x4,
601            ImageData::GRAY8(_) => PixelType::U8,
602            ImageData::GRAYA8(_) => PixelType::U8x2,
603            ImageData::RGB16(_) => PixelType::U16x3,
604            ImageData::RGBA16(_) => PixelType::U16x4,
605            ImageData::GRAY16(_) => PixelType::U16,
606            ImageData::GRAYA16(_) => PixelType::U16x2,
607        })
608    }
609
610    fn width(&self) -> u32 {
611        self.0.width as u32
612    }
613
614    fn height(&self) -> u32 {
615        self.0.height as u32
616    }
617
618    fn image_view<P: fast_image_resize::PixelTrait>(
619        &self,
620    ) -> Option<impl fast_image_resize::ImageView<Pixel = P>> {
621        if P::pixel_type() == self.pixel_type().unwrap() {
622            return fast_image_resize::images::TypedImageRef::<P>::from_buffer(
623                self.width(),
624                self.height(),
625                image_data_as_bytes(&self.0.bitmap),
626            )
627            .ok();
628        }
629        None
630    }
631}
632
633#[cfg(any(
634    feature = "avif",
635    feature = "bmp",
636    feature = "dds",
637    feature = "exr",
638    feature = "ff",
639    feature = "gif",
640    feature = "hdr",
641    feature = "ico",
642    feature = "pnm",
643    feature = "png",
644    feature = "jpeg",
645    feature = "qoi",
646    feature = "tga",
647    feature = "tiff",
648    feature = "webp"
649))]
650fn process_pixels<P: fast_image_resize::pixels::InnerPixel>(
651    output: &mut [u8],
652    dst_image: &fast_image_resize::images::Image<'_>,
653    convert: fn(p: &P) -> crate::color::sRGB,
654) {
655    use crate::color::Premultiplied;
656    for (p, c) in dst_image
657        .typed_image::<P>()
658        .unwrap()
659        .pixels()
660        .iter()
661        .zip(output.chunks_exact_mut(4))
662    {
663        c.copy_from_slice(&convert(p).srgb_pre().as_bgra())
664    }
665}
666
667fn process_load_pixels<T>(
668    output: &mut [u8],
669    source: &[T],
670    convert: fn(p: &T) -> crate::color::sRGB,
671) {
672    use crate::color::Premultiplied;
673    for (p, c) in source.iter().zip(output.chunks_exact_mut(4)) {
674        // Pre-multiply color, then extract in BGRA form.
675        c.copy_from_slice(&convert(p).srgb_pre().as_bgra());
676    }
677}
678
679#[cfg(any(
680    feature = "avif",
681    feature = "bmp",
682    feature = "dds",
683    feature = "exr",
684    feature = "ff",
685    feature = "gif",
686    feature = "hdr",
687    feature = "ico",
688    feature = "pnm",
689    feature = "png",
690    feature = "jpeg",
691    feature = "qoi",
692    feature = "tga",
693    feature = "tiff",
694    feature = "webp"
695))]
696fn image_resize_loader(
697    inner_format: fast_image_resize::PixelType,
698    srgb_map: fast_image_resize::PixelComponentMapper,
699    source: &impl fast_image_resize::IntoImageView,
700    size: atlas::Size,
701) -> Result<(Vec<u8>, u32, u32), Error> {
702    use crate::color::sRGB64;
703    use fast_image_resize::PixelType;
704
705    let mut inner_src_image =
706        fast_image_resize::images::Image::new(source.width(), source.height(), inner_format);
707
708    srgb_map
709        .forward_map(source, &mut inner_src_image)
710        .map_err(|e| Error::ResourceError(Box::new(e)))?;
711
712    let mut dst_image =
713        fast_image_resize::images::Image::new(size.width as u32, size.height as u32, inner_format);
714
715    fast_image_resize::Resizer::new()
716        .resize(
717            &inner_src_image,
718            &mut dst_image,
719            &fast_image_resize::ResizeOptions::new()
720                .use_alpha(true)
721                .resize_alg(fast_image_resize::ResizeAlg::Convolution(
722                    fast_image_resize::FilterType::CatmullRom,
723                )),
724        )
725        .map_err(|e| Error::ResourceError(Box::new(e)))?;
726
727    srgb_map
728        .backward_map_inplace(&mut dst_image)
729        .map_err(|e| Error::ResourceError(Box::new(e)))?;
730
731    let mut output = unsafe {
732        vec![std::mem::zeroed::<u8>(); (dst_image.width() * dst_image.height() * 4) as usize]
733    };
734
735    match inner_format {
736        PixelType::U16 => process_pixels::<fast_image_resize::pixels::U16>(
737            output.as_mut_slice(),
738            &dst_image,
739            |p| sRGB64::new(p.0, p.0, p.0, u16::MAX).as_f32(),
740        ),
741        PixelType::U16x2 => process_pixels::<fast_image_resize::pixels::U16x2>(
742            output.as_mut_slice(),
743            &dst_image,
744            |p| sRGB64::new(p.0[0], p.0[0], p.0[0], p.0[1]).as_f32(),
745        ),
746        PixelType::U16x3 => process_pixels::<fast_image_resize::pixels::U16x3>(
747            output.as_mut_slice(),
748            &dst_image,
749            |p| sRGB64::new(p.0[0], p.0[1], p.0[2], u16::MAX).as_f32(),
750        ),
751        PixelType::U16x4 => process_pixels::<fast_image_resize::pixels::U16x4>(
752            output.as_mut_slice(),
753            &dst_image,
754            |p| sRGB64::new(p.0[0], p.0[1], p.0[2], p.0[3]).as_f32(),
755        ),
756        _ => return Err(Error::InternalFailure),
757    }
758
759    Ok((output, dst_image.width(), dst_image.height()))
760}
761
762/*
763fn gen_test_image() {
764    let mut imgbuf = image::ImageBuffer::new(128, 128);
765
766    for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
767        let v = match ((x / 4) % 2, (y / 4) % 2) {
768            (0, 0) => [255, 0, 0, 0],
769            (1, 0) => [0, 255, 0, 0],
770            (0, 1) => [0, 0, 255, 0],
771            (1, 1) => [0, 0, 0, 255],
772            _ => panic!("math stopped working!"),
773        };
774        *pixel = image::Rgba::<u8>(v);
775    }
776
777    imgbuf
778        .save_with_format("premul_test.png", image::ImageFormat::Png)
779        .unwrap();
780}
781*/
782
783#[cfg(any(feature = "png", feature = "jpeg"))]
784impl Loader for load_image::Image {
785    fn preload(
786        &self,
787        mut size: atlas::Size,
788        _: f32,
789    ) -> Result<(Vec<u8>, Size2D<u32, Pixel>), Error> {
790        use crate::color::{sRGB32, sRGB64};
791
792        size = fill_size(
793            size,
794            atlas::Size::new(self.width as i32, self.height as i32),
795        );
796
797        // If we're too close to the native size of the image, skip resizing it and
798        // simply store the native size to the atlas.
799        let force_native = within_variance(size.height, self.height as i32, 0.05)
800            && within_variance(size.width, self.width as i32, 0.05);
801        let (raw, w, h) = if !(force_native
802            || size.width as usize > self.width && size.height as usize > self.height)
803        {
804            use load_image::ImageData;
805
806            let srgb_map = match self.bitmap {
807                ImageData::RGB8(_)
808                | ImageData::RGBA8(_)
809                | ImageData::RGB16(_)
810                | ImageData::RGBA16(_) => fast_image_resize::create_srgb_mapper(),
811                ImageData::GRAY8(_)
812                | ImageData::GRAY16(_)
813                | ImageData::GRAYA8(_)
814                | ImageData::GRAYA16(_) => fast_image_resize::create_gamma_22_mapper(),
815            };
816
817            let inner_format = match self.bitmap {
818                ImageData::RGB8(_) | ImageData::RGB16(_) => fast_image_resize::PixelType::U16x3,
819                ImageData::RGBA8(_) | ImageData::RGBA16(_) => fast_image_resize::PixelType::U16x4,
820                ImageData::GRAY8(_) | ImageData::GRAY16(_) => fast_image_resize::PixelType::U16,
821                ImageData::GRAYA8(_) | ImageData::GRAYA16(_) => fast_image_resize::PixelType::U16x2,
822            };
823
824            image_resize_loader(inner_format, srgb_map, &LoadImageView(self), size)?
825        } else {
826            let mut output =
827                unsafe { vec![std::mem::zeroed::<u8>(); self.width * self.height * 4] };
828
829            match &self.bitmap {
830                load_image::ImageData::RGB8(rgbs) => {
831                    process_load_pixels(output.as_mut_slice(), rgbs, |p| {
832                        sRGB32::new(p.r, p.g, p.b, u8::MAX).as_f32()
833                    })
834                }
835                load_image::ImageData::RGBA8(rgbas) => {
836                    process_load_pixels(output.as_mut_slice(), rgbas, |p| {
837                        sRGB32::new(p.r, p.g, p.b, p.a).as_f32()
838                    })
839                }
840                load_image::ImageData::RGB16(rgbs) => {
841                    process_load_pixels(output.as_mut_slice(), rgbs, |p| {
842                        sRGB64::new(p.r, p.g, p.b, u16::MAX).as_f32()
843                    })
844                }
845                load_image::ImageData::RGBA16(rgbas) => {
846                    process_load_pixels(output.as_mut_slice(), rgbas, |p| {
847                        sRGB64::new(p.r, p.g, p.b, p.a).as_f32()
848                    })
849                }
850                load_image::ImageData::GRAY8(gray_v08s) => {
851                    process_load_pixels(output.as_mut_slice(), gray_v08s, |p| {
852                        sRGB32::new(p.value(), p.value(), p.value(), u8::MAX).as_f32()
853                    })
854                }
855                load_image::ImageData::GRAY16(gray_v08s) => {
856                    process_load_pixels(output.as_mut_slice(), gray_v08s, |p| {
857                        sRGB64::new(p.value(), p.value(), p.value(), u16::MAX).as_f32()
858                    })
859                }
860                load_image::ImageData::GRAYA8(gray_alpha_v08s) => {
861                    process_load_pixels(output.as_mut_slice(), gray_alpha_v08s, |p| {
862                        sRGB32::new(p.v, p.v, p.v, p.a).as_f32()
863                    })
864                }
865                load_image::ImageData::GRAYA16(gray_alpha_v08s) => {
866                    process_load_pixels(output.as_mut_slice(), gray_alpha_v08s, |p| {
867                        sRGB64::new(p.v, p.v, p.v, p.a).as_f32()
868                    })
869                }
870            };
871
872            (output, self.width as u32, self.height as u32)
873        };
874
875        Ok((raw, Size2D::new(w, h)))
876    }
877    fn load(
878        &self,
879        driver: &crate::graphics::Driver,
880        data: (Vec<u8>, Size2D<u32, Pixel>),
881        resize: bool,
882    ) -> Result<(atlas::Region, atlas::Size), Error> {
883        let region = driver.atlas.write().reserve(
884            &driver.device,
885            atlas::Size::new(data.1.width as i32, data.1.height as i32),
886            if resize { Some(&driver.queue) } else { None },
887            None,
888        )?;
889
890        driver.atlas.read().queue_data(
891            &data.0,
892            &region,
893            &driver.queue,
894            data.1.width,
895            data.1.height,
896        );
897
898        let native = atlas::Size::new(self.width as i32, self.height as i32);
899
900        Ok((region, native))
901    }
902}
903
904#[cfg(any(
905    feature = "avif",
906    feature = "bmp",
907    feature = "dds",
908    feature = "exr",
909    feature = "ff",
910    feature = "gif",
911    feature = "hdr",
912    feature = "ico",
913    feature = "pnm",
914    feature = "qoi",
915    feature = "tga",
916    feature = "tiff",
917    feature = "webp"
918))]
919impl Loader for image::DynamicImage {
920    fn preload(
921        &self,
922        mut size: atlas::Size,
923        _: f32,
924    ) -> Result<(Vec<u8>, Size2D<u32, Pixel>), Error> {
925        use crate::color::{Premultiplied, sRGB32};
926
927        let native = atlas::Size::new(self.width() as i32, self.height() as i32);
928        size = fill_size(size, native);
929
930        // If we're too close to the native size of the image, skip resizing it and
931        // simply store the native size to the atlas.
932        let force_native = within_variance(size.height, self.height() as i32, 0.05)
933            && within_variance(size.width, self.width() as i32, 0.05);
934
935        let (raw, w, h) = if !(force_native
936            || size.width as u32 > self.width() && size.height as u32 > self.height())
937        {
938            let srgb_map = if self.color().has_color() {
939                fast_image_resize::create_srgb_mapper()
940            } else {
941                fast_image_resize::create_gamma_22_mapper()
942            };
943
944            let inner_format = match self.color().channel_count() {
945                4 => fast_image_resize::PixelType::U16x4,
946                3 => fast_image_resize::PixelType::U16x3,
947                2 => fast_image_resize::PixelType::U16x2,
948                1 => fast_image_resize::PixelType::U16,
949                _ => return Err(Error::InternalFailure),
950            };
951
952            image_resize_loader(inner_format, srgb_map, self, size)?
953        } else {
954            let mut raw = self.to_rgba8().into_vec();
955
956            // Raw is in sRGB RGBA but our atlas is in pre-multiplied BGRA format
957            for c in raw.as_mut_slice().chunks_exact_mut(4) {
958                // Pre-multiply color, then extract in BGRA form.
959                c.copy_from_slice(
960                    &sRGB32::new(c[0], c[1], c[2], c[3])
961                        .as_f32()
962                        .srgb_pre()
963                        .as_bgra(),
964                );
965            }
966
967            (raw, self.width(), self.height())
968        };
969
970        Ok((raw, Size2D::new(w, h)))
971    }
972
973    fn load(
974        &self,
975        driver: &crate::graphics::Driver,
976        data: (Vec<u8>, Size2D<u32, Pixel>),
977        resize: bool,
978    ) -> Result<(atlas::Region, atlas::Size), Error> {
979        let region = driver.atlas.write().reserve(
980            &driver.device,
981            atlas::Size::new(data.1.width as i32, data.1.height as i32),
982            if resize { Some(&driver.queue) } else { None },
983            None,
984        )?;
985
986        driver.atlas.read().queue_data(
987            &data.0,
988            &region,
989            &driver.queue,
990            data.1.width,
991            data.1.height,
992        );
993
994        let native = atlas::Size::new(self.width() as i32, self.height() as i32);
995        Ok((region, native))
996    }
997}
998
999pub fn load_icon(location: &dyn Location) -> Result<winit::window::Icon, Error> {
1000    let loader = location.fetch()?;
1001
1002    #[cfg(target_os = "windows")]
1003    use windows_sys::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXICON, SM_CYICON};
1004
1005    #[cfg(target_os = "windows")]
1006    let sz = unsafe { atlas::Size::new(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON)) };
1007
1008    #[cfg(not(target_os = "windows"))]
1009    let sz = atlas::Size::new(128, 128);
1010
1011    let (mut data, dim) = loader.preload(sz, crate::BASE_DPI.width)?;
1012
1013    // This data is in BGRA format, but this wants RGBA, so we swap it (again). Not
1014    // very efficient, but icons shouldn't be very large anyway.
1015    for c in data.as_chunks_mut::<4>().0 {
1016        c.swap(0, 2);
1017    }
1018
1019    winit::window::Icon::from_rgba(data, dim.width, dim.height)
1020        .map_err(|e| Error::ResourceError(Box::new(e)))
1021}
1022
1023#[cfg(any(
1024    feature = "avif",
1025    feature = "bmp",
1026    feature = "dds",
1027    feature = "exr",
1028    feature = "ff",
1029    feature = "gif",
1030    feature = "hdr",
1031    feature = "ico",
1032    feature = "pnm",
1033    feature = "qoi",
1034    feature = "tga",
1035    feature = "tiff",
1036    feature = "webp"
1037))]
1038#[derive(Debug, Clone)]
1039pub struct ImageRef(pub Arc<image::DynamicImage>);
1040
1041#[cfg(any(
1042    feature = "avif",
1043    feature = "bmp",
1044    feature = "dds",
1045    feature = "exr",
1046    feature = "ff",
1047    feature = "gif",
1048    feature = "hdr",
1049    feature = "ico",
1050    feature = "pnm",
1051    feature = "qoi",
1052    feature = "tga",
1053    feature = "tiff",
1054    feature = "webp"
1055))]
1056impl std::hash::Hash for ImageRef {
1057    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1058        Arc::as_ptr(&self.0).hash(state);
1059    }
1060}
1061
1062#[cfg(any(
1063    feature = "avif",
1064    feature = "bmp",
1065    feature = "dds",
1066    feature = "exr",
1067    feature = "ff",
1068    feature = "gif",
1069    feature = "hdr",
1070    feature = "ico",
1071    feature = "pnm",
1072    feature = "qoi",
1073    feature = "tga",
1074    feature = "tiff",
1075    feature = "webp"
1076))]
1077impl PartialEq for ImageRef {
1078    fn eq(&self, other: &Self) -> bool {
1079        Arc::ptr_eq(&self.0, &other.0)
1080    }
1081}
1082
1083#[cfg(any(
1084    feature = "avif",
1085    feature = "bmp",
1086    feature = "dds",
1087    feature = "exr",
1088    feature = "ff",
1089    feature = "gif",
1090    feature = "hdr",
1091    feature = "ico",
1092    feature = "pnm",
1093    feature = "qoi",
1094    feature = "tga",
1095    feature = "tiff",
1096    feature = "webp"
1097))]
1098impl Eq for ImageRef {}
1099
1100#[cfg(any(
1101    feature = "avif",
1102    feature = "bmp",
1103    feature = "dds",
1104    feature = "exr",
1105    feature = "ff",
1106    feature = "gif",
1107    feature = "hdr",
1108    feature = "ico",
1109    feature = "pnm",
1110    feature = "qoi",
1111    feature = "tga",
1112    feature = "tiff",
1113    feature = "webp"
1114))]
1115impl Location for ImageRef {
1116    fn fetch(&self) -> Result<Box<dyn Loader>, Error> {
1117        Ok(Box::new(image::DynamicImage::clone(&self.0)))
1118    }
1119}