manganis_core/
images.rs

1use const_serialize_07 as const_serialize;
2use const_serialize_08::SerializeConst;
3
4use crate::{AssetOptions, AssetOptionsBuilder, AssetVariant};
5
6/// The type of an image. You can read more about the tradeoffs between image formats [here](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types)
7#[derive(
8    Debug,
9    Eq,
10    PartialEq,
11    PartialOrd,
12    Clone,
13    Copy,
14    Hash,
15    SerializeConst,
16    const_serialize::SerializeConst,
17    serde::Serialize,
18    serde::Deserialize,
19)]
20#[const_serialize(crate = const_serialize_08)]
21#[repr(u8)]
22pub enum ImageFormat {
23    /// A png image. Png images cannot contain transparency and tend to compress worse than other formats
24    Png,
25    /// A jpg image. Jpg images can contain transparency and tend to compress better than png images
26    Jpg,
27    /// A webp image. Webp images can contain transparency and tend to compress better than jpg images
28    Webp,
29    /// An avif image. Avif images can compress slightly better than webp images but are not supported by all browsers
30    Avif,
31    /// An unknown image type
32    Unknown,
33}
34
35/// The size of an image asset
36#[derive(
37    Debug,
38    Eq,
39    PartialEq,
40    PartialOrd,
41    Clone,
42    Copy,
43    Hash,
44    SerializeConst,
45    const_serialize::SerializeConst,
46    serde::Serialize,
47    serde::Deserialize,
48)]
49#[const_serialize(crate = const_serialize_08)]
50#[repr(C, u8)]
51pub enum ImageSize {
52    /// A manual size in pixels
53    Manual {
54        /// The width of the image in pixels
55        width: u32,
56        /// The height of the image in pixels
57        height: u32,
58    },
59    /// The size will be automatically determined from the image source
60    Automatic,
61}
62
63/// Options for an image asset
64#[derive(
65    Debug,
66    Eq,
67    PartialEq,
68    PartialOrd,
69    Clone,
70    Copy,
71    Hash,
72    SerializeConst,
73    const_serialize::SerializeConst,
74    serde::Serialize,
75    serde::Deserialize,
76)]
77#[const_serialize(crate = const_serialize_08)]
78pub struct ImageAssetOptions {
79    ty: ImageFormat,
80    low_quality_preview: bool,
81    size: ImageSize,
82    preload: bool,
83}
84
85impl Default for ImageAssetOptions {
86    fn default() -> Self {
87        Self::default()
88    }
89}
90
91impl ImageAssetOptions {
92    /// Create a new builder for image asset options
93    pub const fn new() -> AssetOptionsBuilder<ImageAssetOptions> {
94        AssetOptions::image()
95    }
96
97    /// Create a default image asset options
98    pub const fn default() -> Self {
99        Self {
100            ty: ImageFormat::Unknown,
101            low_quality_preview: false,
102            size: ImageSize::Automatic,
103            preload: false,
104        }
105    }
106
107    /// Check if the asset is preloaded
108    pub const fn preloaded(&self) -> bool {
109        self.preload
110    }
111
112    /// Get the format of the image
113    pub const fn format(&self) -> ImageFormat {
114        self.ty
115    }
116
117    /// Get the size of the image
118    pub const fn size(&self) -> ImageSize {
119        self.size
120    }
121
122    pub(crate) const fn extension(&self) -> Option<&'static str> {
123        match self.ty {
124            ImageFormat::Png => Some("png"),
125            ImageFormat::Jpg => Some("jpg"),
126            ImageFormat::Webp => Some("webp"),
127            ImageFormat::Avif => Some("avif"),
128            ImageFormat::Unknown => None,
129        }
130    }
131}
132
133impl AssetOptions {
134    /// Create a new image asset builder
135    ///
136    /// ```rust
137    /// # use manganis::{asset, Asset, AssetOptions};
138    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image());
139    /// ```
140    pub const fn image() -> AssetOptionsBuilder<ImageAssetOptions> {
141        AssetOptionsBuilder::variant(ImageAssetOptions::default())
142    }
143}
144
145impl AssetOptionsBuilder<ImageAssetOptions> {
146    /// Make the asset preloaded
147    ///
148    /// Preloading an image will make the image start to load as soon as possible. This is useful for images that will be displayed soon after the page loads or images that may not be visible immediately, but should start loading sooner
149    ///
150    /// ```rust
151    /// # use manganis::{asset, Asset, AssetOptions};
152    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_preload(true));
153    /// ```
154    pub const fn with_preload(mut self, preload: bool) -> Self {
155        self.variant.preload = preload;
156        self
157    }
158
159    /// Sets the format of the image
160    ///
161    /// Choosing the right format can make your site load much faster. Webp and avif images tend to be a good default for most images
162    ///
163    /// ```rust
164    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};
165    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_format(ImageFormat::Webp));
166    /// ```
167    pub const fn with_format(mut self, format: ImageFormat) -> Self {
168        self.variant.ty = format;
169        self
170    }
171
172    /// Sets the format of the image to [`ImageFormat::Avif`]
173    ///
174    /// Avif images tend to be a good default for most images rendered in browser because
175    /// they compress images well
176    ///
177    /// ```rust
178    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};
179    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_avif());
180    /// ```
181    pub const fn with_avif(self) -> Self {
182        self.with_format(ImageFormat::Avif)
183    }
184
185    /// Sets the format of the image to [`ImageFormat::Webp`]
186    ///
187    /// Webp images tend to be a good default for most images rendered in browser because
188    /// they compress images well
189    ///
190    /// ```rust
191    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};
192    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_webp());
193    /// ```
194    pub const fn with_webp(self) -> Self {
195        self.with_format(ImageFormat::Webp)
196    }
197
198    /// Sets the format of the image to [`ImageFormat::Jpg`]
199    ///
200    /// Jpeg images compress much better than [`ImageFormat::Png`], but worse than [`ImageFormat::Webp`] or [`ImageFormat::Avif`]
201    ///
202    /// ```rust
203    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};
204    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_jpg());
205    /// ```
206    pub const fn with_jpg(self) -> Self {
207        self.with_format(ImageFormat::Jpg)
208    }
209
210    /// Sets the format of the image to [`ImageFormat::Png`]
211    ///
212    /// Png images don't compress very well, so they are not recommended for large images
213    ///
214    /// ```rust
215    /// # use manganis::{asset, Asset, AssetOptions, ImageFormat};
216    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_png());
217    /// ```
218    pub const fn with_png(self) -> Self {
219        self.with_format(ImageFormat::Png)
220    }
221
222    /// Sets the size of the image
223    ///
224    /// If you only use the image in one place, you can set the size of the image to the size it will be displayed at. This will make the image load faster
225    ///
226    /// ```rust
227    /// # use manganis::{asset, Asset, AssetOptions, ImageSize};
228    /// const _: Asset = asset!("/assets/image.png", AssetOptions::image().with_size(ImageSize::Manual { width: 512, height: 512 }));
229    /// ```
230    pub const fn with_size(mut self, size: ImageSize) -> Self {
231        self.variant.size = size;
232        self
233    }
234
235    /// Convert the options into options for a generic asset
236    pub const fn into_asset_options(self) -> AssetOptions {
237        AssetOptions {
238            add_hash: self.add_hash,
239            variant: AssetVariant::Image(self.variant),
240        }
241    }
242}