1use std::sync::Arc;
7use serde::{Deserialize, Serialize};
8use tracing::debug;
9
10use crate::config::ImageQuality;
11
12pub const THUMBNAIL_SIZE: usize = 256;
14pub const MID_RES_SIZE: usize = 512;
15
16#[derive(Debug, Clone)]
18pub struct MultiResImage {
19 pub original_size: [usize; 2],
21 pub thumbnail: Option<Arc<Vec<u8>>>,
23 pub mid_res: Option<Arc<Vec<u8>>>,
25 pub full_res: Option<Arc<Vec<u8>>>,
27 pub current_quality: ImageQuality,
29 pub loading_full: bool,
31}
32
33impl MultiResImage {
34 pub fn new(original_size: [usize; 2]) -> Self {
36 Self {
37 original_size,
38 thumbnail: None,
39 mid_res: None,
40 full_res: None,
41 current_quality: ImageQuality::Thumbnail,
42 loading_full: false,
43 }
44 }
45
46 pub fn best_available(&self) -> Option<Arc<Vec<u8>>> {
48 if let Some(ref full) = self.full_res {
49 return Some(Arc::clone(full));
50 }
51 if let Some(ref mid) = self.mid_res {
52 return Some(Arc::clone(mid));
53 }
54 if let Some(ref thumb) = self.thumbnail {
55 return Some(Arc::clone(thumb));
56 }
57 None
58 }
59
60 pub fn at_quality(&self, quality: ImageQuality) -> Option<Arc<Vec<u8>>> {
62 match quality {
63 ImageQuality::Thumbnail => self.thumbnail.clone(),
64 ImageQuality::MidRes => self.mid_res.clone().or_else(|| self.thumbnail.clone()),
65 ImageQuality::Full => self.full_res.clone()
66 .or_else(|| self.mid_res.clone())
67 .or_else(|| self.thumbnail.clone()),
68 }
69 }
70
71 pub fn has_quality(&self, quality: ImageQuality) -> bool {
73 match quality {
74 ImageQuality::Thumbnail => self.thumbnail.is_some(),
75 ImageQuality::MidRes => self.mid_res.is_some(),
76 ImageQuality::Full => self.full_res.is_some(),
77 }
78 }
79
80 pub fn memory_bytes(&self) -> usize {
82 let mut total = 0;
83 if let Some(ref t) = self.thumbnail {
84 total += t.len();
85 }
86 if let Some(ref m) = self.mid_res {
87 total += m.len();
88 }
89 if let Some(ref f) = self.full_res {
90 total += f.len();
91 }
92 total
93 }
94}
95
96pub struct ImageResizer;
98
99impl ImageResizer {
100 pub fn generate_thumbnail(
111 data: &[u8],
112 width: usize,
113 height: usize,
114 bits_per_pixel: usize,
115 ) -> Vec<u8> {
116 Self::resize(data, width, height, bits_per_pixel, THUMBNAIL_SIZE, THUMBNAIL_SIZE)
117 }
118
119 pub fn generate_mid_res(
121 data: &[u8],
122 width: usize,
123 height: usize,
124 bits_per_pixel: usize,
125 ) -> Vec<u8> {
126 Self::resize(data, width, height, bits_per_pixel, MID_RES_SIZE, MID_RES_SIZE)
127 }
128
129 pub fn resize(
133 data: &[u8],
134 src_width: usize,
135 src_height: usize,
136 bits_per_pixel: usize,
137 target_width: usize,
138 target_height: usize,
139 ) -> Vec<u8> {
140 let (dst_width, dst_height) = Self::fit_dimensions(
142 src_width, src_height,
143 target_width, target_height,
144 );
145
146 let src_f32: Vec<f32> = if bits_per_pixel == 16 {
148 data.chunks(2)
149 .map(|chunk| {
150 let val = u16::from_le_bytes([chunk[0], chunk.get(1).copied().unwrap_or(0)]);
151 val as f32 / 65535.0
152 })
153 .collect()
154 } else {
155 data.iter().map(|&b| b as f32 / 255.0).collect()
156 };
157
158 let mut result = vec![0u8; dst_width * dst_height];
160
161 let x_ratio = src_width as f32 / dst_width as f32;
162 let y_ratio = src_height as f32 / dst_height as f32;
163
164 for y in 0..dst_height {
165 for x in 0..dst_width {
166 let src_x = x as f32 * x_ratio;
167 let src_y = y as f32 * y_ratio;
168
169 let x0 = src_x.floor() as usize;
170 let y0 = src_y.floor() as usize;
171 let x1 = (x0 + 1).min(src_width - 1);
172 let y1 = (y0 + 1).min(src_height - 1);
173
174 let x_frac = src_x - x0 as f32;
175 let y_frac = src_y - y0 as f32;
176
177 let p00 = src_f32.get(y0 * src_width + x0).copied().unwrap_or(0.0);
179 let p10 = src_f32.get(y0 * src_width + x1).copied().unwrap_or(0.0);
180 let p01 = src_f32.get(y1 * src_width + x0).copied().unwrap_or(0.0);
181 let p11 = src_f32.get(y1 * src_width + x1).copied().unwrap_or(0.0);
182
183 let top = p00 * (1.0 - x_frac) + p10 * x_frac;
185 let bottom = p01 * (1.0 - x_frac) + p11 * x_frac;
186 let value = top * (1.0 - y_frac) + bottom * y_frac;
187
188 result[y * dst_width + x] = (value * 255.0).clamp(0.0, 255.0) as u8;
189 }
190 }
191
192 result
193 }
194
195 pub fn fit_dimensions(
197 src_width: usize,
198 src_height: usize,
199 max_width: usize,
200 max_height: usize,
201 ) -> (usize, usize) {
202 let width_ratio = max_width as f32 / src_width as f32;
203 let height_ratio = max_height as f32 / src_height as f32;
204 let ratio = width_ratio.min(height_ratio);
205
206 let new_width = ((src_width as f32 * ratio).round() as usize).max(1);
207 let new_height = ((src_height as f32 * ratio).round() as usize).max(1);
208
209 (new_width, new_height)
210 }
211
212 pub fn generate_all_levels(
214 data: &[u8],
215 width: usize,
216 height: usize,
217 bits_per_pixel: usize,
218 ) -> MultiResLevels {
219 debug!(
220 "Generating multi-res levels: {}x{} @ {}bpp",
221 width, height, bits_per_pixel
222 );
223
224 let thumbnail = Self::generate_thumbnail(data, width, height, bits_per_pixel);
225 let mid_res = Self::generate_mid_res(data, width, height, bits_per_pixel);
226
227 let full_res = if bits_per_pixel == 16 {
229 data.chunks(2)
231 .map(|chunk| {
232 let val = u16::from_le_bytes([chunk[0], chunk.get(1).copied().unwrap_or(0)]);
233 (val >> 8) as u8
234 })
235 .collect()
236 } else {
237 data.to_vec()
238 };
239
240 MultiResLevels {
241 thumbnail,
242 mid_res,
243 full_res,
244 original_size: [width, height],
245 }
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct MultiResLevels {
252 pub thumbnail: Vec<u8>,
254 pub mid_res: Vec<u8>,
256 pub full_res: Vec<u8>,
258 pub original_size: [usize; 2],
260}
261
262impl MultiResLevels {
263 pub fn get(&self, quality: ImageQuality) -> &[u8] {
265 match quality {
266 ImageQuality::Thumbnail => &self.thumbnail,
267 ImageQuality::MidRes => &self.mid_res,
268 ImageQuality::Full => &self.full_res,
269 }
270 }
271
272 pub fn size_for(&self, quality: ImageQuality) -> [usize; 2] {
274 match quality {
275 ImageQuality::Thumbnail => {
276 let (w, h) = ImageResizer::fit_dimensions(
277 self.original_size[0],
278 self.original_size[1],
279 THUMBNAIL_SIZE,
280 THUMBNAIL_SIZE,
281 );
282 [w, h]
283 }
284 ImageQuality::MidRes => {
285 let (w, h) = ImageResizer::fit_dimensions(
286 self.original_size[0],
287 self.original_size[1],
288 MID_RES_SIZE,
289 MID_RES_SIZE,
290 );
291 [w, h]
292 }
293 ImageQuality::Full => self.original_size,
294 }
295 }
296
297 pub fn total_bytes(&self) -> usize {
299 self.thumbnail.len() + self.mid_res.len() + self.full_res.len()
300 }
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct QualityStatus {
306 pub study_id: String,
308 pub series_id: String,
310 pub slice_index: usize,
312 pub current_quality: ImageQuality,
314 pub available_quality: ImageQuality,
316 pub is_loading: bool,
318 pub progress: u8,
320}
321
322impl QualityStatus {
323 pub fn new(study_id: String, series_id: String, slice_index: usize) -> Self {
325 Self {
326 study_id,
327 series_id,
328 slice_index,
329 current_quality: ImageQuality::Thumbnail,
330 available_quality: ImageQuality::Thumbnail,
331 is_loading: false,
332 progress: 0,
333 }
334 }
335
336 pub fn quality_ready(&mut self, quality: ImageQuality) {
338 self.available_quality = quality;
339 if self.is_loading && quality == ImageQuality::Full {
340 self.is_loading = false;
341 self.progress = 100;
342 }
343 }
344
345 pub fn start_loading(&mut self) {
347 self.is_loading = true;
348 self.progress = 0;
349 }
350
351 pub fn update_progress(&mut self, progress: u8) {
353 self.progress = progress.min(100);
354 }
355
356 pub fn needs_upgrade(&self) -> bool {
358 self.current_quality < self.available_quality
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_fit_dimensions_landscape() {
368 let (w, h) = ImageResizer::fit_dimensions(1024, 512, 256, 256);
369 assert_eq!(w, 256);
370 assert_eq!(h, 128);
371 }
372
373 #[test]
374 fn test_fit_dimensions_portrait() {
375 let (w, h) = ImageResizer::fit_dimensions(512, 1024, 256, 256);
376 assert_eq!(w, 128);
377 assert_eq!(h, 256);
378 }
379
380 #[test]
381 fn test_fit_dimensions_square() {
382 let (w, h) = ImageResizer::fit_dimensions(512, 512, 256, 256);
383 assert_eq!(w, 256);
384 assert_eq!(h, 256);
385 }
386
387 #[test]
388 fn test_resize_8bit() {
389 let data: Vec<u8> = (0..16).map(|i| (i * 17) as u8).collect();
391
392 let result = ImageResizer::resize(&data, 4, 4, 8, 2, 2);
393
394 assert_eq!(result.len(), 4); assert!(result[0] < result[3]); }
398
399 #[test]
400 fn test_resize_16bit() {
401 let mut data = Vec::new();
403 for i in 0..16 {
404 let val = (i * 4096) as u16;
405 data.extend_from_slice(&val.to_le_bytes());
406 }
407
408 let result = ImageResizer::resize(&data, 4, 4, 16, 2, 2);
409
410 assert_eq!(result.len(), 4); }
412
413 #[test]
414 fn test_generate_all_levels() {
415 let data: Vec<u8> = (0..512 * 512).map(|i| (i % 256) as u8).collect();
417
418 let levels = ImageResizer::generate_all_levels(&data, 512, 512, 8);
419
420 assert_eq!(levels.thumbnail.len(), 256 * 256);
422 assert_eq!(levels.mid_res.len(), 512 * 512);
423 assert_eq!(levels.full_res.len(), 512 * 512);
424 }
425
426 #[test]
427 fn test_multi_res_image() {
428 let mut img = MultiResImage::new([512, 512]);
429
430 assert!(!img.has_quality(ImageQuality::Thumbnail));
431
432 img.thumbnail = Some(Arc::new(vec![0u8; 256 * 256]));
433 assert!(img.has_quality(ImageQuality::Thumbnail));
434 assert!(!img.has_quality(ImageQuality::Full));
435
436 assert_eq!(img.current_quality, ImageQuality::Thumbnail);
437 }
438
439 #[test]
440 fn test_quality_status() {
441 let mut status = QualityStatus::new(
442 "study1".to_string(),
443 "series1".to_string(),
444 0,
445 );
446
447 assert_eq!(status.current_quality, ImageQuality::Thumbnail);
448 assert!(!status.is_loading);
449
450 status.start_loading();
451 assert!(status.is_loading);
452
453 status.quality_ready(ImageQuality::Full);
454 assert!(!status.is_loading);
455 assert_eq!(status.available_quality, ImageQuality::Full);
456 }
457}