blur.rs (4705B)
1 use std::collections::HashMap; 2 3 use nostrdb::Note; 4 5 use crate::jobs::{Job, JobError, JobParamsOwned}; 6 7 #[derive(Clone)] 8 pub struct Blur<'a> { 9 pub blurhash: &'a str, 10 pub dimensions: Option<PixelDimensions>, // width and height in pixels 11 } 12 13 #[derive(Clone, Debug)] 14 pub struct PixelDimensions { 15 pub x: u32, 16 pub y: u32, 17 } 18 19 impl PixelDimensions { 20 pub fn to_points(&self, ppp: f32) -> PointDimensions { 21 PointDimensions { 22 x: (self.x as f32) / ppp, 23 y: (self.y as f32) / ppp, 24 } 25 } 26 } 27 28 #[derive(Clone, Debug)] 29 pub struct PointDimensions { 30 pub x: f32, 31 pub y: f32, 32 } 33 34 impl PointDimensions { 35 pub fn to_pixels(self, ui: &egui::Ui) -> PixelDimensions { 36 PixelDimensions { 37 x: (self.x * ui.pixels_per_point()).round() as u32, 38 y: (self.y * ui.pixels_per_point()).round() as u32, 39 } 40 } 41 42 pub fn to_vec(self) -> egui::Vec2 { 43 egui::Vec2::new(self.x, self.y) 44 } 45 } 46 47 impl Blur<'_> { 48 pub fn scaled_pixel_dimensions( 49 &self, 50 ui: &egui::Ui, 51 available_points: PointDimensions, 52 ) -> PixelDimensions { 53 let max_pixels = available_points.to_pixels(ui); 54 55 let Some(defined_dimensions) = &self.dimensions else { 56 return max_pixels; 57 }; 58 59 if defined_dimensions.x == 0 || defined_dimensions.y == 0 { 60 tracing::error!("The blur dimensions should not be zero"); 61 return max_pixels; 62 } 63 64 if defined_dimensions.y <= max_pixels.y { 65 return defined_dimensions.clone(); 66 } 67 68 let scale_factor = (max_pixels.y as f32) / (defined_dimensions.y as f32); 69 let max_width_scaled = scale_factor * (defined_dimensions.x as f32); 70 71 PixelDimensions { 72 x: (max_width_scaled.round() as u32), 73 y: max_pixels.y, 74 } 75 } 76 } 77 78 pub fn imeta_blurhashes<'a>(note: &'a Note) -> HashMap<&'a str, Blur<'a>> { 79 let mut blurs = HashMap::new(); 80 81 for tag in note.tags() { 82 let mut tag_iter = tag.into_iter(); 83 if tag_iter 84 .next() 85 .and_then(|s| s.str()) 86 .filter(|s| *s == "imeta") 87 .is_none() 88 { 89 continue; 90 } 91 92 let Some((url, blur)) = find_blur(tag_iter) else { 93 continue; 94 }; 95 96 blurs.insert(url, blur); 97 } 98 99 blurs 100 } 101 102 fn find_blur(tag_iter: nostrdb::TagIter) -> Option<(&str, Blur)> { 103 let mut url = None; 104 let mut blurhash = None; 105 let mut dims = None; 106 107 for tag_elem in tag_iter { 108 let Some(s) = tag_elem.str() else { continue }; 109 let mut split = s.split_whitespace(); 110 111 let Some(first) = split.next() else { continue }; 112 let Some(second) = split.next() else { continue }; 113 114 match first { 115 "url" => url = Some(second), 116 "blurhash" => blurhash = Some(second), 117 "dim" => dims = Some(second), 118 _ => {} 119 } 120 121 if url.is_some() && blurhash.is_some() && dims.is_some() { 122 break; 123 } 124 } 125 126 let url = url?; 127 let blurhash = blurhash?; 128 129 let dimensions = dims.and_then(|d| { 130 let mut split = d.split('x'); 131 let width = split.next()?.parse::<u32>().ok()?; 132 let height = split.next()?.parse::<u32>().ok()?; 133 134 Some(PixelDimensions { 135 x: width, 136 y: height, 137 }) 138 }); 139 140 Some(( 141 url, 142 Blur { 143 blurhash, 144 dimensions, 145 }, 146 )) 147 } 148 149 #[derive(Clone)] 150 pub enum ObfuscationType<'a> { 151 Blurhash(Blur<'a>), 152 Default, 153 } 154 155 pub(crate) fn compute_blurhash( 156 params: Option<JobParamsOwned>, 157 dims: PixelDimensions, 158 ) -> Result<Job, JobError> { 159 #[allow(irrefutable_let_patterns)] 160 let Some(JobParamsOwned::Blurhash(params)) = params 161 else { 162 return Err(JobError::InvalidParameters); 163 }; 164 165 let maybe_handle = match generate_blurhash_texturehandle( 166 ¶ms.ctx, 167 ¶ms.blurhash, 168 ¶ms.url, 169 dims.x, 170 dims.y, 171 ) { 172 Ok(tex) => Some(tex), 173 Err(e) => { 174 tracing::error!("failed to render blurhash: {e}"); 175 None 176 } 177 }; 178 179 Ok(Job::Blurhash(maybe_handle)) 180 } 181 182 fn generate_blurhash_texturehandle( 183 ctx: &egui::Context, 184 blurhash: &str, 185 url: &str, 186 width: u32, 187 height: u32, 188 ) -> notedeck::Result<egui::TextureHandle> { 189 let bytes = blurhash::decode(blurhash, width, height, 1.0) 190 .map_err(|e| notedeck::Error::Generic(e.to_string()))?; 191 192 let img = egui::ColorImage::from_rgba_unmultiplied([width as usize, height as usize], &bytes); 193 Ok(ctx.load_texture(url, img, Default::default())) 194 }