livesplit_core/settings/image/
mod.rs1use crate::platform::prelude::*;
2use core::{
3 ops::Deref,
4 sync::atomic::{AtomicUsize, Ordering},
5};
6use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
7
8#[cfg(test)]
9mod tests;
10
11#[cfg(all(feature = "std", feature = "image-shrinking"))]
12mod shrinking;
13
14static LAST_IMAGE_ID: AtomicUsize = AtomicUsize::new(0);
15
16#[derive(Debug, Clone)]
21pub struct Image {
22 data: Vec<u8>,
23 id: usize,
24}
25
26#[derive(Debug, Clone)]
31pub struct ImageData(pub Box<[u8]>);
32
33impl<T> From<T> for ImageData
34where
35 Box<[u8]>: From<T>,
36{
37 fn from(value: T) -> Self {
38 ImageData(value.into())
39 }
40}
41
42impl Deref for ImageData {
43 type Target = [u8];
44 fn deref(&self) -> &Self::Target {
45 &self.0
46 }
47}
48
49impl Serialize for ImageData {
50 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
51 where
52 S: Serializer,
53 {
54 if serializer.is_human_readable() {
55 if !self.0.is_empty() {
56 let mut buf = String::from("data:;base64,");
57
58 unsafe {
62 let buf = buf.as_mut_vec();
63 let encoded_len = base64_simd::STANDARD.encoded_length(self.0.len());
64 buf.reserve_exact(encoded_len);
65 let additional_len = base64_simd::STANDARD
66 .encode(
67 &self.0,
68 base64_simd::Out::from_uninit_slice(buf.spare_capacity_mut()),
69 )
70 .len();
71 buf.set_len(buf.len() + additional_len);
72 }
73
74 serializer.serialize_str(&buf)
75 } else {
76 serializer.serialize_str("")
77 }
78 } else {
79 serializer.serialize_bytes(&self.0)
80 }
81 }
82}
83
84impl<'de> Deserialize<'de> for ImageData {
85 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86 where
87 D: Deserializer<'de>,
88 {
89 if deserializer.is_human_readable() {
90 let data: &'de str = Deserialize::deserialize(deserializer)?;
91 if data.is_empty() {
92 Ok(ImageData(Box::new([])))
93 } else if let Some(encoded_image_data) = data.strip_prefix("data:;base64,") {
94 let image_data = base64_simd::STANDARD
95 .decode_to_vec(encoded_image_data.as_bytes())
96 .map_err(de::Error::custom)?;
97
98 Ok(ImageData(image_data.into_boxed_slice()))
99 } else {
100 Err(de::Error::custom("Invalid Data URL for image"))
101 }
102 } else {
103 Ok(ImageData(Deserialize::deserialize(deserializer)?))
104 }
105 }
106}
107
108impl PartialEq for Image {
109 fn eq(&self, other: &Image) -> bool {
110 self.id == other.id || self.data == other.data
111 }
112}
113
114impl Default for Image {
115 fn default() -> Image {
116 Image::new(&[])
117 }
118}
119
120impl<D: AsRef<[u8]>> From<D> for Image {
121 fn from(d: D) -> Self {
122 Image::new(d.as_ref())
123 }
124}
125
126impl Image {
127 pub fn new(data: &[u8]) -> Self {
129 let mut image = Image {
130 data: Vec::new(),
131 id: 0,
132 };
133 image.modify(data);
134 image
135 }
136
137 #[cfg(feature = "std")]
140 pub fn from_file<P>(path: P, buf: &mut Vec<u8>) -> std::io::Result<Image>
141 where
142 P: AsRef<std::path::Path>,
143 {
144 use std::io::Read;
145
146 let mut file = std::fs::File::open(path)?;
147 buf.clear();
148 file.read_to_end(buf)?;
149
150 Ok(Image::new(buf))
151 }
152
153 #[inline]
155 pub const fn id(&self) -> usize {
156 self.id
157 }
158
159 #[inline]
162 pub fn data(&self) -> &[u8] {
163 &self.data
164 }
165
166 pub fn modify(&mut self, data: &[u8]) {
169 #[cfg(all(feature = "std", feature = "image-shrinking"))]
170 let data = {
171 const MAX_IMAGE_SIZE: u32 = 128;
172
173 shrinking::shrink(data, MAX_IMAGE_SIZE)
174 };
175 cfg_if::cfg_if! {
176 if #[cfg(target_has_atomic = "ptr")] {
177 self.id = LAST_IMAGE_ID.fetch_add(1, Ordering::Relaxed);
178 } else {
179 self.id = LAST_IMAGE_ID.load(Ordering::SeqCst) + 1;
180 LAST_IMAGE_ID.store(self.id, Ordering::SeqCst);
181 }
182 }
183 self.data.clear();
184 self.data.extend_from_slice(&data);
185 }
186
187 pub fn is_empty(&self) -> bool {
189 self.data.is_empty()
190 }
191}
192
193#[derive(Copy, Clone, Default, PartialEq, Eq)]
197pub enum CachedImageId {
198 #[default]
200 Uncached,
201 NoImage,
203 Image(usize),
205}
206
207impl CachedImageId {
208 pub fn update_with<'i>(&mut self, image: Option<&'i Image>) -> Option<&'i [u8]> {
213 let new_value = image.map_or(CachedImageId::NoImage, |i| {
214 if i.is_empty() {
215 CachedImageId::NoImage
216 } else {
217 CachedImageId::Image(i.id())
218 }
219 });
220
221 if *self != new_value {
222 *self = new_value;
223 Some(image.map_or(&[], |i| &i.data))
224 } else {
225 None
226 }
227 }
228
229 pub fn reset(&mut self) {
231 *self = CachedImageId::Uncached;
232 }
233}